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
+ *
+ * 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
+#include
+
+
+#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;