Patch for GNU Mailutils to support SASL authentication using Libgsasl. Created by Simon Josefsson
on 2002-10-07. Index: TODO =================================================================== RCS file: /cvsroot/mailutils/mailutils/TODO,v retrieving revision 1.29 diff -u -p -r1.29 TODO --- TODO 2 Sep 2002 14:29:26 -0000 1.29 +++ TODO 6 Oct 2002 22:43:36 -0000 @@ -38,6 +38,12 @@ IMPORTANT: - implement charsets in search: 1 SEARCH CHARSET ISO-8859-2 TEXT ... - implement AUTHENTICATE KERBEROS_V4 and SKEY and CRAM-MD5 and SRP? + - partially done by using libgsasl, however, to support CRAM-MD5 + via mu_* a new callback in auth/ that can retrieve cleartext + passwords is needed. a new field in mu_auth_data called `challenge' can be + used, and the "password" handed to the module could be the cram-md5 + response. currently, imap4d implements its own md5 password + database via libgsasl functions. [mail] Index: configure.ac =================================================================== RCS file: /cvsroot/mailutils/mailutils/configure.ac,v retrieving revision 1.19 diff -u -p -r1.19 configure.ac --- configure.ac 3 Oct 2002 14:30:41 -0000 1.19 +++ configure.ac 6 Oct 2002 22:43:36 -0000 @@ -83,7 +83,6 @@ if test "x$WITH_GSSAPI" != "xno"; then AC_DEFINE(WITH_GSSAPI,1,[Define if mailutils is using GSSAPI]) fi fi -AC_SUBST(AUTHOBJS) AC_ARG_WITH(guile, [ --without-guile do not build guile interface], @@ -102,6 +101,15 @@ AC_ARG_WITH(mail-rc, esac], [SITE_MAIL_RC="\$(sysconfdir)/mail.rc"]) +AC_SUBST(SITE_CRAM_MD5_PWD) +AC_ARG_WITH(cram-md5-pwd, + AC_HELP_STRING([--with-cram-md5-pwd=FILE], [use FILE instead of \$sysconfdir/cram-md5.pwd with libgsasl]), + [case "${withval}" in + /*) SITE_CRAM_MD5_PWD="${withval}";; + *) SITE_CRAM_MD5_PWD="\$(sysconfdir)/${withval}";; + esac], + [SITE_CRAM_MD5_PWD="\$(sysconfdir)/cram-md5.pwd"]) + MU_CONF_MAILDIR= AC_ARG_WITH(mail-spool, [ --with-mail-spool=PATH use PATH instead of /var/spool/mail], @@ -176,6 +184,14 @@ AC_DEFINE_UNQUOTED(LOG_FACILITY, $log_fa [Default syslog facility to use]) AC_MSG_RESULT($log_facility) +AC_ARG_WITH(libgsasl, + AC_HELP_STRING([--with-libgsasl], [use libgsasl for SASL authentication]), + [case "${withval}" in + yes) testgsasl=yes ;; + no) testgsasl=no ;; + *) AC_MSG_ERROR(bad value ${withval} for --with-libgsasl) ;; + esac],[testgsasl=yes]) + dnl Check for headers AC_HEADER_STDC AC_HEADER_DIRENT @@ -353,6 +369,18 @@ char *crypt(const char *key, const char AC_CHECK_LIB(crypt, crypt) +if test x"$testgsasl" = x"yes"; then + AM_PATH_LIBGSASL(0.0.0, [ + AUTHLIBS="$AUTHLIBS $LIBGSASL_LIBS" + AUTHINCS="$AUTHINCS $LIBGSASL_CFLAGS" + AUTHOBJS="$AUTHOBJS auth_gsasl.o" + AC_DEFINE(WITH_GSASL, 1, [Enable use of SASL with libgsasl]) + AC_MSG_RESULT([Using libgsasl for authentication]) + ], [ + AC_MSG_RESULT([GSASL libraries not found]) + ]) +fi +AC_SUBST(AUTHOBJS) AC_SUBST(AUTHLIBS) AC_SUBST(AUTHINCS) Index: imap4d/Makefile.am =================================================================== RCS file: /cvsroot/mailutils/mailutils/imap4d/Makefile.am,v retrieving revision 1.20 diff -u -p -r1.20 Makefile.am --- imap4d/Makefile.am 3 Sep 2002 09:35:06 -0000 1.20 +++ imap4d/Makefile.am 6 Oct 2002 22:43:36 -0000 @@ -5,6 +5,7 @@ AUTOMAKE_OPTIONS = ../lib/ansi2knr INCLUDES = -I$(top_srcdir)/lib -I$(top_srcdir)/include @AUTHINCS@ SUBDIRS = testsuite +AM_CFLAGS = -DSITE_CRAM_MD5_PWD=\"@address@hidden" sbin_PROGRAMS = imap4d @@ -19,4 +20,4 @@ imap4d_SOURCES = append.c authenticate.c ## dependency. Think about better approach --gray imap4d_DEPENDENCIES = @AUTHOBJS@ ../mailbox/libmailbox.la ../lib/libmailutils.la -EXTRA_DIST=auth_gss.c +EXTRA_DIST=auth_gss.c auth_gsasl.c Index: imap4d/auth_gsasl.c =================================================================== RCS file: imap4d/auth_gsasl.c diff -N imap4d/auth_gsasl.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ imap4d/auth_gsasl.c 6 Oct 2002 22:43:37 -0000 @@ -0,0 +1,324 @@ +/* GNU mailutils - a suite of utilities for electronic mail + Copyright (C) 2002 Free Software Foundation, Inc. + + 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include "imap4d.h" + +#ifdef WITH_GSASL + +Gsasl_ctx *gsaslctx; +char *anonymous_user; +char *cram_md5_pwd = SITE_CRAM_MD5_PWD; + +static int callback_external (Gsasl_session_ctx *ctx); +static int callback_anonymous (Gsasl_session_ctx *ctx, + const char *token); +static int callback_validate (Gsasl_session_ctx *ctx, + char *authorization_id, + char *authentication_id, + char *password); +static int callback_retrieve (Gsasl_session_ctx *ctx, + char *authentication_id, + char *authorization_id, + char *realm, + char *key, + size_t *keylen); +static int callback_realm (Gsasl_session_ctx *ctx, + char *out, + size_t *outlen, + size_t nth); +static int +callback_service (Gsasl_session_ctx *ctx, + char *srv, + size_t *srvlen, + char *host, + size_t *hostlen); +static int +callback_gssapi (Gsasl_session_ctx *ctx, + char *clientname, + char *authentication_id); + +void +auth_gsasl_init (void) +{ + FILE *fh; + int res; + + res = gsasl_init(&gsaslctx); + if (res != GSASL_OK) + { + syslog (LOG_NOTICE, "Cannot initialize libgsasl: %s", + gsasl_strerror (res)); + } + + gsasl_server_callback_realm_set (gsaslctx, callback_realm); + gsasl_server_callback_external_set (gsaslctx, callback_external); + gsasl_server_callback_validate_set (gsaslctx, callback_validate); + gsasl_server_callback_service_set (gsaslctx, callback_service); + gsasl_server_callback_gssapi_set (gsaslctx, callback_gssapi); + if (anonymous_user) + gsasl_server_callback_anonymous_set (gsaslctx, callback_anonymous); + fh = fopen(cram_md5_pwd, "r"); + if (fh) + { + gsasl_server_callback_retrieve_set (gsaslctx, callback_retrieve); + fclose(fh); + } +} + +void +auth_gsasl_capability (void) +{ + char *listmech, *token, *s; + size_t listmechlen; + int res; + + res = gsasl_server_listmech (gsaslctx, NULL, &listmechlen); + if (res != GSASL_OK) + return; + listmech = (char*) malloc(listmechlen); + res = gsasl_server_listmech (gsaslctx, listmech, &listmechlen); + if (res != GSASL_OK) + return; + for (token = strtok_r (listmech, " ", &s); token; + token = strtok_r (NULL, " ", &s)) + util_send(" AUTH=%s", token); + free(listmech); +} + +int +auth_gsasl (struct imap4d_command *command, + char *auth_type, + char *client_init, + char **username) +{ + Gsasl_session_ctx *sctx; + char *input = NULL; + char output[512]; + size_t output_len; + int rc; + + rc = gsasl_server_start (gsaslctx, auth_type, &sctx); + if (rc != GSASL_OK) + return 0; + + gsasl_server_application_data_set (sctx, username); + + input = client_init; + output[0] = '\0'; + output_len = sizeof(output); + do + { + rc = gsasl_server_step_base64 (sctx, input, output, output_len); + if (rc != GSASL_NEEDS_MORE) + break; + + util_send("+ %s\r\n", output); + input = imap4d_readline_ex (ifile); + } + while (rc == GSASL_NEEDS_MORE); + + gsasl_server_finish (sctx); + + if (rc != GSASL_OK) + { + syslog (LOG_NOTICE, "GSASL error %d: %s", rc, gsasl_strerror(rc)); + util_finish (command, RESP_NO, "Authentication rejected"); + return 1; + } + + if (*username == NULL) + { + syslog (LOG_NOTICE, "GSASL callback for %s failed to set username", + auth_type); + util_finish (command, RESP_NO, "Authentication rejected"); + return 1; + } + + return 0; +} + +static int +callback_validate (Gsasl_session_ctx *ctx, + char *authorization_id, + char *authentication_id, + char *password) +{ + /* This gets called when e.g. SASL mechanism PLAIN and LOGIN is invoked */ + char **username = gsasl_server_application_data_get (ctx); + int rc; + + auth_data = mu_get_auth_by_name (authentication_id); + + if (auth_data == NULL) + { + syslog (LOG_INFO, "User '%s': nonexistent", authentication_id); + return GSASL_AUTHENTICATION_ERROR; + } + + rc = mu_authenticate (auth_data, password); + if (rc) + { + syslog (LOG_INFO, "Login failed: %s", authentication_id); + return GSASL_AUTHENTICATION_ERROR; + } + + if (username) + *username = strdup(authentication_id); + + return GSASL_OK; +} + +static int +callback_external (Gsasl_session_ctx *ctx) +{ + /* This gets called when SASL mechanism EXTERNAL is invoked */ + + /* This should set username and return GSASL_OK if imap4d is running + under e.g. TLS with SRP user authentication, otherwise return + GSASL_AUTHENTICATION_ERROR. */ + + return GSASL_AUTHENTICATION_ERROR; +} + +static int +callback_anonymous (Gsasl_session_ctx *ctx, const char *token) +{ + /* This gets called when SASL mechanism ANONYMOUS is invoked */ + char **username = gsasl_server_application_data_get (ctx); + + if (token && username && anonymous_user) + { + syslog (LOG_INFO, "Letting `%s' in anonymously as `%s'", + token, anonymous_user); + *username = strdup(anonymous_user); + return GSASL_OK; + } + + return GSASL_AUTHENTICATION_ERROR; +} + +static int +callback_retrieve (Gsasl_session_ctx *ctx, + char *authentication_id, + char *authorization_id, + char *realm, + char *key, + size_t *keylen) +{ + /* This gets called when SASL mechanism CRAM-MD5 or DIGEST-MD5 is invoked */ + char **username = gsasl_server_application_data_get (ctx); + + if (username && authentication_id) + *username = strdup(authentication_id); + + return gsasl_md5pwd_get_password (cram_md5_pwd, authentication_id, + key, keylen); +} + +static int +callback_realm (Gsasl_session_ctx *ctx, + char *out, + size_t *outlen, + size_t nth) +{ + /* This is for DIGEST-MD5 */ + char *realm = util_localname(); + + if (nth > 0) + return GSASL_NO_MORE_REALMS; + + if (out) + { + if (*outlen < strlen(realm)) + return GSASL_TOO_SMALL_BUFFER; + memcpy(out, realm, strlen(realm)); + } + + *outlen = strlen(realm); + + return GSASL_OK; +} + +#define GSSAPI_SERVICE "imap" + +static int +callback_service (Gsasl_session_ctx *ctx, + char *srv, + size_t *srvlen, + char *host, + size_t *hostlen) +{ + char *hostname = util_localname (); + + if (srv) + { + if (*srvlen < strlen(GSSAPI_SERVICE)) + return GSASL_TOO_SMALL_BUFFER; + + memcpy(srv, GSSAPI_SERVICE, strlen(GSSAPI_SERVICE)); + } + + if (srvlen) + *srvlen = strlen(GSSAPI_SERVICE); + + if (host) + { + if (*hostlen < strlen(hostname)) + return GSASL_TOO_SMALL_BUFFER; + + memcpy(host, hostname, strlen(hostname)); + } + + if (hostlen) + *hostlen = strlen(hostname); + + return GSASL_OK; +} + +#ifdef WITH_GSSAPI +#include