diff -Nrbu dovecot-1.0.3/dovecot-example.conf dovecot-1.0.3-OK/dovecot-example.conf --- dovecot-1.0.3/dovecot-example.conf 2007-09-10 18:32:06.000000000 +0400 +++ dovecot-1.0.3-OK/dovecot-example.conf 2007-09-10 17:52:37.000000000 +0400 @@ -745,6 +745,13 @@ # default (usually /etc/krb5.keytab) if not specified. #auth_krb5_keytab = +# Do NTLM authentication using Samba's winbind daemon and ntlm_auth helper. +# +#auth_ntlm_use_winbind = no + +# Path for Samba's ntlm_auth helper binary. +#auth_winbind_helper_path = /usr/bin/ntlm_auth + 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.3/src/auth/Makefile.am dovecot-1.0.3-OK/src/auth/Makefile.am --- dovecot-1.0.3/src/auth/Makefile.am 2007-07-15 23:51:07.000000000 +0400 +++ dovecot-1.0.3-OK/src/auth/Makefile.am 2007-09-10 17:53:42.000000000 +0400 @@ -59,6 +59,7 @@ mech-gssapi.c \ mech-rpa.c \ mech-apop.c \ + mech-winbind.c \ passdb.c \ passdb-blocking.c \ passdb-bsdauth.c \ diff -Nrbu dovecot-1.0.3/src/auth/Makefile.in dovecot-1.0.3-OK/src/auth/Makefile.in --- dovecot-1.0.3/src/auth/Makefile.in 2007-07-23 09:24:08.000000000 +0400 +++ dovecot-1.0.3-OK/src/auth/Makefile.in 2007-09-10 17:49:06.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) \ @@ -326,6 +327,7 @@ mech-cram-md5.c \ mech-digest-md5.c \ mech-ntlm.c \ + mech-winbind.c \ mech-gssapi.c \ mech-rpa.c \ mech-apop.c \ @@ -495,6 +497,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.3/src/auth/mech-winbind.c dovecot-1.0.3-OK/src/auth/mech-winbind.c --- dovecot-1.0.3/src/auth/mech-winbind.c 1970-01-01 03:00:00.000000000 +0300 +++ dovecot-1.0.3-OK/src/auth/mech-winbind.c 2007-09-10 18:31:35.000000000 +0400 @@ -0,0 +1,363 @@ +/* + * NTLM and Negotiate authentication mechanisms, + * using Samba winbind daemon + * + * Copyright (c) 2007 Dmitry Butskoy + * + * This software is released under the MIT license. + */ + +#include "common.h" +#include "lib-signals.h" +#include "mech.h" +#include "str.h" +#include "buffer.h" +#include "safe-memset.h" +#include "base64.h" +#include "istream.h" +#include "ostream.h" + +#include +#include +#include + +#define DEFAULT_WINBIND_HELPER_PATH "/usr/bin/ntlm_auth" + +enum helper_result { + HR_OK = 0, /* OK or continue */ + HR_FAIL = -1, /* authentication failed */ + HR_RESTART = -2 /* FAIL + try to restart helper */ +}; + +struct winbind_helper { + const char *param; + pid_t pid; + + struct istream *in_pipe; + struct ostream *out_pipe; +}; + +struct winbind_auth_request { + struct auth_request auth_request; + + struct winbind_helper *winbind; + bool continued; +}; + +static struct winbind_helper winbind_ntlm_context = { + "--helper-protocol=squid-2.5-ntlmssp", -1, NULL, NULL +}; +static struct winbind_helper winbind_spnego_context = { + "--helper-protocol=gss-spnego", -1, NULL, NULL +}; + +static bool sigchld_handler_set = FALSE; + +static void winbind_helper_disconnect(struct winbind_helper *winbind) +{ + if (winbind->in_pipe != NULL) + i_stream_destroy(&winbind->in_pipe); + if (winbind->out_pipe != NULL) + o_stream_destroy(&winbind->out_pipe); +} + +static void winbind_wait_pid(struct winbind_helper *winbind) +{ + int status, ret; + + if (winbind->pid == -1) + return; + + if ((ret = waitpid(winbind->pid, &status, WNOHANG)) <= 0) { + if (ret < 0 && errno != ECHILD && errno != EINTR) + i_error("waitpid() failed: %m"); + return; + } + + if (WIFSIGNALED(status)) { + i_error("winbind: ntlm_auth died with signal %d", + WTERMSIG(status)); + } else if (WIFEXITED(status)) { + i_error("winbind: ntlm_auth exited with exit code %d", + WEXITSTATUS(status)); + } else { + /* shouldn't happen */ + i_error("winbind: ntlm_auth exited with status %d", + status); + } + winbind->pid = -1; +} + +static void sigchld_handler(int signo __attr_unused__, + void *context __attr_unused__) +{ + winbind_wait_pid(&winbind_ntlm_context); + winbind_wait_pid(&winbind_spnego_context); +} + +static void winbind_helper_connect(struct winbind_helper *winbind) +{ + int infd[2], outfd[2]; + pid_t pid; + + if (winbind->in_pipe != NULL || winbind->pid != -1) + return; + + if (pipe(infd) < 0) { + i_error("pipe() failed: %m"); + return; + } + if (pipe(outfd) < 0) { + (void)close(infd[0]); (void)close(infd[1]); + return; + } + + pid = fork (); + if (pid < 0) { + i_error("fork() failed: %m"); + (void)close(infd[0]); (void)close(infd[1]); + (void)close(outfd[0]); (void)close(outfd[1]); + return; + } + + if (pid == 0) { /* child */ + /* child */ + const char *helper_path, *args[3]; + + (void)close(infd[0]); + (void)close(outfd[1]); + + if (dup2(outfd[0], STDIN_FILENO) < 0 || + dup2(infd[1], STDOUT_FILENO) < 0) + i_fatal("dup2() failed: %m"); + + helper_path = getenv("WINBIND_HELPER_PATH"); + if (helper_path == NULL) + helper_path = DEFAULT_WINBIND_HELPER_PATH; + + args[0] = helper_path; + args[1] = winbind->param; + args[2] = NULL; + execv(args[0], (void *)args); + i_fatal("execv(%s) failed: %m", args[0]); + } + + /* parent */ + (void)close(infd[1]); + (void)close(outfd[0]); + + winbind->pid = pid; + winbind->in_pipe = + i_stream_create_file(infd[0], default_pool, + AUTH_CLIENT_MAX_LINE_LENGTH, FALSE); + winbind->out_pipe = + o_stream_create_file(outfd[1], default_pool, + (size_t)-1, FALSE); + + if (!sigchld_handler_set) { + sigchld_handler_set = TRUE; + lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, NULL); + } +} + +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; + struct istream *in_pipe = request->winbind->in_pipe; + string_t *str; + char *answer; + const char **token; + bool gss_spnego = request->winbind == &winbind_spnego_context; + + if (request->winbind->in_pipe == NULL) + return HR_RESTART; + + 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(request->winbind->out_pipe, str_c(str)) < 0 || + o_stream_flush(request->winbind->out_pipe) < 0) { + auth_request_log_error(auth_request, "winbind", + "write(out_pipe) failed: %m"); + return HR_RESTART; + } + request->continued = FALSE; + + while ((answer = i_stream_read_next_line(in_pipe)) == NULL) { + if (in_pipe->stream_errno != 0) + break; + } + if (answer == NULL) { + auth_request_log_error(auth_request, "winbind", + "read(in_pipe) failed: %m"); + return HR_RESTART; + } + + token = t_strsplit_spaces(answer, " "); + if (!token || token[0] == NULL || + (token[1] == NULL && strcmp(token[0], "BH") != 0) || + (gss_spnego && token[2] == NULL)) { + auth_request_log_error(auth_request, "winbind", + "Invalid input from helper: %s", 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 = gss_spnego ? 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 = gss_spnego ? 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 (gss_spnego && 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_RESTART; + } + else { + auth_request_log_info (auth_request, "winbind", + "Invalid input from helper: %s", answer); + return HR_RESTART; + } + +} + +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) { + if (res == HR_RESTART) + winbind_helper_disconnect(request->winbind); + auth_request_fail(auth_request); + } +} + +static struct auth_request *do_auth_new(struct winbind_helper *winbind) +{ + 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->auth_request.pool = pool; + + request->winbind = winbind; + winbind_helper_connect(request->winbind); + return &request->auth_request; +} + +static struct auth_request *mech_winbind_ntlm_auth_new(void) +{ + return do_auth_new(&winbind_ntlm_context); +} + +static struct auth_request *mech_winbind_spnego_auth_new(void) +{ + return do_auth_new(&winbind_spnego_context); +} + +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.3/src/auth/mech.c dovecot-1.0.3-OK/src/auth/mech.c --- dovecot-1.0.3/src/auth/mech.c 2007-05-19 15:14:04.000000000 +0400 +++ dovecot-1.0.3-OK/src/auth/mech.c 2007-09-10 17:49:06.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.3/src/master/auth-process.c dovecot-1.0.3-OK/src/master/auth-process.c --- dovecot-1.0.3/src/master/auth-process.c 2007-07-15 23:51:07.000000000 +0400 +++ dovecot-1.0.3-OK/src/master/auth-process.c 2007-09-10 17:58:40.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,8 @@ env_put(t_strconcat("GSSAPI_HOSTNAME=", set->gssapi_hostname, NULL)); } + env_put(t_strconcat("WINBIND_HELPER_PATH=", + set->winbind_helper_path, NULL)); restrict_process_size(set->process_size, (unsigned int)-1); } diff -Nrbu dovecot-1.0.3/src/master/master-settings.c dovecot-1.0.3-OK/src/master/master-settings.c --- dovecot-1.0.3/src/master/master-settings.c 2007-09-10 18:32:06.000000000 +0400 +++ dovecot-1.0.3-OK/src/master/master-settings.c 2007-09-10 18:00:28.000000000 +0400 @@ -72,12 +72,14 @@ DEF(SET_STR, anonymous_username), DEF(SET_STR, krb5_keytab), DEF(SET_STR, gssapi_hostname), + DEF(SET_STR, winbind_helper_path), 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 +293,14 @@ MEMBER(anonymous_username) "anonymous", MEMBER(krb5_keytab) "", MEMBER(gssapi_hostname) "", + MEMBER(winbind_helper_path) "/usr/bin/ntlm_auth", 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.3/src/master/master-settings.h dovecot-1.0.3-OK/src/master/master-settings.h --- dovecot-1.0.3/src/master/master-settings.h 2007-07-15 23:51:07.000000000 +0400 +++ dovecot-1.0.3-OK/src/master/master-settings.h 2007-09-10 18:01:28.000000000 +0400 @@ -191,10 +191,12 @@ const char *anonymous_username; const char *krb5_keytab; const char *gssapi_hostname; + const char *winbind_helper_path; 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;