]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Added support for userdb checkpassword. Patch by Sascha Wilde.
authorTimo Sirainen <tss@iki.fi>
Tue, 21 Oct 2008 21:29:54 +0000 (00:29 +0300)
committerTimo Sirainen <tss@iki.fi>
Tue, 21 Oct 2008 21:29:54 +0000 (00:29 +0300)
--HG--
branch : HEAD

configure.in
src/auth/Makefile.am
src/auth/db-checkpassword.c [new file with mode: 0644]
src/auth/db-checkpassword.h [new file with mode: 0644]
src/auth/mech-winbind.c
src/auth/passdb-checkpassword.c
src/auth/userdb-checkpassword.c [new file with mode: 0644]
src/auth/userdb.c

index ee410f8f9374b93942635566c154979db74879cb..545ed26e73b3e125497bf549c45d16da80c4fb9d 100644 (file)
@@ -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 @@ fi
 
 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
index 35451326b725f8bce0600c3b5c04a63e9f88a83b..e9e626f17807e31d837f8456869619f19e10cf25 100644 (file)
@@ -67,6 +67,7 @@ dovecot_auth_SOURCES = \
        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 @@ dovecot_auth_SOURCES = \
        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 @@ headers = \
        db-sql.h \
        db-passwd-file.h \
        common.h \
+       db-checkpassword.h \
        mech.h \
        mycrypt.h \
        otp-skey-common.h \
diff --git a/src/auth/db-checkpassword.c b/src/auth/db-checkpassword.c
new file mode 100644 (file)
index 0000000..e58ee4c
--- /dev/null
@@ -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
diff --git a/src/auth/db-checkpassword.h b/src/auth/db-checkpassword.h
new file mode 100644 (file)
index 0000000..25de9d3
--- /dev/null
@@ -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
index a8b37c6f1ce3a8a8bf6203e9e9a0388f658b4c75..249db165ead489665c8c29cecf7b27b483a3509e 100644 (file)
@@ -67,6 +67,7 @@ static void winbind_wait_pid(struct winbind_helper *winbind)
        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");
index eb50b7cfd210ba22dcd440f6d459e2758fcda3e5..d3a93e1c7633167de2b918245c6453fb5c93014c 100644 (file)
@@ -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 checkpassword_passdb_module {
        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 @@ static void checkpassword_request_finish(struct chkpw_auth_request *request,
        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 @@ static void checkpassword_request_finish(struct chkpw_auth_request *request,
                }
        }
 
-       request->callback(result, request->request);
-       auth_request_unref(&request->request);
-
-        checkpassword_request_close(request);
-
-       if (request->input_buf != NULL)
-               str_free(&request->input_buf);
+       callback(result, request->request);
 
-       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 @@ checkpassword_request_half_finish(struct chkpw_auth_request *request)
        }
 }
 
-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 @@ checkpassword_verify_plain_child(struct auth_request *request,
                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 @@ checkpassword_verify_plain_child(struct auth_request *request,
        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 @@ checkpassword_verify_plain(struct auth_request *request, const char *password,
        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 @@ checkpassword_verify_plain(struct auth_request *request, const char *password,
                       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 @@ checkpassword_preinit(struct auth_passdb *auth_passdb, const char *args)
        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 @@ static void checkpassword_deinit(struct passdb_module *_module)
        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 @@ static void checkpassword_deinit(struct passdb_module *_module)
        }
        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,
diff --git a/src/auth/userdb-checkpassword.c b/src/auth/userdb-checkpassword.c
new file mode 100644 (file)
index 0000000..553e557
--- /dev/null
@@ -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
index bcb0c602c368a77fbaebe67bf1a55af6bcb2ddba..fda8cd24239474be79015035d36c81ac3a12b68e 100644 (file)
@@ -158,6 +158,7 @@ extern struct userdb_module_interface userdb_vpopmail;
 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 @@ void userdbs_init(void)
        userdb_register_module(&userdb_ldap);
        userdb_register_module(&userdb_sql);
        userdb_register_module(&userdb_nss);
+       userdb_register_module(&userdb_checkpassword);
 }
 
 void userdbs_deinit(void)