commit eb1005151cd5084b89ca357c0ee7d7dba3a13444
Author: Oswald Buddenhagen <[email protected]>
Date:   Sun Jul 27 18:41:22 2014 +0200

    add SASL support
    
    patch initially by Jack Stone <[email protected]>,
    cleaned up by Jan Synacek <[email protected]>,
    ... and then almost completely rewritten by me. ^^

 NEWS            |    2 +-
 README          |    2 +-
 TODO            |    4 -
 configure.ac    |   54 ++++++++++-
 src/Makefile.am |    2 +-
 src/drv_imap.c  |  235 +++++++++++++++++++++++++++++++++++++++++++++++
 src/mbsync.1    |    3 +-
 7 files changed, 287 insertions(+), 15 deletions(-)

diff --git a/NEWS b/NEWS
index 7bcc75b..2d2b2c0 100644
--- a/NEWS
+++ b/NEWS
@@ -6,7 +6,7 @@ The SSL/TLS configuration has been re-designed.
 SSL is now explicitly enabled or disabled - "use SSL if available" is gone.
 Notice: Tunnels are assumed to be secure and thus default to no SSL.
 
-More flexible configuration of the used authentication mechanism.
+Support for SASL (flexible authentication) has been added.
 
 [1.1.0]
 
diff --git a/README b/README
index 2c634e5..a973a70 100644
--- a/README
+++ b/README
@@ -32,7 +32,7 @@ isync executable still exists; it is a compatibility wrapper 
around mbsync.
     * Trash functionality: backup messages before removing them
     * IMAP features:
        * Supports TLS/SSL via imaps: (port 993) and STARTTLS (RFC2595)
-       * Supports CRAM-MD5 (RFC2195) for authentication
+       * Supports SASL (RFC4422) for authentication
        * Supports NAMESPACE (RFC2342) for simplified configuration
        * Pipelining for maximum speed
 
diff --git a/TODO b/TODO
index ba556f9..02d80b4 100644
--- a/TODO
+++ b/TODO
@@ -6,10 +6,6 @@ real transactions would be certainly not particularly useful 
...
 make sync_chans() aware of servers, so a bad server (e.g., wrong password)
 won't cause the same error message for every attached store.
 
-add support for more authentication methods: oauth, ntlm, ... use SASL?
-possibly by calling an external command. that might be overkill, and
-wouldn't be very user-friendly, though.
-
 make SSL (connect) timeouts produce a bit more than "Unidentified socket 
error".
 
 network timeout handling in general would be a good idea.
diff --git a/configure.ac b/configure.ac
index 4b4b108..ac696ee 100644
--- a/configure.ac
+++ b/configure.ac
@@ -100,6 +100,45 @@ if test "x$ob_cv_with_ssl" != xno; then
 fi
 AC_SUBST(SSL_LIBS)
 
+have_sasl_paths=
+AC_ARG_WITH(sasl,
+  AS_HELP_STRING([--with-sasl[=PATH]], [where to look for SASL [detect]]),
+  [ob_cv_with_sasl=$withval])
+if test "x$ob_cv_with_sasl" != xno; then
+  case $ob_cv_with_sasl in
+    ""|yes)
+      dnl FIXME: Try various possible paths here...
+      ;;
+    *)
+      SASL_LDFLAGS=-L$ob_cv_with_sasl/lib$libsuff
+      SASL_CPPFLAGS=-I$ob_cv_with_sasl/include
+      ;;
+  esac
+  if test -z "$have_sasl_paths"; then
+    sav_LDFLAGS=$LDFLAGS
+    LDFLAGS="$LDFLAGS $SASL_LDFLAGS"
+    AC_CHECK_LIB(sasl2, sasl_client_init,
+                 [SASL_LIBS="-lsasl2" have_sasl_paths=yes])
+    LDFLAGS=$sav_LDFLAGS
+  fi
+
+  sav_CPPFLAGS=$CPPFLAGS
+  CPPFLAGS="$CPPFLAGS $SASL_CPPFLAGS"
+  AC_CHECK_HEADER(sasl/sasl.h, , [have_sasl_paths=])
+  CPPFLAGS=$sav_CPPFLAGS
+
+  if test -z "$have_sasl_paths"; then
+    if test -n "$ob_cv_with_sasl"; then
+      AC_MSG_ERROR([SASL libs and/or includes were not found where specified])
+    fi
+  else
+    AC_DEFINE(HAVE_LIBSASL, 1, [if you have the SASL libraries])
+    CPPFLAGS="$CPPFLAGS $SASL_CPPFLAGS"
+    LDFLAGS="$LDFLAGS $SASL_LDFLAGS"
+  fi
+fi
+AC_SUBST(SASL_LIBS)
+
 AC_CACHE_CHECK([for Berkley DB >= 4.2], ac_cv_berkdb4,
   [ac_cv_berkdb4=no
    AC_TRY_LINK([#include <db.h>],
@@ -122,12 +161,15 @@ AM_CONDITIONAL(with_compat, test "x$ob_cv_enable_compat" 
!= xno)
 AC_CONFIG_FILES([Makefile src/Makefile src/compat/Makefile isync.spec])
 AC_OUTPUT
 
+AC_MSG_RESULT()
 if test -n "$have_ssl_paths"; then
-    AC_MSG_RESULT([
-Using SSL
-])
+    AC_MSG_RESULT([Using SSL])
+else
+    AC_MSG_RESULT([Not using SSL])
+fi
+if test -n "$have_sasl_paths"; then
+    AC_MSG_RESULT([Using SASL])
 else
-    AC_MSG_RESULT([
-Not using SSL
-])
+    AC_MSG_RESULT([Not using SASL])
 fi
+AC_MSG_RESULT()
diff --git a/src/Makefile.am b/src/Makefile.am
index 9cfd0af..85d5bab 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -6,7 +6,7 @@ SUBDIRS = $(compat_dir)
 bin_PROGRAMS = mbsync mdconvert
 
 mbsync_SOURCES = main.c sync.c config.c util.c socket.c driver.c drv_imap.c 
drv_maildir.c
-mbsync_LDADD = -ldb $(SSL_LIBS) $(SOCK_LIBS)
+mbsync_LDADD = -ldb $(SSL_LIBS) $(SOCK_LIBS) $(SASL_LIBS)
 noinst_HEADERS = common.h config.h driver.h sync.h socket.h
 
 mdconvert_SOURCES = mdconvert.c
diff --git a/src/drv_imap.c b/src/drv_imap.c
index e92d435..4a0ab82 100644
--- a/src/drv_imap.c
+++ b/src/drv_imap.c
@@ -36,6 +36,11 @@
 #include <time.h>
 #include <sys/wait.h>
 
+#ifdef HAVE_LIBSASL
+# include <sasl/sasl.h>
+# include <sasl/saslutil.h>
+#endif
+
 #ifdef HAVE_LIBSSL
 enum { SSL_None, SSL_STARTTLS, SSL_IMAPS };
 #endif
@@ -115,6 +120,10 @@ typedef struct imap_store {
                void (*imap_cancel)( void *aux );
        } callbacks;
        void *callback_aux;
+#ifdef HAVE_LIBSASL
+       sasl_conn_t *sasl;
+       int sasl_cont;
+#endif
 
        conn_t conn; /* this is BIG, so put it last */
 } imap_store_t;
@@ -173,6 +182,9 @@ struct imap_cmd_refcounted {
 
 enum CAPABILITY {
        NOLOGIN = 0,
+#ifdef HAVE_LIBSASL
+       SASLIR,
+#endif
 #ifdef HAVE_LIBSSL
        STARTTLS,
 #endif
@@ -184,6 +196,9 @@ enum CAPABILITY {
 
 static const char *cap_list[] = {
        "LOGINDISABLED",
+#ifdef HAVE_LIBSASL
+       "SASL-IR",
+#endif
 #ifdef HAVE_LIBSSL
        "STARTTLS",
 #endif
@@ -1354,6 +1369,9 @@ imap_cancel_store( store_t *gctx )
 {
        imap_store_t *ctx = (imap_store_t *)gctx;
 
+#ifdef HAVE_LIBSASL
+       sasl_dispose( &ctx->sasl );
+#endif
        socket_close( &ctx->conn );
        cancel_submitted_imap_cmds( ctx );
        cancel_pending_imap_cmds( ctx );
@@ -1708,6 +1726,162 @@ ensure_password( imap_server_conf_t *srvc )
        return srvc->pass;
 }
 
+#ifdef HAVE_LIBSASL
+
+static sasl_callback_t sasl_callbacks[] = {
+       { SASL_CB_USER,     NULL, NULL },
+       { SASL_CB_PASS,     NULL, NULL },
+       { SASL_CB_LIST_END, NULL, NULL }
+};
+
+static int
+process_sasl_interact( sasl_interact_t *interact, imap_server_conf_t *srvc )
+{
+       const char *val;
+
+       for (;; ++interact) {
+               switch (interact->id) {
+               case SASL_CB_LIST_END:
+                       return 0;
+               case SASL_CB_USER:
+                       val = ensure_user( srvc );
+                       break;
+               case SASL_CB_PASS:
+                       val = ensure_password( srvc );
+                       break;
+               default:
+                       error( "Error: Unknown SASL interaction ID\n" );
+                       return -1;
+               }
+               if (!val)
+                       return -1;
+               interact->result = val;
+               interact->len = strlen( val );
+       }
+}
+
+static int
+process_sasl_step( imap_store_t *ctx, int rc, const char *in, unsigned in_len,
+                   sasl_interact_t *interact, const char **out, unsigned 
*out_len )
+{
+       imap_server_conf_t *srvc = ((imap_store_conf_t *)ctx->gen.conf)->server;
+
+       while (rc == SASL_INTERACT) {
+               if (process_sasl_interact( interact, srvc ) < 0)
+                       return -1;
+               rc = sasl_client_step( ctx->sasl, in, in_len, &interact, out, 
out_len );
+       }
+       if (rc == SASL_CONTINUE) {
+               ctx->sasl_cont = 1;
+       } else if (rc == SASL_OK) {
+               ctx->sasl_cont = 0;
+       } else {
+               error( "Error: %s\n", sasl_errdetail( ctx->sasl ) );
+               return -1;
+       }
+       return 0;
+}
+
+static int
+decode_sasl_data( const char *prompt, char **in, unsigned *in_len )
+{
+       if (prompt) {
+               int rc;
+               unsigned prompt_len = strlen( prompt );
+               /* We're decoding, the output will be shorter than prompt_len. 
*/
+               *in = nfmalloc( prompt_len );
+               rc = sasl_decode64( prompt, prompt_len, *in, prompt_len, in_len 
);
+               if (rc != SASL_OK) {
+                       free( *in );
+                       error( "Error: SASL(%d): %s\n", rc, sasl_errstring( rc, 
NULL, NULL ) );
+                       return -1;
+               }
+       } else {
+               *in = NULL;
+               *in_len = 0;
+       }
+       return 0;
+}
+
+static int
+encode_sasl_data( const char *out, unsigned out_len, char **enc, unsigned 
*enc_len )
+{
+       int rc;
+       unsigned enc_len_max = ((out_len + 2) / 3) * 4 + 1;
+       *enc = nfmalloc( enc_len_max );
+       rc = sasl_encode64( out, out_len, *enc, enc_len_max, enc_len );
+       if (rc != SASL_OK) {
+               free( *enc );
+               error( "Error: SASL(%d): %s\n", rc, sasl_errstring( rc, NULL, 
NULL ) );
+               return -1;
+       }
+       return 0;
+}
+
+static int
+do_sasl_auth( imap_store_t *ctx, struct imap_cmd *cmdp ATTR_UNUSED, const char 
*prompt )
+{
+       int rc, ret;
+       unsigned in_len, out_len, enc_len;
+       const char *out;
+       char *in, *enc;
+       sasl_interact_t *interact = NULL;
+
+       if (!ctx->sasl_cont) {
+               error( "Error: IMAP wants more steps despite successful SASL 
authentication.\n" );
+               goto bail;
+       }
+       if (decode_sasl_data( prompt, &in, &in_len ) < 0)
+               goto bail;
+       rc = sasl_client_step( ctx->sasl, in, in_len, &interact, &out, &out_len 
);
+       ret = process_sasl_step( ctx, rc, in, in_len, interact, &out, &out_len 
);
+       free( in );
+       if (ret < 0)
+               goto bail;
+
+       if (out) {
+               if (encode_sasl_data( out, out_len, &enc, &enc_len ) < 0)
+                       goto bail;
+
+               if (DFlags & VERBOSE) {
+                       printf( "%s>+> %s\n", ctx->label, enc );
+                       fflush( stdout );
+               }
+
+               if (socket_write( &ctx->conn, enc, enc_len, GiveOwn ) < 0)
+                       return -1;
+       } else {
+               if (DFlags & VERBOSE) {
+                       printf( "%s>+>\n", ctx->label );
+                       fflush( stdout );
+               }
+       }
+       return socket_write( &ctx->conn, "\r\n", 2, KeepOwn );
+
+  bail:
+       imap_open_store_bail( ctx );
+       return -1;
+}
+
+static void
+done_sasl_auth( imap_store_t *ctx, struct imap_cmd *cmd ATTR_UNUSED, int 
response )
+{
+       if (response == RESP_OK && ctx->sasl_cont) {
+               sasl_interact_t *interact = NULL;
+               const char *out;
+               unsigned out_len;
+               int rc = sasl_client_step( ctx->sasl, NULL, 0, &interact, &out, 
&out_len );
+               if (process_sasl_step( ctx, rc, NULL, 0, interact, &out, 
&out_len ) < 0)
+                       warn( "Warning: SASL reported failure despite 
successful IMAP authentication. Ignoring...\n" );
+               else if (out)
+                       warn( "Warning: SASL wants more steps despite 
successful IMAP authentication. Ignoring...\n" );
+       }
+
+       imap_open_store_authenticate2_p2( ctx, NULL, response );
+}
+
+#endif
+
 static void
 imap_open_store_authenticate2( imap_store_t *ctx )
 {
@@ -1718,6 +1892,9 @@ imap_open_store_authenticate2( imap_store_t *ctx )
        int auth_cram = 0;
 #endif
        int auth_login = 0;
+#ifdef HAVE_LIBSASL
+       char saslmechs[1024], *saslend = saslmechs;
+#endif
 
        info( "Logging in...\n" );
        for (mech = srvc->auth_mechs; mech; mech = mech->next) {
@@ -1734,12 +1911,70 @@ imap_open_store_authenticate2( imap_store_t *ctx )
                                        auth_cram = 1;
 #endif
                                } else {
+#ifdef HAVE_LIBSASL
+                                       int len = strlen( cmech->string );
+                                       if (saslend + len + 2 > saslmechs + 
sizeof(saslmechs))
+                                               oob();
+                                       *saslend++ = ' ';
+                                       memcpy( saslend, cmech->string, len + 1 
);
+                                       saslend += len;
+#else
                                        error( "IMAP error: authentication 
mechanism %s is not supported\n", cmech->string );
                                        goto bail;
+#endif
                                }
                        }
                }
        }
+#ifdef HAVE_LIBSASL
+       if (saslend != saslmechs) {
+               int rc;
+               unsigned out_len = 0;
+               char *enc = NULL;
+               const char *gotmech = NULL, *out = NULL;
+               sasl_interact_t *interact = NULL;
+               struct imap_cmd *cmd;
+               static int sasl_inited;
+
+               if (!sasl_inited) {
+                       rc = sasl_client_init( sasl_callbacks );
+                       if (rc != SASL_OK) {
+                         saslbail:
+                               error( "Error: SASL(%d): %s\n", rc, 
sasl_errstring( rc, NULL, NULL ) );
+                               goto bail;
+                       }
+                       sasl_inited = 1;
+               }
+
+               rc = sasl_client_new( "imap", srvc->sconf.host, NULL, NULL, 
NULL, 0, &ctx->sasl );
+               if (rc != SASL_OK) {
+                       if (!ctx->sasl)
+                               goto saslbail;
+                       error( "Error: %s\n", sasl_errdetail( ctx->sasl ) );
+                       goto bail;
+               }
+
+               rc = sasl_client_start( ctx->sasl, saslmechs + 1, &interact, 
CAP(SASLIR) ? &out : NULL, &out_len, &gotmech );
+               if (gotmech)
+                       info( "Authenticating with SASL mechanism %s...\n", 
gotmech );
+               /* Technically, we are supposed to loop over 
sasl_client_start(),
+                * but it just calls sasl_client_step() anyway. */
+               if (process_sasl_step( ctx, rc, NULL, 0, interact, CAP(SASLIR) 
? &out : NULL, &out_len ) < 0)
+                       goto bail;
+               if (out) {
+                       if (!out_len)
+                               enc = nfstrdup( "=" ); /* A zero-length initial 
response is encoded as padding. */
+                       else if (encode_sasl_data( out, out_len, &enc, NULL ) < 
0)
+                               goto bail;
+               }
+
+               cmd = new_imap_cmd( sizeof(*cmd) );
+               cmd->param.cont = do_sasl_auth;
+               imap_exec( ctx, cmd, done_sasl_auth, enc ? "AUTHENTICATE %s %s" 
: "AUTHENTICATE %s", gotmech, enc );
+               free( enc );
+               return;
+       }
+#endif
 #ifdef HAVE_LIBSSL
        if (auth_cram) {
                struct imap_cmd *cmd = new_imap_cmd( sizeof(*cmd) );
diff --git a/src/mbsync.1 b/src/mbsync.1
index 478fabf..16088bd 100644
--- a/src/mbsync.1
+++ b/src/mbsync.1
@@ -281,8 +281,7 @@ the legacy IMAP \fBLOGIN\fR mechanism is known.
 The wildcard \fB*\fR represents all mechanisms that are deemed secure
 enough for the current \fBSSLType\fR setting.
 The actually used mechanism is the most secure choice from the intersection
-of this list, the list supplied by the server, and the mechanisms actually
-supported by \fBmbsync\fR (currently only \fBCRAM-MD5\fR and \fBLOGIN\fR).
+of this list, the list supplied by the server, and the installed SASL modules.
 (Default: \fB*\fR)
 ..
 .TP

------------------------------------------------------------------------------
Comprehensive Server Monitoring with Site24x7.
Monitor 10 servers for $9/Month.
Get alerted through email, SMS, voice calls or mobile push notifications.
Take corrective actions from your mobile device.
http://p.sf.net/sfu/Zoho
_______________________________________________
isync-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/isync-devel

Reply via email to