Mercurial > dovecot > original-hg > dovecot-1.2
changeset 6181:18f663e23c28 HEAD
Added support for Samba's ntlm_auth helper. It's used for GSS-SPNEGO
mechanism. If auth_ntlm_use_winbind=yes it's also used for NTLM mechanism.
Patch by Dmitry Butskoy.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 06 Aug 2007 21:12:51 +0300 |
parents | 6b0fe0f93896 |
children | 593d2ab4df0d |
files | dovecot-example.conf src/auth/Makefile.am src/auth/mech-winbind.c src/auth/mech.c src/master/auth-process.c src/master/master-settings.c src/master/master-settings.h |
diffstat | 7 files changed, 334 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/dovecot-example.conf Mon Aug 06 20:49:06 2007 +0300 +++ b/dovecot-example.conf Mon Aug 06 21:12:51 2007 +0300 @@ -788,9 +788,17 @@ # default (usually /etc/krb5.keytab) if not specified. #auth_krb5_keytab = +# Do NTLM authentication using Samba's winbind daemon and ntlm_auth helper. +# <doc/wiki/Authentication/Mechanisms/Winbind.txt> +#auth_ntlm_use_winbind = no + +# Path for Samba's ntlm_auth helper binary. +#auth_winbind_helper = /usr/bin/ntlm_auth + auth default { # Space separated list of wanted authentication mechanisms: # plain login digest-md5 cram-md5 ntlm rpa apop anonymous gssapi otp skey + # gss-spnego # NOTE: See also disable_plaintext_auth setting. mechanisms = plain
--- a/src/auth/Makefile.am Mon Aug 06 20:49:06 2007 +0300 +++ b/src/auth/Makefile.am Mon Aug 06 21:12:51 2007 +0300 @@ -62,6 +62,7 @@ mech-gssapi.c \ mech-rpa.c \ mech-apop.c \ + mech-winbind.c \ otp-skey-common.c \ plain-common.c \ passdb.c \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/mech-winbind.c Mon Aug 06 21:12:51 2007 +0300 @@ -0,0 +1,304 @@ +/* + * 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 DEFAULT_WINBIND_HELPER "/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; + 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", NULL, NULL +}; +static struct winbind_helper winbind_spnego_context = { + "--helper-protocol=gss-spnego", NULL, NULL +}; + +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_helper_connect(struct winbind_helper *winbind) +{ + int infd[2], outfd[2]; + pid_t pid; + + i_assert(winbind->in_pipe == NULL); + + 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 */ + 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"); + if (helper_path == NULL) + helper_path = DEFAULT_WINBIND_HELPER; + + 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->in_pipe = + i_stream_create_fd(infd[0], AUTH_CLIENT_MAX_LINE_LENGTH, TRUE); + winbind->out_pipe = + o_stream_create_fd(outfd[1], (size_t)-1, TRUE); +} + +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[0] == NULL || + (token[1] == NULL && strcmp(token[0], "BH") != 0) || + (token[2] == NULL && gss_spnego)) { + 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") == 0) { + buffer_t *buf; + + buf = t_base64_decode_str(token[1]); + 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") == 0) { + 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") == 0) { + const char *user, *p, *error; + + user = gss_spnego ? token[2] : token[1]; + + p = strchr(user, '\\'); + if (p != NULL) { + /* 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; + + buf = t_base64_decode_str(token[1]); + 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") == 0) { + auth_request_log_info(auth_request, "winbind", + "ntlm_auth reports broken helper: %s", + token[1] != NULL ? token[1] : ""); + return HR_RESTART; + } else { + auth_request_log_error(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, + MEMBER(passdb_need_set_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, + MEMBER(passdb_need_set_credentials) FALSE, + + mech_winbind_spnego_auth_new, + mech_generic_auth_initial, + mech_winbind_auth_continue, + mech_generic_auth_free +};
--- a/src/auth/mech.c Mon Aug 06 20:49:06 2007 +0300 +++ b/src/auth/mech.c Mon Aug 06 21:12:51 2007 +0300 @@ -75,6 +75,8 @@ #ifdef HAVE_GSSAPI extern const struct mech_module mech_gssapi; #endif +extern const struct mech_module mech_winbind_ntlm; +extern const struct mech_module mech_winbind_spnego; void mech_init(void) { @@ -83,7 +85,11 @@ mech_register_module(&mech_apop); mech_register_module(&mech_cram_md5); mech_register_module(&mech_digest_md5); - mech_register_module(&mech_ntlm); + if (getenv("NTLM_USE_WINBIND") != NULL) + mech_register_module(&mech_winbind_ntlm); + else + mech_register_module(&mech_ntlm); + mech_register_module(&mech_winbind_spnego); mech_register_module(&mech_otp); mech_register_module(&mech_skey); mech_register_module(&mech_rpa); @@ -100,7 +106,11 @@ mech_unregister_module(&mech_apop); mech_unregister_module(&mech_cram_md5); mech_unregister_module(&mech_digest_md5); - mech_unregister_module(&mech_ntlm); + if (getenv("NTLM_USE_WINBIND") != NULL) + mech_unregister_module(&mech_winbind_ntlm); + else + mech_unregister_module(&mech_ntlm); + mech_unregister_module(&mech_winbind_spnego); mech_unregister_module(&mech_otp); mech_unregister_module(&mech_skey); mech_unregister_module(&mech_rpa);
--- a/src/master/auth-process.c Mon Aug 06 20:49:06 2007 +0300 +++ b/src/master/auth-process.c Mon Aug 06 21:12:51 2007 +0300 @@ -480,6 +480,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)); @@ -488,6 +490,7 @@ env_put(t_strconcat("GSSAPI_HOSTNAME=", set->gssapi_hostname, NULL)); } + env_put(t_strconcat("WINBIND_HELPER=", set->winbind_helper, NULL)); restrict_process_size(set->process_size, (unsigned int)-1); }
--- a/src/master/master-settings.c Mon Aug 06 20:49:06 2007 +0300 +++ b/src/master/master-settings.c Mon Aug 06 21:12:51 2007 +0300 @@ -78,12 +78,14 @@ DEF_STR(anonymous_username), DEF_STR(krb5_keytab), DEF_STR(gssapi_hostname), + DEF_STR(winbind_helper), DEF_BOOL(verbose), DEF_BOOL(debug), DEF_BOOL(debug_passwords), DEF_BOOL(ssl_require_client_cert), DEF_BOOL(ssl_username_from_cert), + DEF_BOOL(ntlm_use_winbind), DEF_INT(count), DEF_INT(worker_max_count), @@ -303,12 +305,14 @@ MEMBER(anonymous_username) "anonymous", MEMBER(krb5_keytab) "", MEMBER(gssapi_hostname) "", + MEMBER(winbind_helper) "/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,
--- a/src/master/master-settings.h Mon Aug 06 20:49:06 2007 +0300 +++ b/src/master/master-settings.h Mon Aug 06 21:12:51 2007 +0300 @@ -204,10 +204,12 @@ const char *anonymous_username; const char *krb5_keytab; const char *gssapi_hostname; + const char *winbind_helper; 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;