Version 3 of the patch. This version sent also to Samba team, in hope they have more chances to test it properly. Dmitry Butskoy diff -Nrbu dovecot-1.0.1/dovecot-example.conf dovecot-1.0.1-OK/dovecot-example.conf --- dovecot-1.0.1/dovecot-example.conf 2007-06-13 04:47:39.000000000 +0400 +++ dovecot-1.0.1-OK/dovecot-example.conf 2007-07-06 18:40:35.000000000 +0400 @@ -748,6 +748,27 @@ # default (usually /etc/krb5.keytab) if not specified. #auth_krb5_keytab = +# If you have "ntlm_auth" helper and running winbind daemon +# (both from Samba package, see http://www.samba.org), +# you can use it for "ntlm" and "gss-spnego" authentication mechanisms. +# It is useful when you need to authenticate users against a Windows domain +# (either AD or NT). +# +# The usernames, returned by winbind, can contain some domain part +# (either "DOMAIN\user" or "user@example.com"). Such usernames +# are always transformed to the form of "user@domain". To strip domain part +# (to obtain correspond local username, for example), specify +# auth_username_format = %n + +# Use "ntlm-auth + winbind" way for "ntlm" mechanism, +# instead of own Dovecot's ntlm support ("gss-spnego" always requires winbind) +#auth_ntlm_use_winbind = no + +# Specify an alternate path and command line for the helper for "ntlm" +#auth_winbind_helper_ntlm = /usr/bin/ntlm_auth --helper-protocol=squid-2.5-ntlmssp" +# The same for "gss-spnego" +#auth_winbind_helper_spnego = /usr/bin/ntlm_auth --helper-protocol=gss-spnego + auth default { # Space separated list of wanted authentication mechanisms: # plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi diff -Nrbu dovecot-1.0.1/src/auth/Makefile.am dovecot-1.0.1-OK/src/auth/Makefile.am --- dovecot-1.0.1/src/auth/Makefile.am 2007-05-19 15:14:04.000000000 +0400 +++ dovecot-1.0.1-OK/src/auth/Makefile.am 2007-07-03 17:53:14.000000000 +0400 @@ -56,6 +56,7 @@ mech-cram-md5.c \ mech-digest-md5.c \ mech-ntlm.c \ + mech-winbind.c \ mech-gssapi.c \ mech-rpa.c \ mech-apop.c \ diff -Nrbu dovecot-1.0.1/src/auth/Makefile.in dovecot-1.0.1-OK/src/auth/Makefile.in --- dovecot-1.0.1/src/auth/Makefile.in 2007-06-14 16:02:13.000000000 +0400 +++ dovecot-1.0.1-OK/src/auth/Makefile.in 2007-07-03 17:53:41.000000000 +0400 @@ -78,6 +78,7 @@ mech.$(OBJEXT) mech-anonymous.$(OBJEXT) mech-plain.$(OBJEXT) \ mech-login.$(OBJEXT) mech-cram-md5.$(OBJEXT) \ mech-digest-md5.$(OBJEXT) mech-ntlm.$(OBJEXT) \ + mech-winbind.$(OBJEXT) \ mech-gssapi.$(OBJEXT) mech-rpa.$(OBJEXT) mech-apop.$(OBJEXT) \ passdb.$(OBJEXT) passdb-blocking.$(OBJEXT) \ passdb-bsdauth.$(OBJEXT) passdb-cache.$(OBJEXT) \ @@ -325,6 +326,7 @@ mech-cram-md5.c \ mech-digest-md5.c \ mech-ntlm.c \ + mech-winbind.c \ mech-gssapi.c \ mech-rpa.c \ mech-apop.c \ @@ -494,6 +496,7 @@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-ntlm.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-plain.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-rpa.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech-winbind.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mech.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mycrypt.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/passdb-blocking.Po@am__quote@ diff -Nrbu dovecot-1.0.1/src/auth/mech-winbind.c dovecot-1.0.1-OK/src/auth/mech-winbind.c --- dovecot-1.0.1/src/auth/mech-winbind.c 1970-01-01 03:00:00.000000000 +0300 +++ dovecot-1.0.1-OK/src/auth/mech-winbind.c 2007-07-06 17:53:00.000000000 +0400 @@ -0,0 +1,310 @@ +/* + * NTLM and Negotiate authentication mechanisms, + * using Samba winbind daemon + * + * Copyright (c) 2007 Dmitry Butskoy <dmitry@butskoy.name> + * + * This software is released under the MIT license. + */ + +#include "common.h" +#include "mech.h" +#include "str.h" +#include "buffer.h" +#include "safe-memset.h" +#include "base64.h" +#include "istream.h" +#include "ostream.h" + +#include <stdlib.h> +#include <unistd.h> + + +#define WINBIND_HELPER_NTLM \ + "/usr/bin/ntlm_auth --helper-protocol=squid-2.5-ntlmssp" +#define WINBIND_HELPER_SPNEGO \ + "/usr/bin/ntlm_auth --helper-protocol=gss-spnego" + + +struct winbind_auth_request { + struct auth_request auth_request; + + pool_t pool; + + bool continued; + + bool negotiate; /* TRUE for GSS-SPNEGO case, else NTLM */ + +}; + +enum helper_result { + HR_OK = 0, /* OK or continue */ + HR_FAIL = -1, /* authentication failed */ + HR_RESTART = -2 /* FAIL + try to restart helper */ +}; + + +static struct istream *in_pipe = NULL; +static struct ostream *out_pipe = NULL; + + +static void init_helper (bool negotiate) { + int infd[2], outfd[2]; + pid_t pid; + + if (in_pipe) i_stream_destroy (&in_pipe); + if (out_pipe) o_stream_destroy (&out_pipe); + + + if (pipe (infd) < 0 || pipe (outfd) < 0) + i_fatal ("pipe creation failed: %m"); + + pid = fork (); + if (pid < 0) i_fatal ("fork() failed: %m"); + + if (pid == 0) { /* child */ + const char *helper; + const char **args; + + close (infd[0]); + close (outfd[1]); + + if (dup2 (outfd[0], 0) < 0 || + dup2 (infd[1], 1) < 0 + ) i_fatal ("dup2 failed after fork(): %m"); + + if (negotiate) { + helper = getenv ("WINBIND_HELPER_SPNEGO"); + if (!helper) helper = WINBIND_HELPER_SPNEGO; + } else { + helper = getenv ("WINBIND_HELPER_NTLM"); + if (!helper) helper = WINBIND_HELPER_NTLM; + } + + args = t_strsplit_spaces (helper, " "); + if (!args || !args[0]) + i_fatal ("bad helper `%s' specified", helper); + + execv (args[0], (char **) args); + i_fatal ("execv of %s failed: %m", args[0]); + _exit (127); /* paranoia */ + } + + /* parent */ + + close (infd[1]); + close (outfd[0]); + + in_pipe = i_stream_create_file (infd[0], default_pool, + AUTH_CLIENT_MAX_LINE_LENGTH, FALSE); + out_pipe = o_stream_create_file (outfd[1], default_pool, + (size_t) -1, FALSE); + + return; +} + + +static enum helper_result do_auth_continue (struct auth_request *auth_request, + const unsigned char *data, size_t data_size) { + struct winbind_auth_request *request = + (struct winbind_auth_request *) auth_request; + string_t *str; + char *answer; + const char **token; + + str = t_str_new (MAX_BASE64_ENCODED_SIZE (data_size + 1) + 4); + + str_printfa (str, "%s ", request->continued ? "KK" : "YR"); + base64_encode (data, data_size, str); + str_append_c (str, '\n'); + + if (o_stream_send_str (out_pipe, str_c (str)) < 0 || + o_stream_flush (out_pipe) < 0 + ) { + auth_request_log_info (auth_request, "winbind", + "cannot write to helper pipe"); + return HR_RESTART; + } + + request->continued = FALSE; + + + answer = i_stream_read_next_line (in_pipe); + if (!answer) { + auth_request_log_info (auth_request, "winbind", + "cannot read from helper pipe"); + return HR_RESTART; + } + + token = t_strsplit_spaces (answer, " \n"); + if (!token || !token[0] || + (!token[1] && strcmp (token[0], "BH") != 0) || + (request->negotiate && !token[2]) + ) { + auth_request_log_info (auth_request, "winbind", + "could not parse `%s' helper callback", answer); + return HR_RESTART; + } + + /* + * NTLM: + * The child's reply contains 2 parts: + * - The code: TT, AF or NA + * - The argument: + * For TT it's the blob to send to the client, coded in base64 + * For AF it's user or DOMAIN\user + * For NA it's the NT error code + * + * GSS-SPNEGO: + * The child's reply contains 3 parts: + * - The code: TT, AF or NA + * - The blob to send to the client, coded in base64 + * - The argument: + * For TT it's a dummy '*' + * For AF it's DOMAIN\user + * For NA it's the NT error code + */ + + if (!strcmp (token[0], "TT")) { + buffer_t *buf; + size_t len = strlen (token[1]); + + buf = buffer_create_dynamic (pool_datastack_create(), + MAX_BASE64_DECODED_SIZE (len)); + base64_decode (token[1], len, NULL, buf); + + auth_request->callback (auth_request, + AUTH_CLIENT_RESULT_CONTINUE, + buf->data, buf->used); + request->continued = TRUE; + return HR_OK; + } + else if (!strcmp (token[0], "NA")) { + const char *error = request->negotiate ? token[2] : token[1]; + + auth_request_log_info (auth_request, "winbind", + "user not authenticated: %s", error); + + return HR_FAIL; + } + else if (!strcmp (token[0], "AF")) { + const char *user, *p, *error; + + user = request->negotiate ? token[2] : token[1]; + + p = strchr (user, '\\'); + if (p) { + /* change "DOMAIN\user" to uniform style "user@DOMAIN" */ + user = t_strconcat (p+1, "@", t_strdup_until (user, p), NULL); + } + + if (!auth_request_set_username (auth_request, user, &error)) { + auth_request_log_info (auth_request, "winbind", "%s", error); + + return HR_FAIL; + } + + if (request->negotiate && strcmp (token[1], "*") != 0) { + buffer_t *buf; + size_t len = strlen (token[1]); + + buf = buffer_create_dynamic (pool_datastack_create(), + MAX_BASE64_DECODED_SIZE (len)); + base64_decode (token[1], len, NULL, buf); + + auth_request_success (&request->auth_request, + buf->data, buf->used); + } else + auth_request_success (&request->auth_request, NULL, 0); + + return HR_OK; + } + else if (!strcmp (token[0], "BH")) { + auth_request_log_info (auth_request, "winbind", + "ntlm_auth reports broken helper: %s", + token[1] ? token[1] : ""); + return HR_FAIL; + } + else { + auth_request_log_info (auth_request, "winbind", + "could not parse `%s' helper callback", answer); + return HR_FAIL; + } + +} + +static void mech_winbind_auth_continue(struct auth_request *auth_request, + const unsigned char *data, size_t data_size) { + struct winbind_auth_request *request = + (struct winbind_auth_request *) auth_request; + enum helper_result res; + + res = do_auth_continue (auth_request, data, data_size); + + if (res == HR_OK) return; + else if (res == HR_RESTART) + init_helper (request->negotiate); /* try to restart */ + + auth_request_fail (auth_request); + return; +} + + +static struct auth_request *do_auth_new (bool negotiate) { + struct winbind_auth_request *request; + pool_t pool; + + pool = pool_alloconly_create("winbind_auth_request", 1024); + request = p_new(pool, struct winbind_auth_request, 1); + request->pool = pool; + + request->negotiate = negotiate; + + request->continued = FALSE; + + if (!in_pipe || !out_pipe) + init_helper (negotiate); + + request->auth_request.pool = pool; + return &request->auth_request; +} + +static struct auth_request *mech_winbind_ntlm_auth_new (void) { + + return do_auth_new (FALSE); +} + +static struct auth_request *mech_winbind_spnego_auth_new (void) { + + return do_auth_new (TRUE); +} + + +const struct mech_module mech_winbind_ntlm = { + "NTLM", + + MEMBER(flags) MECH_SEC_DICTIONARY | MECH_SEC_ACTIVE, + + MEMBER(passdb_need_plain) FALSE, + MEMBER(passdb_need_credentials) FALSE, + + mech_winbind_ntlm_auth_new, + mech_generic_auth_initial, + mech_winbind_auth_continue, + mech_generic_auth_free +}; + +const struct mech_module mech_winbind_spnego = { + "GSS-SPNEGO", + + MEMBER(flags) 0, + + MEMBER(passdb_need_plain) FALSE, + MEMBER(passdb_need_credentials) FALSE, + + mech_winbind_spnego_auth_new, + mech_generic_auth_initial, + mech_winbind_auth_continue, + mech_generic_auth_free +}; + diff -Nrbu dovecot-1.0.1/src/auth/mech.c dovecot-1.0.1-OK/src/auth/mech.c --- dovecot-1.0.1/src/auth/mech.c 2007-05-19 15:14:04.000000000 +0400 +++ dovecot-1.0.1-OK/src/auth/mech.c 2007-07-03 17:53:14.000000000 +0400 @@ -73,6 +73,8 @@ #ifdef HAVE_GSSAPI extern struct mech_module mech_gssapi; #endif +extern struct mech_module mech_winbind_ntlm; +extern struct mech_module mech_winbind_spnego; void mech_init(void) { @@ -81,12 +83,16 @@ mech_register_module(&mech_apop); mech_register_module(&mech_cram_md5); mech_register_module(&mech_digest_md5); + if (getenv("NTLM_USE_WINBIND") != NULL) + mech_register_module(&mech_winbind_ntlm); + else mech_register_module(&mech_ntlm); mech_register_module(&mech_rpa); mech_register_module(&mech_anonymous); #ifdef HAVE_GSSAPI mech_register_module(&mech_gssapi); #endif + mech_register_module(&mech_winbind_spnego); } void mech_deinit(void) @@ -96,10 +102,14 @@ mech_unregister_module(&mech_apop); mech_unregister_module(&mech_cram_md5); mech_unregister_module(&mech_digest_md5); + if (getenv("NTLM_USE_WINBIND") != NULL) + mech_unregister_module(&mech_winbind_ntlm); + else mech_unregister_module(&mech_ntlm); mech_unregister_module(&mech_rpa); mech_unregister_module(&mech_anonymous); #ifdef HAVE_GSSAPI mech_unregister_module(&mech_gssapi); #endif + mech_unregister_module(&mech_winbind_spnego); } diff -Nrbu dovecot-1.0.1/src/master/auth-process.c dovecot-1.0.1-OK/src/master/auth-process.c --- dovecot-1.0.1/src/master/auth-process.c 2007-06-12 20:43:46.000000000 +0400 +++ dovecot-1.0.1-OK/src/master/auth-process.c 2007-07-03 17:53:14.000000000 +0400 @@ -474,6 +474,8 @@ env_put("SSL_REQUIRE_CLIENT_CERT=1"); if (set->ssl_username_from_cert) env_put("SSL_USERNAME_FROM_CERT=1"); + if (set->ntlm_use_winbind) + env_put("NTLM_USE_WINBIND=1"); if (*set->krb5_keytab != '\0') { /* Environment used by Kerberos 5 library directly */ env_put(t_strconcat("KRB5_KTNAME=", set->krb5_keytab, NULL)); @@ -482,6 +484,14 @@ env_put(t_strconcat("GSSAPI_HOSTNAME=", set->gssapi_hostname, NULL)); } + if (*set->winbind_helper_ntlm != '\0') { + env_put(t_strconcat("WINBIND_HELPER_NTLM=", + set->winbind_helper_ntlm, NULL)); + } + if (*set->winbind_helper_spnego != '\0') { + env_put(t_strconcat("WINBIND_HELPER_SPNEGO=", + set->winbind_helper_spnego, NULL)); + } restrict_process_size(set->process_size, (unsigned int)-1); } diff -Nrbu dovecot-1.0.1/src/master/master-settings.c dovecot-1.0.1-OK/src/master/master-settings.c --- dovecot-1.0.1/src/master/master-settings.c 2007-06-12 20:43:06.000000000 +0400 +++ dovecot-1.0.1-OK/src/master/master-settings.c 2007-07-03 17:53:14.000000000 +0400 @@ -72,12 +72,15 @@ DEF(SET_STR, anonymous_username), DEF(SET_STR, krb5_keytab), DEF(SET_STR, gssapi_hostname), + DEF(SET_STR, winbind_helper_ntlm), + DEF(SET_STR, winbind_helper_spnego), DEF(SET_BOOL, verbose), DEF(SET_BOOL, debug), DEF(SET_BOOL, debug_passwords), DEF(SET_BOOL, ssl_require_client_cert), DEF(SET_BOOL, ssl_username_from_cert), + DEF(SET_BOOL, ntlm_use_winbind), DEF(SET_INT, count), DEF(SET_INT, worker_max_count), @@ -291,12 +294,15 @@ MEMBER(anonymous_username) "anonymous", MEMBER(krb5_keytab) "", MEMBER(gssapi_hostname) "", + MEMBER(winbind_helper_ntlm) "", + MEMBER(winbind_helper_spnego) "", MEMBER(verbose) FALSE, MEMBER(debug) FALSE, MEMBER(debug_passwords) FALSE, MEMBER(ssl_require_client_cert) FALSE, MEMBER(ssl_username_from_cert) FALSE, + MEMBER(ntlm_use_winbind) FALSE, MEMBER(count) 1, MEMBER(worker_max_count) 30, diff -Nrbu dovecot-1.0.1/src/master/master-settings.h dovecot-1.0.1-OK/src/master/master-settings.h --- dovecot-1.0.1/src/master/master-settings.h 2007-06-12 20:42:52.000000000 +0400 +++ dovecot-1.0.1-OK/src/master/master-settings.h 2007-07-03 17:53:14.000000000 +0400 @@ -191,10 +191,13 @@ const char *anonymous_username; const char *krb5_keytab; const char *gssapi_hostname; + const char *winbind_helper_ntlm; + const char *winbind_helper_spnego; bool verbose, debug, debug_passwords; bool ssl_require_client_cert; bool ssl_username_from_cert; + bool ntlm_use_winbind; unsigned int count; unsigned int worker_max_count;