Mercurial > dovecot > core-2.2
changeset 8307:33eae1ca0be0 HEAD
Added support for userdb checkpassword. Patch by Sascha Wilde.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Wed, 22 Oct 2008 00:29:54 +0300 |
parents | 3e8f847f68a4 |
children | 9c7c9fa381d4 |
files | configure.in src/auth/Makefile.am src/auth/db-checkpassword.c src/auth/db-checkpassword.h src/auth/mech-winbind.c src/auth/passdb-checkpassword.c src/auth/userdb-checkpassword.c src/auth/userdb.c |
diffstat | 8 files changed, 582 insertions(+), 248 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.in Wed Oct 22 00:28:46 2008 +0300 +++ b/configure.in Wed Oct 22 00:29:54 2008 +0300 @@ -1,5 +1,5 @@ AC_PREREQ([2.59]) -AC_INIT([Dovecot],[1.2.alpha2],[dovecot@dovecot.org]) +AC_INIT([Dovecot],[1.2.alpha3],[dovecot@dovecot.org]) AC_CONFIG_SRCDIR([src]) AM_INIT_AUTOMAKE @@ -1762,7 +1762,9 @@ if test $want_checkpassword != no; then AC_DEFINE(PASSDB_CHECKPASSWORD,, Build with checkpassword passdb support) + AC_DEFINE(USERDB_CHECKPASSWORD,, Build with checkpassword userdb support) passdb="$passdb checkpassword" + userdb="$userdb checkpassword" fi if test $want_bsdauth != no; then
--- a/src/auth/Makefile.am Wed Oct 22 00:28:46 2008 +0300 +++ b/src/auth/Makefile.am Wed Oct 22 00:29:54 2008 +0300 @@ -67,6 +67,7 @@ auth-stream.c \ auth-worker-client.c \ auth-worker-server.c \ + db-checkpassword.c \ db-sql.c \ db-passwd-file.c \ main.c \ @@ -98,6 +99,7 @@ passdb-sql.c \ userdb.c \ userdb-blocking.c \ + userdb-checkpassword.c \ userdb-nss.c \ userdb-passwd.c \ userdb-passwd-file.c \ @@ -125,6 +127,7 @@ db-sql.h \ db-passwd-file.h \ common.h \ + db-checkpassword.h \ mech.h \ mycrypt.h \ otp-skey-common.h \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/db-checkpassword.c Wed Oct 22 00:29:54 2008 +0300 @@ -0,0 +1,209 @@ +/* Copyright (c) 2004-2008 Dovecot authors, see the included COPYING file */ + +#include "common.h" + +#if defined(PASSDB_CHECKPASSWORD) || defined(USERDB_CHECKPASSWORD) + +#include "db-checkpassword.h" + +static void env_put_extra_fields(const char *extra_fields) +{ + const char *const *tmp; + const char *key, *p; + + for (tmp = t_strsplit(extra_fields, "\t"); *tmp != NULL; tmp++) { + key = t_str_ucase(t_strcut(*tmp, '=')); + p = strchr(*tmp, '='); + if (p == NULL) + env_put(t_strconcat(key, "=1", NULL)); + else + env_put(t_strconcat(key, p, NULL)); + } +} + +static void checkpassword_request_close(struct chkpw_auth_request *request) +{ + if (request->io_in != NULL) + io_remove(&request->io_in); + if (request->io_out != NULL) + io_remove(&request->io_out); + + if (request->fd_in != -1) { + if (close(request->fd_in) < 0) + i_error("checkpassword: close() failed: %m"); + request->fd_in = -1; + } + if (request->fd_out != -1) { + if (close(request->fd_out) < 0) + i_error("checkpassword: close() failed: %m"); + } +} + +void checkpassword_request_free(struct chkpw_auth_request *request) +{ + checkpassword_request_close(request); + if (request->input_buf != NULL) + str_free(&request->input_buf); + + if (request->password != NULL) { + safe_memset(request->password, 0, strlen(request->password)); + i_free(request->password); + } + i_free(request); +} + +enum checkpassword_sigchld_handler_result +checkpassword_sigchld_handler(const struct child_wait_status *child_wait_status, + struct chkpw_auth_request *request) +{ + int status = child_wait_status->status; + pid_t pid = child_wait_status->pid; + + if (request == NULL) { + i_error("checkpassword: sighandler called for unknown child %d", pid); + return SIGCHLD_RESULT_UNKNOWN_CHILD; + } + + if (WIFSIGNALED(status)) { + i_error("checkpassword: Child %s died with signal %d", + dec2str(pid), WTERMSIG(status)); + return SIGCHLD_RESULT_DEAD_CHILD; + } else if (WIFEXITED(status)) { + request->exited = TRUE; + request->exit_status = WEXITSTATUS(status); + + auth_request_log_debug(request->request, + "checkpassword", "exit_status=%d", + request->exit_status); + return SIGCHLD_RESULT_OK; + } else { + /* shouldn't happen */ + auth_request_log_debug(request->request, "checkpassword", + "Child exited with status=%d", status); + return SIGCHLD_RESULT_UNKNOWN_ERROR; + } +} + +void checkpassword_setup_env(struct auth_request *request) +{ + /* Besides passing the standard username and password in a + pipe, also pass some other possibly interesting information + via environment. Use UCSPI names for local/remote IPs. */ + env_put("PROTO=TCP"); /* UCSPI */ + env_put(t_strconcat("SERVICE=", request->service, NULL)); + if (request->local_ip.family != 0) { + env_put(t_strconcat("TCPLOCALIP=", + net_ip2addr(&request->local_ip), NULL)); + /* FIXME: for backwards compatibility only, + remove some day */ + env_put(t_strconcat("LOCAL_IP=", + net_ip2addr(&request->local_ip), NULL)); + } + if (request->remote_ip.family != 0) { + env_put(t_strconcat("TCPREMOTEIP=", + net_ip2addr(&request->remote_ip), NULL)); + /* FIXME: for backwards compatibility only, + remove some day */ + env_put(t_strconcat("REMOTE_IP=", + net_ip2addr(&request->remote_ip), NULL)); + } + if (request->local_port != 0) { + env_put(t_strdup_printf("TCPLOCALPORT=%u", + request->local_port)); + } + if (request->remote_port != 0) { + env_put(t_strdup_printf("TCPREMOTEPORT=%u", + request->remote_port)); + } + if (request->master_user != NULL) { + env_put(t_strconcat("MASTER_USER=", + request->master_user, NULL)); + } + if (request->extra_fields != NULL) { + const char *fields = + auth_stream_reply_export(request->extra_fields); + + /* extra fields could come from master db */ + env_put_extra_fields(fields); + } +} + +void checkpassword_child_input(struct chkpw_auth_request *request) +{ + unsigned char buf[1024]; + ssize_t ret; + + ret = read(request->fd_in, buf, sizeof(buf)); + if (ret <= 0) { + if (ret < 0) { + auth_request_log_error(request->request, + "checkpassword", "read() failed: %m"); + } + + auth_request_log_debug(request->request, "checkpassword", + "Received no input"); + checkpassword_request_close(request); + request->half_finish_callback(request); + } else { + if (request->input_buf == NULL) + request->input_buf = str_new(default_pool, 512); + str_append_n(request->input_buf, buf, ret); + + auth_request_log_debug(request->request, "checkpassword", + "Received input: %s", str_c(request->input_buf)); + } +} + +void checkpassword_child_output(struct chkpw_auth_request *request) +{ + /* Send: username \0 password \0 timestamp \0. + Must be 512 bytes or less. The "timestamp" parameter is actually + useful only for APOP authentication. We don't support it, so + keep it empty */ + struct auth_request *auth_request = request->request; + buffer_t *buf; + const unsigned char *data; + size_t size; + ssize_t ret; + + buf = buffer_create_dynamic(pool_datastack_create(), 512+1); + buffer_append(buf, auth_request->user, strlen(auth_request->user)+1); + if (request->password != NULL) + buffer_append(buf, request->password, strlen(request->password)+1); + else + buffer_append_c(buf, '\0'); + buffer_append_c(buf, '\0'); + data = buffer_get_data(buf, &size); + + if (size > 512) { + auth_request_log_error(request->request, "checkpassword", + "output larger than 512 bytes: %"PRIuSIZE_T, size); + request->finish_callback(request, + request->internal_failure_code); + return; + } + + ret = write(request->fd_out, data + request->write_pos, + size - request->write_pos); + if (ret <= 0) { + if (ret < 0) { + auth_request_log_error(request->request, + "checkpassword", "write() failed: %m"); + } + request->finish_callback(request, + request->internal_failure_code); + return; + } + + request->write_pos += ret; + if (request->write_pos < size) + return; + + io_remove(&request->io_out); + + if (close(request->fd_out) < 0) + i_error("checkpassword: close() failed: %m"); + request->fd_out = -1; +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/db-checkpassword.h Wed Oct 22 00:29:54 2008 +0300 @@ -0,0 +1,54 @@ +#ifndef CHECKPASSWORD_COMMON_H +#define CHECKPASSWORD_COMMON_H + +#include "auth-request.h" +#include "lib-signals.h" +#include "buffer.h" +#include "str.h" +#include "ioloop.h" +#include "hash.h" +#include "env-util.h" +#include "safe-memset.h" +#include "child-wait.h" + +#include <stdlib.h> +#include <unistd.h> +#include <sys/wait.h> + + +struct chkpw_auth_request { + int fd_out, fd_in; + struct io *io_out, *io_in; + pid_t pid; + + string_t *input_buf; + char *password; + unsigned int write_pos; + + struct auth_request *request; + void *callback; + void (*half_finish_callback)(); + void (*finish_callback)(); + int internal_failure_code; + + int exit_status; + unsigned int exited:1; +}; + +enum checkpassword_sigchld_handler_result { + SIGCHLD_RESULT_UNKNOWN_CHILD = -1, + SIGCHLD_RESULT_DEAD_CHILD = -2, + SIGCHLD_RESULT_UNKNOWN_ERROR = -3, + SIGCHLD_RESULT_OK = 1, +}; + + +void checkpassword_request_free(struct chkpw_auth_request *request); +enum checkpassword_sigchld_handler_result +checkpassword_sigchld_handler(const struct child_wait_status *child_wait_status, + struct chkpw_auth_request *request); +void checkpassword_setup_env(struct auth_request *request); +void checkpassword_child_input(struct chkpw_auth_request *request); +void checkpassword_child_output(struct chkpw_auth_request *request); + +#endif
--- a/src/auth/mech-winbind.c Wed Oct 22 00:28:46 2008 +0300 +++ b/src/auth/mech-winbind.c Wed Oct 22 00:29:54 2008 +0300 @@ -67,6 +67,7 @@ if (winbind->pid == -1) return; + /* FIXME: use child-wait.h API */ if ((ret = waitpid(winbind->pid, &status, WNOHANG)) <= 0) { if (ret < 0 && errno != ECHILD && errno != EINTR) i_error("waitpid() failed: %m");
--- a/src/auth/passdb-checkpassword.c Wed Oct 22 00:28:46 2008 +0300 +++ b/src/auth/passdb-checkpassword.c Wed Oct 22 00:29:54 2008 +0300 @@ -3,19 +3,9 @@ #include "common.h" #include "passdb.h" -#ifdef PASSDB_CHECKPASSWORD +#ifdef PASSDB_CHECKPASSWORD -#include "lib-signals.h" -#include "buffer.h" -#include "str.h" -#include "ioloop.h" -#include "hash.h" -#include "env-util.h" -#include "safe-memset.h" - -#include <stdlib.h> -#include <unistd.h> -#include <sys/wait.h> +#include "db-checkpassword.h" struct checkpassword_passdb_module { struct passdb_module module; @@ -24,39 +14,7 @@ struct hash_table *clients; }; -struct chkpw_auth_request { - int fd_out, fd_in; - struct io *io_out, *io_in; - pid_t pid; - - string_t *input_buf; - char *password; - unsigned int write_pos; - - struct auth_request *request; - verify_plain_callback_t *callback; - - int exit_status; - unsigned int exited:1; -}; - -static void checkpassword_request_close(struct chkpw_auth_request *request) -{ - if (request->io_in != NULL) - io_remove(&request->io_in); - if (request->io_out != NULL) - io_remove(&request->io_out); - - if (request->fd_in != -1) { - if (close(request->fd_in) < 0) - i_error("checkpassword: close() failed: %m"); - request->fd_in = -1; - } - if (request->fd_out != -1) { - if (close(request->fd_out) < 0) - i_error("checkpassword: close() failed: %m"); - } -} +static struct child_wait *checkpassword_passdb_children = NULL; static void checkpassword_request_finish(struct chkpw_auth_request *request, enum passdb_result result) @@ -64,6 +22,8 @@ struct passdb_module *_module = request->request->passdb->passdb; struct checkpassword_passdb_module *module = (struct checkpassword_passdb_module *)_module; + verify_plain_callback_t *callback = + (verify_plain_callback_t *)request->callback; hash_remove(module->clients, POINTER_CAST(request->pid)); @@ -80,17 +40,10 @@ } } - request->callback(result, request->request); - auth_request_unref(&request->request); - - checkpassword_request_close(request); + callback(result, request->request); - if (request->input_buf != NULL) - str_free(&request->input_buf); - - safe_memset(request->password, 0, strlen(request->password)); - i_free(request->password); - i_free(request); + auth_request_unref(&request->request); + checkpassword_request_free(request); } static void @@ -142,71 +95,24 @@ } } -static void sigchld_handler(int signo ATTR_UNUSED, void *context) +static void sigchld_handler(const struct child_wait_status *status, + struct checkpassword_passdb_module *module) { - struct checkpassword_passdb_module *module = context; - struct chkpw_auth_request *request; - int status; - pid_t pid; - - /* FIXME: if we ever do some other kind of forking, this needs fixing */ - while ((pid = waitpid(-1, &status, WNOHANG)) != 0) { - if (pid == -1) { - if (errno != ECHILD && errno != EINTR) - i_error("waitpid() failed: %m"); - return; - } - - request = hash_lookup(module->clients, POINTER_CAST(pid)); - if (request == NULL) { - /* unknown child finished */ - if (WIFSIGNALED(status)) { - i_error("checkpassword: Unknown child %s died " - "with signal %d", dec2str(pid), - WTERMSIG(status)); - } - continue; - } - - if (WIFSIGNALED(status)) { - i_error("checkpassword: Child %s died with signal %d", - dec2str(pid), WTERMSIG(status)); - } else if (WIFEXITED(status)) { - request->exited = TRUE; - request->exit_status = WEXITSTATUS(status); + struct chkpw_auth_request *request = + hash_lookup(module->clients, POINTER_CAST(status->pid)); - auth_request_log_debug(request->request, - "checkpassword", "exit_status=%d", - request->exit_status); - - checkpassword_request_half_finish(request); - request = NULL; - } else { - /* shouldn't happen */ - auth_request_log_debug(request->request, - "checkpassword", "Child exited with status=%d", - status); - } - - if (request != NULL) { - checkpassword_request_finish(request, - PASSDB_RESULT_INTERNAL_FAILURE); - } - } -} - -static void env_put_extra_fields(const char *extra_fields) -{ - const char *const *tmp; - const char *key, *p; - - for (tmp = t_strsplit(extra_fields, "\t"); *tmp != NULL; tmp++) { - key = t_str_ucase(t_strcut(*tmp, '=')); - p = strchr(*tmp, '='); - if (p == NULL) - env_put(t_strconcat(key, "=1", NULL)); - else - env_put(t_strconcat(key, p, NULL)); + switch (checkpassword_sigchld_handler(status, request)) { + case SIGCHLD_RESULT_UNKNOWN_CHILD: + case SIGCHLD_RESULT_DEAD_CHILD: + break; + case SIGCHLD_RESULT_UNKNOWN_ERROR: + checkpassword_request_finish(request, + PASSDB_RESULT_INTERNAL_FAILURE); + break; + case SIGCHLD_RESULT_OK: + checkpassword_request_half_finish(request); + request = NULL; + break; } } @@ -221,51 +127,7 @@ auth_request_log_error(request, "checkpassword", "dup2() failed: %m"); } else { - /* Besides passing the standard username and password in a - pipe, also pass some other possibly interesting information - via environment. Use UCSPI names for local/remote IPs. */ - env_put("PROTO=TCP"); /* UCSPI */ - env_put(t_strconcat("SERVICE=", request->service, NULL)); - if (request->local_ip.family != 0) { - env_put(t_strconcat("TCPLOCALIP=", - net_ip2addr(&request->local_ip), - NULL)); - /* FIXME: for backwards compatibility only, - remove some day */ - env_put(t_strconcat("LOCAL_IP=", - net_ip2addr(&request->local_ip), - NULL)); - } - if (request->remote_ip.family != 0) { - env_put(t_strconcat("TCPREMOTEIP=", - net_ip2addr(&request->remote_ip), - NULL)); - /* FIXME: for backwards compatibility only, - remove some day */ - env_put(t_strconcat("REMOTE_IP=", - net_ip2addr(&request->remote_ip), - NULL)); - } - if (request->local_port != 0) { - env_put(t_strdup_printf("TCPLOCALPORT=%u", - request->local_port)); - } - if (request->remote_port != 0) { - env_put(t_strdup_printf("TCPREMOTEPORT=%u", - request->remote_port)); - } - if (request->master_user != NULL) { - env_put(t_strconcat("MASTER_USER=", - request->master_user, NULL)); - } - if (request->extra_fields != NULL) { - const char *fields = - auth_stream_reply_export(request->extra_fields); - - /* extra fields could come from master db */ - env_put_extra_fields(fields); - } - + checkpassword_setup_env(request); /* very simple argument splitting. */ cmd = t_strconcat(module->checkpassword_path, " ", module->checkpassword_reply_path, NULL); @@ -280,81 +142,6 @@ exit(2); } -static void checkpassword_child_input(struct chkpw_auth_request *request) -{ - unsigned char buf[1024]; - ssize_t ret; - - ret = read(request->fd_in, buf, sizeof(buf)); - if (ret <= 0) { - if (ret < 0) { - auth_request_log_error(request->request, - "checkpassword", "read() failed: %m"); - } - - auth_request_log_debug(request->request, "checkpassword", - "Received no input"); - checkpassword_request_close(request); - checkpassword_request_half_finish(request); - } else { - if (request->input_buf == NULL) - request->input_buf = str_new(default_pool, 512); - str_append_n(request->input_buf, buf, ret); - - auth_request_log_debug(request->request, "checkpassword", - "Received input: %s", str_c(request->input_buf)); - } -} - -static void checkpassword_child_output(struct chkpw_auth_request *request) -{ - /* Send: username \0 password \0 timestamp \0. - Must be 512 bytes or less. The "timestamp" parameter is actually - useful only for APOP authentication. We don't support it, so - keep it empty */ - struct auth_request *auth_request = request->request; - buffer_t *buf; - const unsigned char *data; - size_t size; - ssize_t ret; - - buf = buffer_create_dynamic(pool_datastack_create(), 512+1); - buffer_append(buf, auth_request->user, strlen(auth_request->user)+1); - buffer_append(buf, request->password, strlen(request->password)+1); - buffer_append_c(buf, '\0'); - data = buffer_get_data(buf, &size); - - if (size > 512) { - auth_request_log_error(request->request, "checkpassword", - "output larger than 512 bytes: %"PRIuSIZE_T, size); - checkpassword_request_finish(request, - PASSDB_RESULT_INTERNAL_FAILURE); - return; - } - - ret = write(request->fd_out, data + request->write_pos, - size - request->write_pos); - if (ret <= 0) { - if (ret < 0) { - auth_request_log_error(request->request, - "checkpassword", "write() failed: %m"); - } - checkpassword_request_finish(request, - PASSDB_RESULT_INTERNAL_FAILURE); - return; - } - - request->write_pos += ret; - if (request->write_pos < size) - return; - - io_remove(&request->io_out); - - if (close(request->fd_out) < 0) - i_error("checkpassword: close() failed: %m"); - request->fd_out = -1; -} - static void checkpassword_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) @@ -415,6 +202,12 @@ chkpw_auth_request->password = i_strdup(password); chkpw_auth_request->request = request; chkpw_auth_request->callback = callback; + chkpw_auth_request->half_finish_callback = + checkpassword_request_half_finish; + chkpw_auth_request->finish_callback = + checkpassword_request_finish; + chkpw_auth_request->internal_failure_code = + PASSDB_RESULT_INTERNAL_FAILURE; chkpw_auth_request->io_in = io_add(fd_in[0], IO_READ, checkpassword_child_input, @@ -424,6 +217,13 @@ chkpw_auth_request); hash_insert(module->clients, POINTER_CAST(pid), chkpw_auth_request); + + if (checkpassword_passdb_children != NULL) + child_wait_add_pid(checkpassword_passdb_children, pid); + else { + checkpassword_passdb_children = + child_wait_new_with_pid(pid, sigchld_handler, module); + } } static struct passdb_module * @@ -443,12 +243,6 @@ return &module->module; } -static void checkpassword_init(struct passdb_module *module, - const char *args ATTR_UNUSED) -{ - lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, module); -} - static void checkpassword_deinit(struct passdb_module *_module) { struct checkpassword_passdb_module *module = @@ -456,8 +250,6 @@ struct hash_iterate_context *iter; void *key, *value; - lib_signals_unset_handler(SIGCHLD, sigchld_handler, module); - iter = hash_iterate_init(module->clients); while (hash_iterate(iter, &key, &value)) { checkpassword_request_finish(value, @@ -465,13 +257,16 @@ } hash_iterate_deinit(&iter); hash_destroy(&module->clients); + + if (checkpassword_passdb_children != NULL) + child_wait_free(&checkpassword_passdb_children); } struct passdb_module_interface passdb_checkpassword = { "checkpassword", checkpassword_preinit, - checkpassword_init, + NULL, checkpassword_deinit, checkpassword_verify_plain,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/userdb-checkpassword.c Wed Oct 22 00:29:54 2008 +0300 @@ -0,0 +1,268 @@ +/* Copyright (c) 2004-2008 Dovecot authors, see the included COPYING file */ + +#include "common.h" +#include "userdb.h" + +#ifdef USERDB_CHECKPASSWORD + +#include "db-checkpassword.h" + +struct checkpassword_userdb_module { + struct userdb_module module; + + const char *checkpassword_path, *checkpassword_reply_path; + struct hash_table *clients; +}; + +static struct child_wait *checkpassword_userdb_children = NULL; + +static void checkpassword_request_finish(struct chkpw_auth_request *request, + enum userdb_result result) +{ + struct userdb_module *_module = request->request->userdb->userdb; + struct checkpassword_userdb_module *module = + (struct checkpassword_userdb_module *)_module; + userdb_callback_t *callback = + (userdb_callback_t *)request->callback; + + hash_remove(module->clients, POINTER_CAST(request->pid)); + + if (result == USERDB_RESULT_OK) { + if (strchr(str_c(request->input_buf), '\n') != NULL) { + auth_request_log_error(request->request, + "userdb-checkpassword", + "LF characters in checkpassword reply"); + result = USERDB_RESULT_INTERNAL_FAILURE; + } else { + auth_request_init_userdb_reply(request->request); + auth_request_set_fields(request->request, + t_strsplit(str_c(request->input_buf), "\t"), + NULL); + } + } + + callback(result, request->request); + + auth_request_unref(&request->request); + checkpassword_request_free(request); +} + +static void +checkpassword_request_half_finish(struct chkpw_auth_request *request) +{ + if (!request->exited || request->fd_in != -1) + return; + + switch (request->exit_status) { + case 3: + /* User does not exist. */ + auth_request_log_info(request->request, "userdb-checkpassword", + "User unknown"); + checkpassword_request_finish(request, + USERDB_RESULT_USER_UNKNOWN); + break; + case 2: + /* This is intentionally not 0. checkpassword-reply exits with + 2 on success when AUTHORIZED is set. */ + if (request->input_buf != NULL) { + checkpassword_request_finish(request, USERDB_RESULT_OK); + break; + } + /* missing input - fall through */ + default: + /* whatever error... */ + auth_request_log_error(request->request, "userdb-checkpassword", + "Child %s exited with status %d", + dec2str(request->pid), request->exit_status); + checkpassword_request_finish(request, + USERDB_RESULT_INTERNAL_FAILURE); + break; + } +} + +static void sigchld_handler(const struct child_wait_status *status, + struct checkpassword_userdb_module *module) +{ + struct chkpw_auth_request *request = + hash_lookup(module->clients, POINTER_CAST(status->pid)); + + switch (checkpassword_sigchld_handler(status, request)) { + case SIGCHLD_RESULT_UNKNOWN_CHILD: + case SIGCHLD_RESULT_DEAD_CHILD: + break; + case SIGCHLD_RESULT_UNKNOWN_ERROR: + checkpassword_request_finish(request, + USERDB_RESULT_INTERNAL_FAILURE); + break; + case SIGCHLD_RESULT_OK: + checkpassword_request_half_finish(request); + request = NULL; + break; + } +} + +static void ATTR_NORETURN +checkpassword_lookup_child(struct auth_request *request, + struct checkpassword_userdb_module *module, + int fd_in, int fd_out) +{ + const char *cmd, *const *args; + + if (dup2(fd_out, 3) < 0 || dup2(fd_in, 4) < 0) { + auth_request_log_error(request, "userdb-checkpassword", + "dup2() failed: %m"); + } else { + /* We want to retrieve user data and don't do + authorization, so we need to signalize the + checkpassword program that the password shall be + ignored by setting AUTHORIZED. This needs a + special checkpassword program which knows how to + handle this. */ + env_put("AUTHORIZED=YES"); + checkpassword_setup_env(request); + /* very simple argument splitting. */ + cmd = t_strconcat(module->checkpassword_path, " ", + module->checkpassword_reply_path, NULL); + auth_request_log_debug(request, "userdb-checkpassword", + "execute: %s", cmd); + + args = t_strsplit(cmd, " "); + execv(args[0], (char **)args); + auth_request_log_error(request, "userdb-checkpassword", + "execv(%s) failed: %m", args[0]); + } + exit(2); +} + +static void +checkpassword_lookup(struct auth_request *request, userdb_callback_t *callback) +{ + struct userdb_module *_module = request->userdb->userdb; + struct checkpassword_userdb_module *module = + (struct checkpassword_userdb_module *)_module; + struct chkpw_auth_request *chkpw_auth_request; + int fd_in[2], fd_out[2]; + pid_t pid; + + fd_in[0] = -1; + if (pipe(fd_in) < 0 || pipe(fd_out) < 0) { + auth_request_log_error(request, "userdb-checkpassword", + "pipe() failed: %m"); + callback(USERDB_RESULT_INTERNAL_FAILURE, request); + if (fd_in[0] != -1) { + (void)close(fd_in[0]); + (void)close(fd_in[1]); + } + return; + } + + pid = fork(); + if (pid == -1) { + auth_request_log_error(request, "userdb-checkpassword", + "fork() failed: %m"); + callback(USERDB_RESULT_INTERNAL_FAILURE, request); + (void)close(fd_in[0]); + (void)close(fd_in[1]); + (void)close(fd_out[0]); + (void)close(fd_out[1]); + return; + } + + if (pid == 0) { + (void)close(fd_in[0]); + (void)close(fd_out[1]); + checkpassword_lookup_child(request, module, + fd_in[1], fd_out[0]); + /* not reached */ + } + + if (close(fd_in[1]) < 0) { + auth_request_log_error(request, "userdb-checkpassword", + "close(fd_in[1]) failed: %m"); + } + if (close(fd_out[0]) < 0) { + auth_request_log_error(request, "userdb-checkpassword", + "close(fd_out[0]) failed: %m"); + } + + auth_request_ref(request); + chkpw_auth_request = i_new(struct chkpw_auth_request, 1); + chkpw_auth_request->fd_in = fd_in[0]; + chkpw_auth_request->fd_out = fd_out[1]; + chkpw_auth_request->pid = pid; + chkpw_auth_request->request = request; + chkpw_auth_request->callback = callback; + chkpw_auth_request->half_finish_callback = + checkpassword_request_half_finish; + chkpw_auth_request->finish_callback = + checkpassword_request_finish; + chkpw_auth_request->internal_failure_code = + USERDB_RESULT_INTERNAL_FAILURE; + + chkpw_auth_request->io_in = + io_add(fd_in[0], IO_READ, checkpassword_child_input, + chkpw_auth_request); + chkpw_auth_request->io_out = + io_add(fd_out[1], IO_WRITE, checkpassword_child_output, + chkpw_auth_request); + + hash_insert(module->clients, POINTER_CAST(pid), chkpw_auth_request); + + if (checkpassword_userdb_children != NULL) + child_wait_add_pid(checkpassword_userdb_children, pid); + else { + checkpassword_userdb_children = + child_wait_new_with_pid(pid, sigchld_handler, module); + } +} + +static struct userdb_module * +checkpassword_preinit(struct auth_userdb *auth_userdb, const char *args) +{ + struct checkpassword_userdb_module *module; + + module = p_new(auth_userdb->auth->pool, + struct checkpassword_userdb_module, 1); + module->checkpassword_path = p_strdup(auth_userdb->auth->pool, args); + module->checkpassword_reply_path = + PKG_LIBEXECDIR"/checkpassword-reply"; + + module->clients = + hash_create(default_pool, default_pool, 0, NULL, NULL); + + return &module->module; +} + +static void checkpassword_deinit(struct userdb_module *_module) +{ + struct checkpassword_userdb_module *module = + (struct checkpassword_userdb_module *)_module; + struct hash_iterate_context *iter; + void *key, *value; + + iter = hash_iterate_init(module->clients); + while (hash_iterate(iter, &key, &value)) { + checkpassword_request_finish(value, + USERDB_RESULT_INTERNAL_FAILURE); + } + hash_iterate_deinit(&iter); + hash_destroy(&module->clients); + + if (checkpassword_userdb_children != NULL) + child_wait_free(&checkpassword_userdb_children); +} + +struct userdb_module_interface userdb_checkpassword = { + "checkpassword", + + checkpassword_preinit, + NULL, + checkpassword_deinit, + + checkpassword_lookup, +}; +#else +struct userdb_module_interface userdb_checkpassword = { + MEMBER(name) "checkpassword" +}; +#endif
--- a/src/auth/userdb.c Wed Oct 22 00:28:46 2008 +0300 +++ b/src/auth/userdb.c Wed Oct 22 00:29:54 2008 +0300 @@ -158,6 +158,7 @@ extern struct userdb_module_interface userdb_ldap; extern struct userdb_module_interface userdb_sql; extern struct userdb_module_interface userdb_nss; +extern struct userdb_module_interface userdb_checkpassword; void userdbs_init(void) { @@ -170,6 +171,7 @@ userdb_register_module(&userdb_ldap); userdb_register_module(&userdb_sql); userdb_register_module(&userdb_nss); + userdb_register_module(&userdb_checkpassword); } void userdbs_deinit(void)