/* * Copyright (c) 1999-2006, 2008 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. * */ #include SM_RCSID("@(#)$Id: sfsasl.c,v 8.118 2008/07/22 15:12:48 ca Exp $") #include #include #include #include /* allow to disable error handling code just in case... */ #ifndef DEAL_WITH_ERROR_SSL # define DEAL_WITH_ERROR_SSL 1 #endif /* ! DEAL_WITH_ERROR_SSL */ #if SASL # include "sfsasl.h" /* Structure used by the "sasl" file type */ struct sasl_obj { SM_FILE_T *fp; sasl_conn_t *conn; }; struct sasl_info { SM_FILE_T *fp; sasl_conn_t *conn; }; /* ** SASL_GETINFO - returns requested information about a "sasl" file ** descriptor. ** ** Parameters: ** fp -- the file descriptor ** what -- the type of information requested ** valp -- the thang to return the information in ** ** Returns: ** -1 for unknown requests ** >=0 on success with valp filled in (if possible). */ static int sasl_getinfo __P((SM_FILE_T *, int, void *)); static int sasl_getinfo(fp, what, valp) SM_FILE_T *fp; int what; void *valp; { struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie; switch (what) { case SM_IO_WHAT_FD: if (so->fp == NULL) return -1; return so->fp->f_file; /* for stdio fileno() compatability */ case SM_IO_IS_READABLE: if (so->fp == NULL) return 0; /* get info from underlying file */ return sm_io_getinfo(so->fp, what, valp); default: return -1; } } /* ** SASL_OPEN -- creates the sasl specific information for opening a ** file of the sasl type. ** ** Parameters: ** fp -- the file pointer associated with the new open ** info -- contains the sasl connection information pointer and ** the original SM_FILE_T that holds the open ** flags -- ignored ** rpool -- ignored ** ** Returns: ** 0 on success */ static int sasl_open __P((SM_FILE_T *, const void *, int, const void *)); /* ARGSUSED2 */ static int sasl_open(fp, info, flags, rpool) SM_FILE_T *fp; const void *info; int flags; const void *rpool; { struct sasl_obj *so; struct sasl_info *si = (struct sasl_info *) info; so = (struct sasl_obj *) sm_malloc(sizeof(struct sasl_obj)); if (so == NULL) { errno = ENOMEM; return -1; } so->fp = si->fp; so->conn = si->conn; /* ** The underlying 'fp' is set to SM_IO_NOW so that the entire ** encoded string is written in one chunk. Otherwise there is ** the possibility that it may appear illegal, bogus or ** mangled to the other side of the connection. ** We will read or write through 'fp' since it is the opaque ** connection for the communications. We need to treat it this ** way in case the encoded string is to be sent down a TLS ** connection rather than, say, sm_io's stdio. */ (void) sm_io_setvbuf(so->fp, SM_TIME_DEFAULT, NULL, SM_IO_NOW, 0); fp->f_cookie = so; return 0; } /* ** SASL_CLOSE -- close the sasl specific parts of the sasl file pointer ** ** Parameters: ** fp -- the file pointer to close ** ** Returns: ** 0 on success */ static int sasl_close __P((SM_FILE_T *)); static int sasl_close(fp) SM_FILE_T *fp; { struct sasl_obj *so; so = (struct sasl_obj *) fp->f_cookie; if (so == NULL) return 0; if (so->fp != NULL) { sm_io_close(so->fp, SM_TIME_DEFAULT); so->fp = NULL; } sm_free(so); so = NULL; return 0; } /* how to deallocate a buffer allocated by SASL */ extern void sm_sasl_free __P((void *)); # define SASL_DEALLOC(b) sm_sasl_free(b) /* ** SASL_READ -- read encrypted information and decrypt it for the caller ** ** Parameters: ** fp -- the file pointer ** buf -- the location to place the decrypted information ** size -- the number of bytes to read after decryption ** ** Results: ** -1 on error ** otherwise the number of bytes read */ static ssize_t sasl_read __P((SM_FILE_T *, char *, size_t)); static ssize_t sasl_read(fp, buf, size) SM_FILE_T *fp; char *buf; size_t size; { int result; ssize_t len; # if SASL >= 20000 static const char *outbuf = NULL; # else /* SASL >= 20000 */ static char *outbuf = NULL; # endif /* SASL >= 20000 */ static unsigned int outlen = 0; static unsigned int offset = 0; struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie; /* ** sasl_decode() may require more data than a single read() returns. ** Hence we have to put a loop around the decoding. ** This also requires that we may have to split up the returned ** data since it might be larger than the allowed size. ** Therefore we use a static pointer and return portions of it ** if necessary. ** XXX Note: This function is not thread-safe nor can it be used ** on more than one file. A correct implementation would store ** this data in fp->f_cookie. */ # if SASL >= 20000 while (outlen == 0) # else /* SASL >= 20000 */ while (outbuf == NULL && outlen == 0) # endif /* SASL >= 20000 */ { len = sm_io_read(so->fp, SM_TIME_DEFAULT, buf, size); if (len <= 0) return len; result = sasl_decode(so->conn, buf, (unsigned int) len, &outbuf, &outlen); if (result != SASL_OK) { if (LogLevel > 7) sm_syslog(LOG_WARNING, NOQID, "AUTH: sasl_decode error=%d", result); outbuf = NULL; offset = 0; outlen = 0; return -1; } } if (outbuf == NULL) { /* be paranoid: outbuf == NULL but outlen != 0 */ syserr("@sasl_read failure: outbuf == NULL but outlen != 0"); /* NOTREACHED */ } if (outlen - offset > size) { /* return another part of the buffer */ (void) memcpy(buf, outbuf + offset, size); offset += size; len = size; } else { /* return the rest of the buffer */ len = outlen - offset; (void) memcpy(buf, outbuf + offset, (size_t) len); # if SASL < 20000 SASL_DEALLOC(outbuf); # endif /* SASL < 20000 */ outbuf = NULL; offset = 0; outlen = 0; } return len; } /* ** SASL_WRITE -- write information out after encrypting it ** ** Parameters: ** fp -- the file pointer ** buf -- holds the data to be encrypted and written ** size -- the number of bytes to have encrypted and written ** ** Returns: ** -1 on error ** otherwise number of bytes written */ static ssize_t sasl_write __P((SM_FILE_T *, const char *, size_t)); static ssize_t sasl_write(fp, buf, size) SM_FILE_T *fp; const char *buf; size_t size; { int result; # if SASL >= 20000 const char *outbuf; # else /* SASL >= 20000 */ char *outbuf; # endif /* SASL >= 20000 */ unsigned int outlen, *maxencode; size_t ret = 0, total = 0; struct sasl_obj *so = (struct sasl_obj *) fp->f_cookie; /* ** Fetch the maximum input buffer size for sasl_encode(). ** This can be less than the size set in attemptauth() ** due to a negotiation with the other side, e.g., ** Cyrus IMAP lmtp program sets maxbuf=4096, ** digestmd5 substracts 25 and hence we'll get 4071 ** instead of 8192 (MAXOUTLEN). ** Hack (for now): simply reduce the size, callers are (must be) ** able to deal with that and invoke sasl_write() again with ** the rest of the data. ** Note: it would be better to store this value in the context ** after the negotiation. */ result = sasl_getprop(so->conn, SASL_MAXOUTBUF, (const void **) &maxencode); if (result == SASL_OK && size > *maxencode && *maxencode > 0) size = *maxencode; result = sasl_encode(so->conn, buf, (unsigned int) size, &outbuf, &outlen); if (result != SASL_OK) { if (LogLevel > 7) sm_syslog(LOG_WARNING, NOQID, "AUTH: sasl_encode error=%d", result); return -1; } if (outbuf != NULL) { while (outlen > 0) { errno = 0; /* XXX result == 0? */ ret = sm_io_write(so->fp, SM_TIME_DEFAULT, &outbuf[total], outlen); if (ret <= 0) return ret; outlen -= ret; total += ret; } # if SASL < 20000 SASL_DEALLOC(outbuf); # endif /* SASL < 20000 */ } return size; } /* ** SFDCSASL -- create sasl file type and open in and out file pointers ** for sendmail to read from and write to. ** ** Parameters: ** fin -- the sm_io file encrypted data to be read from ** fout -- the sm_io file encrypted data to be written to ** conn -- the sasl connection pointer ** tmo -- timeout ** ** Returns: ** -1 on error ** 0 on success ** ** Side effects: ** The arguments "fin" and "fout" are replaced with the new ** SM_FILE_T pointers. */ int sfdcsasl(fin, fout, conn, tmo) SM_FILE_T **fin; SM_FILE_T **fout; sasl_conn_t *conn; int tmo; { SM_FILE_T *newin, *newout; SM_FILE_T SM_IO_SET_TYPE(sasl_vector, "sasl", sasl_open, sasl_close, sasl_read, sasl_write, NULL, sasl_getinfo, NULL, SM_TIME_DEFAULT); struct sasl_info info; if (conn == NULL) { /* no need to do anything */ return 0; } SM_IO_INIT_TYPE(sasl_vector, "sasl", sasl_open, sasl_close, sasl_read, sasl_write, NULL, sasl_getinfo, NULL, SM_TIME_DEFAULT); info.fp = *fin; info.conn = conn; newin = sm_io_open(&sasl_vector, SM_TIME_DEFAULT, &info, SM_IO_RDONLY_B, NULL); if (newin == NULL) return -1; info.fp = *fout; info.conn = conn; newout = sm_io_open(&sasl_vector, SM_TIME_DEFAULT, &info, SM_IO_WRONLY_B, NULL); if (newout == NULL) { (void) sm_io_close(newin, SM_TIME_DEFAULT); return -1; } sm_io_automode(newin, newout); sm_io_setinfo(*fin, SM_IO_WHAT_TIMEOUT, &tmo); sm_io_setinfo(*fout, SM_IO_WHAT_TIMEOUT, &tmo); *fin = newin; *fout = newout; return 0; } #endif /* SASL */ #if STARTTLS # include "sfsasl.h" # include /* Structure used by the "tls" file type */ struct tls_obj { SM_FILE_T *fp; SSL *con; }; struct tls_info { SM_FILE_T *fp; SSL *con; }; /* ** TLS_GETINFO - returns requested information about a "tls" file ** descriptor. ** ** Parameters: ** fp -- the file descriptor ** what -- the type of information requested ** valp -- the thang to return the information in (unused) ** ** Returns: ** -1 for unknown requests ** >=0 on success with valp filled in (if possible). */ static int tls_getinfo __P((SM_FILE_T *, int, void *)); /* ARGSUSED2 */ static int tls_getinfo(fp, what, valp) SM_FILE_T *fp; int what; void *valp; { struct tls_obj *so = (struct tls_obj *) fp->f_cookie; switch (what) { case SM_IO_WHAT_FD: if (so->fp == NULL) return -1; return so->fp->f_file; /* for stdio fileno() compatability */ case SM_IO_IS_READABLE: return SSL_pending(so->con) > 0; default: return -1; } } /* ** TLS_OPEN -- creates the tls specific information for opening a ** file of the tls type. ** ** Parameters: ** fp -- the file pointer associated with the new open ** info -- the sm_io file pointer holding the open and the ** TLS encryption connection to be read from or written to ** flags -- ignored ** rpool -- ignored ** ** Returns: ** 0 on success */ static int tls_open __P((SM_FILE_T *, const void *, int, const void *)); /* ARGSUSED2 */ static int tls_open(fp, info, flags, rpool) SM_FILE_T *fp; const void *info; int flags; const void *rpool; { struct tls_obj *so; struct tls_info *ti = (struct tls_info *) info; so = (struct tls_obj *) sm_malloc(sizeof(struct tls_obj)); if (so == NULL) { errno = ENOMEM; return -1; } so->fp = ti->fp; so->con = ti->con; /* ** We try to get the "raw" file descriptor that TLS uses to ** do the actual read/write with. This is to allow us control ** over the file descriptor being a blocking or non-blocking type. ** Under the covers TLS handles the change and this allows us ** to do timeouts with sm_io. */ fp->f_file = sm_io_getinfo(so->fp, SM_IO_WHAT_FD, NULL); (void) sm_io_setvbuf(so->fp, SM_TIME_DEFAULT, NULL, SM_IO_NOW, 0); fp->f_cookie = so; return 0; } /* ** TLS_CLOSE -- close the tls specific parts of the tls file pointer ** ** Parameters: ** fp -- the file pointer to close ** ** Returns: ** 0 on success */ static int tls_close __P((SM_FILE_T *)); static int tls_close(fp) SM_FILE_T *fp; { struct tls_obj *so; so = (struct tls_obj *) fp->f_cookie; if (so == NULL) return 0; if (so->fp != NULL) { sm_io_close(so->fp, SM_TIME_DEFAULT); so->fp = NULL; } sm_free(so); so = NULL; return 0; } /* maximum number of retries for TLS related I/O due to handshakes */ # define MAX_TLS_IOS 4 /* ** TLS_RETRY -- check whether a failed SSL operation can be retried ** ** Parameters: ** ssl -- TLS structure ** rfd -- read fd ** wfd -- write fd ** tlsstart -- start time of TLS operation ** timeout -- timeout for TLS operation ** err -- SSL error ** where -- description of operation ** ** Results: ** >0 on success ** 0 on timeout ** <0 on error */ int tls_retry(ssl, rfd, wfd, tlsstart, timeout, err, where) SSL *ssl; int rfd; int wfd; time_t tlsstart; int timeout; int err; const char *where; { int ret; time_t left; time_t now = curtime(); struct timeval tv; ret = -1; /* ** For SSL_ERROR_WANT_{READ,WRITE}: ** There is not a complete SSL record available yet ** or there is only a partial SSL record removed from ** the network (socket) buffer into the SSL buffer. ** The SSL_connect will only succeed when a full ** SSL record is available (assuming a "real" error ** doesn't happen). To handle when a "real" error ** does happen the select is set for exceptions too. ** The connection may be re-negotiated during this time ** so both read and write "want errors" need to be handled. ** A select() exception loops back so that a proper SSL ** error message can be gotten. */ left = timeout - (now - tlsstart); if (left <= 0) return 0; /* timeout */ tv.tv_sec = left; tv.tv_usec = 0; if (LogLevel > 14) { sm_syslog(LOG_INFO, NOQID, "STARTTLS=%s, info: fds=%d/%d, err=%d", where, rfd, wfd, err); } if (FD_SETSIZE > 0 && ((err == SSL_ERROR_WANT_READ && rfd >= FD_SETSIZE) || (err == SSL_ERROR_WANT_WRITE && wfd >= FD_SETSIZE))) { if (LogLevel > 5) { sm_syslog(LOG_ERR, NOQID, "STARTTLS=%s, error: fd %d/%d too large", where, rfd, wfd); if (LogLevel > 8) tlslogerr(where); } errno = EINVAL; } else if (err == SSL_ERROR_WANT_READ) { fd_set ssl_maskr, ssl_maskx; FD_ZERO(&ssl_maskr); FD_SET(rfd, &ssl_maskr); FD_ZERO(&ssl_maskx); FD_SET(rfd, &ssl_maskx); do { ret = select(rfd + 1, &ssl_maskr, NULL, &ssl_maskx, &tv); } while (ret < 0 && errno == EINTR); if (ret < 0 && errno > 0) ret = -errno; } else if (err == SSL_ERROR_WANT_WRITE) { fd_set ssl_maskw, ssl_maskx; FD_ZERO(&ssl_maskw); FD_SET(wfd, &ssl_maskw); FD_ZERO(&ssl_maskx); FD_SET(rfd, &ssl_maskx); do { ret = select(wfd + 1, NULL, &ssl_maskw, &ssl_maskx, &tv); } while (ret < 0 && errno == EINTR); if (ret < 0 && errno > 0) ret = -errno; } return ret; } /* errno to force refill() etc to stop (see IS_IO_ERROR()) */ #ifdef ETIMEDOUT # define SM_ERR_TIMEOUT ETIMEDOUT #else /* ETIMEDOUT */ # define SM_ERR_TIMEOUT EIO #endif /* ETIMEDOUT */ /* ** SET_TLS_RD_TMO -- read secured information for the caller ** ** Parameters: ** rd_tmo -- read timeout ** ** Results: ** none ** This is a hack: there is no way to pass it in */ static int tls_rd_tmo = -1; void set_tls_rd_tmo(rd_tmo) int rd_tmo; { tls_rd_tmo = rd_tmo; } /* ** TLS_READ -- read secured information for the caller ** ** Parameters: ** fp -- the file pointer ** buf -- the location to place the data ** size -- the number of bytes to read from connection ** ** Results: ** -1 on error ** otherwise the number of bytes read */ static ssize_t tls_read __P((SM_FILE_T *, char *, size_t)); static ssize_t tls_read(fp, buf, size) SM_FILE_T *fp; char *buf; size_t size; { int r, rfd, wfd, try, ssl_err; struct tls_obj *so = (struct tls_obj *) fp->f_cookie; time_t tlsstart; char *err; try = 99; err = NULL; tlsstart = curtime(); retry: r = SSL_read(so->con, (char *) buf, size); if (r > 0) return r; err = NULL; switch (ssl_err = SSL_get_error(so->con, r)) { case SSL_ERROR_NONE: case SSL_ERROR_ZERO_RETURN: break; case SSL_ERROR_WANT_WRITE: err = "read W BLOCK"; /* FALLTHROUGH */ case SSL_ERROR_WANT_READ: if (err == NULL) err = "read R BLOCK"; rfd = SSL_get_rfd(so->con); wfd = SSL_get_wfd(so->con); try = tls_retry(so->con, rfd, wfd, tlsstart, (tls_rd_tmo < 0) ? TimeOuts.to_datablock : tls_rd_tmo, ssl_err, "read"); if (try > 0) goto retry; errno = SM_ERR_TIMEOUT; break; case SSL_ERROR_WANT_X509_LOOKUP: err = "write X BLOCK"; break; case SSL_ERROR_SYSCALL: if (r == 0 && errno == 0) /* out of protocol EOF found */ break; err = "syscall error"; /* get_last_socket_error()); */ break; case SSL_ERROR_SSL: #if DEAL_WITH_ERROR_SSL if (r == 0 && errno == 0) /* out of protocol EOF found */ break; #endif /* DEAL_WITH_ERROR_SSL */ err = "generic SSL error"; if (LogLevel > 9) tlslogerr("read"); #if DEAL_WITH_ERROR_SSL /* avoid repeated calls? */ if (r == 0) r = -1; #endif /* DEAL_WITH_ERROR_SSL */ break; } if (err != NULL) { int save_errno; save_errno = (errno == 0) ? EIO : errno; if (try == 0 && save_errno == SM_ERR_TIMEOUT) { if (LogLevel > 7) sm_syslog(LOG_WARNING, NOQID, "STARTTLS: read error=timeout"); } else if (LogLevel > 8) sm_syslog(LOG_WARNING, NOQID, "STARTTLS: read error=%s (%d), errno=%d, get_error=%s, retry=%d, ssl_err=%d", err, r, errno, ERR_error_string(ERR_get_error(), NULL), try, ssl_err); else if (LogLevel > 7) sm_syslog(LOG_WARNING, NOQID, "STARTTLS: read error=%s (%d), retry=%d, ssl_err=%d", err, r, errno, try, ssl_err); errno = save_errno; } return r; } /* ** TLS_WRITE -- write information out through secure connection ** ** Parameters: ** fp -- the file pointer ** buf -- holds the data to be securely written ** size -- the number of bytes to write ** ** Returns: ** -1 on error ** otherwise number of bytes written */ static ssize_t tls_write __P((SM_FILE_T *, const char *, size_t)); static ssize_t tls_write(fp, buf, size) SM_FILE_T *fp; const char *buf; size_t size; { int r, rfd, wfd, try, ssl_err; struct tls_obj *so = (struct tls_obj *) fp->f_cookie; time_t tlsstart; char *err; try = 99; err = NULL; tlsstart = curtime(); retry: r = SSL_write(so->con, (char *) buf, size); if (r > 0) return r; err = NULL; switch (ssl_err = SSL_get_error(so->con, r)) { case SSL_ERROR_NONE: case SSL_ERROR_ZERO_RETURN: break; case SSL_ERROR_WANT_WRITE: err = "read W BLOCK"; /* FALLTHROUGH */ case SSL_ERROR_WANT_READ: if (err == NULL) err = "read R BLOCK"; rfd = SSL_get_rfd(so->con); wfd = SSL_get_wfd(so->con); try = tls_retry(so->con, rfd, wfd, tlsstart, DATA_PROGRESS_TIMEOUT, ssl_err, "write"); if (try > 0) goto retry; errno = SM_ERR_TIMEOUT; break; case SSL_ERROR_WANT_X509_LOOKUP: err = "write X BLOCK"; break; case SSL_ERROR_SYSCALL: if (r == 0 && errno == 0) /* out of protocol EOF found */ break; err = "syscall error"; /* get_last_socket_error()); */ break; case SSL_ERROR_SSL: err = "generic SSL error"; /* ERR_GET_REASON(ERR_peek_error())); */ if (LogLevel > 9) tlslogerr("write"); #if DEAL_WITH_ERROR_SSL /* avoid repeated calls? */ if (r == 0) r = -1; #endif /* DEAL_WITH_ERROR_SSL */ break; } if (err != NULL) { int save_errno; save_errno = (errno == 0) ? EIO : errno; if (try == 0 && save_errno == SM_ERR_TIMEOUT) { if (LogLevel > 7) sm_syslog(LOG_WARNING, NOQID, "STARTTLS: write error=timeout"); } else if (LogLevel > 8) sm_syslog(LOG_WARNING, NOQID, "STARTTLS: write error=%s (%d), errno=%d, get_error=%s, retry=%d, ssl_err=%d", err, r, errno, ERR_error_string(ERR_get_error(), NULL), try, ssl_err); else if (LogLevel > 7) sm_syslog(LOG_WARNING, NOQID, "STARTTLS: write error=%s (%d), errno=%d, retry=%d, ssl_err=%d", err, r, errno, try, ssl_err); errno = save_errno; } return r; } /* ** SFDCTLS -- create tls file type and open in and out file pointers ** for sendmail to read from and write to. ** ** Parameters: ** fin -- data input source being replaced ** fout -- data output source being replaced ** con -- the tls connection pointer ** ** Returns: ** -1 on error ** 0 on success ** ** Side effects: ** The arguments "fin" and "fout" are replaced with the new ** SM_FILE_T pointers. ** The original "fin" and "fout" are preserved in the tls file ** type but are not actually used because of the design of TLS. */ int sfdctls(fin, fout, con) SM_FILE_T **fin; SM_FILE_T **fout; SSL *con; { SM_FILE_T *tlsin, *tlsout; SM_FILE_T SM_IO_SET_TYPE(tls_vector, "tls", tls_open, tls_close, tls_read, tls_write, NULL, tls_getinfo, NULL, SM_TIME_FOREVER); struct tls_info info; SM_ASSERT(con != NULL); SM_IO_INIT_TYPE(tls_vector, "tls", tls_open, tls_close, tls_read, tls_write, NULL, tls_getinfo, NULL, SM_TIME_FOREVER); info.fp = *fin; info.con = con; tlsin = sm_io_open(&tls_vector, SM_TIME_DEFAULT, &info, SM_IO_RDONLY_B, NULL); if (tlsin == NULL) return -1; info.fp = *fout; tlsout = sm_io_open(&tls_vector, SM_TIME_DEFAULT, &info, SM_IO_WRONLY_B, NULL); if (tlsout == NULL) { (void) sm_io_close(tlsin, SM_TIME_DEFAULT); return -1; } sm_io_automode(tlsin, tlsout); *fin = tlsin; *fout = tlsout; return 0; } #endif /* STARTTLS */