To authenticate to IMAP servers, mbsync uses SASL if linked with the
Cyrus SASL library (libsasl2) and falls back to LOGIN otherwise. This
logic was inline in the IMAP driver logic, with the two implementations
closely intertwined.
Factor out this authentication logic into its own independent interface
(imap_auth_*) and select which implementation to use at configuration by
adding only the corresponding source file to compilation. This
introduces a clean separation between the two implementations and
provides the infrastructure to later add further implementations (e.g.
GNU SASL).
---
configure.ac | 1 +
src/Makefile.am | 8 +
src/common.h | 4 +
src/drv_imap.c | 319 +++++-------------------------
src/imap_auth.c | 238 ++++++++++++++++++++++
src/imap_auth.h | 224 +++++++++++++++++++++
src/imap_auth_cyrus.c | 439 +++++++++++++++++++++++++++++++++++++++++
src/imap_auth_simple.c | 149 ++++++++++++++
src/util.c | 36 ++++
9 files changed, 1149 insertions(+), 269 deletions(-)
create mode 100644 src/imap_auth.c
create mode 100644 src/imap_auth.h
create mode 100644 src/imap_auth_cyrus.c
create mode 100644 src/imap_auth_simple.c
diff --git a/configure.ac b/configure.ac
index 1cff65a07d6a..f4155bd55c82 100644
--- a/configure.ac
+++ b/configure.ac
@@ -226,6 +226,7 @@ if test "x$ob_cv_with_zlib" != xno; then
)
fi
+AM_CONDITIONAL(with_sasl, test -n "$have_sasl_paths")
AM_CONDITIONAL(with_mdconvert, test "x$ac_cv_berkdb4" = xyes)
case $target_os in
diff --git a/src/Makefile.am b/src/Makefile.am
index 1f492e667c53..b43723433271 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -8,14 +8,22 @@ mbsync_SOURCES = \
drv_imap.c imap_msgs.c imap_utf7.c \
drv_maildir.c \
sync.c sync_state.c sync_msg_cvt.c \
+ imap_auth.c \
main.c main_sync.c main_list.c
noinst_HEADERS = \
common.h config.h socket.h \
driver.h imap_p.h \
sync.h sync_p.h \
+ imap_auth.h \
main_p.h
mbsync_LDADD = $(DB_LIBS) $(SSL_LIBS) $(SOCK_LIBS) $(SASL_LIBS) $(Z_LIBS)
$(KEYCHAIN_LIBS)
+if with_sasl
+mbsync_SOURCES += imap_auth_cyrus.c
+else
+mbsync_SOURCES += imap_auth_simple.c
+endif
+
drv_proxy.$(OBJEXT): drv_proxy.inc
drv_proxy.inc: $(srcdir)/driver.h $(srcdir)/drv_proxy.c
$(srcdir)/drv_proxy_gen.pl
perl $(srcdir)/drv_proxy_gen.pl $(srcdir)/driver.h
$(srcdir)/drv_proxy.c drv_proxy.inc
diff --git a/src/common.h b/src/common.h
index 35b4e640e1ea..fbdf5b6968c9 100644
--- a/src/common.h
+++ b/src/common.h
@@ -238,6 +238,10 @@ typedef struct string_list {
void add_string_list_n( string_list_t **list, const char *str, uint len );
void add_string_list( string_list_t **list, const char *str );
void free_string_list( string_list_t *list );
+int find_string_list( const string_list_t *list, const char *str );
+int find_string_list_case( const string_list_t *list, const char *str );
+int find_string_list_case_n(
+ const string_list_t *list, const char *str, uint len );
#ifndef HAVE_MEMRCHR
void *memrchr( const void *s, int c, size_t n );
diff --git a/src/drv_imap.c b/src/drv_imap.c
index fc38d908bb49..610c36ee8391 100644
--- a/src/drv_imap.c
+++ b/src/drv_imap.c
@@ -8,16 +8,12 @@
#include "imap_p.h"
+#include "imap_auth.h"
#include "socket.h"
#include <ctype.h>
#include <sys/wait.h>
-#ifdef HAVE_LIBSASL
-# include <sasl/sasl.h>
-# include <sasl/saslutil.h>
-#endif
-
#ifdef HAVE_MACOS_KEYCHAIN
# include <Security/Security.h>
#endif
@@ -176,10 +172,7 @@ union imap_store {
void (*imap_cancel)( void *aux );
} callbacks;
void *callback_aux;
-#ifdef HAVE_LIBSASL
- sasl_conn_t *sasl;
- int sasl_cont;
-#endif
+ imap_auth_client_t *auth_client;
void (*expunge_callback)( message_t *msg, void *aux );
void (*bad_callback)( void *aux );
@@ -273,9 +266,7 @@ typedef union {
enum CAPABILITY {
IMAP4REV1,
NOLOGIN,
-#ifdef HAVE_LIBSASL
SASLIR,
-#endif
#ifdef HAVE_LIBSSL
STARTTLS,
#endif
@@ -295,9 +286,7 @@ static const struct {
} cap_list[] = {
{ "IMAP4REV1", 9 },
{ "LOGINDISABLED", 13 },
-#ifdef HAVE_LIBSASL
{ "SASL-IR", 7 },
-#endif
#ifdef HAVE_LIBSSL
{ "STARTTLS", 8 },
#endif
@@ -1967,9 +1956,7 @@ imap_cancel_store( store_t *gctx )
{
imap_store_t *ctx = (imap_store_t *)gctx;
-#ifdef HAVE_LIBSASL
- sasl_dispose( &ctx->sasl );
-#endif
+ imap_auth_free_client( ctx->auth_client );
socket_close( &ctx->conn );
cancel_sent_imap_cmds( ctx );
cancel_pending_imap_cmds( ctx );
@@ -2441,6 +2428,13 @@ search_macos_keychain( const char *host, const char
*user )
}
#endif /* HAVE_MACOS_KEYCHAIN */
+static const char *
+ensure_user_cb( void *arg )
+{
+ imap_server_conf_t *srvc = arg;
+ return ensure_user( srvc );
+}
+
static const char *
ensure_password( imap_server_conf_t *srvc )
{
@@ -2471,126 +2465,26 @@ 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_AUTHNAME, 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: // aka authorization id - who to act as
- case SASL_CB_AUTHNAME: // who is really logging in
- 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, uint in_len,
- sasl_interact_t *interact, const char **out, uint *out_len )
-{
- imap_server_conf_t *srvc = ctx->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 performing SASL authentication step: %s\n",
sasl_errdetail( ctx->sasl ) );
- return -1;
- }
- return 0;
-}
-
-static int
-decode_sasl_data( const char *prompt, char **in, uint *in_len )
+static const char *
+ensure_password_cb( void *arg )
{
- if (prompt) {
- int rc;
- uint 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 decoding SASL prompt: %s\n",
sasl_errstring( rc, NULL, NULL ) );
- return -1;
- }
- } else {
- *in = NULL;
- *in_len = 0;
- }
- return 0;
-}
-
-static int
-encode_sasl_data( const char *out, uint out_len, char **enc, uint *enc_len )
-{
- int rc;
- uint 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 encoding SASL response: %s\n", sasl_errstring(
rc, NULL, NULL ) );
- return -1;
- }
- return 0;
+ imap_server_conf_t *srvc = arg;
+ return ensure_password( srvc );
}
static int
do_sasl_auth( imap_store_t *ctx, imap_cmd_t *cmdp ATTR_UNUSED, const char
*prompt )
{
- int rc, ret, iovcnt = 0;
- uint in_len, out_len, enc_len;
- const char *out;
- char *in, *enc;
- sasl_interact_t *interact = NULL;
+ int iovcnt = 0;
+ uint enc_len;
+ char *enc;
conn_iovec_t iov[2];
- 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)
+ int bad_cont = imap_auth_cont( ctx->auth_client, prompt, &enc, &enc_len
);
+ if (bad_cont)
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 (enc) {
iov[0].buf = enc;
iov[0].len = enc_len;
iov[0].takeOwn = GiveOwn;
@@ -2618,37 +2512,10 @@ do_sasl_auth( imap_store_t *ctx, imap_cmd_t *cmdp
ATTR_UNUSED, const char *promp
return -1;
}
-static void
-done_sasl_auth( imap_store_t *ctx, imap_cmd_t *cmd ATTR_UNUSED, int response )
-{
- if (response == RESP_OK && ctx->sasl_cont) {
- sasl_interact_t *interact = NULL;
- const char *out;
- uint 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_len > 0)
- 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 )
{
imap_server_conf_t *srvc = ctx->conf->server;
- string_list_t *mech, *cmech;
- int auth_login = 0;
- int skipped_login = 0;
-#ifdef HAVE_LIBSASL
- const char *saslavail;
- char saslmechs[1024], *saslend = saslmechs;
- int want_external = 0;
-#endif
// Ensure that there are no leftovers from previous runs. This is
needed in case
// the credentials have a timing dependency or otherwise lose validity
after use.
@@ -2662,138 +2529,52 @@ imap_open_store_authenticate2( imap_store_t *ctx )
}
info( "Logging in...\n" );
- for (mech = srvc->auth_mechs; mech; mech = mech->next) {
- int any = equals( mech->string, -1, "*", 1 );
- for (cmech = ctx->auth_mechs; cmech; cmech = cmech->next) {
- if (any || !strcasecmp( mech->string, cmech->string )) {
- if (!strcasecmp( cmech->string, "LOGIN" )) {
+ static int auth_inited = 0;
+ if (!auth_inited) {
+ if (imap_auth_init())
+ goto bail;
+ auth_inited = 1;
+ }
+
+ ctx->auth_client = imap_auth_new_client();
+ if (CAP(SASLIR))
+ imap_auth_using_saslir( ctx->auth_client );
#ifdef HAVE_LIBSSL
- if (ctx->conn.ssl ||
ctx->conn.conf->tunnel || !any)
+ if (ctx->conn.ssl || ctx->conn.conf->tunnel) {
#else
- if (ctx->conn.conf->tunnel || !any)
+ if (ctx->conn.conf->tunnel) {
#endif
- auth_login = 1;
- else
- skipped_login = 1;
-#ifdef HAVE_LIBSASL
- } else {
- uint len = strlen( cmech->string );
- if (saslend + len + 2 > saslmechs +
sizeof(saslmechs))
- oob();
- *saslend++ = ' ';
- memcpy( saslend, cmech->string, len + 1
);
- saslend += len;
-
- if (!strcasecmp( cmech->string,
"EXTERNAL" ))
- want_external = 1;
-#endif
- }
- }
- }
+ imap_auth_over_secure( ctx->auth_client );
}
-#ifdef HAVE_LIBSASL
- if (saslend != saslmechs) {
- int rc;
- uint out_len = 0;
- char *enc = NULL;
- const char *gotmech = NULL, *out = NULL;
- sasl_interact_t *interact = NULL;
- imap_cmd_t *cmd;
- static int sasl_inited;
- if (!sasl_inited) {
- rc = sasl_client_init( sasl_callbacks );
- if (rc != SASL_OK) {
- saslbail:
- error( "Error initializing SASL client: %s\n",
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 (rc == SASL_NOMECH)
- goto notsasl;
- if (!ctx->sasl)
- goto saslbail;
- error( "Error initializing SASL context: %s\n",
sasl_errdetail( ctx->sasl ) );
- goto bail;
- }
+ imap_auth_server_domain( ctx->auth_client, srvc->sconf.host );
+ imap_auth_username( ctx->auth_client, ensure_user_cb, srvc );
+ imap_auth_password( ctx->auth_client, ensure_password_cb, srvc );
- // The built-in EXTERNAL mechanism wants the authentication id
to be set
- // even before instantiation; consequently it won't prompt for
it, either.
- // While this clearly makes sense on the server side, it
arguably does not
- // on the client side. Ah, well ...
- if (want_external && ensure_user( srvc )) {
- rc = sasl_setprop( ctx->sasl, SASL_AUTH_EXTERNAL,
srvc->user );
- if (rc != SASL_OK ) {
- error( "Error setting SASL authentication id:
%s\n", sasl_errdetail( ctx->sasl ) );
- goto bail;
- }
- }
+ imap_auth_filter_mech( ctx->auth_client, srvc->auth_mechs );
+ imap_auth_filter_mech( ctx->auth_client, ctx->auth_mechs );
- rc = sasl_client_start( ctx->sasl, saslmechs + 1, &interact,
CAP(SASLIR) ? &out : NULL, &out_len, &gotmech );
- if (rc == SASL_NOMECH)
- goto notsasl;
- 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;
- }
+ char *enc = imap_auth_create_cmd( ctx->auth_client );
+ if (!enc)
+ goto bail;
- cmd = new_imap_cmd( sizeof(*cmd) );
- cmd->param.cont = do_sasl_auth;
- ctx->caps = 0;
- imap_exec( ctx, cmd, done_sasl_auth, enc ? "AUTHENTICATE %s %s"
: "AUTHENTICATE %s", gotmech, enc );
- free( enc );
- return;
- notsasl:
- if (!ctx->sasl || sasl_listmech( ctx->sasl, NULL, "", " ", "",
&saslavail, NULL, NULL ) != SASL_OK)
- saslavail = "(none)";
- if (!auth_login) {
- error( "IMAP error: selected SASL mechanism(s) not
available;\n"
- " selected:%s\n available: %s\n", saslmechs,
saslavail );
- goto skipnote;
- }
- info( "NOT using available SASL mechanism(s): %s\n", saslavail
);
- sasl_dispose( &ctx->sasl );
- }
-#endif
- if (auth_login) {
- if (!ensure_user( srvc ) || !ensure_password( srvc ))
- goto bail;
-#ifdef HAVE_LIBSSL
- if (!ctx->conn.ssl && !ctx->conn.conf->tunnel)
-#endif
- warn( "*** IMAP Warning *** Password is being sent in
the clear\n" );
- ctx->caps = 0;
- imap_exec( ctx, NULL, imap_open_store_authenticate2_p2,
- "LOGIN \"%\\s\" \"%\\s\"", srvc->user, srvc->pass );
- return;
- }
- error( "IMAP error: server supports no acceptable authentication
mechanism\n" );
-#ifdef HAVE_LIBSASL
- skipnote:
-#endif
- if (skipped_login)
- error( "Note: not using LOGIN because connection is not
encrypted;\n"
- " use 'AuthMechs LOGIN' explicitly to force it.\n"
);
+ ctx->caps = 0;
+ imap_cmd_t *cmd = new_imap_cmd( sizeof(*cmd) );
+ cmd->cmd = enc;
+ cmd->param.cont = do_sasl_auth;
+ cmd->param.done = imap_open_store_authenticate2_p2;
+ submit_imap_cmd( ctx, cmd );
+ return;
bail:
imap_open_store_bail( ctx, FAIL_FINAL );
}
static void
imap_open_store_authenticate2_p2( imap_store_t *ctx, imap_cmd_t *cmd
ATTR_UNUSED, int response )
{
+ imap_auth_done( ctx->auth_client, response );
+
if (response == RESP_NO) {
imap_open_store_bail( ctx, FAIL_FINAL );
} else if (response == RESP_OK) {
diff --git a/src/imap_auth.c b/src/imap_auth.c
new file mode 100644
index 000000000000..2af1d107fcfe
--- /dev/null
+++ b/src/imap_auth.c
@@ -0,0 +1,238 @@
+// SPDX-FileCopyrightText: 2026 Oswald Buddenhagen <[email protected]>
+// SPDX-FileCopyrightText: 2026 Seth McDonald <[email protected]>
+// SPDX-License-Identifier: GPL-2.0-or-later WITH
LicenseRef-isync-GPL-exception
+/*
+ * mbsync - mailbox synchronizer
+ */
+
+#include "imap_auth.h"
+
+#include <ctype.h>
+
+static int
+is_mech_char( int ch )
+{
+ return (isdigit( ch ) || isupper( ch ) || ch == '-' || ch == '_');
+}
+
+static int
+has_mech_char( const char *str )
+{
+ assert( str );
+
+ for (size_t i = 0; str[i]; i++) {
+ int ch = str[i];
+ if (is_mech_char( ch ))
+ return 1;
+ }
+ return 0;
+}
+
+static const char *
+format_mech_list( const char *list )
+{
+ return (list && has_mech_char( list )) ? list : "(none)";
+}
+
+int
+imap_auth_copy_mech( char *dst, const char *src, int len )
+{
+ assert( dst );
+ assert( src );
+
+ if (len < MIN_SASL_MECH_LEN)
+ return 0;
+ if (len > MAX_SASL_MECH_LEN)
+ len = MAX_SASL_MECH_LEN;
+
+ for (int i = 0; i < len; i++) {
+ int ch = src[i];
+ ch = toupper( ch );
+ if (!is_mech_char( ch ))
+ return i;
+ dst[i] = ch;
+ }
+ return len;
+}
+
+char *
+imap_auth_find_mech( const char *list, const char *mech, int len )
+{
+ assert( list );
+ assert( mech );
+ assert( len >= MIN_SASL_MECH_LEN );
+
+ if (len > MAX_SASL_MECH_LEN)
+ len = MAX_SASL_MECH_LEN;
+
+ char name[MAX_SASL_MECH_LEN + 3];
+ name[0] = ' ';
+ memcpy( name + 1, mech, len );
+ name[len + 1] = ' ';
+ name[len + 2] = '\0';
+
+ char *elm = strcasestr( list, name );
+ return elm ? elm + 1 : NULL;
+}
+
+char *
+imap_auth_union_mechs( char *mechs, const string_list_t *new_mechs )
+{
+ assert( mechs );
+
+ size_t total_size = strlen( mechs ) + 1;
+
+ for (const string_list_t *node = new_mechs; node; node = node->next) {
+ const char *new_mech = node->string;
+ size_t new_mech_len = strlen( new_mech );
+ if (total_size > SIZE_MAX - new_mech_len - 1)
+ continue; // Ignore - would cause overflow
+ if (imap_auth_find_mech( mechs, new_mech, new_mech_len ))
+ continue; // Ignore - don't want duplicates
+
+ size_t mem_size = total_size + new_mech_len + 1;
+ char *mem = realloc( mechs, mem_size );
+ if (!mem)
+ continue; // Ignore - too large to allocate
+
+ int new_mech_valid_len = imap_auth_copy_mech(
+ mem + total_size - 1, new_mech, new_mech_len );
+ total_size = mem_size - (new_mech_len - new_mech_valid_len);
+ mechs = mem;
+
+ mechs[total_size - 2] = ' ';
+ mechs[total_size - 1] = '\0';
+ }
+ return mechs;
+}
+
+char *
+imap_auth_intersect_mechs( char *mechs, const string_list_t *new_mechs )
+{
+ assert( mechs );
+
+ size_t total_size = strlen( mechs ) + 1;
+
+ for (char *mech = mechs + 1; *mech;) {
+ int mech_len = strchr( mech, ' ' ) - mech;
+ int shared = find_string_list_case_n( new_mechs, mech, mech_len
);
+ if (shared) {
+ mech += mech_len + 1;
+ } else {
+ size_t tail_len = total_size - (mech - mechs) - 1;
+ memmove( mech, mech + mech_len + 1, tail_len - mech_len
);
+ total_size -= mech_len + 1;
+ }
+ }
+
+ // Try to trim memory (not strictly needed)
+ char *mem = realloc( mechs, total_size );
+ return mem ? mem : mechs;
+}
+
+char *
+imap_auth_login_cmd( const char *user, const char *pass, int secure, int force
)
+{
+ if (!secure && !force) {
+ imap_auth_no_common_mech();
+ imap_auth_no_secure_login();
+ return NULL;
+ }
+ if (!user || !pass)
+ return NULL;
+ if (!secure)
+ warn( "*** IMAP Warning *** Password is being sent in the
clear.\n" );
+
+ // Ensure credentials are backslash-escaped
+ return xasprintf( "LOGIN \"%\\s\" \"%\\s\"", user, pass );
+}
+
+const char *
+imap_auth_cred_stub( void *arg )
+{
+ (void)arg;
+ return NULL;
+}
+
+void
+imap_auth_choose_mech( const char *mech )
+{
+ mech = format_mech_list( mech );
+ info( "Authenticating with SASL mechanism %s...\n", mech );
+}
+
+void
+imap_auth_bad_library_init( const char *err )
+{
+ assert( err );
+
+ error( "Error initializing SASL library: %s\n", err );
+}
+
+void
+imap_auth_bad_client_init( const char *err )
+{
+ assert( err );
+
+ error( "Error initializing SASL client: %s\n", err );
+}
+
+void
+imap_auth_no_common_mech( void )
+{
+ error(
+ "IMAP error: "
+ "server supports no acceptable authentication
mechanism.\n" );
+}
+
+void
+imap_auth_no_good_mech( const char *mechs )
+{
+ mechs = format_mech_list( mechs );
+ info( "NOT using available SASL mechanism(s): %s\n", mechs );
+}
+
+void
+imap_auth_no_common_and_good_mech( const char *common, const char *good )
+{
+ common = format_mech_list( common );
+ good = format_mech_list( good );
+ error(
+ "IMAP error: selected SASL mechanism(s) not
available;\n"
+ " selected: %s\n"
+ " available: %s\n",
+ common,
+ good );
+}
+
+void
+imap_auth_no_secure_login( void )
+{
+ error(
+ "Note: not using LOGIN because connection is not
encrypted;\n"
+ " use 'AuthMechs LOGIN' explicitly to force it.\n"
);
+}
+
+void
+imap_auth_cont_too_many_steps( void )
+{
+ error(
+ "Error: IMAP wants more steps "
+ "despite successful SASL authentication.\n" );
+}
+
+void
+imap_auth_done_too_many_steps( void )
+{
+ warn(
+ "Warning: SASL wants more steps "
+ "despite successful IMAP authentication. Ignoring...\n"
);
+}
+
+void
+imap_auth_done_bad_step( void )
+{
+ warn(
+ "Warning: SASL reported failure "
+ "despite successful IMAP authentication. Ignoring...\n"
);
+}
diff --git a/src/imap_auth.h b/src/imap_auth.h
new file mode 100644
index 000000000000..b7272b1dc7db
--- /dev/null
+++ b/src/imap_auth.h
@@ -0,0 +1,224 @@
+// SPDX-FileCopyrightText: 2026 Seth McDonald <[email protected]>
+// SPDX-License-Identifier: GPL-2.0-or-later WITH
LicenseRef-isync-GPL-exception
+/*
+ * mbsync - mailbox synchronizer
+ */
+
+#ifndef IMAP_AUTH_H
+#define IMAP_AUTH_H
+
+#include "common.h"
+
+/* Min and max length of a SASL mechanism name as stated by RFC 4422. */
+#define MIN_SASL_MECH_LEN 1
+#define MAX_SASL_MECH_LEN 20
+
+/* Ensure SASL mechanism length makes sense and can be stored in an int. */
+static_assert( MIN_SASL_MECH_LEN > 0, "Unexpected minimum mech length" );
+static_assert( MAX_SASL_MECH_LEN < INT_MAX, "Unexpected maximum mech length" );
+
+/*
+ * An opaque object representing the client-side of an authentication process
+ * over an IMAP connection.
+ * The contents of the structure will likely differ depending on the
+ * implementation used.
+ */
+typedef struct imap_auth_client imap_auth_client_t;
+
+/*
+ * A callback to obtain a particular credential.
+ * When called, it is passed a user-specified pointer and should return the
+ * credential as a string.
+ * Returning NULL is permitted but may be treated as an error.
+ * The caller does NOT retain ownership of the returned string, although it
must
+ * remain valid for the duration of the authentication process.
+ */
+typedef const char *(*imap_auth_cred)( void * );
+
+/*
+ * Initialises the SASL library.
+ * Should only be called once, prior to any other imap_auth_* functions.
+ * Returns zero on success and nonzero otherwise.
+ */
+int imap_auth_init( void );
+
+/*
+ * Deinitialises the SASL library.
+ * Should only be called once, following all other imap_auth_* functions.
+ */
+void imap_auth_cleanup( void );
+
+/*
+ * Creates a new auth client and returns the corresponding reference.
+ * The caller retains ownership of the returned client.
+ */
+imap_auth_client_t *imap_auth_new_client( void );
+
+/*
+ * Frees all resources associated with an auth client.
+ * The caller relinquishes ownership of the given client.
+ */
+void imap_auth_free_client( imap_auth_client_t *client );
+
+/*
+ * Informs the implementation that the IMAP connection is over a secure medium
+ * such as TLS.
+ * If not called, it is assumed the connection is insecure (e.g. public and
+ * unencrypted).
+ * 'client' must be non-NULL.
+ */
+void imap_auth_over_secure( imap_auth_client_t *client );
+
+/*
+ * Informs the implementation that the IMAP connection is using the SASL-IR
+ * capability.
+ * If not called, it is assumed the capability is not present.
+ * 'client' must be non-NULL.
+ */
+void imap_auth_using_saslir( imap_auth_client_t *client );
+
+/*
+ * Informs the implementation that the IMAP server has the FQDN 'server'.
+ * If not called, or if called with a NULL 'server', some SASL mechanisms may
be
+ * unavailable.
+ * 'client' must be non-NULL.
+ */
+void imap_auth_server_domain( imap_auth_client_t *client, const char *server );
+
+/*
+ * Sets the callback to use to obtain the username for authentication.
+ * The callback will only be used if the username is needed.
+ * A NULL 'callback' will cause username-based authentication to fail.
+ * 'client' must be non-NULL.
+ */
+void imap_auth_username(
+ imap_auth_client_t *client, imap_auth_cred callback, void *arg
);
+
+/*
+ * Sets the callback to use to obtain the password for authentication.
+ * The callback will only be used if the password is needed.
+ * A NULL 'callback' will cause password-based authentication to fail.
+ * 'client' must be non-NULL.
+ */
+void imap_auth_password(
+ imap_auth_client_t *client, imap_auth_cred callback, void *arg
);
+
+/*
+ * Adds the SASL mechanisms given by 'mechs' to the list of mechanisms
available
+ * to the client.
+ * If called multiple times, only mechanisms present in all calls are
considered
+ * available.
+ * Mechanisms are parsed in a case-insensitive manner.
+ * 'client' must be non-NULL.
+ */
+void imap_auth_filter_mech(
+ imap_auth_client_t *client, const string_list_t *mechs );
+
+/*
+ * Constructs an appropriate IMAP authentication command to begin
authentication
+ * of the client via SASL or LOGIN.
+ * Returns this command on success and NULL otherwise.
+ * The caller retains ownership of the returned string.
+ * 'client' must be non-NULL.
+ */
+char *imap_auth_create_cmd( imap_auth_client_t *client );
+
+/*
+ * Continues the SASL authentication process by constructing an appropriate
+ * response to the 'prompt' received from the IMAP server.
+ * This response is stored in 'enc' and its length in 'len'.
+ * A NULL '*enc' indicates an empty response.
+ * Returns zero on success and nonzero otherwise.
+ * 'client', 'enc', and 'len' must be non-NULL.
+ */
+int imap_auth_cont(
+ imap_auth_client_t *client, const char *prompt, char **enc,
uint *len );
+
+/*
+ * Takes appropriate action upon completion of the authentication process
+ * according to whether the server response was OK (i.e. success).
+ * This does NOT relinquish ownership of the client.
+ * 'client' must be non-NULL.
+ */
+void imap_auth_done( imap_auth_client_t *client, int resp_ok );
+
+/******************* Internal functions *******************/
+
+/*
+ * Copies the SASL mechanism of length 'len' given by 'src' to the memory at
+ * 'dst'.
+ * The copy is additionally formatted as a proper SASL mechanism name.
+ * Notably, lowercase characters are converted to uppercase and the name is
+ * trimmed from the first invalid character (if any).
+ *
+ * Returns the length of the copied mechanism, accounting for formatting.
+ * Does NOT expect nor add a NUL terminator.
+ */
+int imap_auth_copy_mech( char *dst, const char *src, int len );
+
+/*
+ * Searches for a SASL mechanism of a given length in a space-delimited list.
+ * The list should start and end with whitespace.
+ * The search is case-insensitive.
+ *
+ * Returns a pointer to the mechanism in the list if found, and NULL otherwise.
+ * Does NOT expect 'mech' to be NUL terminated.
+ */
+char *imap_auth_find_mech( const char *list, const char *mech, int len );
+
+/*
+ * Determines the union of SASL mechanisms present in 'mechs' and 'new_mechs'.
+ * 'mechs' is space-delimited, including starting and ending with whitespace.
+ * Mechanisms are considered case-insensitive.
+ *
+ * Returns a pointer to the unioned list of mechanisms.
+ * The caller relinquishes ownership of 'mechs'.
+ * The caller retains ownership of the returned string.
+ */
+char *imap_auth_union_mechs( char *mechs, const string_list_t *new_mechs );
+
+/*
+ * Determines the intersection of SASL mechanisms present in 'mechs' and
+ * 'new_mechs'.
+ * 'mechs' is space-delimited, including starting and ending with whitespace.
+ * Mechanisms are considered case-insensitive.
+ *
+ * Returns a pointer to the intersected list of mechanisms.
+ * The caller relinquishes ownership of 'mechs'.
+ * The caller retains ownership of the returned string.
+ */
+char *imap_auth_intersect_mechs( char *mechs, const string_list_t *new_mechs );
+
+/*
+ * Constructs the LOGIN IMAP command using the provided username and password,
+ * accounting for whether a secure connection is being used.
+ * 'force' indicates whether to force the use of LOGIN even over an insecure
+ * channel.
+ *
+ * Returns the command on success and NULL otherwise.
+ * The caller retains ownership of the returned string.
+ */
+char *imap_auth_login_cmd(
+ const char *user, const char *pass, int secure, int force );
+
+/*
+ * Stub implementation of an imap_auth_cred function.
+ * Returns NULL.
+ */
+const char *imap_auth_cred_stub( void *arg );
+
+/*
+ * Display appropriate output to user for various situations.
+ */
+void imap_auth_choose_mech( const char *mech );
+void imap_auth_bad_library_init( const char *err );
+void imap_auth_bad_client_init( const char *err );
+void imap_auth_no_common_mech( void );
+void imap_auth_no_good_mech( const char *mechs );
+void imap_auth_no_common_and_good_mech( const char *common, const char *good );
+void imap_auth_no_secure_login( void );
+void imap_auth_cont_too_many_steps( void );
+void imap_auth_done_too_many_steps( void );
+void imap_auth_done_bad_step( void );
+
+#endif
diff --git a/src/imap_auth_cyrus.c b/src/imap_auth_cyrus.c
new file mode 100644
index 000000000000..a9746144e1ef
--- /dev/null
+++ b/src/imap_auth_cyrus.c
@@ -0,0 +1,439 @@
+// SPDX-FileCopyrightText: 2026 Oswald Buddenhagen <[email protected]>
+// SPDX-FileCopyrightText: 2026 Seth McDonald <[email protected]>
+// SPDX-License-Identifier: GPL-2.0-or-later WITH
LicenseRef-isync-GPL-exception
+/*
+ * mbsync - mailbox synchronizer
+ */
+
+#include "imap_auth.h"
+
+#include <stdint.h>
+
+#include <sasl/sasl.h>
+#include <sasl/saslutil.h>
+
+struct imap_auth_client {
+ /* Callback to obtain username. */
+ imap_auth_cred user_callback; /* Is non-NULL. */
+ void *user_arg; /* May be NULL. */
+
+ /* Callback to obtain password. */
+ imap_auth_cred pass_callback; /* Is non-NULL. */
+ void *pass_arg; /* May be NULL. */
+
+ sasl_conn_t *ctx; /* Non-NULL iff SASL has initiated. */
+ char *mechs; /* " MECH1 MECH2 ... MECHN "; is non-NULL. */
+ char *server; /* e.g. "imap.server.com"; may be NULL. */
+
+ uint filter_count; /* # times mechs have been filtered. */
+ int cont; /* Whether another authentication step is expected. */
+ int any_mech; /* Whether the user can use any SASL mechanism. */
+ int conn_secure; /* Whether connection is over a secure channel. */
+ int has_ir; /* Whether server has the SASL-IR capability. */
+};
+
+static sasl_callback_t sasl_callbacks[] = {
+ { SASL_CB_USER, NULL, NULL },
+ { SASL_CB_AUTHNAME, NULL, NULL },
+ { SASL_CB_PASS, NULL, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+
+/*
+ * Returns a description of the most recent SASL error that occurred on
+ * connection 'ctx' (if any) with the return code 'rc'.
+ */
+static const char *
+query_sasl_error( sasl_conn_t *ctx, int rc )
+{
+ const char *err = ctx
+ ? sasl_errdetail( ctx )
+ : sasl_errstring( rc, NULL, NULL );
+ return err ? err : "Unknown error";
+}
+
+/*
+ * Searches for a SASL mechanism in a space-delimited list.
+ * If found, removes the mechanism from the list and returns zero.
+ * Otherwise, returns nonzero.
+ */
+static int
+remove_mech( char *list, const char *mech, int len )
+{
+ assert( list );
+ assert( mech );
+ assert( len >= 0 );
+
+ char *elm = imap_auth_find_mech( list, mech, len );
+ if (!elm)
+ return -1;
+
+ size_t tail_len = strlen( elm );
+ memmove( elm, elm + len + 1, tail_len - len );
+ return 0;
+}
+
+static int
+process_sasl_interact(
+ const imap_auth_client_t *client, sasl_interact_t *interact )
+{
+ const char *val;
+
+ for (;; ++interact) {
+ switch (interact->id) {
+ case SASL_CB_LIST_END:
+ return 0;
+ case SASL_CB_USER: // aka authorization id - who to act as
+ case SASL_CB_AUTHNAME: // who is really logging in
+ val = client->user_callback( client->user_arg );
+ break;
+ case SASL_CB_PASS:
+ val = client->pass_callback( client->pass_arg );
+ 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_auth_client_t *client,
+ int rc,
+ const char *in,
+ uint in_len,
+ sasl_interact_t *interact,
+ const char **out,
+ uint *out_len )
+{
+ while (rc == SASL_INTERACT) {
+ if (process_sasl_interact( client, interact ) < 0)
+ return -1;
+ rc = sasl_client_step(
+ client->ctx, in, in_len, &interact, out,
out_len );
+ }
+ if (rc == SASL_CONTINUE) {
+ client->cont = 1;
+ } else if (rc == SASL_OK) {
+ client->cont = 0;
+ } else {
+ const char *errstr = query_sasl_error( client->ctx, rc );
+ error( "Error performing SASL authentication step: %s\n",
errstr );
+ return -1;
+ }
+ return 0;
+}
+
+static int
+decode_sasl_data( const char *prompt, char **in, uint *in_len )
+{
+ if (prompt) {
+ int rc;
+ uint 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 );
+ const char *errstr = query_sasl_error( NULL, rc );
+ error( "Error decoding SASL prompt: %s\n", errstr );
+ return -1;
+ }
+ } else {
+ *in = NULL;
+ *in_len = 0;
+ }
+ return 0;
+}
+
+static int
+encode_sasl_data( const char *out, uint out_len, char **enc, uint *enc_len )
+{
+ int rc;
+ uint 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 );
+ const char *errstr = query_sasl_error( NULL, rc );
+ error( "Error encoding SASL response: %s\n", errstr );
+ return -1;
+ }
+ return 0;
+}
+
+int
+imap_auth_init( void )
+{
+ int rc = sasl_client_init( sasl_callbacks );
+ if (rc != SASL_OK) {
+ const char *errstr = query_sasl_error( NULL, rc );
+ imap_auth_bad_library_init( errstr );
+ return -1;
+ }
+ return 0;
+}
+
+void
+imap_auth_cleanup( void )
+{
+ sasl_client_done();
+}
+
+imap_auth_client_t *
+imap_auth_new_client( void )
+{
+ imap_auth_client_t *client = nfzalloc( sizeof(*client) );
+ client->user_callback = imap_auth_cred_stub;
+ client->pass_callback = imap_auth_cred_stub;
+ client->mechs = nfstrdup( " " );
+ return client;
+}
+
+void
+imap_auth_free_client( imap_auth_client_t *client )
+{
+ if (!client)
+ return;
+
+ sasl_dispose( &client->ctx ); // Allows NULL input
+ free( client->mechs );
+ free( client->server );
+ free( client );
+}
+
+void
+imap_auth_over_secure( imap_auth_client_t *client )
+{
+ assert( client );
+
+ client->conn_secure = 1;
+}
+
+void
+imap_auth_using_saslir( imap_auth_client_t *client )
+{
+ assert( client );
+
+ client->has_ir = 1;
+}
+
+void
+imap_auth_server_domain( imap_auth_client_t *client, const char *server )
+{
+ assert( client );
+
+ free( client->server );
+ client->server = server ? strdup( server ) : NULL;
+}
+
+void
+imap_auth_username(
+ imap_auth_client_t *client, imap_auth_cred callback, void *arg )
+{
+ assert( client );
+
+ client->user_callback = callback ? callback : imap_auth_cred_stub;
+ client->user_arg = arg;
+}
+
+void
+imap_auth_password(
+ imap_auth_client_t *client, imap_auth_cred callback, void *arg )
+{
+ assert( client );
+
+ client->pass_callback = callback ? callback : imap_auth_cred_stub;
+ client->pass_arg = arg;
+}
+
+void
+imap_auth_filter_mech( imap_auth_client_t *client, const string_list_t *mechs )
+{
+ assert( client );
+
+ if (find_string_list( mechs, "*" ))
+ client->any_mech = 1;
+
+ client->mechs = (!client->filter_count || client->any_mech)
+ ? imap_auth_union_mechs( client->mechs, mechs )
+ : imap_auth_intersect_mechs( client->mechs, mechs );
+
+ client->filter_count++;
+}
+
+static int
+prepare_external_auth( imap_auth_client_t *client )
+{
+ assert( client );
+
+ const char *username = client->user_callback( client->user_arg );
+ if (!username)
+ return 0;
+
+ int rc = sasl_setprop( client->ctx, SASL_AUTH_EXTERNAL, username );
+ if (rc == SASL_OK)
+ return 0;
+
+ const char *errstr = query_sasl_error( client->ctx, rc );
+ error( "Error setting SASL authentication id: %s\n", errstr );
+ return -1;
+}
+
+static char *
+try_login( imap_auth_client_t *client, const char *good_mechs )
+{
+ assert( client );
+
+ const char *common_mechs = client->mechs + 1;
+ int force_login = !client->any_mech;
+ if (common_mechs[0]) {
+ if (!client->conn_secure && !force_login) {
+ imap_auth_no_common_and_good_mech( common_mechs,
good_mechs );
+ imap_auth_no_secure_login();
+ return NULL;
+ }
+ imap_auth_no_good_mech( good_mechs );
+ }
+
+ const char *username = client->user_callback( client->user_arg );
+ const char *password = client->pass_callback( client->pass_arg );
+ return imap_auth_login_cmd(
+ username, password, client->conn_secure, force_login );
+}
+
+char *
+imap_auth_create_cmd( imap_auth_client_t *client )
+{
+ assert( client );
+
+ // Ensure there are proper (non-LOGIN) SASL mechanisms
+ int has_login = !remove_mech( client->mechs, "LOGIN", strlen( "LOGIN" )
);
+ if (!client->mechs[1]) {
+ if (has_login)
+ return try_login( client, NULL );
+ imap_auth_no_common_mech();
+ return NULL;
+ }
+
+ int rc = sasl_client_new(
+ "imap", client->server, NULL, NULL, NULL, 0,
&client->ctx );
+ if (rc == SASL_NOMECH) {
+ if (has_login)
+ return try_login( client, NULL );
+ imap_auth_no_common_and_good_mech( client->mechs + 1, NULL );
+ return NULL;
+ }
+ if (rc != SASL_OK) {
+ const char *errstr = query_sasl_error( client->ctx, rc );
+ imap_auth_bad_client_init( errstr );
+ return NULL;
+ }
+
+ // The built-in EXTERNAL mechanism wants the authentication id to be set
+ // even before instantiation; consequently it won't prompt for it,
either.
+ // While this clearly makes sense on the server side, it arguably does
not
+ // on the client side. Ah, well ...
+ if (imap_auth_find_mech(
+ client->mechs, "EXTERNAL", strlen( "EXTERNAL" )
)) {
+ if (prepare_external_auth( client ))
+ return NULL;
+ }
+
+ sasl_interact_t *interact = NULL;
+ const char *out = NULL;
+ uint out_len = 0;
+ const char *mech = NULL;
+ rc = sasl_client_start(
+ client->ctx, client->mechs, &interact, client->has_ir ?
&out : NULL,
+ &out_len, &mech );
+ if (rc == SASL_NOMECH) {
+ const char *good_mechs = NULL;
+ sasl_listmech(
+ client->ctx, NULL, "", " ", "", &good_mechs,
NULL, NULL );
+ if (has_login)
+ return try_login( client, good_mechs );
+ imap_auth_no_common_and_good_mech( client->mechs + 1,
good_mechs );
+ return NULL;
+ }
+ imap_auth_choose_mech( mech );
+
+ /* Technically, we are supposed to loop over sasl_client_start(),
+ * but it just calls sasl_client_step() anyway. */
+ int ret = process_sasl_step(
+ client, rc, NULL, 0, interact, client->has_ir ? &out :
NULL,
+ &out_len );
+ if (ret < 0)
+ return NULL;
+
+ char *enc = NULL;
+ 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)
+ return NULL;
+ }
+
+ char *cmd;
+ nfasprintf(
+ &cmd, enc ? "AUTHENTICATE %s %s" : "AUTHENTICATE %s",
mech, enc );
+ free( enc );
+ return cmd;
+}
+
+int
+imap_auth_cont(
+ imap_auth_client_t *client, const char *prompt, char **enc,
uint *len )
+{
+ assert( client );
+ assert( enc );
+ assert( len );
+
+ int rc, ret;
+ uint in_len, out_len;
+ const char *out;
+ char *in;
+ sasl_interact_t *interact = NULL;
+
+ if (!client->cont) {
+ imap_auth_cont_too_many_steps();
+ return -1;
+ }
+ if (decode_sasl_data( prompt, &in, &in_len ) < 0)
+ return -1;
+
+ rc = sasl_client_step(
+ client->ctx, in, in_len, &interact, &out, &out_len );
+ ret = process_sasl_step(
+ client, rc, in, in_len, interact, &out, &out_len );
+ free( in );
+ if (ret < 0)
+ return -1;
+ if (out)
+ return encode_sasl_data( out, out_len, enc, len );
+ return 0;
+}
+
+void
+imap_auth_done( imap_auth_client_t *client, int resp_ok )
+{
+ assert( client );
+
+ if (!resp_ok || !client->cont)
+ return;
+
+ sasl_interact_t *interact = NULL;
+ const char *out;
+ uint out_len;
+ int rc = sasl_client_step(
+ client->ctx, NULL, 0, &interact, &out, &out_len );
+ if (process_sasl_step( client, rc, NULL, 0, interact, &out, &out_len )
< 0)
+ imap_auth_done_bad_step();
+ else if (out_len > 0)
+ imap_auth_done_too_many_steps();
+}
diff --git a/src/imap_auth_simple.c b/src/imap_auth_simple.c
new file mode 100644
index 000000000000..6ebd308998cb
--- /dev/null
+++ b/src/imap_auth_simple.c
@@ -0,0 +1,149 @@
+// SPDX-FileCopyrightText: 2026 Seth McDonald <[email protected]>
+// SPDX-License-Identifier: GPL-2.0-or-later WITH
LicenseRef-isync-GPL-exception
+/*
+ * mbsync - mailbox synchronizer
+ */
+
+#include "imap_auth.h"
+
+struct imap_auth_client {
+ /* Credentials to use for LOGIN. */
+ char *username;
+ char *password;
+
+ /* Whether to allow plaintext authentication over insecure connections.
+ * Nonzero if true; zero if false. */
+ int allow_insecure;
+
+ /* Whether the LOGIN capability is available.
+ * Positive if true; zero if false; negative if unknown. */
+ int has_login;
+
+ /* Whether the connection is over a secure channel.
+ * Nonzero if true; zero if false. */
+ int conn_secure;
+};
+
+int
+imap_auth_init( void )
+{
+ return 0;
+}
+
+void
+imap_auth_cleanup( void )
+{
+}
+
+imap_auth_client_t *
+imap_auth_new_client( void )
+{
+ imap_auth_client_t *client = nfzalloc( sizeof(*client) );
+ client->allow_insecure = 1;
+ client->has_login = -1;
+ return client;
+}
+
+void
+imap_auth_free_client( imap_auth_client_t *client )
+{
+ free( client );
+}
+
+void
+imap_auth_over_secure( imap_auth_client_t *client )
+{
+ assert( client );
+
+ client->conn_secure = 1;
+}
+
+void
+imap_auth_using_saslir( imap_auth_client_t *client )
+{
+ (void)client;
+
+ assert( client );
+}
+
+void
+imap_auth_server_domain( imap_auth_client_t *client, const char *server )
+{
+ (void)client;
+ (void)server;
+
+ assert( client );
+}
+
+void
+imap_auth_username(
+ imap_auth_client *client, imap_auth_cred callback, void *arg )
+{
+ assert( client );
+
+ if (callback)
+ client->username = callback( arg );
+}
+
+void
+imap_auth_password(
+ imap_auth_client *client, imap_auth_cred callback, void *arg )
+{
+ assert( client );
+
+ if (callback)
+ client->password = callback( arg );
+}
+
+void
+imap_auth_filter_mech( imap_auth_client_t *client, const string_list_t *mechs )
+{
+ assert( client );
+
+ if (!client->has_login)
+ return;
+
+ int has_any = find_string_list( mechs, "*" );
+ int has_login = find_string_list_case( mechs, "LOGIN" );
+
+ client->has_login = (has_any || has_login);
+ if (!has_login)
+ client->allow_insecure = 0;
+}
+
+char *
+imap_auth_create_cmd( imap_auth_client_t *client )
+{
+ assert( client );
+
+ if (client->has_login <= 0) {
+ imap_auth_no_common_mech();
+ return NULL;
+ }
+ return imap_auth_login_cmd(
+ client->username,
+ client->password,
+ client->conn_secure,
+ client->allow_insecure );
+}
+
+int
+imap_auth_cont(
+ imap_auth_client_t *client, const char *prompt, char **enc,
uint *len )
+{
+ (void)client;
+ (void)prompt;
+ (void)enc;
+ (void)len;
+
+ // LOGIN is single-step; cont. should never be called.
+ imap_auth_cont_too_many_steps();
+ return -1;
+}
+
+void
+imap_auth_done( imap_auth_client_t *client, int resp_ok )
+{
+ (void)client;
+ (void)resp_ok;
+}
diff --git a/src/util.c b/src/util.c
index 62723b04cb93..c5b06099bf6c 100644
--- a/src/util.c
+++ b/src/util.c
@@ -467,6 +467,42 @@ free_string_list( string_list_t *list )
}
}
+int
+find_string_list( const string_list_t *list, const char *str )
+{
+ assert( str );
+
+ for (const string_list_t *elem = list; elem; elem = elem->next) {
+ if (!strcmp( elem->string, str ))
+ return 1;
+ }
+ return 0;
+}
+
+int
+find_string_list_case( const string_list_t *list, const char *str )
+{
+ assert( str );
+
+ for (const string_list_t *elem = list; elem; elem = elem->next) {
+ if (!strcasecmp( elem->string, str ))
+ return 1;
+ }
+ return 0;
+}
+
+int
+find_string_list_case_n( const string_list_t *list, const char *str, uint len )
+{
+ assert( str );
+
+ for (const string_list_t *elem = list; elem; elem = elem->next) {
+ if (!strncasecmp( elem->string, str, len ) &&
!elem->string[len])
+ return 1;
+ }
+ return 0;
+}
+
#ifndef HAVE_VASPRINTF
static int
vasprintf( char **strp, const char *fmt, va_list ap )
--
2.50.1 (Apple Git-155)
_______________________________________________
isync-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/isync-devel