# HG changeset patch # User Timo Sirainen # Date 1186483615 -10800 # Node ID 74df0c0743c49b2bdecc2859dd2b770b1ca36ee2 # Parent 06743e1e4c138e0153efb86ed044bb5dcb2131e4 PAM lookups are now always done in auth worker processes. diff -r 06743e1e4c13 -r 74df0c0743c4 dovecot-example.conf --- a/dovecot-example.conf Tue Aug 07 13:34:49 2007 +0300 +++ b/dovecot-example.conf Tue Aug 07 13:46:55 2007 +0300 @@ -803,13 +803,9 @@ # REMEMBER: You'll need /etc/pam.d/dovecot file created for PAM # authentication to actually work. passdb pam { - # [blocking=yes] [session=yes] [setcred=yes] [failure_show_msg=yes] + # [session=yes] [setcred=yes] [failure_show_msg=yes] # [cache_key=] [] # - # By default a new process is forked from dovecot-auth for each PAM lookup. - # Setting blocking=yes uses the alternative way: dovecot-auth worker - # processes do the PAM lookups. - # # session=yes makes Dovecot open and immediately close PAM session. Some # PAM plugins need this to work, such as pam_mkhomedir. # diff -r 06743e1e4c13 -r 74df0c0743c4 src/auth/passdb-pam.c --- a/src/auth/passdb-pam.c Tue Aug 07 13:34:49 2007 +0300 +++ b/src/auth/passdb-pam.c Tue Aug 07 13:46:55 2007 +0300 @@ -12,9 +12,6 @@ #ifdef PASSDB_PAM #include "lib-signals.h" -#include "buffer.h" -#include "ioloop.h" -#include "hash.h" #include "str.h" #include "var-expand.h" #include "network.h" @@ -22,12 +19,6 @@ #include "safe-memset.h" #include -#include -#include -#include - -#define PAM_CHILD_TIMEOUT (60*2) -#define PAM_CHILD_CHECK_TIMEOUT (10*1000) #ifdef HAVE_SECURITY_PAM_APPL_H # include @@ -55,27 +46,12 @@ unsigned int failure_show_msg:1; }; -struct pam_auth_request { - int refcount; - int fd; - struct io *io; - - time_t start_time; - pid_t pid; - - struct auth_request *request; - verify_plain_callback_t *callback; -}; - struct pam_conv_context { struct auth_request *request; const char *pass; const char *failure_msg; }; -static struct hash_table *pam_requests; -static struct timeout *to; - static int pam_userpass_conv(int num_msg, linux_const struct pam_message **msg, struct pam_response **resp_r, void *appdata_ptr) @@ -140,19 +116,17 @@ return PAM_SUCCESS; } -static int pam_auth(struct auth_request *request, - pam_handle_t *pamh, const char **error) +static int try_pam_auth(struct auth_request *request, pam_handle_t *pamh) { struct passdb_module *_module = request->passdb->passdb; struct pam_passdb_module *module = (struct pam_passdb_module *)_module; pam_item_t item; int status; - *error = NULL; - if ((status = pam_authenticate(pamh, 0)) != PAM_SUCCESS) { - *error = t_strdup_printf("pam_authenticate() failed: %s", - pam_strerror(pamh, status)); + auth_request_log_error(request, "pam", + "pam_authenticate() failed: %s", + pam_strerror(pamh, status)); return status; } @@ -160,59 +134,70 @@ if (module->pam_setcred) { if ((status = pam_setcred(pamh, PAM_ESTABLISH_CRED)) != PAM_SUCCESS) { - *error = t_strdup_printf("pam_setcred() failed: %s", - pam_strerror(pamh, status)); + auth_request_log_error(request, "pam", + "pam_setcred() failed: %s", + pam_strerror(pamh, status)); return status; } } #endif if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS) { - *error = t_strdup_printf("pam_acct_mgmt() failed: %s", - pam_strerror(pamh, status)); + auth_request_log_error(request, "pam", + "pam_acct_mgmt() failed: %s", + pam_strerror(pamh, status)); return status; } if (module->pam_session) { if ((status = pam_open_session(pamh, 0)) != PAM_SUCCESS) { - *error = t_strdup_printf( - "pam_open_session() failed: %s", - pam_strerror(pamh, status)); + auth_request_log_error(request, "pam", + "pam_open_session() failed: %s", + pam_strerror(pamh, status)); return status; } if ((status = pam_close_session(pamh, 0)) != PAM_SUCCESS) { - *error = t_strdup_printf( - "pam_close_session() failed: %s", - pam_strerror(pamh, status)); - return status; + auth_request_log_error(request, "pam", + "pam_close_session() failed: %s", + pam_strerror(pamh, status)); + return status; } } - /* FIXME: this works only with blocking=yes */ status = pam_get_item(pamh, PAM_USER, &item); if (status != PAM_SUCCESS) { - *error = t_strdup_printf("pam_get_item() failed: %s", - pam_strerror(pamh, status)); + auth_request_log_error(request, "pam", + "pam_get_item(PAM_USER) failed: %s", + pam_strerror(pamh, status)); return status; } - auth_request_set_field(request, "user", item, NULL); - + auth_request_set_field(request, "user", item, NULL); return PAM_SUCCESS; } +static void set_pam_items(struct auth_request *request, pam_handle_t *pamh) +{ + const char *host; + + /* These shouldn't fail, and we don't really care if they do. */ + host = net_ip2addr(&request->remote_ip); + if (host != NULL) + (void)pam_set_item(pamh, PAM_RHOST, host); + (void)pam_set_item(pamh, PAM_RUSER, request->user); + /* TTY is needed by eg. pam_access module */ + (void)pam_set_item(pamh, PAM_TTY, "dovecot"); +} + static enum passdb_result -pam_verify_plain_child(struct auth_request *request, const char *service, - const char *password, int fd) +pam_verify_plain_call(struct auth_request *request, const char *service, + const char *password) { pam_handle_t *pamh; struct pam_conv_context ctx; struct pam_conv conv; enum passdb_result result; - int ret, status, status2; - const char *str; - size_t size; - buffer_t *buf; + int status, status2; conv.conv = pam_userpass_conv; conv.appdata_ptr = &ctx; @@ -223,178 +208,51 @@ status = pam_start(service, request->user, &conv, &pamh); if (status != PAM_SUCCESS) { - result = PASSDB_RESULT_INTERNAL_FAILURE; - str = t_strdup_printf("pam_start() failed: %s", - pam_strerror(pamh, status)); - } else { - const char *host = net_ip2addr(&request->remote_ip); - - /* Set some PAM items. They shouldn't fail, and we don't really - care if they do. */ - if (host != NULL) - (void)pam_set_item(pamh, PAM_RHOST, host); - (void)pam_set_item(pamh, PAM_RUSER, request->user); - /* TTY is needed by eg. pam_access module */ - (void)pam_set_item(pamh, PAM_TTY, "dovecot"); + auth_request_log_error(request, "pam", "pam_start() failed: %s", + pam_strerror(pamh, status)); + return PASSDB_RESULT_INTERNAL_FAILURE; + } - status = pam_auth(request, pamh, &str); - if ((status2 = pam_end(pamh, status)) == PAM_SUCCESS) { - switch (status) { - case PAM_SUCCESS: - result = PASSDB_RESULT_OK; - break; - case PAM_USER_UNKNOWN: - result = PASSDB_RESULT_USER_UNKNOWN; - break; - case PAM_NEW_AUTHTOK_REQD: - case PAM_ACCT_EXPIRED: - result = PASSDB_RESULT_PASS_EXPIRED; - break; - default: - result = PASSDB_RESULT_PASSWORD_MISMATCH; - break; - } - } else { - result = PASSDB_RESULT_INTERNAL_FAILURE; - str = t_strdup_printf("pam_end() failed: %s", - pam_strerror(pamh, status2)); - } - if (result != PASSDB_RESULT_OK && ctx.failure_msg != NULL) { - auth_request_set_field(request, "reason", - ctx.failure_msg, NULL); - } + set_pam_items(request, pamh); + status = try_pam_auth(request, pamh); + if ((status2 = pam_end(pamh, status)) != PAM_SUCCESS) { + auth_request_log_error(request, "pam", "pam_end() failed: %s", + pam_strerror(pamh, status2)); + return PASSDB_RESULT_INTERNAL_FAILURE; } - if (worker) { - /* blocking=yes code path in auth worker */ - return result; + switch (status) { + case PAM_SUCCESS: + result = PASSDB_RESULT_OK; + break; + case PAM_USER_UNKNOWN: + result = PASSDB_RESULT_USER_UNKNOWN; + break; + case PAM_NEW_AUTHTOK_REQD: + case PAM_ACCT_EXPIRED: + result = PASSDB_RESULT_PASS_EXPIRED; + break; + default: + result = PASSDB_RESULT_PASSWORD_MISMATCH; + break; } - buf = buffer_create_dynamic(pool_datastack_create(), 512); - buffer_append(buf, &result, sizeof(result)); - - if (str != NULL) - buffer_append(buf, str, strlen(str)); - - /* Don't send larger writes than what would block. truncated error - message isn't that bad.. */ - size = I_MIN(buf->used, PIPE_BUF); - if ((ret = write(fd, buf->data, size)) != (int)size) { - if (ret < 0) - i_error("write() failed: %m"); - else { - i_error("write() failed: %d != %"PRIuSIZE_T, - ret, buf->used); - } + if (result != PASSDB_RESULT_OK && ctx.failure_msg != NULL) { + auth_request_set_field(request, "reason", + ctx.failure_msg, NULL); } return result; } -static void pam_child_input(struct pam_auth_request *request) -{ - struct auth_request *auth_request = request->request; - enum passdb_result result; - char buf[PIPE_BUF + 1]; - ssize_t ret; - - /* POSIX guarantees that writing PIPE_BUF bytes or less to pipes is - atomic. We rely on that. */ - ret = read(request->fd, buf, sizeof(buf)-1); - if (ret < 0) { - auth_request_log_error(auth_request, "pam", - "read() from child process failed: %m"); - result = PASSDB_RESULT_INTERNAL_FAILURE; - } else if (ret == 0) { - /* it died */ - auth_request_log_error(auth_request, "pam", - "Child process died"); - result = PASSDB_RESULT_INTERNAL_FAILURE; - } else if ((size_t)ret < sizeof(result)) { - auth_request_log_error(auth_request, "pam", - "Child process returned only %d bytes", (int)ret); - result = PASSDB_RESULT_INTERNAL_FAILURE; - } else { - memcpy(&result, buf, sizeof(result)); - - if ((size_t)ret > sizeof(result)) { - /* error message included */ - buf[ret] = '\0'; - - if (result == PASSDB_RESULT_INTERNAL_FAILURE) { - auth_request_log_error(auth_request, "pam", - "%s", buf + sizeof(result)); - } else { - auth_request_log_info(auth_request, "pam", - "%s", buf + sizeof(result)); - } - } - } - - io_remove(&request->io); - if (close(request->fd) < 0) { - auth_request_log_error(auth_request, "pam", - "close(child input) failed: %m"); - } - - request->callback(result, auth_request); - - if (--request->refcount == 0) { - auth_request_unref(&auth_request); - i_free(request); - } -} - -static void sigchld_handler(int signo __attr_unused__, - void *context __attr_unused__) -{ - struct pam_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(pam_requests, POINTER_CAST(pid)); - if (request == NULL) { - i_error("PAM: Unknown child %s exited with status %d", - dec2str(pid), status); - continue; - } - - if (WIFSIGNALED(status)) { - i_error("PAM: Child %s died with signal %d", - dec2str(pid), WTERMSIG(status)); - } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { - i_error("PAM: Child %s exited unexpectedly with " - "exit code %d", dec2str(pid), - WEXITSTATUS(status)); - } - - hash_remove(pam_requests, POINTER_CAST(request->pid)); - if (--request->refcount == 0) { - auth_request_unref(&request->request); - i_free(request); - } - } -} - static void pam_verify_plain(struct auth_request *request, const char *password, verify_plain_callback_t *callback) { struct passdb_module *_module = request->passdb->passdb; struct pam_passdb_module *module = (struct pam_passdb_module *)_module; - struct pam_auth_request *pam_auth_request; enum passdb_result result; string_t *expanded_service; const char *service; - int fd[2]; - pid_t pid; expanded_service = t_str_new(64); var_expand(expanded_service, module->service_name, @@ -403,51 +261,8 @@ auth_request_log_debug(request, "pam", "lookup service=%s", service); - if (worker) { - /* blocking=yes code path in auth worker */ - result = pam_verify_plain_child(request, service, password, -1); - callback(result, request); - return; - } - - if (pipe(fd) < 0) { - auth_request_log_error(request, "pam", "pipe() failed: %m"); - callback(PASSDB_RESULT_INTERNAL_FAILURE, request); - return; - } - - pid = fork(); - if (pid == -1) { - auth_request_log_error(request, "pam", "fork() failed: %m"); - callback(PASSDB_RESULT_INTERNAL_FAILURE, request); - (void)close(fd[0]); - (void)close(fd[1]); - return; - } - - if (pid == 0) { - (void)close(fd[0]); - pam_verify_plain_child(request, service, password, fd[1]); - _exit(0); - } - - if (close(fd[1]) < 0) { - auth_request_log_error(request, "pam", - "close(fd[1]) failed: %m"); - } - - auth_request_ref(request); - pam_auth_request = i_new(struct pam_auth_request, 1); - pam_auth_request->refcount = 2; - pam_auth_request->fd = fd[0]; - pam_auth_request->request = request; - pam_auth_request->callback = callback; - pam_auth_request->pid = pid; - pam_auth_request->start_time = ioloop_time; - - pam_auth_request->io = - io_add(fd[0], IO_READ, pam_child_input, pam_auth_request); - hash_insert(pam_requests, POINTER_CAST(pid), pam_auth_request); + result = pam_verify_plain_call(request, service, password); + callback(result, request); } static struct passdb_module * @@ -459,6 +274,10 @@ module = p_new(auth_passdb->auth->pool, struct pam_passdb_module, 1); module->service_name = "dovecot"; + /* we're caching the password by using directly the plaintext password + given by the auth mechanism */ + module->module.default_pass_scheme = "PLAIN"; + module->module.blocking = TRUE; t_push(); t_args = t_strsplit_spaces(args, " "); @@ -474,7 +293,7 @@ p_strdup(auth_passdb->auth->pool, t_args[i] + 10); } else if (strcmp(t_args[i], "blocking=yes") == 0) { - module->module.blocking = TRUE; + /* ignore, for backwards compatibility */ } else if (strcmp(t_args[i], "failure_show_msg=yes") == 0) { module->failure_show_msg = TRUE; } else if (strcmp(t_args[i], "*") == 0) { @@ -489,69 +308,15 @@ } t_pop(); - lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, NULL); return &module->module; } -static void pam_child_timeout(void *context __attr_unused__) -{ - struct hash_iterate_context *iter; - void *key, *value; - time_t timeout = ioloop_time - PAM_CHILD_TIMEOUT; - - iter = hash_iterate_init(pam_requests); - while (hash_iterate(iter, &key, &value)) { - struct pam_auth_request *request = value; - - if (request->start_time > timeout) - continue; - - auth_request_log_error(request->request, "pam", - "PAM child process %s timed out, killing it", - dec2str(request->pid)); - if (kill(request->pid, SIGKILL) < 0) { - i_error("PAM: kill(%s) failed: %m", - dec2str(request->pid)); - } - } - hash_iterate_deinit(iter); -} - -static void pam_init(struct passdb_module *_module __attr_unused__, - const char *args __attr_unused__) -{ - if (pam_requests != NULL) - i_fatal("Can't support more than one PAM passdb"); - - /* we're caching the password by using directly the plaintext password - given by the auth mechanism */ - _module->default_pass_scheme = "PLAIN"; - - if (!_module->blocking) { - pam_requests = hash_create(default_pool, default_pool, 0, - NULL, NULL); - to = timeout_add(PAM_CHILD_CHECK_TIMEOUT, - pam_child_timeout, NULL); - - lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, NULL); - } -} - -static void pam_deinit(struct passdb_module *_module __attr_unused__) -{ - if (!_module->blocking) { - lib_signals_unset_handler(SIGCHLD, sigchld_handler, NULL); - hash_destroy(pam_requests); - timeout_remove(&to); - } -} - struct passdb_module_interface passdb_pam = { "pam", pam_preinit, - pam_init, - pam_deinit, + NULL, + NULL, pam_verify_plain, NULL,