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