--On Saturday, 2002 January 19 10:14 -0500 Lawrence Greenfield <[EMAIL PROTECTED]> wrote:
> Please use context or unified diffs. > > diff -c or diff -u. [OK. This is a repost of the same stuff in the requested format. Just more lines.] I've searched the list and while several have asked about implementing this, the only approach suggested was with stunnel. I looked at the code and it seemed pretty easy just to do it there. I thought I should publish it. I've tested it with IMSPd running on Mac OS X Server with a Mulberry client. Works fine for me. All changes are against the posted cyrus-imspd-v1.6a3 baseline. 1. There is some reference to a HAVE_SSL compile flag within the lib code, but none of this is really useful. But I did adopt the flag since the installation instructions implied one could just define an environment variable DEFS. You could but it wouldn't get picked up. So I changed the configure file: configure: *** ../cyrus-imspd-v1.6a3/configure Thu Dec 21 15:26:50 2000 --- ./configure Sun Jan 13 22:11:16 2002 *************** *** 2709,2715 **** trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 ! DEFS=-DHAVE_CONFIG_H # Without the "./", some shells look in PATH for config.status. : ${CONFIG_STATUS=./config.status} --- 2709,2715 ---- trap 'rm -f $CONFIG_STATUS conftest*; exit 1' 1 2 15 ! DEFS="-DHAVE_CONFIG_H ${DEFS}" # Without the "./", some shells look in PATH for config.status. : ${CONFIG_STATUS=./config.status} ------------------------------------------------------------------------- 2. Grab the tls.c and tls.h files from IMAPd and put them in the imsp directory. You need to change the makefiles and I'm sure you can do a better job than my hack: imsp/Makefile.in: *** ../../cyrus-imspd-v1.6a3/imsp/Makefile.in Mon Dec 18 03:27:42 2000 --- ./Makefile.in Mon Jan 14 23:29:35 2002 *************** *** 48,61 **** DEFS = @DEFS@ $(DEFINES) CPPFLAGS = -I.. -I. -I$(srcdir) -I$(srcdir)/../lib @CPPFLAGS@ ! LIBS = @LIBS@ DEPLIBS = ../lib/libcyrus.a @DEPLIBS@ CFLAGS = @CFLAGS@ LDFLAGS = @LDFLAGS@ IMSPDOBJS= main.o dispatch.o imsp_server.o option.o syncdb.o adate.o \ ! im_util.o abook.o authize.o alock.o sasl_support.o @HAVE_LDAP_OBJS@ PROGS = cyrus-imspd PUREPROGS = cyrus-imspd.pure --- 48,61 ---- DEFS = @DEFS@ $(DEFINES) CPPFLAGS = -I.. -I. -I$(srcdir) -I$(srcdir)/../lib @CPPFLAGS@ ! LIBS = @LIBS@ -lssl -lcrypto DEPLIBS = ../lib/libcyrus.a @DEPLIBS@ CFLAGS = @CFLAGS@ LDFLAGS = @LDFLAGS@ IMSPDOBJS= main.o dispatch.o imsp_server.o option.o syncdb.o adate.o \ ! im_util.o abook.o authize.o alock.o sasl_support.o tls.o @HAVE_LDAP_OBJS@ PROGS = cyrus-imspd PUREPROGS = cyrus-imspd.pure ------------------------------------------------------------------------- 3. The rest of the changes are to code in the imsp directory. Most of it is just adapted from IMAPd. The only surprise was in dispatch.c which caused problems when literals are read separately from the line terminators. This stalled and never discovered that the line terminators were in fact available. I recalled an OpenSSL quirk and changing dispatch seemed the easiest way to maintain code compatibility. imsp/dispatch.c: *** ../../cyrus-imspd-v1.6a3/imsp/dispatch.c Wed Dec 20 14:02:40 2000 --- ./dispatch.c Fri Jan 18 22:39:30 2002 *************** *** 119,124 **** --- 119,128 ---- fbuf->telem = -1; fbuf->saslconn = NULL; + #ifdef HAVE_SSL + fbuf->tls_conn=NULL; + #endif /* HAVE_SSL */ + } /* set dispatch err function *************** *** 276,281 **** --- 280,311 ---- return (0); } + /* compact input buffer to make more room for reads + * extracted from original parse_line + */ + static void compact_input(fbuf) + fbuf_t *fbuf; + { + char *src, *dst; + int count, bytes; + + dst = fbuf->ibuf; + src = fbuf->uend; + bytes = fbuf->iptr - src; + count = src - dst; + if (!bytes) { + fbuf->uend = fbuf->iptr = dst; + fbuf->ileft = MAX_BUF; + } else if (count) { + fbuf->uend = dst; + fbuf->iptr -= count; + fbuf->ileft += count; + do { + *dst++ = *src++; + } while (--bytes); + } + } + /* try to parse a CRLF terminated line from the input buffer * start search at "*pscan" * returns -1 for failure, 0 for success *************** *** 285,292 **** char **pscan; { char *scan = *pscan; ! char *src, *dst; ! int count, bytes, result = -1; /* try to grab a line */ while (scan + 1 < fbuf->iptr && (scan[0] != '\r' || scan[1] != '\n')) { --- 315,321 ---- char **pscan; { char *scan = *pscan; ! int result = -1; /* try to grab a line */ while (scan + 1 < fbuf->iptr && (scan[0] != '\r' || scan[1] != '\n')) { *************** *** 301,322 **** result = 0; } else { /* if not, shift the buffer to make room for more stuff */ ! dst = fbuf->ibuf; ! src = fbuf->uend; ! bytes = fbuf->iptr - src; ! count = src - dst; ! scan -= count; ! if (!bytes) { ! fbuf->uend = fbuf->iptr = dst; ! fbuf->ileft = MAX_BUF; ! } else if (count) { ! fbuf->uend = dst; ! fbuf->iptr -= count; ! fbuf->ileft += count; ! do { ! *dst++ = *src++; ! } while (--bytes); ! } } *pscan = scan; --- 330,337 ---- result = 0; } else { /* if not, shift the buffer to make room for more stuff */ ! scan -= fbuf->uend - fbuf->ibuf; ! compact_input(); } *pscan = scan; *************** *** 355,361 **** --- 370,385 ---- if (!fbuf->nonblocking && dispatch_loop(fbuf->fd, 0) < 0) { result = -1; } else { + #ifdef HAVE_SSL + /* just do a SSL read instead if we're under a tls layer */ + if (fbuf->tls_conn != NULL) { + count = SSL_read(fbuf->tls_conn, ptr, len); + } else { + count = read(fbuf->fd, ptr, len); + } + #else /* HAVE_SSL */ count = read(fbuf->fd, ptr, len); + #endif /* HAVE_SSL */ if (count == 0) { fbuf->eof = 1; break; *************** *** 413,438 **** char *buf; int size; { ! int count, remaining = size, total; ! total = count = fbuf->iptr - fbuf->uend; ! if (count) { ! if (remaining < count) count = remaining; ! memcpy(buf, fbuf->uend, count); ! remaining -= count; ! fbuf->uend += count; ! buf += count; ! } ! if (remaining > 0) { ! do { ! count = fill_buf(fbuf, buf, remaining); ! if (count == 0) break; ! if (count < 0) { (*err_proc)(DISPATCH_READ_ERR); return (count); } total += count; - buf += count; remaining -= count; ! } while ((remaining > 0) && !fbuf->nonblocking); ! } return (total); } --- 437,464 ---- char *buf; int size; { ! int count, remaining = size, total = 0; ! do { ! if (fbuf->iptr - fbuf->uend < remaining) { ! compact_input(fbuf); ! count = fill_buf(fbuf, fbuf->iptr, fbuf->ileft); ! if (count == 0) remaining = fbuf->iptr - fbuf->uend; ! if (count < 0) { (*err_proc)(DISPATCH_READ_ERR); return (count); } ! fbuf->iptr += count; ! fbuf->ileft -= count; ! } ! ! count = fbuf->iptr - fbuf->uend; ! if (count) { ! if (remaining < count) count = remaining; ! memcpy(buf, fbuf->uend, count); total += count; remaining -= count; ! fbuf->uend += count; ! buf += count; ! } ! } while ((remaining > 0) && !fbuf->nonblocking); return (total); } *************** *** 504,510 **** --- 530,544 ---- (*err_proc)(DISPATCH_WRITE_ERR); return (-1); } + #ifdef HAVE_SSL + if (fbuf->tls_conn != NULL) { + count = SSL_write(fbuf->tls_conn, ptr, elen); + } else { + count = write(fbuf->fd, ptr, elen); + } + #else /* HAVE_SSL */ count = write(fbuf->fd, ptr, elen); + #endif /* HAVE_SSL */ if (count < 0) { if (errno != EINTR && errno != EINPROGRESS) { (*err_proc)(DISPATCH_WRITE_ERR); *************** *** 631,633 **** --- 665,684 ---- return 0; } + + #ifdef HAVE_SSL + + /* + * Turn on TLS for this connection + */ + + int dispatch_settls(fbuf_t *fbuf, SSL *tlsconn) + { + fbuf->tls_conn = tlsconn; + + return 0; + } + + #endif /* HAVE_SSL */ + + ------------------------------------------------------------------------- imsp/dispatch.h: *** ../../cyrus-imspd-v1.6a3/imsp/dispatch.h Sun Dec 17 19:13:15 2000 --- ./dispatch.h Mon Jan 14 21:51:24 2002 *************** *** 46,51 **** --- 46,55 ---- #include <sasl.h> + #ifdef HAVE_SSL + #include <openssl/ssl.h> + #endif /* HAVE_SSL */ + /* a file buffer structure */ typedef struct fbuf_t { *************** *** 75,80 **** --- 79,88 ---- char pbuf[MAX_BUF+4]; /* protection buffered data */ sasl_conn_t *saslconn; + + #ifdef HAVE_SSL + SSL *tls_conn; + #endif /* HAVE_SSL */ } fbuf_t; /* a dispatch structure *************** *** 143,148 **** --- 151,161 ---- /* Add SASL */ int dispatch_addsasl(fbuf_t *fbuf, sasl_conn_t *conn); + + #ifdef HAVE_SSL + /* Set TLS */ + int dispatch_settls(fbuf_t *fbuf, SSL *tls_conn); + #endif /* HAVE_SSL */ /* activate telemetry for user */ void dispatch_telemetry(fbuf_t *, char *); ------------------------------------------------------------------------- imsp/imsp_server.c *** ../../cyrus-imspd-v1.6a3/imsp/imsp_server.c Mon Dec 18 02:21:42 2000 --- ./imsp_server.c Fri Jan 18 22:38:43 2002 *************** *** 75,82 **** #include "alock.h" #include "sasl_support.h" ! /* import from OS */ ! extern char *malloc(), *realloc(); /* structure used for command dispatch list */ typedef struct command_t { --- 75,84 ---- #include "alock.h" #include "sasl_support.h" ! #ifdef HAVE_SSL ! #include <sysexits.h> ! #include "tls.h" ! #endif /* HAVE_SSL */ /* structure used for command dispatch list */ typedef struct command_t { *************** *** 134,139 **** --- 136,142 ---- #define IMSP_LMARKED 31 #define IMSP_LAST 32 #define IMSP_SEEN 33 + #define IMSP_STARTTLS 34 /* IMSP find options */ #define FIND_MAILBOXES 0 *************** *** 167,172 **** --- 170,192 ---- /* the sasl connection context */ sasl_conn_t *imsp_saslconn; + static int imsp_starttls_done; /* have we done a successful starttls? */ + + #ifdef HAVE_SSL + /* our tls connection, if any */ + static SSL *tls_conn = NULL; + + static char *tls_ca_file; + static char *tls_ca_path; + static char *tls_cert_file; + static char *tls_key_file; + + static char opt_tls_ca_file[] = "imsp.tls.ca_file"; + static char opt_tls_ca_path[] = "imsp.tls.ca_path"; + static char opt_tls_cert_file[] = "imsp.tls.cert_file"; + static char opt_tls_key_file[] = "imsp.tls.key_file"; + #endif /* HAVE_SSL */ + /* file buffer used by idle procedure */ static fbuf_t im_fbuf; *************** *** 278,283 **** --- 298,309 ---- static char rpl_lockfail[] = "%a NO failed to %a %a%a '%p'\r\n"; /* SEEN/LAST messages */ static char rpl_dbfail[] = "NO failed to update mailbox database\r\n"; + /* STARTTLS messages */ + static char rpl_latetls[] = "BAD Can't STARTTLS after authentication\r\n"; + static char rpl_tlscomplete[] = "BAD Already did a successful STARTTLS\r\n"; + static char rpl_tlserror[] = "NO Error initializing TLS\r\n"; + static char rpl_tlsneg[] = "OK Begin TLS negotiation now\r\n"; + static char rpl_tlsfail[] = "NO Starttls failed\r\n"; /* macros to send messages */ #define SEND_STRING(fbuf, str) dispatch_write((fbuf), (str), sizeof (str) - 1) *************** *** 318,323 **** --- 344,361 ---- /* clean up authorization */ auth_free(imsp_id); + if (imsp_saslconn) { + sasl_dispose(&imsp_saslconn); + imsp_saslconn = NULL; + } + imsp_starttls_done = 0; + #ifdef HAVE_SSL + if (tls_conn) { + tls_free(&tls_conn); + tls_conn = NULL; + } + #endif + exit(0); } *************** *** 455,460 **** --- 493,601 ---- return (len); } + #ifdef HAVE_SSL + /* + * this implements the STARTTLS command, as described in RFC 2595. + * one caveat: it assumes that no external layer is currently present. + * if a client executes this command, information about the external + * layer that was passed on the command line is disgarded. this should + * be fixed. + */ + int starttls_enabled(void) + { + if (strlen(tls_cert_file) == 0) return 0; + if (strlen(tls_key_file) == 0) return 0; + return 1; + } + + /* imsps - whether this is an imsps transaction or not */ + void cmd_starttls(fbuf_t *fbuf, char *tag, char *host, int imsps) + { + int result; + int *layerp; + sasl_external_properties_t external; + + /* SASL and openssl have different ideas about whether ssf is signed */ + layerp = (int *) &(external.ssf); + + result=tls_init_serverengine(5, /* depth to verify */ + !imsps, /* can client auth? */ + 0, /* require client to auth? */ + !imsps, /* TLS only? */ + tls_ca_file, + tls_ca_path, + tls_cert_file, + tls_key_file); + + if (result == -1) { + + syslog(LOG_ERR, "error initializing TLS: " + "[CA_file: %s] [CA_path: %s] [cert_file: %s] [key_file: %s]", + tls_ca_file, + tls_ca_path, + tls_cert_file, + tls_key_file); + + if (imsps == 0) { + SEND_RESPONSE(fbuf, tag, rpl_tlserror); + } else { + fatal("tls_init() failed", EX_TEMPFAIL); + } + + return; + } + + if (imsps == 0) + { + SEND_RESPONSE(fbuf, tag, rpl_tlsneg); + /* must flush our buffers before starting tls */ + dispatch_flush(fbuf); + } + + result=tls_start_servertls(fbuf->fd, /* read */ + fbuf->fd, /* write */ + layerp, + &(external.auth_id), + &tls_conn); + + /* if error */ + if (result==-1) { + if (imsps == 0) { + SEND_RESPONSE(fbuf, tag, rpl_tlsfail); + syslog(LOG_NOTICE, "STARTTLS failed: %s", host); + return; + } else { + syslog(LOG_NOTICE, "imaps failed: %s", host); + fatal("tls_start_servertls() failed", EX_TEMPFAIL); + return; + } + } + + /* tell SASL about the negotiated layer */ + result = sasl_setprop(imsp_saslconn, SASL_SSF_EXTERNAL, &external); + + if (result != SASL_OK) { + fatal("sasl_setprop() failed: cmd_starttls()", EX_TEMPFAIL); + } + + /* tell the prot layer about our new layers */ + dispatch_settls(fbuf, tls_conn); + + imsp_starttls_done = 1; + } + #else + int starttls_enabled(void) + { + return 0; + } + + void cmd_starttls(fbuf_t *fbuf, char *tag, char *host, int imaps) + { + fatal("cmd_starttls() executed, but starttls isn't implemented!", + EC_SOFTWARE); + } + #endif /* HAVE_SSL */ + /* authenticate the user */ static void imsp_authenticate(fbuf, cp, tag, id, host) *************** *** 1696,1701 **** --- 1837,1847 ---- /* send the first part */ SEND_STRING(fbuf, msg_capability); + #ifdef HAVE_SSL + if (starttls_enabled()) + SEND_STRING(fbuf, " STARTTLS"); + #endif + /* maybe send the sasl stuff */ if (sasl_listmech(imsp_saslconn, NULL, " AUTH=", " AUTH=", "", *************** *** 1763,1768 **** --- 1909,1916 ---- SEND_RESPONSE(fbuf, tag, rpl_badauth); } else if (bb_seen(mbox, uid, uname) < 0) { SEND_RESPONSE(fbuf, tag, rpl_dbfail); + } else if (imsp_starttls_done) { + SEND_RESPONSE(fbuf, tag, rpl_tlscomplete); } else { SEND_RESPONSE1(fbuf, tag, rpl_complete, cp->word); } *************** *** 1774,1779 **** --- 1922,1952 ---- #endif } + /* do the "STARTTLS" command + */ + static void imsp_starttls(fbuf, cp, tag, id, host) + fbuf_t *fbuf; + command_t *cp; + char *tag, *host; + auth_id *id; + { + if (!starttls_enabled()) { + /* we don't support starttls */ + SEND_RESPONSE1(fbuf, tag, rpl_invalcommand, cp->word); + } else if (fbuf->upos != fbuf->lend) { + SEND_RESPONSE1(fbuf, tag, rpl_noargs, cp->word); + } else if (imsp_id != NULL) { + SEND_RESPONSE(fbuf, tag, rpl_latetls); + } else { + cmd_starttls(fbuf, tag, host, 0); + } + } + + char *option_get_string(char *opt) { + char *value = option_get("", opt, 1, NULL); + return value ? value : ""; + } + /* list of commands & procedures to manage them */ static command_t com_list[] = { *************** *** 1811,1816 **** --- 1984,1990 ---- {"lmarked", IMSP_LMARKED, imsp_list}, {"last", IMSP_LAST, imsp_last}, {"seen", IMSP_SEEN, imsp_seen}, + {"starttls", IMSP_STARTTLS, imsp_starttls}, {NULL, 0, NULL} }; *************** *** 1826,1831 **** --- 2000,2008 ---- char *p; const char *errstr; + imsp_saslconn = 0; + imsp_starttls_done = 0; + /* if the IMAP shutdown file exists, send its contents as an alert and exit */ if ((shutdown = fopen(SHUTDOWNFILENAME, "r")) != NULL) { *************** *** 1869,1874 **** --- 2046,2059 ---- /* initialize signal handlers to nuke password */ imsp_set_signals(); + + #ifdef HAVE_SSL + /* pull in the STARTTLS specific info */ + tls_ca_file = option_get_string(opt_tls_ca_file); + tls_ca_path = option_get_string(opt_tls_ca_path); + tls_cert_file = option_get_string(opt_tls_cert_file); + tls_key_file = option_get_string(opt_tls_key_file); + #endif /* HAVE_SSL */ /* main protocol loop */ while (dispatch_readline(fbuf) != NULL) { ------------------------------------------------------------------------- imsp/im_util.c: *** ../../cyrus-imspd-v1.6a3/imsp/im_util.c Sun Dec 17 19:16:25 2000 --- ./im_util.c Fri Jan 18 22:17:21 2002 *************** *** 56,63 **** #include <varargs.h> #endif - /* import from OS: */ - extern char *malloc(), *realloc(); #define MAX_DIGITS 32 /* max number of digits in long integer */ /* flag that a literal is ready to be sent */ --- 56,61 ---- ------------------------------------------------------------------------- 4. I'm not into make and configure files anymore, and because all of my client machines are configured the same, I don't care. So anyone who enjoys doing this can certainly make it more general. Just to indicate what needs to be done for builds, I changed: imsp/Makefile.in: *** ../../cyrus-imspd-v1.6a3/imsp/Makefile.in Mon Dec 18 03:27:42 2000 --- ./Makefile.in Mon Jan 14 23:29:35 2002 *************** *** 48,61 **** DEFS = @DEFS@ $(DEFINES) CPPFLAGS = -I.. -I. -I$(srcdir) -I$(srcdir)/../lib @CPPFLAGS@ ! LIBS = @LIBS@ DEPLIBS = ../lib/libcyrus.a @DEPLIBS@ CFLAGS = @CFLAGS@ LDFLAGS = @LDFLAGS@ IMSPDOBJS= main.o dispatch.o imsp_server.o option.o syncdb.o adate.o \ ! im_util.o abook.o authize.o alock.o sasl_support.o @HAVE_LDAP_OBJS@ PROGS = cyrus-imspd PUREPROGS = cyrus-imspd.pure --- 48,61 ---- DEFS = @DEFS@ $(DEFINES) CPPFLAGS = -I.. -I. -I$(srcdir) -I$(srcdir)/../lib @CPPFLAGS@ ! LIBS = @LIBS@ -lssl -lcrypto DEPLIBS = ../lib/libcyrus.a @DEPLIBS@ CFLAGS = @CFLAGS@ LDFLAGS = @LDFLAGS@ IMSPDOBJS= main.o dispatch.o imsp_server.o option.o syncdb.o adate.o \ ! im_util.o abook.o authize.o alock.o sasl_support.o tls.o @HAVE_LDAP_OBJS@ PROGS = cyrus-imspd PUREPROGS = cyrus-imspd.pure ------------------------------------------------------------------------- The build proceeds as normal, but I define an environment variable before starting: setenv DEFS "-DHAVE_SSL" As far as configuration goes, there are new options to be set in the normal IMSPd options file: imsp.tls.ca_file imsp.tls.ca_path imsp.tls.cert_file imsp.tls.key_file This is all the same as for Cyrus IMAPd, so refer to that documentation for more information. Cheers, Mark