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 +#endif + +static int +callback_gssapi (Gsasl_session_ctx *ctx, + char *clientname, + char *authentication_id) +{ + /* This gets called when SASL mechanism GSSAPI is invoked */ + char **username = gsasl_server_application_data_get (ctx); + int rc = GSASL_AUTHENTICATION_ERROR; + +#ifdef WITH_GSSAPI + krb5_principal p; + krb5_context kcontext; + + krb5_init_context (&kcontext); + + if (krb5_parse_name (kcontext, clientname, &p) != 0) + return -1; + if (krb5_kuserok (kcontext, p, authentication_id)) + rc = GSASL_OK; + krb5_free_principal (kcontext, p); +#endif + + if (rc == GSASL_OK && username && authentication_id) + *username = strdup(authentication_id); + + return rc; +} + +#endif /* WITH_GSASL */ Index: imap4d/auth_gss.c =================================================================== RCS file: /cvsroot/mailutils/mailutils/imap4d/auth_gss.c,v retrieving revision 1.2 diff -u -p -r1.2 auth_gss.c --- imap4d/auth_gss.c 11 Feb 2002 08:36:36 -0000 1.2 +++ imap4d/auth_gss.c 6 Oct 2002 22:43:37 -0000 @@ -232,7 +232,12 @@ auth_gssapi (struct imap4d_command *comm protection_mech = mech; client_buffer_size = sec_level & 0x00ffffffff; - *username = strdup ((char*)outbuf.value + 4); + *username = malloc(outbuf.length - 4 + 1); + if (*username) + { + memcpy(*username, (char*)outbuf.value + 4, outbuf.length - 4); + (*username)[outbuf.length - 4] = '\0'; + } gss_release_buffer (&min_stat, &outbuf); maj_stat = gss_display_name(&min_stat, client, @@ -270,8 +275,6 @@ auth_gssapi (struct imap4d_command *comm gss_release_buffer (&min_stat, &client_name); maj_stat = gss_delete_sec_context (&min_stat, &context, &outbuf); gss_release_buffer (&min_stat, &outbuf); - util_finish (command, RESP_OK, - "GSSAPI authentication successful"); return 0; } Index: imap4d/authenticate.c =================================================================== RCS file: /cvsroot/mailutils/mailutils/imap4d/authenticate.c,v retrieving revision 1.9 diff -u -p -r1.9 authenticate.c --- imap4d/authenticate.c 13 Aug 2002 13:24:57 -0000 1.9 +++ imap4d/authenticate.c 6 Oct 2002 22:43:37 -0000 @@ -18,6 +18,8 @@ #include "imap4d.h" extern int auth_gssapi __P((struct imap4d_command *, char **username)); +extern int auth_gsasl __P((struct imap4d_command *command, char *auth_type, char *client_init, char **username)); +extern void auth_gsasl_capability (void); struct imap_auth { char *name; @@ -35,6 +37,9 @@ imap4d_auth_capability () struct imap_auth *ap; for (ap = imap_auth_tab; ap->name; ap++) util_send(" AUTH=%s", ap->name); +#ifdef WITH_GSASL + auth_gsasl_capability (); +#endif } int @@ -42,6 +47,7 @@ imap4d_authenticate (struct imap4d_comma { char *sp = NULL; char *auth_type; + char *client_init; struct imap_auth *ap; char *username = NULL; @@ -49,13 +55,20 @@ imap4d_authenticate (struct imap4d_comma util_unquote (&auth_type); if (!auth_type) return util_finish (command, RESP_BAD, "Too few arguments"); + client_init = util_getword (sp, &sp); + util_unquote (&client_init); - for (ap = imap_auth_tab; ap->name; ap++) - if (strcmp (auth_type, ap->name) == 0) - { - if (ap->handler (command, &username)) - return 1; - } +#ifdef WITH_GSASL + if (auth_gsasl (command, auth_type, client_init, &username)) + return 1; + else if (username == NULL) +#endif + for (ap = imap_auth_tab; ap->name; ap++) + if (strcmp (auth_type, ap->name) == 0) + { + if (ap->handler (command, &username)) + return 1; + } if (username) { @@ -72,6 +85,10 @@ imap4d_authenticate (struct imap4d_comma chdir (homedir); namespace_init (homedir); syslog (LOG_INFO, "User '%s' logged in", username); + + util_finish (command, RESP_OK, "%s successful for user `%s'", + auth_type, username); + return 0; } Index: imap4d/imap4d.c =================================================================== RCS file: /cvsroot/mailutils/mailutils/imap4d/imap4d.c,v retrieving revision 1.27 diff -u -p -r1.27 imap4d.c --- imap4d/imap4d.c 13 Aug 2002 13:24:57 -0000 1.27 +++ imap4d/imap4d.c 6 Oct 2002 22:43:37 -0000 @@ -17,6 +17,12 @@ #include "imap4d.h" +#ifdef WITH_GSASL +extern void auth_gsasl_init __P((void)); +extern char *anonymous_user; +extern char *cram_md5_pwd; +#endif + FILE *ifile; FILE *ofile; mailbox_t mbox; @@ -45,6 +51,12 @@ static struct argp_option options[] = "set the `other' namespace", 0}, {"shared-namespace", 'S', "PATHLIST", 0, "set the `shared' namespace", 0}, +#ifdef WITH_GSASL + {"anonymous-user", 'a', "USERNAME", 0, + "Enable anonymous mode and use USERNAME account for it", 0}, + {"cram-md5-pwd-file", 'c', "FILENAME", 0, + "Use FILENAME for SASL username and passwords (default is " SITE_CRAM_MD5_PWD ")", 0}, +#endif { NULL, 0, NULL, 0, NULL, 0 } }; @@ -90,7 +102,17 @@ imap4d_parse_opt (int key, char *arg, st case 'S': set_namespace (NS_SHARED, arg); break; - + +#ifdef WITH_GSASL + case 'a': + anonymous_user = strdup(arg); + break; + + case 'c': + cram_md5_pwd = strdup(arg); + break; +#endif + default: return ARGP_ERR_UNKNOWN; } @@ -173,6 +195,10 @@ main (int argc, char **argv) mu_error_set_print (mu_syslog_error_printer); umask (S_IROTH | S_IWOTH | S_IXOTH); /* 007 */ + +#ifdef WITH_GSASL + auth_gsasl_init(); +#endif /* Actually run the daemon. */ if (daemon_param.mode == MODE_DAEMON) Index: imap4d/imap4d.h =================================================================== RCS file: /cvsroot/mailutils/mailutils/imap4d/imap4d.h,v retrieving revision 1.44 diff -u -p -r1.44 imap4d.h --- imap4d/imap4d.h 23 Aug 2002 06:48:41 -0000 1.44 +++ imap4d/imap4d.h 6 Oct 2002 22:43:37 -0000 @@ -69,6 +69,10 @@ # include #endif +#ifdef WITH_GSASL +# include +#endif + #include #include #include