changeset 6218:74df0c0743c4 HEAD

PAM lookups are now always done in auth worker processes.
author Timo Sirainen <tss@iki.fi>
date Tue, 07 Aug 2007 13:46:55 +0300
parents 06743e1e4c13
children 22060906360e
files dovecot-example.conf src/auth/passdb-pam.c
diffstat 2 files changed, 74 insertions(+), 313 deletions(-) [+]
line wrap: on
line diff
--- 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. <doc/wiki/PasswordDatabase.PAM.txt>
   passdb pam {
-    # [blocking=yes] [session=yes] [setcred=yes] [failure_show_msg=yes]
+    # [session=yes] [setcred=yes] [failure_show_msg=yes]
     # [cache_key=<key>] [<service name>]
     #
-    # 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.
     #
--- 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 <stdlib.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <sys/wait.h>
-
-#define PAM_CHILD_TIMEOUT (60*2)
-#define PAM_CHILD_CHECK_TIMEOUT (10*1000)
 
 #ifdef HAVE_SECURITY_PAM_APPL_H
 #  include <security/pam_appl.h>
@@ -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,