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"

UW-IMAP'd extensions by yuuji