summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin McCarthy <kevin@8t8.us>2021-12-15 17:50:00 -0800
committerKevin McCarthy <kevin@8t8.us>2021-12-23 14:59:01 -0800
commit68caf9140c8217ecf6c848460c4b4d27996b2922 (patch)
tree37e5ee6ded751efaa7e9460baaaca9ba9d109926
parente3faeb0355ad3ac53696d0d4d9eee3e2fd9b595e (diff)
Add GNU SASL support for authentication.
It turns out Cyrus SASL's license may not be compatible with GPL programs, see Debian Bug 999672. So, add support for the GNU SASL library, using configure option --with-gsasl. I haven't touched the Cyrus SASL code in Mutt all that much in the past, but I've done my best to keep the gsasl code clean and simple. There are likely mistakes to be fixed and additions to be made, though. I queried the gsasl mailing list about the need for a socket wrapper (as is done for the cyrus code), and it seems this should no longer be needed. As long as GSASL_QOP is left at the default (qop-auth), the client should ask for authentication, and not negotiate integrity or confidentiality. (Thanks to Phil Pennock and Simon Josefsson for their reponses - although the blame is fully on *me* if this turns out to be incorrect). Therefore there is no CONNECTION wrapping in this implementation. Add multiline response support for SMTP authentication (which is probably not actually needed). Also add arbitrary line length for the SASL server responses (the RFCs note that for SASL, the protocol line lengths don't apply).
-rw-r--r--Makefile.am6
-rw-r--r--ascii.h13
-rw-r--r--configure.ac48
-rw-r--r--doc/makedoc-defs.h6
-rw-r--r--imap/Makefile.am8
-rw-r--r--imap/auth.c5
-rw-r--r--imap/auth.h5
-rw-r--r--imap/auth_sasl_gnu.c140
-rw-r--r--main.c18
-rw-r--r--mutt_sasl_gnu.c227
-rw-r--r--mutt_sasl_gnu.h32
-rw-r--r--pop_auth.c107
-rw-r--r--smtp.c144
13 files changed, 738 insertions, 21 deletions
diff --git a/Makefile.am b/Makefile.am
index a4377892..7036b62b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -66,7 +66,8 @@ AM_DISTCHECK_CONFIGURE_FLAGS = --with-homespool
EXTRA_mutt_SOURCES = account.c bcache.c compress.c crypt-gpgme.c crypt-mod-pgp-classic.c \
crypt-mod-pgp-gpgme.c crypt-mod-smime-classic.c \
crypt-mod-smime-gpgme.c dotlock.c gnupgparse.c hcache.c md5.c monitor.c \
- mutt_idna.c mutt_sasl.c mutt_socket.c mutt_ssl.c mutt_ssl_gnutls.c \
+ mutt_idna.c mutt_sasl.c mutt_sasl_gnu.c mutt_socket.c mutt_ssl.c \
+ mutt_ssl_gnutls.c \
mutt_tunnel.c pgp.c pgpinvoke.c pgpkey.c pgplib.c pgpmicalg.c \
pgppacket.c pop.c pop_auth.c pop_lib.c remailer.c resize.c sha1.c \
sidebar.c smime.c smtp.c utf8.c wcwidth.c mutt_zstrm.c \
@@ -79,7 +80,8 @@ EXTRA_DIST = COPYRIGHT GPL OPS OPS.PGP OPS.CRYPT OPS.SMIME TODO UPDATING \
dotlock.h functions.h gen_defs gettext.h \
globals.h hash.h history.h init.h keymap.h mutt_crypt.h \
mailbox.h mapping.h md5.h mime.h mutt.h mutt_curses.h mutt_menu.h \
- mutt_regex.h mutt_sasl.h mutt_socket.h mutt_ssl.h mutt_tunnel.h \
+ mutt_regex.h mutt_sasl.h mutt_sasl_gnu.h mutt_socket.h mutt_ssl.h \
+ mutt_tunnel.h \
mx.h pager.h pgp.h pop.h protos.h rfc1524.h rfc2047.h \
rfc2231.h rfc822.h rfc3676.h sha1.h sort.h mime.types VERSION prepare \
_mutt_regex.h OPS.MIX README.SECURITY remailer.c remailer.h browser.h \
diff --git a/ascii.h b/ascii.h
index 92730469..fe756298 100644
--- a/ascii.h
+++ b/ascii.h
@@ -52,4 +52,17 @@ static inline char* ascii_strlower (char *s)
return s;
}
+static inline char* ascii_strupper (char *s)
+{
+ char *p = s;
+
+ while (*p)
+ {
+ *p = ascii_toupper ((unsigned int) *p);
+ p++;
+ }
+
+ return s;
+}
+
#endif
diff --git a/configure.ac b/configure.ac
index 81bfb9f5..8b86e421 100644
--- a/configure.ac
+++ b/configure.ac
@@ -849,11 +849,55 @@ AC_ARG_WITH(sasl, AS_HELP_STRING([--with-sasl@<:@=PFX@:>@],[Use SASL network sec
LIBS="$saved_LIBS"
AC_DEFINE(USE_SASL,1,
- [ Define if want to use the SASL library for POP/IMAP authentication. ])
+ [ Define if want support for SASL. ])
+ AC_DEFINE(USE_SASL_CYRUS,1,
+ [ Define if want to use the Cyrus SASL library for POP/IMAP authentication. ])
need_sasl=yes
+ need_sasl_cyrus=yes
fi
])
-AM_CONDITIONAL(USE_SASL, test x$need_sasl = xyes)
+AC_ARG_WITH(gsasl, AS_HELP_STRING([--with-gsasl@<:@=PFX@:>@],[Use GNU SASL network security library]),
+ [
+ if test "$with_gsasl" != "no"
+ then
+ if test "$need_socket" != "yes"
+ then
+ AC_MSG_ERROR([GNU SASL support is only useful with POP or IMAP support])
+ fi
+
+ if test x"$need_sasl" = "xyes"
+ then
+ AC_MSG_ERROR([Both --with-gsasl and --with-sasl can not be enabled at the same time])
+ fi
+
+ if test "$with_gsasl" != "yes"
+ then
+ CPPFLAGS="$CPPFLAGS -I$with_gsasl/include"
+ LDFLAGS="$LDFLAGS -L$with_gsasl/lib"
+ fi
+
+ saved_LIBS="$LIBS"
+ LIBS=
+
+ AC_CHECK_HEADER(gsasl.h,
+ AC_CHECK_LIB(gsasl, gsasl_check_version,,
+ AC_MSG_ERROR([GNU SASL library not found])),
+ AC_MSG_ERROR([GNU SASL headers not found]))
+
+ MUTTLIBS="$MUTTLIBS -lgsasl"
+ MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS mutt_sasl_gnu.o"
+ LIBS="$saved_LIBS"
+
+ AC_DEFINE(USE_SASL,1,
+ [ Define if want support for SASL. ])
+ AC_DEFINE(USE_SASL_GNU,1,
+ [ Define if want to use the GNU SASL library for POP/IMAP authentication. ])
+ need_sasl=yes
+ need_sasl_gnu=yes
+ fi
+ ])
+AM_CONDITIONAL(USE_SASL_CYRUS, test x$need_sasl_cyrus = xyes)
+AM_CONDITIONAL(USE_SASL_GNU, test x$need_sasl_gnu = xyes)
dnl -- end socket --
diff --git a/doc/makedoc-defs.h b/doc/makedoc-defs.h
index 20ed17d6..5a83a110 100644
--- a/doc/makedoc-defs.h
+++ b/doc/makedoc-defs.h
@@ -61,6 +61,12 @@
# ifndef USE_SASL
# define USE_SASL
# endif
+# ifndef USE_SASL_CYRUS
+# define USE_SASL_CYRUS
+# endif
+# ifndef USE_SASL_GNU
+# define USE_SASL_GNU
+# endif
# ifndef USE_SIDEBAR
# define USE_SIDEBAR
# endif
diff --git a/imap/Makefile.am b/imap/Makefile.am
index 3cfb1a57..6d3cac3b 100644
--- a/imap/Makefile.am
+++ b/imap/Makefile.am
@@ -7,14 +7,18 @@ if USE_GSS
GSSSOURCES = auth_gss.c
endif
-if USE_SASL
+if USE_SASL_CYRUS
AUTHENTICATORS = auth_sasl.c
else
+if USE_SASL_GNU
+AUTHENTICATORS = auth_sasl_gnu.c
+else
AUTHENTICATORS = auth_anon.c auth_cram.c
endif
+endif
EXTRA_DIST = README TODO auth_anon.c auth_cram.c auth_gss.c auth_oauth.c \
- auth_sasl.c
+ auth_sasl.c auth_sasl_gnu.c
AM_CPPFLAGS = -I$(top_srcdir)
diff --git a/imap/auth.c b/imap/auth.c
index 83c87f01..81829324 100644
--- a/imap/auth.c
+++ b/imap/auth.c
@@ -32,7 +32,12 @@ static const imap_auth_t imap_authenticators[] = {
{ imap_auth_oauthbearer, "oauthbearer" },
{ imap_auth_xoauth2, "xoauth2" },
#ifdef USE_SASL
+# ifdef USE_SASL_CYRUS
{ imap_auth_sasl, NULL },
+# endif
+# ifdef USE_SASL_GNU
+ { imap_auth_gsasl, NULL },
+# endif
#else
{ imap_auth_anon, "anonymous" },
#endif
diff --git a/imap/auth.h b/imap/auth.h
index f022beae..b6bb0603 100644
--- a/imap/auth.h
+++ b/imap/auth.h
@@ -48,9 +48,12 @@ imap_auth_res_t imap_auth_login (IMAP_DATA* idata, const char* method);
#ifdef USE_GSS
imap_auth_res_t imap_auth_gss (IMAP_DATA* idata, const char* method);
#endif
-#ifdef USE_SASL
+#ifdef USE_SASL_CYRUS
imap_auth_res_t imap_auth_sasl (IMAP_DATA* idata, const char* method);
#endif
+#ifdef USE_SASL_GNU
+imap_auth_res_t imap_auth_gsasl (IMAP_DATA* idata, const char* method);
+#endif
imap_auth_res_t imap_auth_oauthbearer (IMAP_DATA* idata, const char* method);
imap_auth_res_t imap_auth_xoauth2 (IMAP_DATA* idata, const char* method);
diff --git a/imap/auth_sasl_gnu.c b/imap/auth_sasl_gnu.c
new file mode 100644
index 00000000..8d0db6ab
--- /dev/null
+++ b/imap/auth_sasl_gnu.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 Kevin J. McCarthy <kevin@8t8.us>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "mutt.h"
+#include "mutt_sasl_gnu.h"
+#include "imap_private.h"
+#include "auth.h"
+
+#include <gsasl.h>
+
+imap_auth_res_t imap_auth_gsasl (IMAP_DATA* idata, const char* method)
+{
+ Gsasl_session *gsasl_session = NULL;
+ const char *chosen_mech;
+ BUFFER *output_buf = NULL;
+ char *gsasl_step_output = NULL, *imap_step_output;
+ int rc = IMAP_AUTH_FAILURE;
+ int gsasl_rc = GSASL_OK, imap_step_rc = IMAP_CMD_CONTINUE;
+
+ chosen_mech = mutt_gsasl_get_mech (method, idata->capstr);
+ if (!chosen_mech)
+ {
+ dprint (2, (debugfile, "mutt_gsasl_get_mech() returned no usable mech\n"));
+ return IMAP_AUTH_UNAVAIL;
+ }
+
+ dprint (2, (debugfile, "imap_auth_gsasl: using mech %s\n", chosen_mech));
+
+ if (mutt_gsasl_client_new (idata->conn, chosen_mech, &gsasl_session) < 0)
+ {
+ dprint (1, (debugfile,
+ "imap_auth_gsasl: Error allocating GSASL connection.\n"));
+ return IMAP_AUTH_UNAVAIL;
+ }
+
+ mutt_message (_("Authenticating (%s)..."), chosen_mech);
+
+ output_buf = mutt_buffer_pool_get ();
+ mutt_buffer_printf (output_buf, "AUTHENTICATE %s", chosen_mech);
+ if (mutt_bit_isset (idata->capabilities, SASL_IR))
+ {
+ gsasl_rc = gsasl_step64 (gsasl_session, "", &gsasl_step_output);
+ if (gsasl_rc != GSASL_NEEDS_MORE && gsasl_rc != GSASL_OK)
+ {
+ dprint (1, (debugfile, "gsasl_step64() failed (%d): %s\n", gsasl_rc,
+ gsasl_strerror (gsasl_rc)));
+ rc = IMAP_AUTH_UNAVAIL;
+ goto bail;
+ }
+
+ mutt_buffer_addch (output_buf, ' ');
+ mutt_buffer_addstr (output_buf, gsasl_step_output);
+ gsasl_free (gsasl_step_output);
+ }
+ imap_cmd_start (idata, mutt_b2s (output_buf));
+
+ do
+ {
+ do
+ imap_step_rc = imap_cmd_step (idata);
+ while (imap_step_rc == IMAP_CMD_CONTINUE);
+ if (imap_step_rc == IMAP_CMD_BAD || imap_step_rc == IMAP_CMD_NO)
+ goto bail;
+
+ if (imap_step_rc != IMAP_CMD_RESPOND)
+ break;
+
+ imap_step_output = imap_next_word (idata->buf);
+
+ gsasl_rc = gsasl_step64 (gsasl_session, imap_step_output,
+ &gsasl_step_output);
+ if (gsasl_rc == GSASL_NEEDS_MORE || gsasl_rc == GSASL_OK)
+ {
+ mutt_buffer_strcpy (output_buf, gsasl_step_output);
+ gsasl_free (gsasl_step_output);
+ }
+ /* if a sasl error occured, send an abort string */
+ else
+ {
+ dprint (1, (debugfile, "gsasl_step64() failed (%d): %s\n", gsasl_rc,
+ gsasl_strerror (gsasl_rc)));
+ mutt_buffer_strcpy(output_buf, "*");
+ }
+
+ mutt_buffer_addstr (output_buf, "\r\n");
+ mutt_socket_write (idata->conn, mutt_b2s (output_buf));
+ }
+ while (gsasl_rc == GSASL_NEEDS_MORE || gsasl_rc == GSASL_OK);
+
+ if (imap_step_rc != IMAP_CMD_OK)
+ {
+ do
+ imap_step_rc = imap_cmd_step (idata);
+ while (imap_step_rc == IMAP_CMD_CONTINUE);
+ }
+
+ if (imap_step_rc == IMAP_CMD_RESPOND)
+ {
+ mutt_socket_write (idata->conn, "*\r\n");
+ goto bail;
+ }
+
+ if ((gsasl_rc != GSASL_OK) || (imap_step_rc != IMAP_CMD_OK))
+ goto bail;
+
+ if (imap_code (idata->buf))
+ rc = IMAP_AUTH_SUCCESS;
+
+bail:
+ mutt_buffer_pool_release (&output_buf);
+ mutt_gsasl_client_finish (&gsasl_session);
+
+ if (rc == IMAP_AUTH_FAILURE)
+ {
+ dprint (2, (debugfile, "imap_auth_gsasl: %s failed\n", chosen_mech));
+ mutt_error _("SASL authentication failed.");
+ mutt_sleep (2);
+ }
+
+ return rc;
+}
diff --git a/main.c b/main.c
index 3fd930b6..f281fa27 100644
--- a/main.c
+++ b/main.c
@@ -38,10 +38,14 @@
#include "sidebar.h"
#endif
-#ifdef USE_SASL
+#ifdef USE_SASL_CYRUS
#include "mutt_sasl.h"
#endif
+#ifdef USE_SASL_GNU
+#include "mutt_sasl_gnu.h"
+#endif
+
#ifdef USE_IMAP
#include "imap/imap.h"
#endif
@@ -339,11 +343,16 @@ static void show_version (void)
"-USE_SSL_GNUTLS "
#endif
-#ifdef USE_SASL
+#ifdef USE_SASL_CYRUS
"+USE_SASL "
#else
"-USE_SASL "
#endif
+#ifdef USE_SASL_GNU
+ "+USE_GSASL "
+#else
+ "-USE_GSASL "
+#endif
#ifdef USE_GSS
"+USE_GSS "
#else
@@ -1394,9 +1403,12 @@ cleanup_and_exit:
#ifdef USE_IMAP
imap_logout_all ();
#endif
-#ifdef USE_SASL
+#ifdef USE_SASL_CYRUS
mutt_sasl_done ();
#endif
+#ifdef USE_SASL_GNU
+ mutt_gsasl_done ();
+#endif
#ifdef USE_AUTOCRYPT
mutt_autocrypt_cleanup ();
#endif
diff --git a/mutt_sasl_gnu.c b/mutt_sasl_gnu.c
new file mode 100644
index 00000000..7ebe4293
--- /dev/null
+++ b/mutt_sasl_gnu.c
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2021 Kevin J. McCarthy <kevin@8t8.us>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#if HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "mutt.h"
+#include "account.h"
+#include "mutt_sasl_gnu.h"
+#include "mutt_socket.h"
+
+#include <errno.h>
+#include <gsasl.h>
+
+static Gsasl *mutt_gsasl_ctx = NULL;
+
+static int mutt_gsasl_callback (Gsasl *ctx, Gsasl_session *sctx,
+ Gsasl_property prop);
+
+
+/* mutt_gsasl_start: called before doing a SASL exchange - initialises library
+ * (if necessary). */
+static int mutt_gsasl_init (void)
+{
+ int rc;
+
+ if (mutt_gsasl_ctx)
+ return 0;
+
+ rc = gsasl_init (&mutt_gsasl_ctx);
+ if (rc != GSASL_OK)
+ {
+ mutt_gsasl_ctx = NULL;
+ dprint (1, (debugfile,
+ "mutt_gsasl_start: libgsasl initialisation failed (%d): %s.\n",
+ rc, gsasl_strerror (rc)));
+ return -1;
+ }
+
+ gsasl_callback_set (mutt_gsasl_ctx, mutt_gsasl_callback);
+
+ return 0;
+}
+
+void mutt_gsasl_done (void)
+{
+ if (mutt_gsasl_ctx)
+ {
+ gsasl_done (mutt_gsasl_ctx);
+ mutt_gsasl_ctx = NULL;
+ }
+}
+
+static const char *VALID_MECHANISM_CHARACTERS =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
+
+/* This logic is derived from the libgsasl suggest code */
+static int mechlist_contains (const char *uc_mech, const char *uc_mechlist)
+{
+ size_t mech_len, mechlist_len, fragment_len, i;
+
+ mech_len = mutt_strlen (uc_mech);
+ mechlist_len = mutt_strlen (uc_mechlist);
+
+ if (!mech_len || !mechlist_len)
+ return 0;
+
+ for (i = 0; i < mechlist_len;)
+ {
+ fragment_len = strspn (uc_mechlist + i, VALID_MECHANISM_CHARACTERS);
+ if ((mech_len == fragment_len) &&
+ !ascii_strncmp (uc_mech, uc_mechlist + i, mech_len))
+ return 1;
+ i += fragment_len + 1;
+ }
+ return 0;
+}
+
+const char *mutt_gsasl_get_mech (const char *requested_mech,
+ const char *server_mechlist)
+{
+ char *uc_requested_mech, *uc_server_mechlist;
+ const char *rv = NULL;
+
+ if (mutt_gsasl_init ())
+ return NULL;
+
+ /* libgsasl does not do case-independent string comparisons, and stores
+ * its methods internally in uppercase.
+ */
+ uc_server_mechlist = safe_strdup (server_mechlist);
+ if (uc_server_mechlist)
+ ascii_strupper (uc_server_mechlist);
+
+ uc_requested_mech = safe_strdup (requested_mech);
+ if (uc_requested_mech)
+ ascii_strupper (uc_requested_mech);
+
+ if (uc_requested_mech)
+ {
+ if (mechlist_contains (uc_requested_mech, uc_server_mechlist))
+ rv = gsasl_client_suggest_mechanism (mutt_gsasl_ctx, uc_requested_mech);
+ }
+ else
+ rv = gsasl_client_suggest_mechanism (mutt_gsasl_ctx, uc_server_mechlist);
+
+ FREE (&uc_requested_mech);
+ FREE (&uc_server_mechlist);
+
+ return rv;
+}
+
+int mutt_gsasl_client_new (CONNECTION *conn, const char *mech,
+ Gsasl_session **sctx)
+{
+ int rc;
+
+ if (mutt_gsasl_init ())
+ return -1;
+
+ rc = gsasl_client_start (mutt_gsasl_ctx, mech, sctx);
+ if (rc != GSASL_OK)
+ {
+ *sctx = NULL;
+ dprint (1, (debugfile,
+ "mutt_gsasl_client_new: gsasl_client_start failed (%d): %s.\n",
+ rc, gsasl_strerror (rc)));
+ return -1;
+ }
+
+ gsasl_session_hook_set (*sctx, conn);
+
+ return 0;
+}
+
+void mutt_gsasl_client_finish (Gsasl_session **sctx)
+{
+ gsasl_finish (*sctx);
+ *sctx = NULL;
+}
+
+
+static int mutt_gsasl_callback (Gsasl *ctx, Gsasl_session *sctx,
+ Gsasl_property prop)
+{
+ int rc = GSASL_NO_CALLBACK;
+ CONNECTION *conn;
+ const char* service;
+
+ conn = gsasl_session_hook_get (sctx);
+ if (!conn)
+ {
+ dprint (1, (debugfile, "mutt_gsasl_callback(): missing session hook data!\n"));
+ return rc;
+ }
+
+ switch (prop)
+ {
+ case GSASL_PASSWORD:
+ if (mutt_account_getpass (&conn->account))
+ return rc;
+ gsasl_property_set (sctx, GSASL_PASSWORD, conn->account.pass);
+ rc = GSASL_OK;
+ break;
+
+ case GSASL_AUTHID:
+ /* whom the provided password belongs to: login */
+ if (mutt_account_getlogin (&conn->account))
+ return rc;
+ gsasl_property_set (sctx, GSASL_AUTHID, conn->account.login);
+ rc = GSASL_OK;
+ break;
+
+ case GSASL_AUTHZID:
+ /* name of the user whose mail/resources you intend to access: user */
+ if (mutt_account_getuser (&conn->account))
+ return rc;
+ gsasl_property_set (sctx, GSASL_AUTHZID, conn->account.user);
+ rc = GSASL_OK;
+ break;
+
+ case GSASL_ANONYMOUS_TOKEN:
+ gsasl_property_set (sctx, GSASL_ANONYMOUS_TOKEN, "dummy");
+ rc = GSASL_OK;
+ break;
+
+ case GSASL_SERVICE:
+ switch (conn->account.type)
+ {
+ case MUTT_ACCT_TYPE_IMAP:
+ service = "imap";
+ break;
+ case MUTT_ACCT_TYPE_POP:
+ service = "pop";
+ break;
+ case MUTT_ACCT_TYPE_SMTP:
+ service = "smtp";
+ break;
+ default:
+ return rc;
+ }
+ gsasl_property_set (sctx, GSASL_SERVICE, service);
+ rc = GSASL_OK;
+ break;
+
+ default:
+ break;
+ }
+
+ return rc;
+}
diff --git a/mutt_sasl_gnu.h b/mutt_sasl_gnu.h
new file mode 100644
index 00000000..ead2588f
--- /dev/null
+++ b/mutt_sasl_gnu.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 Kevin J. McCarthy <kevin@8t8.us>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _MUTT_SASL_GNU_H_
+#define _MUTT_SASL_GNU_H_ 1
+
+#include <gsasl.h>
+
+#include "mutt_socket.h"
+
+void mutt_gsasl_done (void);
+const char *mutt_gsasl_get_mech (const char *requested_mech,
+ const char *server_mechlist);
+int mutt_gsasl_client_new (CONNECTION *, const char *, Gsasl_session **);
+void mutt_gsasl_client_finish (Gsasl_session **sctx);
+
+#endif /* _MUTT_SASL_GNU_H_ */
diff --git a/pop_auth.c b/pop_auth.c
index 31fb4983..77c6e764 100644
--- a/pop_auth.c
+++ b/pop_auth.c
@@ -28,14 +28,19 @@
#include <string.h>
#include <unistd.h>
-#ifdef USE_SASL
+#ifdef USE_SASL_CYRUS
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#include "mutt_sasl.h"
#endif
-#ifdef USE_SASL
+#ifdef USE_SASL_GNU
+#include "mutt_sasl_gnu.h"
+#include <gsasl.h>
+#endif
+
+#ifdef USE_SASL_CYRUS
/* SASL authenticator */
static pop_auth_res_t pop_auth_sasl (POP_DATA *pop_data, const char *method)
{
@@ -189,6 +194,99 @@ bail:
}
#endif
+
+#ifdef USE_SASL_GNU
+static pop_auth_res_t pop_auth_gsasl (POP_DATA *pop_data, const char *method)
+{
+ Gsasl_session *gsasl_session = NULL;
+ const char *chosen_mech, *pop_auth_data;
+ BUFFER *output_buf = NULL, *input_buf = NULL;
+ char *gsasl_step_output = NULL;
+ int rc = POP_A_FAILURE, gsasl_rc = GSASL_OK;
+
+ chosen_mech = mutt_gsasl_get_mech (method, pop_data->auth_list);
+ if (!chosen_mech)
+ {
+ dprint (2, (debugfile, "mutt_gsasl_get_mech() returned no usable mech\n"));
+ return POP_A_UNAVAIL;
+ }
+
+ dprint (2, (debugfile, "pop_auth_gsasl: using mech %s\n", chosen_mech));
+
+ if (mutt_gsasl_client_new (pop_data->conn, chosen_mech, &gsasl_session) < 0)
+ {
+ dprint (1, (debugfile,
+ "pop_auth_gsasl: Error allocating GSASL connection.\n"));
+ return POP_A_UNAVAIL;
+ }
+
+ mutt_message (_("Authenticating (%s)..."), chosen_mech);
+
+ output_buf = mutt_buffer_pool_get ();
+ input_buf = mutt_buffer_pool_get ();
+ mutt_buffer_printf (output_buf, "AUTH %s\r\n", chosen_mech);
+
+ do
+ {
+ if (mutt_socket_write (pop_data->conn, mutt_b2s (output_buf)) < 0)
+ {
+ pop_data->status = POP_DISCONNECTED;
+ rc = POP_A_SOCKET;
+ goto fail;
+ }
+
+ if (mutt_socket_buffer_readln (input_buf, pop_data->conn) < 0)
+ {
+ pop_data->status = POP_DISCONNECTED;
+ rc = POP_A_SOCKET;
+ goto fail;
+ }
+
+ if (mutt_strncmp (mutt_b2s (input_buf), "+ ", 2))
+ break;
+
+ pop_auth_data = mutt_b2s (input_buf) + 2;
+ gsasl_rc = gsasl_step64 (gsasl_session, pop_auth_data, &gsasl_step_output);
+ if (gsasl_rc == GSASL_NEEDS_MORE || gsasl_rc == GSASL_OK)
+ {
+ mutt_buffer_strcpy (output_buf, gsasl_step_output);
+ mutt_buffer_addstr (output_buf, "\r\n");
+ gsasl_free (gsasl_step_output);
+ }
+ else
+ {
+ dprint (1, (debugfile, "gsasl_step64() failed (%d): %s\n", gsasl_rc,
+ gsasl_strerror (gsasl_rc)));
+ }
+ }
+ while (gsasl_rc == GSASL_NEEDS_MORE || gsasl_rc == GSASL_OK);
+
+ if (!mutt_strncmp (mutt_b2s (input_buf), "+ ", 2))
+ {
+ mutt_socket_write (pop_data->conn, "*\r\n");
+ goto fail;
+ }
+
+ if (!mutt_strncmp (mutt_b2s (input_buf), "+OK", 3) && (gsasl_rc == GSASL_OK))
+ rc = POP_A_SUCCESS;
+
+fail:
+ mutt_buffer_pool_release (&input_buf);
+ mutt_buffer_pool_release (&output_buf);
+ mutt_gsasl_client_finish (&gsasl_session);
+
+ if (rc == POP_A_FAILURE)
+ {
+ dprint (2, (debugfile, "pop_auth_gsasl: %s failed\n", chosen_mech));
+ mutt_error _("SASL authentication failed.");
+ mutt_sleep (2);
+ }
+
+ return rc;
+}
+#endif
+
+
/* Get the server timestamp for APOP authentication */
void pop_apop_timestamp (POP_DATA *pop_data, char *buf)
{
@@ -410,9 +508,12 @@ static pop_auth_res_t pop_auth_xoauth2 (POP_DATA *pop_data, const char *method)
static const pop_auth_t pop_authenticators[] = {
{ pop_auth_oauthbearer, "oauthbearer" },
{ pop_auth_xoauth2, "xoauth2" },
-#ifdef USE_SASL
+#ifdef USE_SASL_CYRUS
{ pop_auth_sasl, NULL },
#endif
+#ifdef USE_SASL_GNU
+ { pop_auth_gsasl, NULL },
+#endif
{ pop_auth_apop, "apop" },
{ pop_auth_user, "user" },
{ NULL, NULL }
diff --git a/smtp.c b/smtp.c
index 9f4f0454..8b46402f 100644
--- a/smtp.c
+++ b/smtp.c
@@ -29,12 +29,16 @@
#ifdef USE_SSL
# include "mutt_ssl.h"
#endif
-#ifdef USE_SASL
+#ifdef USE_SASL_CYRUS
#include "mutt_sasl.h"
#include <sasl/sasl.h>
#include <sasl/saslutil.h>
#endif
+#ifdef USE_SASL_GNU
+#include "mutt_sasl_gnu.h"
+#include <gsasl.h>
+#endif
#include <netdb.h>
#include <netinet/in.h>
@@ -68,9 +72,12 @@ enum {
static int smtp_auth (CONNECTION* conn);
static int smtp_auth_oauth (CONNECTION* conn, int xoauth2);
-#ifdef USE_SASL
+#ifdef USE_SASL_CYRUS
static int smtp_auth_sasl (CONNECTION* conn, const char* mechanisms);
#endif
+#ifdef USE_SASL_GNU
+static int smtp_auth_gsasl (CONNECTION* conn, const char* method);
+#endif
static int smtp_fill_account (ACCOUNT* account);
static int smtp_open (CONNECTION* conn);
@@ -79,7 +86,12 @@ static int Esmtp = 0;
static char* AuthMechs = NULL;
static unsigned char Capabilities[(CAPMAX + 7)/ 8];
-static int smtp_code (char *buf, size_t len, int *n)
+/* Note: the 'len' parameter is actually the number of bytes, as
+ * returned by mutt_socket_readln(). If all callers are converted to
+ * mutt_socket_buffer_readln() we can pass in the actual len, or
+ * perhaps the buffer itself.
+ */
+static int smtp_code (const char *buf, size_t len, int *n)
{
char code[4];
@@ -149,6 +161,36 @@ smtp_get_resp (CONNECTION * conn)
}
static int
+smtp_get_auth_response (CONNECTION *conn, BUFFER *input_buf, int *smtp_rc,
+ BUFFER *response_buf)
+{
+ const char *smtp_response;
+
+ mutt_buffer_clear (response_buf);
+ do
+ {
+ if (mutt_socket_buffer_readln (input_buf, conn) < 0)
+ return -1;
+ if (smtp_code (mutt_b2s (input_buf),
+ mutt_buffer_len (input_buf) + 1, /* number of bytes */
+ smtp_rc) < 0)
+ return -1;
+
+ if (*smtp_rc != smtp_ready)
+ break;
+
+ smtp_response = mutt_b2s (input_buf) + 3;
+ if (*smtp_response)
+ {
+ smtp_response++;
+ mutt_buffer_addstr (response_buf, smtp_response);
+ }
+ } while (mutt_b2s (input_buf)[3] == '-');
+
+ return 0;
+}
+
+static int
smtp_rcpt_to (CONNECTION * conn, const ADDRESS * a)
{
char buf[1024];
@@ -547,8 +589,10 @@ static int smtp_auth (CONNECTION* conn)
}
else
{
-#ifdef USE_SASL
+#if defined(USE_SASL_CYRUS)
r = smtp_auth_sasl (conn, method);
+#elif defined(USE_SASL_GNU)
+ r = smtp_auth_gsasl (conn, method);
#else
mutt_error (_("SMTP authentication method %s requires SASL"), method);
mutt_sleep (1);
@@ -568,8 +612,10 @@ static int smtp_auth (CONNECTION* conn)
}
else
{
-#ifdef USE_SASL
- r = smtp_auth_sasl (conn, AuthMechs);
+#if defined(USE_SASL_CYRUS)
+ r = smtp_auth_sasl (conn, AuthMechs);
+#elif defined(USE_SASL_GNU)
+ r = smtp_auth_gsasl (conn, NULL);
#else
mutt_error (_("SMTP authentication requires SASL"));
mutt_sleep (1);
@@ -594,7 +640,7 @@ static int smtp_auth (CONNECTION* conn)
return r == SMTP_AUTH_SUCCESS ? 0 : -1;
}
-#ifdef USE_SASL
+#ifdef USE_SASL_CYRUS
static int smtp_auth_sasl (CONNECTION* conn, const char* mechlist)
{
sasl_conn_t* saslconn;
@@ -702,7 +748,89 @@ fail:
FREE (&buf);
return SMTP_AUTH_FAIL;
}
-#endif /* USE_SASL */
+#endif /* USE_SASL_CYRUS */
+
+#ifdef USE_SASL_GNU
+static int smtp_auth_gsasl (CONNECTION *conn, const char *method)
+{
+ Gsasl_session *gsasl_session = NULL;
+ const char *chosen_mech;
+ BUFFER *input_buf = NULL, *output_buf = NULL, *smtp_response_buf = NULL;
+ char *gsasl_step_output = NULL;
+ int rc = SMTP_AUTH_FAIL, gsasl_rc = GSASL_OK, smtp_rc;
+
+ chosen_mech = mutt_gsasl_get_mech (method, AuthMechs);
+ if (!chosen_mech)
+ {
+ dprint (2, (debugfile, "mutt_gsasl_get_mech() returned no usable mech\n"));
+ return SMTP_AUTH_UNAVAIL;
+ }
+
+ dprint (2, (debugfile, "smtp_auth_gsasl: using mech %s\n", chosen_mech));
+
+ if (mutt_gsasl_client_new (conn, chosen_mech, &gsasl_session) < 0)
+ {
+ dprint (1, (debugfile,
+ "smtp_auth_gsasl: Error allocating GSASL connection.\n"));
+ return SMTP_AUTH_UNAVAIL;
+ }
+
+ if (!option(OPTNOCURSES))
+ mutt_message (_("Authenticating (%s)..."), chosen_mech);
+
+ input_buf = mutt_buffer_pool_get ();
+ output_buf = mutt_buffer_pool_get ();
+ smtp_response_buf = mutt_buffer_pool_get ();
+ mutt_buffer_printf (output_buf, "AUTH %s\r\n", chosen_mech);
+
+ do
+ {
+ if (mutt_socket_write (conn, mutt_b2s (output_buf)) < 0)
+ goto fail;
+
+ if (smtp_get_auth_response (conn, input_buf, &smtp_rc, smtp_response_buf) < 0)
+ goto fail;
+
+ if (smtp_rc != smtp_ready)
+ break;
+
+ gsasl_rc = gsasl_step64 (gsasl_session, mutt_b2s (smtp_response_buf),
+ &gsasl_step_output);
+ if (gsasl_rc == GSASL_NEEDS_MORE || gsasl_rc == GSASL_OK)
+ {
+ mutt_buffer_strcpy (output_buf, gsasl_step_output);
+ mutt_buffer_addstr (output_buf, "\r\n");
+ gsasl_free (gsasl_step_output);
+ }
+ else
+ {
+ dprint (1, (debugfile, "gsasl_step64() failed (%d): %s\n", gsasl_rc,
+ gsasl_strerror (gsasl_rc)));
+ }
+ }
+ while (gsasl_rc == GSASL_NEEDS_MORE || gsasl_rc == GSASL_OK);
+
+ if (smtp_rc == smtp_ready)
+ {
+ mutt_socket_write (conn, "*\r\n");
+ goto fail;
+ }
+
+ if (smtp_success (smtp_rc) && (gsasl_rc == GSASL_OK))
+ rc = SMTP_AUTH_SUCCESS;
+
+fail:
+ mutt_buffer_pool_release (&input_buf);
+ mutt_buffer_pool_release (&output_buf);
+ mutt_buffer_pool_release (&smtp_response_buf);
+ mutt_gsasl_client_finish (&gsasl_session);
+
+ if (rc == SMTP_AUTH_FAIL)
+ dprint (2, (debugfile, "smtp_auth_gsasl: %s failed\n", chosen_mech));
+
+ return rc;
+}
+#endif
/* smtp_auth_oauth: AUTH=OAUTHBEARER support. See RFC 7628 */