imapext-2007

annotate src/osdep/unix/ssl_unix.c @ 0:ada5e610ab86

imap-2007e
author yuuji@gentei.org
date Mon, 14 Sep 2009 15:17:45 +0900
parents
children a5aee41f2fb9
rev   line source
yuuji@0 1 /* ========================================================================
yuuji@0 2 * Copyright 1988-2008 University of Washington
yuuji@0 3 *
yuuji@0 4 * Licensed under the Apache License, Version 2.0 (the "License");
yuuji@0 5 * you may not use this file except in compliance with the License.
yuuji@0 6 * You may obtain a copy of the License at
yuuji@0 7 *
yuuji@0 8 * http://www.apache.org/licenses/LICENSE-2.0
yuuji@0 9 *
yuuji@0 10 *
yuuji@0 11 * ========================================================================
yuuji@0 12 */
yuuji@0 13
yuuji@0 14 /*
yuuji@0 15 * Program: SSL authentication/encryption module
yuuji@0 16 *
yuuji@0 17 * Author: Mark Crispin
yuuji@0 18 * Networks and Distributed Computing
yuuji@0 19 * Computing & Communications
yuuji@0 20 * University of Washington
yuuji@0 21 * Administration Building, AG-44
yuuji@0 22 * Seattle, WA 98195
yuuji@0 23 * Internet: MRC@CAC.Washington.EDU
yuuji@0 24 *
yuuji@0 25 * Date: 22 September 1998
yuuji@0 26 * Last Edited: 13 January 2007
yuuji@0 27 */
yuuji@0 28
yuuji@0 29 #define crypt ssl_private_crypt
yuuji@0 30 #include <x509v3.h>
yuuji@0 31 #include <ssl.h>
yuuji@0 32 #include <err.h>
yuuji@0 33 #include <pem.h>
yuuji@0 34 #include <buffer.h>
yuuji@0 35 #include <bio.h>
yuuji@0 36 #include <crypto.h>
yuuji@0 37 #include <rand.h>
yuuji@0 38 #undef crypt
yuuji@0 39
yuuji@0 40 #define SSLBUFLEN 8192
yuuji@0 41 #define SSLCIPHERLIST "ALL:!LOW"
yuuji@0 42
yuuji@0 43
yuuji@0 44 /* SSL I/O stream */
yuuji@0 45
yuuji@0 46 typedef struct ssl_stream {
yuuji@0 47 TCPSTREAM *tcpstream; /* TCP stream */
yuuji@0 48 SSL_CTX *context; /* SSL context */
yuuji@0 49 SSL *con; /* SSL connection */
yuuji@0 50 int ictr; /* input counter */
yuuji@0 51 char *iptr; /* input pointer */
yuuji@0 52 char ibuf[SSLBUFLEN]; /* input buffer */
yuuji@0 53 } SSLSTREAM;
yuuji@0 54
yuuji@0 55 #include "sslio.h"
yuuji@0 56
yuuji@0 57 /* Function prototypes */
yuuji@0 58
yuuji@0 59 static SSLSTREAM *ssl_start(TCPSTREAM *tstream,char *host,unsigned long flags);
yuuji@0 60 static char *ssl_start_work (SSLSTREAM *stream,char *host,unsigned long flags);
yuuji@0 61 static int ssl_open_verify (int ok,X509_STORE_CTX *ctx);
yuuji@0 62 static char *ssl_validate_cert (X509 *cert,char *host);
yuuji@0 63 static long ssl_compare_hostnames (unsigned char *s,unsigned char *pat);
yuuji@0 64 static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
yuuji@0 65 long *contd);
yuuji@0 66 static long ssl_abort (SSLSTREAM *stream);
yuuji@0 67 static RSA *ssl_genkey (SSL *con,int export,int keylength);
yuuji@0 68
yuuji@0 69
yuuji@0 70 /* Secure Sockets Layer network driver dispatch */
yuuji@0 71
yuuji@0 72 static struct ssl_driver ssldriver = {
yuuji@0 73 ssl_open, /* open connection */
yuuji@0 74 ssl_aopen, /* open preauthenticated connection */
yuuji@0 75 ssl_getline, /* get a line */
yuuji@0 76 ssl_getbuffer, /* get a buffer */
yuuji@0 77 ssl_soutr, /* output pushed data */
yuuji@0 78 ssl_sout, /* output string */
yuuji@0 79 ssl_close, /* close connection */
yuuji@0 80 ssl_host, /* return host name */
yuuji@0 81 ssl_remotehost, /* return remote host name */
yuuji@0 82 ssl_port, /* return port number */
yuuji@0 83 ssl_localhost /* return local host name */
yuuji@0 84 };
yuuji@0 85 /* non-NIL if doing SSL primary I/O */
yuuji@0 86 static SSLSTDIOSTREAM *sslstdio = NIL;
yuuji@0 87 static char *start_tls = NIL; /* non-NIL if start TLS requested */
yuuji@0 88
yuuji@0 89 /* One-time SSL initialization */
yuuji@0 90
yuuji@0 91 static int sslonceonly = 0;
yuuji@0 92
yuuji@0 93 void ssl_onceonlyinit (void)
yuuji@0 94 {
yuuji@0 95 if (!sslonceonly++) { /* only need to call it once */
yuuji@0 96 int fd;
yuuji@0 97 char tmp[MAILTMPLEN];
yuuji@0 98 struct stat sbuf;
yuuji@0 99 /* if system doesn't have /dev/urandom */
yuuji@0 100 if (stat ("/dev/urandom",&sbuf)) {
yuuji@0 101 while ((fd = open (tmpnam (tmp),O_WRONLY|O_CREAT|O_EXCL,0600)) < 0)
yuuji@0 102 sleep (1);
yuuji@0 103 unlink (tmp); /* don't need the file */
yuuji@0 104 fstat (fd,&sbuf); /* get information about the file */
yuuji@0 105 close (fd); /* flush descriptor */
yuuji@0 106 /* not great but it'll have to do */
yuuji@0 107 sprintf (tmp + strlen (tmp),"%.80s%lx%.80s%lx%lx%lx%lx%lx",
yuuji@0 108 tcp_serveraddr (),(unsigned long) tcp_serverport (),
yuuji@0 109 tcp_clientaddr (),(unsigned long) tcp_clientport (),
yuuji@0 110 (unsigned long) sbuf.st_ino,(unsigned long) time (0),
yuuji@0 111 (unsigned long) gethostid (),(unsigned long) getpid ());
yuuji@0 112 RAND_seed (tmp,strlen (tmp));
yuuji@0 113 }
yuuji@0 114 /* apply runtime linkage */
yuuji@0 115 mail_parameters (NIL,SET_SSLDRIVER,(void *) &ssldriver);
yuuji@0 116 mail_parameters (NIL,SET_SSLSTART,(void *) ssl_start);
yuuji@0 117 SSL_library_init (); /* add all algorithms */
yuuji@0 118 }
yuuji@0 119 }
yuuji@0 120
yuuji@0 121 /* SSL open
yuuji@0 122 * Accepts: host name
yuuji@0 123 * contact service name
yuuji@0 124 * contact port number
yuuji@0 125 * Returns: SSL stream if success else NIL
yuuji@0 126 */
yuuji@0 127
yuuji@0 128 SSLSTREAM *ssl_open (char *host,char *service,unsigned long port)
yuuji@0 129 {
yuuji@0 130 TCPSTREAM *stream = tcp_open (host,service,port);
yuuji@0 131 return stream ? ssl_start (stream,host,port) : NIL;
yuuji@0 132 }
yuuji@0 133
yuuji@0 134
yuuji@0 135 /* SSL authenticated open
yuuji@0 136 * Accepts: host name
yuuji@0 137 * service name
yuuji@0 138 * returned user name buffer
yuuji@0 139 * Returns: SSL stream if success else NIL
yuuji@0 140 */
yuuji@0 141
yuuji@0 142 SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf)
yuuji@0 143 {
yuuji@0 144 return NIL; /* don't use this mechanism with SSL */
yuuji@0 145 }
yuuji@0 146
yuuji@0 147 /* Start SSL/TLS negotiations
yuuji@0 148 * Accepts: open TCP stream of session
yuuji@0 149 * user's host name
yuuji@0 150 * flags
yuuji@0 151 * Returns: SSL stream if success else NIL
yuuji@0 152 */
yuuji@0 153
yuuji@0 154 static SSLSTREAM *ssl_start (TCPSTREAM *tstream,char *host,unsigned long flags)
yuuji@0 155 {
yuuji@0 156 char *reason,tmp[MAILTMPLEN];
yuuji@0 157 sslfailure_t sf = (sslfailure_t) mail_parameters (NIL,GET_SSLFAILURE,NIL);
yuuji@0 158 blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
yuuji@0 159 void *data = (*bn) (BLOCK_SENSITIVE,NIL);
yuuji@0 160 SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
yuuji@0 161 sizeof (SSLSTREAM));
yuuji@0 162 stream->tcpstream = tstream; /* bind TCP stream */
yuuji@0 163 /* do the work */
yuuji@0 164 reason = ssl_start_work (stream,host,flags);
yuuji@0 165 (*bn) (BLOCK_NONSENSITIVE,data);
yuuji@0 166 if (reason) { /* failed? */
yuuji@0 167 ssl_close (stream); /* failed to do SSL */
yuuji@0 168 stream = NIL; /* no stream returned */
yuuji@0 169 switch (*reason) { /* analyze reason */
yuuji@0 170 case '*': /* certificate failure */
yuuji@0 171 ++reason; /* skip over certificate failure indication */
yuuji@0 172 /* pass to error callback */
yuuji@0 173 if (sf) (*sf) (host,reason,flags);
yuuji@0 174 else { /* no error callback, build error message */
yuuji@0 175 sprintf (tmp,"Certificate failure for %.80s: %.512s",host,reason);
yuuji@0 176 mm_log (tmp,ERROR);
yuuji@0 177 }
yuuji@0 178 case '\0': /* user answered no to certificate callback */
yuuji@0 179 if (flags & NET_TRYSSL) /* return dummy stream to stop tryssl */
yuuji@0 180 stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
yuuji@0 181 sizeof (SSLSTREAM));
yuuji@0 182 break;
yuuji@0 183 default: /* non-certificate failure */
yuuji@0 184 if (flags & NET_TRYSSL); /* no error output if tryssl */
yuuji@0 185 /* pass to error callback */
yuuji@0 186 else if (sf) (*sf) (host,reason,flags);
yuuji@0 187 else { /* no error callback, build error message */
yuuji@0 188 sprintf (tmp,"TLS/SSL failure for %.80s: %.512s",host,reason);
yuuji@0 189 mm_log (tmp,ERROR);
yuuji@0 190 }
yuuji@0 191 break;
yuuji@0 192 }
yuuji@0 193 }
yuuji@0 194 return stream;
yuuji@0 195 }
yuuji@0 196
yuuji@0 197 /* Start SSL/TLS negotiations worker routine
yuuji@0 198 * Accepts: SSL stream
yuuji@0 199 * user's host name
yuuji@0 200 * flags
yuuji@0 201 * Returns: NIL if success, else error reason
yuuji@0 202 */
yuuji@0 203
yuuji@0 204 /* evil but I had no choice */
yuuji@0 205 static char *ssl_last_error = NIL;
yuuji@0 206 static char *ssl_last_host = NIL;
yuuji@0 207
yuuji@0 208 static char *ssl_start_work (SSLSTREAM *stream,char *host,unsigned long flags)
yuuji@0 209 {
yuuji@0 210 BIO *bio;
yuuji@0 211 X509 *cert;
yuuji@0 212 unsigned long sl,tl;
yuuji@0 213 char *s,*t,*err,tmp[MAILTMPLEN];
yuuji@0 214 sslcertificatequery_t scq =
yuuji@0 215 (sslcertificatequery_t) mail_parameters (NIL,GET_SSLCERTIFICATEQUERY,NIL);
yuuji@0 216 sslclientcert_t scc =
yuuji@0 217 (sslclientcert_t) mail_parameters (NIL,GET_SSLCLIENTCERT,NIL);
yuuji@0 218 sslclientkey_t sck =
yuuji@0 219 (sslclientkey_t) mail_parameters (NIL,GET_SSLCLIENTKEY,NIL);
yuuji@0 220 if (ssl_last_error) fs_give ((void **) &ssl_last_error);
yuuji@0 221 ssl_last_host = host;
yuuji@0 222 if (!(stream->context = SSL_CTX_new ((flags & NET_TLSCLIENT) ?
yuuji@0 223 TLSv1_client_method () :
yuuji@0 224 SSLv23_client_method ())))
yuuji@0 225 return "SSL context failed";
yuuji@0 226 SSL_CTX_set_options (stream->context,0);
yuuji@0 227 /* disable certificate validation? */
yuuji@0 228 if (flags & NET_NOVALIDATECERT)
yuuji@0 229 SSL_CTX_set_verify (stream->context,SSL_VERIFY_NONE,NIL);
yuuji@0 230 else SSL_CTX_set_verify (stream->context,SSL_VERIFY_PEER,ssl_open_verify);
yuuji@0 231 /* set default paths to CAs... */
yuuji@0 232 SSL_CTX_set_default_verify_paths (stream->context);
yuuji@0 233 /* ...unless a non-standard path desired */
yuuji@0 234 if (s = (char *) mail_parameters (NIL,GET_SSLCAPATH,NIL))
yuuji@0 235 SSL_CTX_load_verify_locations (stream->context,NIL,s);
yuuji@0 236 /* want to send client certificate? */
yuuji@0 237 if (scc && (s = (*scc) ()) && (sl = strlen (s))) {
yuuji@0 238 if (cert = PEM_read_bio_X509 (bio = BIO_new_mem_buf (s,sl),NIL,NIL,NIL)) {
yuuji@0 239 SSL_CTX_use_certificate (stream->context,cert);
yuuji@0 240 X509_free (cert);
yuuji@0 241 }
yuuji@0 242 BIO_free (bio);
yuuji@0 243 if (!cert) return "SSL client certificate failed";
yuuji@0 244 /* want to supply private key? */
yuuji@0 245 if ((t = (sck ? (*sck) () : s)) && (tl = strlen (t))) {
yuuji@0 246 EVP_PKEY *key;
yuuji@0 247 if (key = PEM_read_bio_PrivateKey (bio = BIO_new_mem_buf (t,tl),
yuuji@0 248 NIL,NIL,"")) {
yuuji@0 249 SSL_CTX_use_PrivateKey (stream->context,key);
yuuji@0 250 EVP_PKEY_free (key);
yuuji@0 251 }
yuuji@0 252 BIO_free (bio);
yuuji@0 253 memset (t,0,tl); /* erase key */
yuuji@0 254 }
yuuji@0 255 if (s != t) memset (s,0,sl);/* erase certificate if different from key */
yuuji@0 256 }
yuuji@0 257
yuuji@0 258 /* create connection */
yuuji@0 259 if (!(stream->con = (SSL *) SSL_new (stream->context)))
yuuji@0 260 return "SSL connection failed";
yuuji@0 261 bio = BIO_new_socket (stream->tcpstream->tcpsi,BIO_NOCLOSE);
yuuji@0 262 SSL_set_bio (stream->con,bio,bio);
yuuji@0 263 SSL_set_connect_state (stream->con);
yuuji@0 264 if (SSL_in_init (stream->con)) SSL_total_renegotiations (stream->con);
yuuji@0 265 /* now negotiate SSL */
yuuji@0 266 if (SSL_write (stream->con,"",0) < 0)
yuuji@0 267 return ssl_last_error ? ssl_last_error : "SSL negotiation failed";
yuuji@0 268 /* need to validate host names? */
yuuji@0 269 if (!(flags & NET_NOVALIDATECERT) &&
yuuji@0 270 (err = ssl_validate_cert (cert = SSL_get_peer_certificate (stream->con),
yuuji@0 271 host))) {
yuuji@0 272 /* application callback */
yuuji@0 273 if (scq) return (*scq) (err,host,cert ? cert->name : "???") ? NIL : "";
yuuji@0 274 /* error message to return via mm_log() */
yuuji@0 275 sprintf (tmp,"*%.128s: %.255s",err,cert ? cert->name : "???");
yuuji@0 276 return ssl_last_error = cpystr (tmp);
yuuji@0 277 }
yuuji@0 278 return NIL;
yuuji@0 279 }
yuuji@0 280
yuuji@0 281 /* SSL certificate verification callback
yuuji@0 282 * Accepts: error flag
yuuji@0 283 * X509 context
yuuji@0 284 * Returns: error flag
yuuji@0 285 */
yuuji@0 286
yuuji@0 287 static int ssl_open_verify (int ok,X509_STORE_CTX *ctx)
yuuji@0 288 {
yuuji@0 289 char *err,cert[256],tmp[MAILTMPLEN];
yuuji@0 290 sslcertificatequery_t scq =
yuuji@0 291 (sslcertificatequery_t) mail_parameters (NIL,GET_SSLCERTIFICATEQUERY,NIL);
yuuji@0 292 if (!ok) { /* in case failure */
yuuji@0 293 err = (char *) X509_verify_cert_error_string
yuuji@0 294 (X509_STORE_CTX_get_error (ctx));
yuuji@0 295 X509_NAME_oneline (X509_get_subject_name
yuuji@0 296 (X509_STORE_CTX_get_current_cert (ctx)),cert,255);
yuuji@0 297 if (!scq) { /* mm_log() error message if no callback */
yuuji@0 298 sprintf (tmp,"*%.128s: %.255s",err,cert);
yuuji@0 299 ssl_last_error = cpystr (tmp);
yuuji@0 300 }
yuuji@0 301 /* ignore error if application says to */
yuuji@0 302 else if ((*scq) (err,ssl_last_host,cert)) ok = T;
yuuji@0 303 /* application wants punt */
yuuji@0 304 else ssl_last_error = cpystr ("");
yuuji@0 305 }
yuuji@0 306 return ok;
yuuji@0 307 }
yuuji@0 308
yuuji@0 309
yuuji@0 310 /* SSL validate certificate
yuuji@0 311 * Accepts: certificate
yuuji@0 312 * host to validate against
yuuji@0 313 * Returns: NIL if validated, else string of error message
yuuji@0 314 */
yuuji@0 315
yuuji@0 316 static char *ssl_validate_cert (X509 *cert,char *host)
yuuji@0 317 {
yuuji@0 318 int i,n;
yuuji@0 319 char *s,*t,*ret;
yuuji@0 320 void *ext;
yuuji@0 321 GENERAL_NAME *name;
yuuji@0 322 /* make sure have a certificate */
yuuji@0 323 if (!cert) ret = "No certificate from server";
yuuji@0 324 /* and that it has a name */
yuuji@0 325 else if (!cert->name) ret = "No name in certificate";
yuuji@0 326 /* locate CN */
yuuji@0 327 else if (s = strstr (cert->name,"/CN=")) {
yuuji@0 328 if (t = strchr (s += 4,'/')) *t = '\0';
yuuji@0 329 /* host name matches pattern? */
yuuji@0 330 ret = ssl_compare_hostnames (host,s) ? NIL :
yuuji@0 331 "Server name does not match certificate";
yuuji@0 332 if (t) *t = '/'; /* restore smashed delimiter */
yuuji@0 333 /* if mismatch, see if in extensions */
yuuji@0 334 if (ret && (ext = X509_get_ext_d2i (cert,NID_subject_alt_name,NIL,NIL)) &&
yuuji@0 335 (n = sk_GENERAL_NAME_num (ext)))
yuuji@0 336 /* older versions of OpenSSL use "ia5" instead of dNSName */
yuuji@0 337 for (i = 0; ret && (i < n); i++)
yuuji@0 338 if ((name = sk_GENERAL_NAME_value (ext,i)) &&
yuuji@0 339 (name->type = GEN_DNS) && (s = name->d.ia5->data) &&
yuuji@0 340 ssl_compare_hostnames (host,s)) ret = NIL;
yuuji@0 341 }
yuuji@0 342 else ret = "Unable to locate common name in certificate";
yuuji@0 343 return ret;
yuuji@0 344 }
yuuji@0 345
yuuji@0 346 /* Case-independent wildcard pattern match
yuuji@0 347 * Accepts: base string
yuuji@0 348 * pattern string
yuuji@0 349 * Returns: T if pattern matches base, else NIL
yuuji@0 350 */
yuuji@0 351
yuuji@0 352 static long ssl_compare_hostnames (unsigned char *s,unsigned char *pat)
yuuji@0 353 {
yuuji@0 354 long ret = NIL;
yuuji@0 355 switch (*pat) {
yuuji@0 356 case '*': /* wildcard */
yuuji@0 357 if (pat[1]) { /* there must be a pattern suffix */
yuuji@0 358 /* there is, scan base against it */
yuuji@0 359 do if (ssl_compare_hostnames (s,pat+1)) ret = LONGT;
yuuji@0 360 while (!ret && (*s != '.') && *s++);
yuuji@0 361 }
yuuji@0 362 break;
yuuji@0 363 case '\0': /* end of pattern */
yuuji@0 364 if (!*s) ret = LONGT; /* success if base is also at end */
yuuji@0 365 break;
yuuji@0 366 default: /* non-wildcard, recurse if match */
yuuji@0 367 if (!compare_uchar (*pat,*s)) ret = ssl_compare_hostnames (s+1,pat+1);
yuuji@0 368 break;
yuuji@0 369 }
yuuji@0 370 return ret;
yuuji@0 371 }
yuuji@0 372
yuuji@0 373 /* SSL receive line
yuuji@0 374 * Accepts: SSL stream
yuuji@0 375 * Returns: text line string or NIL if failure
yuuji@0 376 */
yuuji@0 377
yuuji@0 378 char *ssl_getline (SSLSTREAM *stream)
yuuji@0 379 {
yuuji@0 380 unsigned long n,contd;
yuuji@0 381 char *ret = ssl_getline_work (stream,&n,&contd);
yuuji@0 382 if (ret && contd) { /* got a line needing continuation? */
yuuji@0 383 STRINGLIST *stl = mail_newstringlist ();
yuuji@0 384 STRINGLIST *stc = stl;
yuuji@0 385 do { /* collect additional lines */
yuuji@0 386 stc->text.data = (unsigned char *) ret;
yuuji@0 387 stc->text.size = n;
yuuji@0 388 stc = stc->next = mail_newstringlist ();
yuuji@0 389 ret = ssl_getline_work (stream,&n,&contd);
yuuji@0 390 } while (ret && contd);
yuuji@0 391 if (ret) { /* stash final part of line on list */
yuuji@0 392 stc->text.data = (unsigned char *) ret;
yuuji@0 393 stc->text.size = n;
yuuji@0 394 /* determine how large a buffer we need */
yuuji@0 395 for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
yuuji@0 396 ret = fs_get (n + 1); /* copy parts into buffer */
yuuji@0 397 for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
yuuji@0 398 memcpy (ret + n,stc->text.data,stc->text.size);
yuuji@0 399 ret[n] = '\0';
yuuji@0 400 }
yuuji@0 401 mail_free_stringlist (&stl);/* either way, done with list */
yuuji@0 402 }
yuuji@0 403 return ret;
yuuji@0 404 }
yuuji@0 405
yuuji@0 406 /* SSL receive line or partial line
yuuji@0 407 * Accepts: SSL stream
yuuji@0 408 * pointer to return size
yuuji@0 409 * pointer to return continuation flag
yuuji@0 410 * Returns: text line string, size and continuation flag, or NIL if failure
yuuji@0 411 */
yuuji@0 412
yuuji@0 413 static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size,
yuuji@0 414 long *contd)
yuuji@0 415 {
yuuji@0 416 unsigned long n;
yuuji@0 417 char *s,*ret,c,d;
yuuji@0 418 *contd = NIL; /* assume no continuation */
yuuji@0 419 /* make sure have data */
yuuji@0 420 if (!ssl_getdata (stream)) return NIL;
yuuji@0 421 for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
yuuji@0 422 d = *stream->iptr++; /* slurp another character */
yuuji@0 423 if ((c == '\015') && (d == '\012')) {
yuuji@0 424 ret = (char *) fs_get (n--);
yuuji@0 425 memcpy (ret,s,*size = n); /* copy into a free storage string */
yuuji@0 426 ret[n] = '\0'; /* tie off string with null */
yuuji@0 427 return ret;
yuuji@0 428 }
yuuji@0 429 }
yuuji@0 430 /* copy partial string from buffer */
yuuji@0 431 memcpy ((ret = (char *) fs_get (n)),s,*size = n);
yuuji@0 432 /* get more data from the net */
yuuji@0 433 if (!ssl_getdata (stream)) fs_give ((void **) &ret);
yuuji@0 434 /* special case of newline broken by buffer */
yuuji@0 435 else if ((c == '\015') && (*stream->iptr == '\012')) {
yuuji@0 436 stream->iptr++; /* eat the line feed */
yuuji@0 437 stream->ictr--;
yuuji@0 438 ret[*size = --n] = '\0'; /* tie off string with null */
yuuji@0 439 }
yuuji@0 440 else *contd = LONGT; /* continuation needed */
yuuji@0 441 return ret;
yuuji@0 442 }
yuuji@0 443
yuuji@0 444 /* SSL receive buffer
yuuji@0 445 * Accepts: SSL stream
yuuji@0 446 * size in bytes
yuuji@0 447 * buffer to read into
yuuji@0 448 * Returns: T if success, NIL otherwise
yuuji@0 449 */
yuuji@0 450
yuuji@0 451 long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer)
yuuji@0 452 {
yuuji@0 453 unsigned long n;
yuuji@0 454 while (size > 0) { /* until request satisfied */
yuuji@0 455 if (!ssl_getdata (stream)) return NIL;
yuuji@0 456 n = min (size,stream->ictr);/* number of bytes to transfer */
yuuji@0 457 /* do the copy */
yuuji@0 458 memcpy (buffer,stream->iptr,n);
yuuji@0 459 buffer += n; /* update pointer */
yuuji@0 460 stream->iptr += n;
yuuji@0 461 size -= n; /* update # of bytes to do */
yuuji@0 462 stream->ictr -= n;
yuuji@0 463 }
yuuji@0 464 buffer[0] = '\0'; /* tie off string */
yuuji@0 465 return T;
yuuji@0 466 }
yuuji@0 467
yuuji@0 468 /* SSL receive data
yuuji@0 469 * Accepts: TCP/IP stream
yuuji@0 470 * Returns: T if success, NIL otherwise
yuuji@0 471 */
yuuji@0 472
yuuji@0 473 long ssl_getdata (SSLSTREAM *stream)
yuuji@0 474 {
yuuji@0 475 int i,sock;
yuuji@0 476 fd_set fds,efds;
yuuji@0 477 struct timeval tmo;
yuuji@0 478 tcptimeout_t tmoh = (tcptimeout_t) mail_parameters (NIL,GET_TIMEOUT,NIL);
yuuji@0 479 long ttmo_read = (long) mail_parameters (NIL,GET_READTIMEOUT,NIL);
yuuji@0 480 time_t t = time (0);
yuuji@0 481 blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
yuuji@0 482 if (!stream->con || ((sock = SSL_get_fd (stream->con)) < 0)) return NIL;
yuuji@0 483 /* tcp_unix should have prevented this */
yuuji@0 484 if (sock >= FD_SETSIZE) fatal ("unselectable socket in ssl_getdata()");
yuuji@0 485 (*bn) (BLOCK_TCPREAD,NIL);
yuuji@0 486 while (stream->ictr < 1) { /* if nothing in the buffer */
yuuji@0 487 time_t tl = time (0); /* start of request */
yuuji@0 488 time_t now = tl;
yuuji@0 489 int ti = ttmo_read ? now + ttmo_read : 0;
yuuji@0 490 if (SSL_pending (stream->con)) i = 1;
yuuji@0 491 else {
yuuji@0 492 if (tcpdebug) mm_log ("Reading SSL data",TCPDEBUG);
yuuji@0 493 tmo.tv_usec = 0;
yuuji@0 494 FD_ZERO (&fds); /* initialize selection vector */
yuuji@0 495 FD_ZERO (&efds); /* handle errors too */
yuuji@0 496 FD_SET (sock,&fds); /* set bit in selection vector */
yuuji@0 497 FD_SET (sock,&efds); /* set bit in error selection vector */
yuuji@0 498 errno = NIL; /* block and read */
yuuji@0 499 do { /* block under timeout */
yuuji@0 500 tmo.tv_sec = ti ? ti - now : 0;
yuuji@0 501 i = select (sock+1,&fds,0,&efds,ti ? &tmo : 0);
yuuji@0 502 now = time (0); /* fake timeout if interrupt & time expired */
yuuji@0 503 if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
yuuji@0 504 } while ((i < 0) && (errno == EINTR));
yuuji@0 505 }
yuuji@0 506 if (i) { /* non-timeout result from select? */
yuuji@0 507 errno = 0; /* just in case */
yuuji@0 508 if (i > 0) /* read what we can */
yuuji@0 509 while (((i = SSL_read (stream->con,stream->ibuf,SSLBUFLEN)) < 0) &&
yuuji@0 510 ((errno == EINTR) ||
yuuji@0 511 (SSL_get_error (stream->con,i) == SSL_ERROR_WANT_READ)));
yuuji@0 512 if (i <= 0) { /* error seen? */
yuuji@0 513 if (tcpdebug) {
yuuji@0 514 char *s,tmp[MAILTMPLEN];
yuuji@0 515 if (i) sprintf (s = tmp,"SSL data read I/O error %d SSL error %d",
yuuji@0 516 errno,SSL_get_error (stream->con,i));
yuuji@0 517 else s = "SSL data read end of file";
yuuji@0 518 mm_log (s,TCPDEBUG);
yuuji@0 519 }
yuuji@0 520 return ssl_abort (stream);
yuuji@0 521 }
yuuji@0 522 stream->iptr = stream->ibuf;/* point at TCP buffer */
yuuji@0 523 stream->ictr = i; /* set new byte count */
yuuji@0 524 if (tcpdebug) mm_log ("Successfully read SSL data",TCPDEBUG);
yuuji@0 525 }
yuuji@0 526 /* timeout, punt unless told not to */
yuuji@0 527 else if (!tmoh || !(*tmoh) (now - t,now - tl)) {
yuuji@0 528 if (tcpdebug) mm_log ("SSL data read timeout",TCPDEBUG);
yuuji@0 529 return ssl_abort (stream);
yuuji@0 530 }
yuuji@0 531 }
yuuji@0 532 (*bn) (BLOCK_NONE,NIL);
yuuji@0 533 return T;
yuuji@0 534 }
yuuji@0 535
yuuji@0 536 /* SSL send string as record
yuuji@0 537 * Accepts: SSL stream
yuuji@0 538 * string pointer
yuuji@0 539 * Returns: T if success else NIL
yuuji@0 540 */
yuuji@0 541
yuuji@0 542 long ssl_soutr (SSLSTREAM *stream,char *string)
yuuji@0 543 {
yuuji@0 544 return ssl_sout (stream,string,(unsigned long) strlen (string));
yuuji@0 545 }
yuuji@0 546
yuuji@0 547
yuuji@0 548 /* SSL send string
yuuji@0 549 * Accepts: SSL stream
yuuji@0 550 * string pointer
yuuji@0 551 * byte count
yuuji@0 552 * Returns: T if success else NIL
yuuji@0 553 */
yuuji@0 554
yuuji@0 555 long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size)
yuuji@0 556 {
yuuji@0 557 long i;
yuuji@0 558 blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
yuuji@0 559 if (!stream->con) return NIL;
yuuji@0 560 (*bn) (BLOCK_TCPWRITE,NIL);
yuuji@0 561 if (tcpdebug) mm_log ("Writing to SSL",TCPDEBUG);
yuuji@0 562 /* until request satisfied */
yuuji@0 563 for (i = 0; size > 0; string += i,size -= i)
yuuji@0 564 /* write as much as we can */
yuuji@0 565 if ((i = SSL_write (stream->con,string,(int) min (SSLBUFLEN,size))) < 0) {
yuuji@0 566 if (tcpdebug) {
yuuji@0 567 char tmp[MAILTMPLEN];
yuuji@0 568 sprintf (tmp,"SSL data write I/O error %d SSL error %d",
yuuji@0 569 errno,SSL_get_error (stream->con,i));
yuuji@0 570 mm_log (tmp,TCPDEBUG);
yuuji@0 571 }
yuuji@0 572 return ssl_abort (stream);/* write failed */
yuuji@0 573 }
yuuji@0 574 if (tcpdebug) mm_log ("successfully wrote to TCP",TCPDEBUG);
yuuji@0 575 (*bn) (BLOCK_NONE,NIL);
yuuji@0 576 return LONGT; /* all done */
yuuji@0 577 }
yuuji@0 578
yuuji@0 579 /* SSL close
yuuji@0 580 * Accepts: SSL stream
yuuji@0 581 */
yuuji@0 582
yuuji@0 583 void ssl_close (SSLSTREAM *stream)
yuuji@0 584 {
yuuji@0 585 ssl_abort (stream); /* nuke the stream */
yuuji@0 586 fs_give ((void **) &stream); /* flush the stream */
yuuji@0 587 }
yuuji@0 588
yuuji@0 589
yuuji@0 590 /* SSL abort stream
yuuji@0 591 * Accepts: SSL stream
yuuji@0 592 * Returns: NIL always
yuuji@0 593 */
yuuji@0 594
yuuji@0 595 static long ssl_abort (SSLSTREAM *stream)
yuuji@0 596 {
yuuji@0 597 blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
yuuji@0 598 if (stream->con) { /* close SSL connection */
yuuji@0 599 SSL_shutdown (stream->con);
yuuji@0 600 SSL_free (stream->con);
yuuji@0 601 stream->con = NIL;
yuuji@0 602 }
yuuji@0 603 if (stream->context) { /* clean up context */
yuuji@0 604 SSL_CTX_free (stream->context);
yuuji@0 605 stream->context = NIL;
yuuji@0 606 }
yuuji@0 607 if (stream->tcpstream) { /* close TCP stream */
yuuji@0 608 tcp_close (stream->tcpstream);
yuuji@0 609 stream->tcpstream = NIL;
yuuji@0 610 }
yuuji@0 611 (*bn) (BLOCK_NONE,NIL);
yuuji@0 612 return NIL;
yuuji@0 613 }
yuuji@0 614
yuuji@0 615 /* SSL get host name
yuuji@0 616 * Accepts: SSL stream
yuuji@0 617 * Returns: host name for this stream
yuuji@0 618 */
yuuji@0 619
yuuji@0 620 char *ssl_host (SSLSTREAM *stream)
yuuji@0 621 {
yuuji@0 622 return tcp_host (stream->tcpstream);
yuuji@0 623 }
yuuji@0 624
yuuji@0 625
yuuji@0 626 /* SSL get remote host name
yuuji@0 627 * Accepts: SSL stream
yuuji@0 628 * Returns: host name for this stream
yuuji@0 629 */
yuuji@0 630
yuuji@0 631 char *ssl_remotehost (SSLSTREAM *stream)
yuuji@0 632 {
yuuji@0 633 return tcp_remotehost (stream->tcpstream);
yuuji@0 634 }
yuuji@0 635
yuuji@0 636
yuuji@0 637 /* SSL return port for this stream
yuuji@0 638 * Accepts: SSL stream
yuuji@0 639 * Returns: port number for this stream
yuuji@0 640 */
yuuji@0 641
yuuji@0 642 unsigned long ssl_port (SSLSTREAM *stream)
yuuji@0 643 {
yuuji@0 644 return tcp_port (stream->tcpstream);
yuuji@0 645 }
yuuji@0 646
yuuji@0 647
yuuji@0 648 /* SSL get local host name
yuuji@0 649 * Accepts: SSL stream
yuuji@0 650 * Returns: local host name
yuuji@0 651 */
yuuji@0 652
yuuji@0 653 char *ssl_localhost (SSLSTREAM *stream)
yuuji@0 654 {
yuuji@0 655 return tcp_localhost (stream->tcpstream);
yuuji@0 656 }
yuuji@0 657
yuuji@0 658 /* Start TLS
yuuji@0 659 * Accepts: /etc/services service name
yuuji@0 660 * Returns: cpystr'd error string if TLS failed, else NIL for success
yuuji@0 661 */
yuuji@0 662
yuuji@0 663 char *ssl_start_tls (char *server)
yuuji@0 664 {
yuuji@0 665 char tmp[MAILTMPLEN];
yuuji@0 666 struct stat sbuf;
yuuji@0 667 if (sslstdio) return cpystr ("Already in an SSL session");
yuuji@0 668 if (start_tls) return cpystr ("TLS already started");
yuuji@0 669 if (server) { /* build specific certificate/key file name */
yuuji@0 670 sprintf (tmp,"%s/%s-%s.pem",SSL_CERT_DIRECTORY,server,tcp_serveraddr ());
yuuji@0 671 if (stat (tmp,&sbuf)) { /* use non-specific name if no specific file */
yuuji@0 672 sprintf (tmp,"%s/%s.pem",SSL_CERT_DIRECTORY,server);
yuuji@0 673 if (stat (tmp,&sbuf)) return cpystr ("Server certificate not installed");
yuuji@0 674 }
yuuji@0 675 start_tls = server; /* switch to STARTTLS mode */
yuuji@0 676 }
yuuji@0 677 return NIL;
yuuji@0 678 }
yuuji@0 679
yuuji@0 680 /* Init server for SSL
yuuji@0 681 * Accepts: server name
yuuji@0 682 */
yuuji@0 683
yuuji@0 684 void ssl_server_init (char *server)
yuuji@0 685 {
yuuji@0 686 char cert[MAILTMPLEN],key[MAILTMPLEN];
yuuji@0 687 unsigned long i;
yuuji@0 688 struct stat sbuf;
yuuji@0 689 SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0,
yuuji@0 690 sizeof (SSLSTREAM));
yuuji@0 691 ssl_onceonlyinit (); /* make sure algorithms added */
yuuji@0 692 ERR_load_crypto_strings ();
yuuji@0 693 SSL_load_error_strings ();
yuuji@0 694 /* build specific certificate/key file names */
yuuji@0 695 sprintf (cert,"%s/%s-%s.pem",SSL_CERT_DIRECTORY,server,tcp_serveraddr ());
yuuji@0 696 sprintf (key,"%s/%s-%s.pem",SSL_KEY_DIRECTORY,server,tcp_serveraddr ());
yuuji@0 697 /* use non-specific name if no specific cert */
yuuji@0 698 if (stat (cert,&sbuf)) sprintf (cert,"%s/%s.pem",SSL_CERT_DIRECTORY,server);
yuuji@0 699 if (stat (key,&sbuf)) { /* use non-specific name if no specific key */
yuuji@0 700 sprintf (key,"%s/%s.pem",SSL_KEY_DIRECTORY,server);
yuuji@0 701 /* use cert file as fallback for key */
yuuji@0 702 if (stat (key,&sbuf)) strcpy (key,cert);
yuuji@0 703 }
yuuji@0 704 /* create context */
yuuji@0 705 if (!(stream->context = SSL_CTX_new (start_tls ?
yuuji@0 706 TLSv1_server_method () :
yuuji@0 707 SSLv23_server_method ())))
yuuji@0 708 syslog (LOG_ALERT,"Unable to create SSL context, host=%.80s",
yuuji@0 709 tcp_clienthost ());
yuuji@0 710 else { /* set context options */
yuuji@0 711 SSL_CTX_set_options (stream->context,SSL_OP_ALL);
yuuji@0 712 /* set cipher list */
yuuji@0 713 if (!SSL_CTX_set_cipher_list (stream->context,SSLCIPHERLIST))
yuuji@0 714 syslog (LOG_ALERT,"Unable to set cipher list %.80s, host=%.80s",
yuuji@0 715 SSLCIPHERLIST,tcp_clienthost ());
yuuji@0 716 /* load certificate */
yuuji@0 717 else if (!SSL_CTX_use_certificate_chain_file (stream->context,cert))
yuuji@0 718 syslog (LOG_ALERT,"Unable to load certificate from %.80s, host=%.80s",
yuuji@0 719 cert,tcp_clienthost ());
yuuji@0 720 /* load key */
yuuji@0 721 else if (!(SSL_CTX_use_RSAPrivateKey_file (stream->context,key,
yuuji@0 722 SSL_FILETYPE_PEM)))
yuuji@0 723 syslog (LOG_ALERT,"Unable to load private key from %.80s, host=%.80s",
yuuji@0 724 key,tcp_clienthost ());
yuuji@0 725
yuuji@0 726 else { /* generate key if needed */
yuuji@0 727 if (SSL_CTX_need_tmp_RSA (stream->context))
yuuji@0 728 SSL_CTX_set_tmp_rsa_callback (stream->context,ssl_genkey);
yuuji@0 729 /* create new SSL connection */
yuuji@0 730 if (!(stream->con = SSL_new (stream->context)))
yuuji@0 731 syslog (LOG_ALERT,"Unable to create SSL connection, host=%.80s",
yuuji@0 732 tcp_clienthost ());
yuuji@0 733 else { /* set file descriptor */
yuuji@0 734 SSL_set_fd (stream->con,0);
yuuji@0 735 /* all OK if accepted */
yuuji@0 736 if (SSL_accept (stream->con) < 0)
yuuji@0 737 syslog (LOG_INFO,"Unable to accept SSL connection, host=%.80s",
yuuji@0 738 tcp_clienthost ());
yuuji@0 739 else { /* server set up */
yuuji@0 740 sslstdio = (SSLSTDIOSTREAM *)
yuuji@0 741 memset (fs_get (sizeof(SSLSTDIOSTREAM)),0,sizeof (SSLSTDIOSTREAM));
yuuji@0 742 sslstdio->sslstream = stream;
yuuji@0 743 /* available space in output buffer */
yuuji@0 744 sslstdio->octr = SSLBUFLEN;
yuuji@0 745 /* current output buffer pointer */
yuuji@0 746 sslstdio->optr = sslstdio->obuf;
yuuji@0 747 /* allow plaintext if disable value was 2 */
yuuji@0 748 if ((long) mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL) > 1)
yuuji@0 749 mail_parameters (NIL,SET_DISABLEPLAINTEXT,NIL);
yuuji@0 750 /* unhide PLAIN SASL authenticator */
yuuji@0 751 mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"PLAIN");
yuuji@0 752 mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"LOGIN");
yuuji@0 753 return;
yuuji@0 754 }
yuuji@0 755 }
yuuji@0 756 }
yuuji@0 757 }
yuuji@0 758 while (i = ERR_get_error ()) /* SSL failure */
yuuji@0 759 syslog (LOG_ERR,"SSL error status: %.80s",ERR_error_string (i,NIL));
yuuji@0 760 ssl_close (stream); /* punt stream */
yuuji@0 761 exit (1); /* punt this program too */
yuuji@0 762 }
yuuji@0 763
yuuji@0 764 /* Generate one-time key for server
yuuji@0 765 * Accepts: SSL connection
yuuji@0 766 * export flag
yuuji@0 767 * keylength
yuuji@0 768 * Returns: generated key, always
yuuji@0 769 */
yuuji@0 770
yuuji@0 771 static RSA *ssl_genkey (SSL *con,int export,int keylength)
yuuji@0 772 {
yuuji@0 773 unsigned long i;
yuuji@0 774 static RSA *key = NIL;
yuuji@0 775 if (!key) { /* if don't have a key already */
yuuji@0 776 /* generate key */
yuuji@0 777 if (!(key = RSA_generate_key (export ? keylength : 1024,RSA_F4,NIL,NIL))) {
yuuji@0 778 syslog (LOG_ALERT,"Unable to generate temp key, host=%.80s",
yuuji@0 779 tcp_clienthost ());
yuuji@0 780 while (i = ERR_get_error ())
yuuji@0 781 syslog (LOG_ALERT,"SSL error status: %s",ERR_error_string (i,NIL));
yuuji@0 782 exit (1);
yuuji@0 783 }
yuuji@0 784 }
yuuji@0 785 return key;
yuuji@0 786 }
yuuji@0 787
yuuji@0 788 /* Wait for stdin input
yuuji@0 789 * Accepts: timeout in seconds
yuuji@0 790 * Returns: T if have input on stdin, else NIL
yuuji@0 791 */
yuuji@0 792
yuuji@0 793 long ssl_server_input_wait (long seconds)
yuuji@0 794 {
yuuji@0 795 int i,sock;
yuuji@0 796 fd_set fds,efd;
yuuji@0 797 struct timeval tmo;
yuuji@0 798 SSLSTREAM *stream;
yuuji@0 799 if (!sslstdio) return server_input_wait (seconds);
yuuji@0 800 /* input available in buffer */
yuuji@0 801 if (((stream = sslstdio->sslstream)->ictr > 0) ||
yuuji@0 802 !stream->con || ((sock = SSL_get_fd (stream->con)) < 0)) return LONGT;
yuuji@0 803 /* sock ought to be 0 always */
yuuji@0 804 if (sock >= FD_SETSIZE) fatal ("unselectable socket in ssl_getdata()");
yuuji@0 805 /* input available from SSL */
yuuji@0 806 if (SSL_pending (stream->con) &&
yuuji@0 807 ((i = SSL_read (stream->con,stream->ibuf,SSLBUFLEN)) > 0)) {
yuuji@0 808 stream->iptr = stream->ibuf;/* point at TCP buffer */
yuuji@0 809 stream->ictr = i; /* set new byte count */
yuuji@0 810 return LONGT;
yuuji@0 811 }
yuuji@0 812 FD_ZERO (&fds); /* initialize selection vector */
yuuji@0 813 FD_ZERO (&efd); /* initialize selection vector */
yuuji@0 814 FD_SET (sock,&fds); /* set bit in selection vector */
yuuji@0 815 FD_SET (sock,&efd); /* set bit in selection vector */
yuuji@0 816 tmo.tv_sec = seconds; tmo.tv_usec = 0;
yuuji@0 817 /* see if input available from the socket */
yuuji@0 818 return select (sock+1,&fds,0,&efd,&tmo) ? LONGT : NIL;
yuuji@0 819 }
yuuji@0 820
yuuji@0 821 #include "sslstdio.c"

UW-IMAP'd extensions by yuuji