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)