--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





Reply via email to