imapext-2007
diff 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 |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/osdep/unix/ssl_unix.c Mon Sep 14 15:17:45 2009 +0900 1.3 @@ -0,0 +1,821 @@ 1.4 +/* ======================================================================== 1.5 + * Copyright 1988-2008 University of Washington 1.6 + * 1.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 1.8 + * you may not use this file except in compliance with the License. 1.9 + * You may obtain a copy of the License at 1.10 + * 1.11 + * http://www.apache.org/licenses/LICENSE-2.0 1.12 + * 1.13 + * 1.14 + * ======================================================================== 1.15 + */ 1.16 + 1.17 +/* 1.18 + * Program: SSL authentication/encryption module 1.19 + * 1.20 + * Author: Mark Crispin 1.21 + * Networks and Distributed Computing 1.22 + * Computing & Communications 1.23 + * University of Washington 1.24 + * Administration Building, AG-44 1.25 + * Seattle, WA 98195 1.26 + * Internet: MRC@CAC.Washington.EDU 1.27 + * 1.28 + * Date: 22 September 1998 1.29 + * Last Edited: 13 January 2007 1.30 + */ 1.31 + 1.32 +#define crypt ssl_private_crypt 1.33 +#include <x509v3.h> 1.34 +#include <ssl.h> 1.35 +#include <err.h> 1.36 +#include <pem.h> 1.37 +#include <buffer.h> 1.38 +#include <bio.h> 1.39 +#include <crypto.h> 1.40 +#include <rand.h> 1.41 +#undef crypt 1.42 + 1.43 +#define SSLBUFLEN 8192 1.44 +#define SSLCIPHERLIST "ALL:!LOW" 1.45 + 1.46 + 1.47 +/* SSL I/O stream */ 1.48 + 1.49 +typedef struct ssl_stream { 1.50 + TCPSTREAM *tcpstream; /* TCP stream */ 1.51 + SSL_CTX *context; /* SSL context */ 1.52 + SSL *con; /* SSL connection */ 1.53 + int ictr; /* input counter */ 1.54 + char *iptr; /* input pointer */ 1.55 + char ibuf[SSLBUFLEN]; /* input buffer */ 1.56 +} SSLSTREAM; 1.57 + 1.58 +#include "sslio.h" 1.59 + 1.60 +/* Function prototypes */ 1.61 + 1.62 +static SSLSTREAM *ssl_start(TCPSTREAM *tstream,char *host,unsigned long flags); 1.63 +static char *ssl_start_work (SSLSTREAM *stream,char *host,unsigned long flags); 1.64 +static int ssl_open_verify (int ok,X509_STORE_CTX *ctx); 1.65 +static char *ssl_validate_cert (X509 *cert,char *host); 1.66 +static long ssl_compare_hostnames (unsigned char *s,unsigned char *pat); 1.67 +static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size, 1.68 + long *contd); 1.69 +static long ssl_abort (SSLSTREAM *stream); 1.70 +static RSA *ssl_genkey (SSL *con,int export,int keylength); 1.71 + 1.72 + 1.73 +/* Secure Sockets Layer network driver dispatch */ 1.74 + 1.75 +static struct ssl_driver ssldriver = { 1.76 + ssl_open, /* open connection */ 1.77 + ssl_aopen, /* open preauthenticated connection */ 1.78 + ssl_getline, /* get a line */ 1.79 + ssl_getbuffer, /* get a buffer */ 1.80 + ssl_soutr, /* output pushed data */ 1.81 + ssl_sout, /* output string */ 1.82 + ssl_close, /* close connection */ 1.83 + ssl_host, /* return host name */ 1.84 + ssl_remotehost, /* return remote host name */ 1.85 + ssl_port, /* return port number */ 1.86 + ssl_localhost /* return local host name */ 1.87 +}; 1.88 + /* non-NIL if doing SSL primary I/O */ 1.89 +static SSLSTDIOSTREAM *sslstdio = NIL; 1.90 +static char *start_tls = NIL; /* non-NIL if start TLS requested */ 1.91 + 1.92 +/* One-time SSL initialization */ 1.93 + 1.94 +static int sslonceonly = 0; 1.95 + 1.96 +void ssl_onceonlyinit (void) 1.97 +{ 1.98 + if (!sslonceonly++) { /* only need to call it once */ 1.99 + int fd; 1.100 + char tmp[MAILTMPLEN]; 1.101 + struct stat sbuf; 1.102 + /* if system doesn't have /dev/urandom */ 1.103 + if (stat ("/dev/urandom",&sbuf)) { 1.104 + while ((fd = open (tmpnam (tmp),O_WRONLY|O_CREAT|O_EXCL,0600)) < 0) 1.105 + sleep (1); 1.106 + unlink (tmp); /* don't need the file */ 1.107 + fstat (fd,&sbuf); /* get information about the file */ 1.108 + close (fd); /* flush descriptor */ 1.109 + /* not great but it'll have to do */ 1.110 + sprintf (tmp + strlen (tmp),"%.80s%lx%.80s%lx%lx%lx%lx%lx", 1.111 + tcp_serveraddr (),(unsigned long) tcp_serverport (), 1.112 + tcp_clientaddr (),(unsigned long) tcp_clientport (), 1.113 + (unsigned long) sbuf.st_ino,(unsigned long) time (0), 1.114 + (unsigned long) gethostid (),(unsigned long) getpid ()); 1.115 + RAND_seed (tmp,strlen (tmp)); 1.116 + } 1.117 + /* apply runtime linkage */ 1.118 + mail_parameters (NIL,SET_SSLDRIVER,(void *) &ssldriver); 1.119 + mail_parameters (NIL,SET_SSLSTART,(void *) ssl_start); 1.120 + SSL_library_init (); /* add all algorithms */ 1.121 + } 1.122 +} 1.123 + 1.124 +/* SSL open 1.125 + * Accepts: host name 1.126 + * contact service name 1.127 + * contact port number 1.128 + * Returns: SSL stream if success else NIL 1.129 + */ 1.130 + 1.131 +SSLSTREAM *ssl_open (char *host,char *service,unsigned long port) 1.132 +{ 1.133 + TCPSTREAM *stream = tcp_open (host,service,port); 1.134 + return stream ? ssl_start (stream,host,port) : NIL; 1.135 +} 1.136 + 1.137 + 1.138 +/* SSL authenticated open 1.139 + * Accepts: host name 1.140 + * service name 1.141 + * returned user name buffer 1.142 + * Returns: SSL stream if success else NIL 1.143 + */ 1.144 + 1.145 +SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf) 1.146 +{ 1.147 + return NIL; /* don't use this mechanism with SSL */ 1.148 +} 1.149 + 1.150 +/* Start SSL/TLS negotiations 1.151 + * Accepts: open TCP stream of session 1.152 + * user's host name 1.153 + * flags 1.154 + * Returns: SSL stream if success else NIL 1.155 + */ 1.156 + 1.157 +static SSLSTREAM *ssl_start (TCPSTREAM *tstream,char *host,unsigned long flags) 1.158 +{ 1.159 + char *reason,tmp[MAILTMPLEN]; 1.160 + sslfailure_t sf = (sslfailure_t) mail_parameters (NIL,GET_SSLFAILURE,NIL); 1.161 + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); 1.162 + void *data = (*bn) (BLOCK_SENSITIVE,NIL); 1.163 + SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0, 1.164 + sizeof (SSLSTREAM)); 1.165 + stream->tcpstream = tstream; /* bind TCP stream */ 1.166 + /* do the work */ 1.167 + reason = ssl_start_work (stream,host,flags); 1.168 + (*bn) (BLOCK_NONSENSITIVE,data); 1.169 + if (reason) { /* failed? */ 1.170 + ssl_close (stream); /* failed to do SSL */ 1.171 + stream = NIL; /* no stream returned */ 1.172 + switch (*reason) { /* analyze reason */ 1.173 + case '*': /* certificate failure */ 1.174 + ++reason; /* skip over certificate failure indication */ 1.175 + /* pass to error callback */ 1.176 + if (sf) (*sf) (host,reason,flags); 1.177 + else { /* no error callback, build error message */ 1.178 + sprintf (tmp,"Certificate failure for %.80s: %.512s",host,reason); 1.179 + mm_log (tmp,ERROR); 1.180 + } 1.181 + case '\0': /* user answered no to certificate callback */ 1.182 + if (flags & NET_TRYSSL) /* return dummy stream to stop tryssl */ 1.183 + stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0, 1.184 + sizeof (SSLSTREAM)); 1.185 + break; 1.186 + default: /* non-certificate failure */ 1.187 + if (flags & NET_TRYSSL); /* no error output if tryssl */ 1.188 + /* pass to error callback */ 1.189 + else if (sf) (*sf) (host,reason,flags); 1.190 + else { /* no error callback, build error message */ 1.191 + sprintf (tmp,"TLS/SSL failure for %.80s: %.512s",host,reason); 1.192 + mm_log (tmp,ERROR); 1.193 + } 1.194 + break; 1.195 + } 1.196 + } 1.197 + return stream; 1.198 +} 1.199 + 1.200 +/* Start SSL/TLS negotiations worker routine 1.201 + * Accepts: SSL stream 1.202 + * user's host name 1.203 + * flags 1.204 + * Returns: NIL if success, else error reason 1.205 + */ 1.206 + 1.207 + /* evil but I had no choice */ 1.208 +static char *ssl_last_error = NIL; 1.209 +static char *ssl_last_host = NIL; 1.210 + 1.211 +static char *ssl_start_work (SSLSTREAM *stream,char *host,unsigned long flags) 1.212 +{ 1.213 + BIO *bio; 1.214 + X509 *cert; 1.215 + unsigned long sl,tl; 1.216 + char *s,*t,*err,tmp[MAILTMPLEN]; 1.217 + sslcertificatequery_t scq = 1.218 + (sslcertificatequery_t) mail_parameters (NIL,GET_SSLCERTIFICATEQUERY,NIL); 1.219 + sslclientcert_t scc = 1.220 + (sslclientcert_t) mail_parameters (NIL,GET_SSLCLIENTCERT,NIL); 1.221 + sslclientkey_t sck = 1.222 + (sslclientkey_t) mail_parameters (NIL,GET_SSLCLIENTKEY,NIL); 1.223 + if (ssl_last_error) fs_give ((void **) &ssl_last_error); 1.224 + ssl_last_host = host; 1.225 + if (!(stream->context = SSL_CTX_new ((flags & NET_TLSCLIENT) ? 1.226 + TLSv1_client_method () : 1.227 + SSLv23_client_method ()))) 1.228 + return "SSL context failed"; 1.229 + SSL_CTX_set_options (stream->context,0); 1.230 + /* disable certificate validation? */ 1.231 + if (flags & NET_NOVALIDATECERT) 1.232 + SSL_CTX_set_verify (stream->context,SSL_VERIFY_NONE,NIL); 1.233 + else SSL_CTX_set_verify (stream->context,SSL_VERIFY_PEER,ssl_open_verify); 1.234 + /* set default paths to CAs... */ 1.235 + SSL_CTX_set_default_verify_paths (stream->context); 1.236 + /* ...unless a non-standard path desired */ 1.237 + if (s = (char *) mail_parameters (NIL,GET_SSLCAPATH,NIL)) 1.238 + SSL_CTX_load_verify_locations (stream->context,NIL,s); 1.239 + /* want to send client certificate? */ 1.240 + if (scc && (s = (*scc) ()) && (sl = strlen (s))) { 1.241 + if (cert = PEM_read_bio_X509 (bio = BIO_new_mem_buf (s,sl),NIL,NIL,NIL)) { 1.242 + SSL_CTX_use_certificate (stream->context,cert); 1.243 + X509_free (cert); 1.244 + } 1.245 + BIO_free (bio); 1.246 + if (!cert) return "SSL client certificate failed"; 1.247 + /* want to supply private key? */ 1.248 + if ((t = (sck ? (*sck) () : s)) && (tl = strlen (t))) { 1.249 + EVP_PKEY *key; 1.250 + if (key = PEM_read_bio_PrivateKey (bio = BIO_new_mem_buf (t,tl), 1.251 + NIL,NIL,"")) { 1.252 + SSL_CTX_use_PrivateKey (stream->context,key); 1.253 + EVP_PKEY_free (key); 1.254 + } 1.255 + BIO_free (bio); 1.256 + memset (t,0,tl); /* erase key */ 1.257 + } 1.258 + if (s != t) memset (s,0,sl);/* erase certificate if different from key */ 1.259 + } 1.260 + 1.261 + /* create connection */ 1.262 + if (!(stream->con = (SSL *) SSL_new (stream->context))) 1.263 + return "SSL connection failed"; 1.264 + bio = BIO_new_socket (stream->tcpstream->tcpsi,BIO_NOCLOSE); 1.265 + SSL_set_bio (stream->con,bio,bio); 1.266 + SSL_set_connect_state (stream->con); 1.267 + if (SSL_in_init (stream->con)) SSL_total_renegotiations (stream->con); 1.268 + /* now negotiate SSL */ 1.269 + if (SSL_write (stream->con,"",0) < 0) 1.270 + return ssl_last_error ? ssl_last_error : "SSL negotiation failed"; 1.271 + /* need to validate host names? */ 1.272 + if (!(flags & NET_NOVALIDATECERT) && 1.273 + (err = ssl_validate_cert (cert = SSL_get_peer_certificate (stream->con), 1.274 + host))) { 1.275 + /* application callback */ 1.276 + if (scq) return (*scq) (err,host,cert ? cert->name : "???") ? NIL : ""; 1.277 + /* error message to return via mm_log() */ 1.278 + sprintf (tmp,"*%.128s: %.255s",err,cert ? cert->name : "???"); 1.279 + return ssl_last_error = cpystr (tmp); 1.280 + } 1.281 + return NIL; 1.282 +} 1.283 + 1.284 +/* SSL certificate verification callback 1.285 + * Accepts: error flag 1.286 + * X509 context 1.287 + * Returns: error flag 1.288 + */ 1.289 + 1.290 +static int ssl_open_verify (int ok,X509_STORE_CTX *ctx) 1.291 +{ 1.292 + char *err,cert[256],tmp[MAILTMPLEN]; 1.293 + sslcertificatequery_t scq = 1.294 + (sslcertificatequery_t) mail_parameters (NIL,GET_SSLCERTIFICATEQUERY,NIL); 1.295 + if (!ok) { /* in case failure */ 1.296 + err = (char *) X509_verify_cert_error_string 1.297 + (X509_STORE_CTX_get_error (ctx)); 1.298 + X509_NAME_oneline (X509_get_subject_name 1.299 + (X509_STORE_CTX_get_current_cert (ctx)),cert,255); 1.300 + if (!scq) { /* mm_log() error message if no callback */ 1.301 + sprintf (tmp,"*%.128s: %.255s",err,cert); 1.302 + ssl_last_error = cpystr (tmp); 1.303 + } 1.304 + /* ignore error if application says to */ 1.305 + else if ((*scq) (err,ssl_last_host,cert)) ok = T; 1.306 + /* application wants punt */ 1.307 + else ssl_last_error = cpystr (""); 1.308 + } 1.309 + return ok; 1.310 +} 1.311 + 1.312 + 1.313 +/* SSL validate certificate 1.314 + * Accepts: certificate 1.315 + * host to validate against 1.316 + * Returns: NIL if validated, else string of error message 1.317 + */ 1.318 + 1.319 +static char *ssl_validate_cert (X509 *cert,char *host) 1.320 +{ 1.321 + int i,n; 1.322 + char *s,*t,*ret; 1.323 + void *ext; 1.324 + GENERAL_NAME *name; 1.325 + /* make sure have a certificate */ 1.326 + if (!cert) ret = "No certificate from server"; 1.327 + /* and that it has a name */ 1.328 + else if (!cert->name) ret = "No name in certificate"; 1.329 + /* locate CN */ 1.330 + else if (s = strstr (cert->name,"/CN=")) { 1.331 + if (t = strchr (s += 4,'/')) *t = '\0'; 1.332 + /* host name matches pattern? */ 1.333 + ret = ssl_compare_hostnames (host,s) ? NIL : 1.334 + "Server name does not match certificate"; 1.335 + if (t) *t = '/'; /* restore smashed delimiter */ 1.336 + /* if mismatch, see if in extensions */ 1.337 + if (ret && (ext = X509_get_ext_d2i (cert,NID_subject_alt_name,NIL,NIL)) && 1.338 + (n = sk_GENERAL_NAME_num (ext))) 1.339 + /* older versions of OpenSSL use "ia5" instead of dNSName */ 1.340 + for (i = 0; ret && (i < n); i++) 1.341 + if ((name = sk_GENERAL_NAME_value (ext,i)) && 1.342 + (name->type = GEN_DNS) && (s = name->d.ia5->data) && 1.343 + ssl_compare_hostnames (host,s)) ret = NIL; 1.344 + } 1.345 + else ret = "Unable to locate common name in certificate"; 1.346 + return ret; 1.347 +} 1.348 + 1.349 +/* Case-independent wildcard pattern match 1.350 + * Accepts: base string 1.351 + * pattern string 1.352 + * Returns: T if pattern matches base, else NIL 1.353 + */ 1.354 + 1.355 +static long ssl_compare_hostnames (unsigned char *s,unsigned char *pat) 1.356 +{ 1.357 + long ret = NIL; 1.358 + switch (*pat) { 1.359 + case '*': /* wildcard */ 1.360 + if (pat[1]) { /* there must be a pattern suffix */ 1.361 + /* there is, scan base against it */ 1.362 + do if (ssl_compare_hostnames (s,pat+1)) ret = LONGT; 1.363 + while (!ret && (*s != '.') && *s++); 1.364 + } 1.365 + break; 1.366 + case '\0': /* end of pattern */ 1.367 + if (!*s) ret = LONGT; /* success if base is also at end */ 1.368 + break; 1.369 + default: /* non-wildcard, recurse if match */ 1.370 + if (!compare_uchar (*pat,*s)) ret = ssl_compare_hostnames (s+1,pat+1); 1.371 + break; 1.372 + } 1.373 + return ret; 1.374 +} 1.375 + 1.376 +/* SSL receive line 1.377 + * Accepts: SSL stream 1.378 + * Returns: text line string or NIL if failure 1.379 + */ 1.380 + 1.381 +char *ssl_getline (SSLSTREAM *stream) 1.382 +{ 1.383 + unsigned long n,contd; 1.384 + char *ret = ssl_getline_work (stream,&n,&contd); 1.385 + if (ret && contd) { /* got a line needing continuation? */ 1.386 + STRINGLIST *stl = mail_newstringlist (); 1.387 + STRINGLIST *stc = stl; 1.388 + do { /* collect additional lines */ 1.389 + stc->text.data = (unsigned char *) ret; 1.390 + stc->text.size = n; 1.391 + stc = stc->next = mail_newstringlist (); 1.392 + ret = ssl_getline_work (stream,&n,&contd); 1.393 + } while (ret && contd); 1.394 + if (ret) { /* stash final part of line on list */ 1.395 + stc->text.data = (unsigned char *) ret; 1.396 + stc->text.size = n; 1.397 + /* determine how large a buffer we need */ 1.398 + for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size; 1.399 + ret = fs_get (n + 1); /* copy parts into buffer */ 1.400 + for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next) 1.401 + memcpy (ret + n,stc->text.data,stc->text.size); 1.402 + ret[n] = '\0'; 1.403 + } 1.404 + mail_free_stringlist (&stl);/* either way, done with list */ 1.405 + } 1.406 + return ret; 1.407 +} 1.408 + 1.409 +/* SSL receive line or partial line 1.410 + * Accepts: SSL stream 1.411 + * pointer to return size 1.412 + * pointer to return continuation flag 1.413 + * Returns: text line string, size and continuation flag, or NIL if failure 1.414 + */ 1.415 + 1.416 +static char *ssl_getline_work (SSLSTREAM *stream,unsigned long *size, 1.417 + long *contd) 1.418 +{ 1.419 + unsigned long n; 1.420 + char *s,*ret,c,d; 1.421 + *contd = NIL; /* assume no continuation */ 1.422 + /* make sure have data */ 1.423 + if (!ssl_getdata (stream)) return NIL; 1.424 + for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) { 1.425 + d = *stream->iptr++; /* slurp another character */ 1.426 + if ((c == '\015') && (d == '\012')) { 1.427 + ret = (char *) fs_get (n--); 1.428 + memcpy (ret,s,*size = n); /* copy into a free storage string */ 1.429 + ret[n] = '\0'; /* tie off string with null */ 1.430 + return ret; 1.431 + } 1.432 + } 1.433 + /* copy partial string from buffer */ 1.434 + memcpy ((ret = (char *) fs_get (n)),s,*size = n); 1.435 + /* get more data from the net */ 1.436 + if (!ssl_getdata (stream)) fs_give ((void **) &ret); 1.437 + /* special case of newline broken by buffer */ 1.438 + else if ((c == '\015') && (*stream->iptr == '\012')) { 1.439 + stream->iptr++; /* eat the line feed */ 1.440 + stream->ictr--; 1.441 + ret[*size = --n] = '\0'; /* tie off string with null */ 1.442 + } 1.443 + else *contd = LONGT; /* continuation needed */ 1.444 + return ret; 1.445 +} 1.446 + 1.447 +/* SSL receive buffer 1.448 + * Accepts: SSL stream 1.449 + * size in bytes 1.450 + * buffer to read into 1.451 + * Returns: T if success, NIL otherwise 1.452 + */ 1.453 + 1.454 +long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer) 1.455 +{ 1.456 + unsigned long n; 1.457 + while (size > 0) { /* until request satisfied */ 1.458 + if (!ssl_getdata (stream)) return NIL; 1.459 + n = min (size,stream->ictr);/* number of bytes to transfer */ 1.460 + /* do the copy */ 1.461 + memcpy (buffer,stream->iptr,n); 1.462 + buffer += n; /* update pointer */ 1.463 + stream->iptr += n; 1.464 + size -= n; /* update # of bytes to do */ 1.465 + stream->ictr -= n; 1.466 + } 1.467 + buffer[0] = '\0'; /* tie off string */ 1.468 + return T; 1.469 +} 1.470 + 1.471 +/* SSL receive data 1.472 + * Accepts: TCP/IP stream 1.473 + * Returns: T if success, NIL otherwise 1.474 + */ 1.475 + 1.476 +long ssl_getdata (SSLSTREAM *stream) 1.477 +{ 1.478 + int i,sock; 1.479 + fd_set fds,efds; 1.480 + struct timeval tmo; 1.481 + tcptimeout_t tmoh = (tcptimeout_t) mail_parameters (NIL,GET_TIMEOUT,NIL); 1.482 + long ttmo_read = (long) mail_parameters (NIL,GET_READTIMEOUT,NIL); 1.483 + time_t t = time (0); 1.484 + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); 1.485 + if (!stream->con || ((sock = SSL_get_fd (stream->con)) < 0)) return NIL; 1.486 + /* tcp_unix should have prevented this */ 1.487 + if (sock >= FD_SETSIZE) fatal ("unselectable socket in ssl_getdata()"); 1.488 + (*bn) (BLOCK_TCPREAD,NIL); 1.489 + while (stream->ictr < 1) { /* if nothing in the buffer */ 1.490 + time_t tl = time (0); /* start of request */ 1.491 + time_t now = tl; 1.492 + int ti = ttmo_read ? now + ttmo_read : 0; 1.493 + if (SSL_pending (stream->con)) i = 1; 1.494 + else { 1.495 + if (tcpdebug) mm_log ("Reading SSL data",TCPDEBUG); 1.496 + tmo.tv_usec = 0; 1.497 + FD_ZERO (&fds); /* initialize selection vector */ 1.498 + FD_ZERO (&efds); /* handle errors too */ 1.499 + FD_SET (sock,&fds); /* set bit in selection vector */ 1.500 + FD_SET (sock,&efds); /* set bit in error selection vector */ 1.501 + errno = NIL; /* block and read */ 1.502 + do { /* block under timeout */ 1.503 + tmo.tv_sec = ti ? ti - now : 0; 1.504 + i = select (sock+1,&fds,0,&efds,ti ? &tmo : 0); 1.505 + now = time (0); /* fake timeout if interrupt & time expired */ 1.506 + if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0; 1.507 + } while ((i < 0) && (errno == EINTR)); 1.508 + } 1.509 + if (i) { /* non-timeout result from select? */ 1.510 + errno = 0; /* just in case */ 1.511 + if (i > 0) /* read what we can */ 1.512 + while (((i = SSL_read (stream->con,stream->ibuf,SSLBUFLEN)) < 0) && 1.513 + ((errno == EINTR) || 1.514 + (SSL_get_error (stream->con,i) == SSL_ERROR_WANT_READ))); 1.515 + if (i <= 0) { /* error seen? */ 1.516 + if (tcpdebug) { 1.517 + char *s,tmp[MAILTMPLEN]; 1.518 + if (i) sprintf (s = tmp,"SSL data read I/O error %d SSL error %d", 1.519 + errno,SSL_get_error (stream->con,i)); 1.520 + else s = "SSL data read end of file"; 1.521 + mm_log (s,TCPDEBUG); 1.522 + } 1.523 + return ssl_abort (stream); 1.524 + } 1.525 + stream->iptr = stream->ibuf;/* point at TCP buffer */ 1.526 + stream->ictr = i; /* set new byte count */ 1.527 + if (tcpdebug) mm_log ("Successfully read SSL data",TCPDEBUG); 1.528 + } 1.529 + /* timeout, punt unless told not to */ 1.530 + else if (!tmoh || !(*tmoh) (now - t,now - tl)) { 1.531 + if (tcpdebug) mm_log ("SSL data read timeout",TCPDEBUG); 1.532 + return ssl_abort (stream); 1.533 + } 1.534 + } 1.535 + (*bn) (BLOCK_NONE,NIL); 1.536 + return T; 1.537 +} 1.538 + 1.539 +/* SSL send string as record 1.540 + * Accepts: SSL stream 1.541 + * string pointer 1.542 + * Returns: T if success else NIL 1.543 + */ 1.544 + 1.545 +long ssl_soutr (SSLSTREAM *stream,char *string) 1.546 +{ 1.547 + return ssl_sout (stream,string,(unsigned long) strlen (string)); 1.548 +} 1.549 + 1.550 + 1.551 +/* SSL send string 1.552 + * Accepts: SSL stream 1.553 + * string pointer 1.554 + * byte count 1.555 + * Returns: T if success else NIL 1.556 + */ 1.557 + 1.558 +long ssl_sout (SSLSTREAM *stream,char *string,unsigned long size) 1.559 +{ 1.560 + long i; 1.561 + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); 1.562 + if (!stream->con) return NIL; 1.563 + (*bn) (BLOCK_TCPWRITE,NIL); 1.564 + if (tcpdebug) mm_log ("Writing to SSL",TCPDEBUG); 1.565 + /* until request satisfied */ 1.566 + for (i = 0; size > 0; string += i,size -= i) 1.567 + /* write as much as we can */ 1.568 + if ((i = SSL_write (stream->con,string,(int) min (SSLBUFLEN,size))) < 0) { 1.569 + if (tcpdebug) { 1.570 + char tmp[MAILTMPLEN]; 1.571 + sprintf (tmp,"SSL data write I/O error %d SSL error %d", 1.572 + errno,SSL_get_error (stream->con,i)); 1.573 + mm_log (tmp,TCPDEBUG); 1.574 + } 1.575 + return ssl_abort (stream);/* write failed */ 1.576 + } 1.577 + if (tcpdebug) mm_log ("successfully wrote to TCP",TCPDEBUG); 1.578 + (*bn) (BLOCK_NONE,NIL); 1.579 + return LONGT; /* all done */ 1.580 +} 1.581 + 1.582 +/* SSL close 1.583 + * Accepts: SSL stream 1.584 + */ 1.585 + 1.586 +void ssl_close (SSLSTREAM *stream) 1.587 +{ 1.588 + ssl_abort (stream); /* nuke the stream */ 1.589 + fs_give ((void **) &stream); /* flush the stream */ 1.590 +} 1.591 + 1.592 + 1.593 +/* SSL abort stream 1.594 + * Accepts: SSL stream 1.595 + * Returns: NIL always 1.596 + */ 1.597 + 1.598 +static long ssl_abort (SSLSTREAM *stream) 1.599 +{ 1.600 + blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL); 1.601 + if (stream->con) { /* close SSL connection */ 1.602 + SSL_shutdown (stream->con); 1.603 + SSL_free (stream->con); 1.604 + stream->con = NIL; 1.605 + } 1.606 + if (stream->context) { /* clean up context */ 1.607 + SSL_CTX_free (stream->context); 1.608 + stream->context = NIL; 1.609 + } 1.610 + if (stream->tcpstream) { /* close TCP stream */ 1.611 + tcp_close (stream->tcpstream); 1.612 + stream->tcpstream = NIL; 1.613 + } 1.614 + (*bn) (BLOCK_NONE,NIL); 1.615 + return NIL; 1.616 +} 1.617 + 1.618 +/* SSL get host name 1.619 + * Accepts: SSL stream 1.620 + * Returns: host name for this stream 1.621 + */ 1.622 + 1.623 +char *ssl_host (SSLSTREAM *stream) 1.624 +{ 1.625 + return tcp_host (stream->tcpstream); 1.626 +} 1.627 + 1.628 + 1.629 +/* SSL get remote host name 1.630 + * Accepts: SSL stream 1.631 + * Returns: host name for this stream 1.632 + */ 1.633 + 1.634 +char *ssl_remotehost (SSLSTREAM *stream) 1.635 +{ 1.636 + return tcp_remotehost (stream->tcpstream); 1.637 +} 1.638 + 1.639 + 1.640 +/* SSL return port for this stream 1.641 + * Accepts: SSL stream 1.642 + * Returns: port number for this stream 1.643 + */ 1.644 + 1.645 +unsigned long ssl_port (SSLSTREAM *stream) 1.646 +{ 1.647 + return tcp_port (stream->tcpstream); 1.648 +} 1.649 + 1.650 + 1.651 +/* SSL get local host name 1.652 + * Accepts: SSL stream 1.653 + * Returns: local host name 1.654 + */ 1.655 + 1.656 +char *ssl_localhost (SSLSTREAM *stream) 1.657 +{ 1.658 + return tcp_localhost (stream->tcpstream); 1.659 +} 1.660 + 1.661 +/* Start TLS 1.662 + * Accepts: /etc/services service name 1.663 + * Returns: cpystr'd error string if TLS failed, else NIL for success 1.664 + */ 1.665 + 1.666 +char *ssl_start_tls (char *server) 1.667 +{ 1.668 + char tmp[MAILTMPLEN]; 1.669 + struct stat sbuf; 1.670 + if (sslstdio) return cpystr ("Already in an SSL session"); 1.671 + if (start_tls) return cpystr ("TLS already started"); 1.672 + if (server) { /* build specific certificate/key file name */ 1.673 + sprintf (tmp,"%s/%s-%s.pem",SSL_CERT_DIRECTORY,server,tcp_serveraddr ()); 1.674 + if (stat (tmp,&sbuf)) { /* use non-specific name if no specific file */ 1.675 + sprintf (tmp,"%s/%s.pem",SSL_CERT_DIRECTORY,server); 1.676 + if (stat (tmp,&sbuf)) return cpystr ("Server certificate not installed"); 1.677 + } 1.678 + start_tls = server; /* switch to STARTTLS mode */ 1.679 + } 1.680 + return NIL; 1.681 +} 1.682 + 1.683 +/* Init server for SSL 1.684 + * Accepts: server name 1.685 + */ 1.686 + 1.687 +void ssl_server_init (char *server) 1.688 +{ 1.689 + char cert[MAILTMPLEN],key[MAILTMPLEN]; 1.690 + unsigned long i; 1.691 + struct stat sbuf; 1.692 + SSLSTREAM *stream = (SSLSTREAM *) memset (fs_get (sizeof (SSLSTREAM)),0, 1.693 + sizeof (SSLSTREAM)); 1.694 + ssl_onceonlyinit (); /* make sure algorithms added */ 1.695 + ERR_load_crypto_strings (); 1.696 + SSL_load_error_strings (); 1.697 + /* build specific certificate/key file names */ 1.698 + sprintf (cert,"%s/%s-%s.pem",SSL_CERT_DIRECTORY,server,tcp_serveraddr ()); 1.699 + sprintf (key,"%s/%s-%s.pem",SSL_KEY_DIRECTORY,server,tcp_serveraddr ()); 1.700 + /* use non-specific name if no specific cert */ 1.701 + if (stat (cert,&sbuf)) sprintf (cert,"%s/%s.pem",SSL_CERT_DIRECTORY,server); 1.702 + if (stat (key,&sbuf)) { /* use non-specific name if no specific key */ 1.703 + sprintf (key,"%s/%s.pem",SSL_KEY_DIRECTORY,server); 1.704 + /* use cert file as fallback for key */ 1.705 + if (stat (key,&sbuf)) strcpy (key,cert); 1.706 + } 1.707 + /* create context */ 1.708 + if (!(stream->context = SSL_CTX_new (start_tls ? 1.709 + TLSv1_server_method () : 1.710 + SSLv23_server_method ()))) 1.711 + syslog (LOG_ALERT,"Unable to create SSL context, host=%.80s", 1.712 + tcp_clienthost ()); 1.713 + else { /* set context options */ 1.714 + SSL_CTX_set_options (stream->context,SSL_OP_ALL); 1.715 + /* set cipher list */ 1.716 + if (!SSL_CTX_set_cipher_list (stream->context,SSLCIPHERLIST)) 1.717 + syslog (LOG_ALERT,"Unable to set cipher list %.80s, host=%.80s", 1.718 + SSLCIPHERLIST,tcp_clienthost ()); 1.719 + /* load certificate */ 1.720 + else if (!SSL_CTX_use_certificate_chain_file (stream->context,cert)) 1.721 + syslog (LOG_ALERT,"Unable to load certificate from %.80s, host=%.80s", 1.722 + cert,tcp_clienthost ()); 1.723 + /* load key */ 1.724 + else if (!(SSL_CTX_use_RSAPrivateKey_file (stream->context,key, 1.725 + SSL_FILETYPE_PEM))) 1.726 + syslog (LOG_ALERT,"Unable to load private key from %.80s, host=%.80s", 1.727 + key,tcp_clienthost ()); 1.728 + 1.729 + else { /* generate key if needed */ 1.730 + if (SSL_CTX_need_tmp_RSA (stream->context)) 1.731 + SSL_CTX_set_tmp_rsa_callback (stream->context,ssl_genkey); 1.732 + /* create new SSL connection */ 1.733 + if (!(stream->con = SSL_new (stream->context))) 1.734 + syslog (LOG_ALERT,"Unable to create SSL connection, host=%.80s", 1.735 + tcp_clienthost ()); 1.736 + else { /* set file descriptor */ 1.737 + SSL_set_fd (stream->con,0); 1.738 + /* all OK if accepted */ 1.739 + if (SSL_accept (stream->con) < 0) 1.740 + syslog (LOG_INFO,"Unable to accept SSL connection, host=%.80s", 1.741 + tcp_clienthost ()); 1.742 + else { /* server set up */ 1.743 + sslstdio = (SSLSTDIOSTREAM *) 1.744 + memset (fs_get (sizeof(SSLSTDIOSTREAM)),0,sizeof (SSLSTDIOSTREAM)); 1.745 + sslstdio->sslstream = stream; 1.746 + /* available space in output buffer */ 1.747 + sslstdio->octr = SSLBUFLEN; 1.748 + /* current output buffer pointer */ 1.749 + sslstdio->optr = sslstdio->obuf; 1.750 + /* allow plaintext if disable value was 2 */ 1.751 + if ((long) mail_parameters (NIL,GET_DISABLEPLAINTEXT,NIL) > 1) 1.752 + mail_parameters (NIL,SET_DISABLEPLAINTEXT,NIL); 1.753 + /* unhide PLAIN SASL authenticator */ 1.754 + mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"PLAIN"); 1.755 + mail_parameters (NIL,UNHIDE_AUTHENTICATOR,"LOGIN"); 1.756 + return; 1.757 + } 1.758 + } 1.759 + } 1.760 + } 1.761 + while (i = ERR_get_error ()) /* SSL failure */ 1.762 + syslog (LOG_ERR,"SSL error status: %.80s",ERR_error_string (i,NIL)); 1.763 + ssl_close (stream); /* punt stream */ 1.764 + exit (1); /* punt this program too */ 1.765 +} 1.766 + 1.767 +/* Generate one-time key for server 1.768 + * Accepts: SSL connection 1.769 + * export flag 1.770 + * keylength 1.771 + * Returns: generated key, always 1.772 + */ 1.773 + 1.774 +static RSA *ssl_genkey (SSL *con,int export,int keylength) 1.775 +{ 1.776 + unsigned long i; 1.777 + static RSA *key = NIL; 1.778 + if (!key) { /* if don't have a key already */ 1.779 + /* generate key */ 1.780 + if (!(key = RSA_generate_key (export ? keylength : 1024,RSA_F4,NIL,NIL))) { 1.781 + syslog (LOG_ALERT,"Unable to generate temp key, host=%.80s", 1.782 + tcp_clienthost ()); 1.783 + while (i = ERR_get_error ()) 1.784 + syslog (LOG_ALERT,"SSL error status: %s",ERR_error_string (i,NIL)); 1.785 + exit (1); 1.786 + } 1.787 + } 1.788 + return key; 1.789 +} 1.790 + 1.791 +/* Wait for stdin input 1.792 + * Accepts: timeout in seconds 1.793 + * Returns: T if have input on stdin, else NIL 1.794 + */ 1.795 + 1.796 +long ssl_server_input_wait (long seconds) 1.797 +{ 1.798 + int i,sock; 1.799 + fd_set fds,efd; 1.800 + struct timeval tmo; 1.801 + SSLSTREAM *stream; 1.802 + if (!sslstdio) return server_input_wait (seconds); 1.803 + /* input available in buffer */ 1.804 + if (((stream = sslstdio->sslstream)->ictr > 0) || 1.805 + !stream->con || ((sock = SSL_get_fd (stream->con)) < 0)) return LONGT; 1.806 + /* sock ought to be 0 always */ 1.807 + if (sock >= FD_SETSIZE) fatal ("unselectable socket in ssl_getdata()"); 1.808 + /* input available from SSL */ 1.809 + if (SSL_pending (stream->con) && 1.810 + ((i = SSL_read (stream->con,stream->ibuf,SSLBUFLEN)) > 0)) { 1.811 + stream->iptr = stream->ibuf;/* point at TCP buffer */ 1.812 + stream->ictr = i; /* set new byte count */ 1.813 + return LONGT; 1.814 + } 1.815 + FD_ZERO (&fds); /* initialize selection vector */ 1.816 + FD_ZERO (&efd); /* initialize selection vector */ 1.817 + FD_SET (sock,&fds); /* set bit in selection vector */ 1.818 + FD_SET (sock,&efd); /* set bit in selection vector */ 1.819 + tmo.tv_sec = seconds; tmo.tv_usec = 0; 1.820 + /* see if input available from the socket */ 1.821 + return select (sock+1,&fds,0,&efd,&tmo) ? LONGT : NIL; 1.822 +} 1.823 + 1.824 +#include "sslstdio.c"