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:

2712c2712
< DEFS=-DHAVE_CONFIG_H
---
> DEFS="-DHAVE_CONFIG_H ${DEFS}"

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:

51c51
< LIBS = @LIBS@
---
> LIBS = @LIBS@ -lssl -lcrypto
58c58
<       im_util.o abook.o authize.o alock.o sasl_support.o @HAVE_LDAP_OBJS@
---
>       im_util.o abook.o authize.o alock.o sasl_support.o tls.o 
@HAVE_LDAP_OBJS@

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:

121a122,125
> #ifdef HAVE_SSL
>     fbuf->tls_conn=NULL;
> #endif /* HAVE_SSL */
>
278a283,308
> /* 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);
>     }
> }
>
288,289c318
<     char *src, *dst;
<     int count, bytes, result = -1;
---
>     int result = -1;
304,319c333,334
<       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);
<       }
---
>       scan -= fbuf->uend - fbuf->ibuf;
>       compact_input();
357a373,380
> #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 */
358a382
> #endif /* HAVE_SSL */
416c440
<     int count, remaining = size, total;
---
>     int count, remaining = size, total = 0;
418,430c442,455
<     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); }
---
>     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);
432d456
<           buf += count;
434,435c458,461
<       } while ((remaining > 0) && !fbuf->nonblocking);
<     }
---
>           fbuf->uend += count;
>           buf += count;
>       }
>     } while ((remaining > 0) && !fbuf->nonblocking);
506a533,539
> #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 */
507a541
> #endif /* HAVE_SSL */
633a668,684
>
> #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:

48a49,52
> #ifdef HAVE_SSL
> #include <openssl/ssl.h>
> #endif /* HAVE_SSL */
>
77a82,85
>
> #ifdef HAVE_SSL
>     SSL *tls_conn;
> #endif /* HAVE_SSL */
145a154,158
>
> #ifdef HAVE_SSL
> /* Set TLS */
> int dispatch_settls(fbuf_t *fbuf, SSL *tls_conn);
> #endif /* HAVE_SSL */
-------------------------------------------------------------------------

imsp/imsp_server.c

78,79c78,81
< /* import from OS */
< extern char *malloc(), *realloc();
---
> #ifdef HAVE_SSL
> #include <sysexits.h>
> #include "tls.h"
> #endif /* HAVE_SSL */
136a139
> #define IMSP_STARTTLS      34
169a173,189
> 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 */
>
280a301,306
> /* 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";
320a347,358
>     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
>
457a496,598
> #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 */
>
1698a1840,1844
> #ifdef HAVE_SSL
>   if (starttls_enabled())
>     SEND_STRING(fbuf, " STARTTLS");
> #endif
>
1765a1912,1913
>     } else if (imsp_starttls_done) {
>       SEND_RESPONSE(fbuf, tag, rpl_tlscomplete);
1776a1925,1949
> /* 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 : "";
> }
>
1813a1987
>     {"starttls", IMSP_STARTTLS, imsp_starttls},
1828a2003,2005
>     imsp_saslconn = 0;
>     imsp_starttls_done = 0;
>
1871a2049,2056
>
> #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 */
-------------------------------------------------------------------------

imsp/im_util.c:

59,60d58
< /* import from OS: */
< extern char *malloc(), *realloc();
-------------------------------------------------------------------------

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:

51c51
< LIBS = @LIBS@
---
> LIBS = @LIBS@ -lssl -lcrypto
58c58
<       im_util.o abook.o authize.o alock.o sasl_support.o @HAVE_LDAP_OBJS@
---
>       im_util.o abook.o authize.o alock.o sasl_support.o tls.o 
@HAVE_LDAP_OBJS@

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




Reply via email to