Version 2 of the patch. Options "auth_winbind_helper_ntlm" and "auth_winbind_helper_spnego" added. (The default values for them are actually placed in mech-winbind.c . Hmm... ) dup2() and fork() errors reported. Don't want to report close() errors here :) Usernames in form "DOMAIN\user" are transformed to "user@DOMAIN" (call to auth_request_set_username() is used). Still don't want to simplify output by "write_full()" -- for more clean code it is better to do input/output in a similar style. Does an extra "ostream" grab a significant amount of resources? I've not found an easy way how the io can be re-coded "non-blocking". I hope in the case of the network failures (where Dovecot is reachable, but domain controller not) the winbind daemon itself can do timeouts (which will be "transferred" then to the pending ntlm_auth etc.) Maybe to leave things as is (i.e. blocking), and later some new common code (a worker for mech) could help for all such mechanisms (including gssapi) ? Regards, Dmitry Butskoy 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-03 17:53:14.000000000 +0400 @@ -0,0 +1,298 @@ +/* + * 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; + + int continued; + + int negotiate; /* 1 -- GSS-SPNEGO, 0 -- NTLM */ + +}; + + +static struct istream *in_pipe = NULL; +static struct ostream *out_pipe = NULL; + + +static void init_helper (int 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"); + + pid = fork (); + if (pid < 0) i_fatal ("fork() failed"); + + 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()"); + + 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", 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 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; + 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"); + goto restart; + } + + request->continued = 0; + + + answer = i_stream_read_next_line (in_pipe); + if (!answer) { + auth_request_log_info (auth_request, "winbind", + "cannot read from helper pipe"); + goto 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); + goto 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 = 1; + return; + } + 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); + + auth_request_fail (auth_request); + return; + } + 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); + + auth_request_fail (auth_request); + return; + } + + 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; + } + else if (!strcmp (token[0], "BH")) { + auth_request_log_info (auth_request, "winbind", + "ntlm_auth reports broken helper: %s", + token[1] ? token[1] : ""); + auth_request_fail (auth_request); + return; + } + else { + auth_request_log_info (auth_request, "winbind", + "could not parse `%s' helper callback", answer); + auth_request_fail (auth_request); + return; + } + + +restart: + init_helper (request->negotiate); /* try to restart */ + + auth_request_fail (auth_request); + return; +} + + +static struct auth_request *do_auth_new (int 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 = 0; + + 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 (0); +} + +static struct auth_request *mech_winbind_spnego_auth_new (void) { + + return do_auth_new (1); +} + + +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;