]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
imap, pop3: Added back ability to run post-login scripts.
authorTimo Sirainen <tss@iki.fi>
Fri, 11 Dec 2009 23:38:33 +0000 (18:38 -0500)
committerTimo Sirainen <tss@iki.fi>
Fri, 11 Dec 2009 23:38:33 +0000 (18:38 -0500)
--HG--
branch : HEAD

src/imap/main.c
src/lib-master/master-login.c
src/lib-master/master-login.h
src/pop3/main.c
src/util/Makefile.am
src/util/script.c [new file with mode: 0644]

index 5d9710121de0b60f64c89e98044aa4cb9d90b03a..ab97dbb7d4501902d7c9ba62100aa9af49bb39b6 100644 (file)
@@ -4,6 +4,7 @@
 #include "ioloop.h"
 #include "istream.h"
 #include "ostream.h"
+#include "abspath.h"
 #include "str.h"
 #include "base64.h"
 #include "process-title.h"
@@ -252,6 +253,7 @@ int main(int argc, char *argv[])
        };
        enum master_service_flags service_flags = 0;
        enum mail_storage_service_flags storage_service_flags = 0;
+       const char *postlogin_socket_path;
 
        if (IS_STANDALONE() && getuid() == 0 &&
            net_getpeername(1, NULL, NULL) == 0) {
@@ -273,6 +275,8 @@ int main(int argc, char *argv[])
                                             &argc, &argv, NULL);
        if (master_getopt(master_service) > 0)
                return FATAL_DEFAULT;
+       postlogin_socket_path = t_abspath(argv[1]);
+
        master_service_init_finish(master_service);
        master_service_set_die_callback(master_service, imap_die);
 
@@ -294,6 +298,7 @@ int main(int argc, char *argv[])
                } T_END;
        } else {
                master_login = master_login_init(master_service, "auth-master",
+                                                postlogin_socket_path,
                                                 login_client_connected);
                io_loop_set_running(current_ioloop);
        }
index ea4a664015761d47811d2addacf4a9a818e9ec1c..87bdff6bf4a140dba32302e8c0a5aa039e3a34a4 100644 (file)
@@ -6,6 +6,8 @@
 #include "fdpass.h"
 #include "fd-close-on-exec.h"
 #include "llist.h"
+#include "str.h"
+#include "strescape.h"
 #include "master-service-private.h"
 #include "master-login.h"
 #include "master-login-auth.h"
@@ -13,6 +15,8 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
+#define MASTER_LOGIN_POSTLOGIN_TIMEOUT_MSECS (60*1000)
+
 struct master_login_connection {
        struct master_login_connection *prev, *next;
 
@@ -22,17 +26,28 @@ struct master_login_connection {
        struct ostream *output;
 };
 
+struct master_login_postlogin {
+       struct master_login_client *client;
+
+       int fd;
+       struct io *io;
+       struct timeout *to;
+       string_t *input;
+};
+
 struct master_login {
        struct master_service *service;
        master_login_callback_t *callback;
        struct master_login_connection *conns;
        struct master_login_auth *auth;
+       char *postlogin_socket_path;
 };
 
 static void master_login_conn_deinit(struct master_login_connection **_conn);
 
 struct master_login *
 master_login_init(struct master_service *service, const char *auth_socket_path,
+                 const char *postlogin_socket_path,
                  master_login_callback_t *callback)
 {
        struct master_login *login;
@@ -41,6 +56,7 @@ master_login_init(struct master_service *service, const char *auth_socket_path,
        login->service = service;
        login->callback = callback;
        login->auth = master_login_auth_init(auth_socket_path);
+       login->postlogin_socket_path = i_strdup(postlogin_socket_path);
        service->login_connections = TRUE;
        return login;
 }
@@ -57,6 +73,7 @@ void master_login_deinit(struct master_login **_login)
 
                master_login_conn_deinit(&conn);
        }
+       i_free(login->postlogin_socket_path);
        i_free(login);
 }
 
@@ -126,44 +143,185 @@ master_login_conn_read_request(struct master_login_connection *conn,
        return 1;
 }
 
+static void master_login_auth_finish(struct master_login_client *client,
+                                    const char *const *auth_args)
+{
+       struct master_login_connection *conn = client->conn;
+       struct master_service *service = conn->login->service;
+       bool close_config;
+
+       close_config = service->master_status.available_count == 0 &&
+               service->service_count_left == 1;
+
+       conn->login->callback(client, auth_args[0], auth_args+1);
+       i_free(client);
+
+       if (close_config) {
+               /* we're dying as soon as this connection closes. */
+               master_service_close_config_fd(service);
+               master_login_conn_deinit(&conn);
+       }
+}
+
+static void master_login_client_free(struct master_login_client *client)
+{
+       if (close(client->fd) < 0)
+               i_error("close(fd_read client) failed: %m");
+       i_free(client);
+}
+
+static void master_login_postlogin_free(struct master_login_postlogin *pl)
+{
+       timeout_remove(&pl->to);
+       io_remove(&pl->io);
+       if (close(pl->fd) < 0)
+               i_error("close(postlogin) failed: %m");
+       str_free(&pl->input);
+       i_free(pl);
+}
+
+static void master_login_postlogin_input(struct master_login_postlogin *pl)
+{
+       struct master_login *login = pl->client->conn->login;
+       char buf[1024];
+       const char **auth_args, **p;
+       unsigned int len;
+       ssize_t ret;
+       int fd = -1;
+
+       while ((ret = fd_read(pl->fd, buf, sizeof(buf), &fd)) > 0) {
+               if (fd != -1) {
+                       /* post-login script replaced fd */
+                       if (close(pl->client->fd) < 0)
+                               i_error("close(client) failed: %m");
+                       pl->client->fd = fd;
+               }
+               str_append_n(pl->input, buf, ret);
+       }
+
+       len = str_len(pl->input);
+       if (len > 0 && str_c(pl->input)[len-1] == '\n') {
+               /* finished reading the input */
+               str_truncate(pl->input, len-1);
+       } else {
+               if (ret < 0) {
+                       if (errno == EAGAIN)
+                               return;
+
+                       i_error("fd_read(%s) failed: %m",
+                               login->postlogin_socket_path);
+               } else {
+                       i_error("fd_read(%s) failed: disconnected",
+                               login->postlogin_socket_path);
+               }
+               master_login_client_free(pl->client);
+               master_login_postlogin_free(pl);
+               master_service_client_connection_destroyed(login->service);
+               return;
+       }
+
+       auth_args = t_strsplit(str_c(pl->input), "\t");
+       for (p = auth_args; *p != NULL; p++)
+               *p = str_tabunescape(t_strdup_noconst(*p));
+
+       master_login_auth_finish(pl->client, auth_args);
+       master_login_postlogin_free(pl);
+}
+
+static void master_login_postlogin_timeout(struct master_login_postlogin *pl)
+{
+       struct master_login *login = pl->client->conn->login;
+
+       i_error("%s: Timeout waiting for post-login script to finish, aborting",
+               login->postlogin_socket_path);
+
+       master_login_client_free(pl->client);
+       master_login_postlogin_free(pl);
+       master_service_client_connection_destroyed(login->service);
+}
+
+static int master_login_postlogin(struct master_login_client *client,
+                                 const char *const *auth_args)
+{
+       struct master_login *login = client->conn->login;
+       struct master_login_postlogin *pl;
+       string_t *str;
+       unsigned int i;
+       int fd;
+       ssize_t ret;
+
+       fd = net_connect_unix_with_retries(login->postlogin_socket_path, 1000);
+       if (fd == -1) {
+               i_error("net_connect_unix(%s) failed: %m",
+                       login->postlogin_socket_path);
+               return -1;
+       }
+
+       str = t_str_new(256);
+       str_printfa(str, "%s\t%s", net_ip2addr(&client->auth_req.local_ip),
+                   net_ip2addr(&client->auth_req.remote_ip));
+       for (i = 0; auth_args[i] != NULL; i++) {
+               str_append_c(str, '\t');
+               str_tabescape_write(str, auth_args[i]);
+       }
+       str_append_c(str, '\n');
+       ret = fd_send(fd, client->fd, str_data(str), str_len(str));
+       if (ret != (ssize_t)str_len(str)) {
+               if (ret < 0) {
+                       i_error("write(%s) failed: %m",
+                               login->postlogin_socket_path);
+               } else {
+                       i_error("write(%s) failed: partial write",
+                               login->postlogin_socket_path);
+               }
+               (void)close(fd);
+               return -1;
+       }
+       net_set_nonblock(fd, TRUE);
+
+       pl = i_new(struct master_login_postlogin, 1);
+       pl->client = client;
+       pl->fd = fd;
+       pl->io = io_add(fd, IO_READ, master_login_postlogin_input, pl);
+       pl->to = timeout_add(MASTER_LOGIN_POSTLOGIN_TIMEOUT_MSECS,
+                            master_login_postlogin_timeout, pl);
+       pl->input = str_new(default_pool, 512);
+       return 0;
+}
+
 static void
 master_login_auth_callback(const char *const *auth_args, void *context)
 {
        struct master_login_client *client = context;
        struct master_auth_reply reply;
-       struct master_login_connection *conn = client->conn;
-       struct master_service *service = conn->login->service;
-       bool close_config;
+       struct master_service *service = client->conn->login->service;
 
        memset(&reply, 0, sizeof(reply));
        reply.tag = client->auth_req.tag;
        reply.status = auth_args != NULL ? MASTER_AUTH_STATUS_OK :
                MASTER_AUTH_STATUS_INTERNAL_ERROR;
        reply.mail_pid = getpid();
-       o_stream_send(conn->output, &reply, sizeof(reply));
+       o_stream_send(client->conn->output, &reply, sizeof(reply));
 
        if (auth_args == NULL || auth_args[0] == NULL) {
                if (auth_args != NULL)
                        i_error("login client: Username missing from auth reply");
-               if (close(client->fd) < 0)
-                       i_error("close(fd_read client) failed: %m");
-               i_free(client);
+               master_login_client_free(client);
                return;
        }
 
        i_assert(service->master_status.available_count > 0);
        service->master_status.available_count--;
        master_status_update(service);
-       close_config = service->master_status.available_count == 0 &&
-               service->service_count_left == 1;
 
-       conn->login->callback(client, auth_args[0], auth_args+1);
-       i_free(client);
-
-       if (close_config) {
-               /* we're dying as soon as this connection closes. */
-               master_service_close_config_fd(service);
-               master_login_conn_deinit(&conn);
+       if (client->conn->login->postlogin_socket_path == NULL)
+               master_login_auth_finish(client, auth_args);
+       else {
+               /* execute post-login scripts before finishing auth */
+               if (master_login_postlogin(client, auth_args) < 0) {
+                       master_login_client_free(client);
+                       master_service_client_connection_destroyed(service);
+               }
        }
 }
 
index 815f29666b7420ca9fc9249cf520cc39e4b72d90..4b4468226392970f4bd79d1dce305207665adb4b 100644 (file)
@@ -17,6 +17,7 @@ master_login_callback_t(const struct master_login_client *client,
 
 struct master_login *
 master_login_init(struct master_service *service, const char *auth_socket_path,
+                 const char *postlogin_socket_path,
                  master_login_callback_t *callback);
 void master_login_deinit(struct master_login **login);
 
index 71e4915b7cff4118ebee8675fbe67cf0efd1673c..2440567624fcce01e18d6b5afbaac0d15a8e255f 100644 (file)
@@ -5,6 +5,7 @@
 #include "buffer.h"
 #include "istream.h"
 #include "ostream.h"
+#include "abspath.h"
 #include "base64.h"
 #include "str.h"
 #include "process-title.h"
@@ -178,6 +179,7 @@ int main(int argc, char *argv[])
        };
        enum master_service_flags service_flags = 0;
        enum mail_storage_service_flags storage_service_flags = 0;
+       const char *postlogin_socket_path;
 
        if (IS_STANDALONE() && getuid() == 0 &&
            net_getpeername(1, NULL, NULL) == 0) {
@@ -199,6 +201,8 @@ int main(int argc, char *argv[])
                                             &argc, &argv, NULL);
        if (master_getopt(master_service) > 0)
                return FATAL_DEFAULT;
+       postlogin_socket_path = t_abspath(argv[1]);
+
        master_service_init_finish(master_service);
        master_service_set_die_callback(master_service, pop3_die);
 
@@ -216,6 +220,7 @@ int main(int argc, char *argv[])
                } T_END;
        } else {
                master_login = master_login_init(master_service, "auth-master",
+                                                postlogin_socket_path,
                                                 login_client_connected);
                io_loop_set_running(current_ioloop);
        }
index 9b4b601fa930beea9ce4d2a52c31b7aeea65b2d6..395ff619fb4ad52d3a3c59d61aa3116eba9d8212 100644 (file)
@@ -2,6 +2,7 @@ pkglibexecdir = $(libexecdir)/dovecot
 
 pkglibexec_PROGRAMS = \
        rawlog \
+       script \
        gdbhelper \
        imap-utf7 \
        listview \
@@ -17,6 +18,7 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib-index \
        -I$(top_srcdir)/src/lib-storage \
        -I$(top_srcdir)/src/auth \
+       -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \
        -DPKG_RUNDIR=\""$(rundir)"\"
 
 rawlog_LDADD = $(LIBDOVECOT)
@@ -24,6 +26,11 @@ rawlog_DEPENDENCIES = $(LIBDOVECOT_DEPS)
 rawlog_SOURCES = \
        rawlog.c
 
+script_LDADD = $(LIBDOVECOT)
+script_DEPENDENCIES = $(LIBDOVECOT_DEPS)
+script_SOURCES = \
+       script.c
+
 gdbhelper_LDADD = $(LIBDOVECOT)
 gdbhelper_DEPENDENCIES = $(LIBDOVECOT_DEPS)
 gdbhelper_SOURCES = \
diff --git a/src/util/script.c b/src/util/script.c
new file mode 100644 (file)
index 0000000..06463a7
--- /dev/null
@@ -0,0 +1,156 @@
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "env-util.h"
+#include "fdpass.h"
+#include "str.h"
+#include "strescape.h"
+#include "master-interface.h"
+#include "master-service.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define ENV_USERDB_KEYS "USERDB_KEYS"
+#define SCRIPT_COMM_FD 3
+#define SCRIPT_CLIENT_FD 1
+
+static char **exec_args;
+
+static void client_connected(const struct master_service_connection *conn)
+{
+       string_t *input, *keys;
+       const char **args, *arg, *key, *value, *username;
+       char buf[1024];
+       unsigned int i;
+       int fd = -1;
+       ssize_t ret;
+
+       input = t_str_new(1024);
+       ret = fd_read(conn->fd, buf, sizeof(buf), &fd);
+       while (ret > 0) {
+               str_append_n(input, buf, ret);
+               if (buf[ret-1] == '\n') {
+                       str_truncate(input, str_len(input)-1);
+                       break;
+               }
+
+               ret = read(conn->fd, buf, sizeof(buf));
+       }
+       if (ret <= 0) {
+               if (ret < 0)
+                       i_fatal("read() failed: %m");
+               else
+                       i_fatal("read() failed: disconnected");
+               (void)close(conn->fd);
+               return;
+       }
+       if (fd == -1)
+               i_fatal("client fd not received");
+
+       /* put everything to environment */
+       env_clean();
+       keys = t_str_new(256);
+       args = t_strsplit(str_c(input), "\t");
+
+       if (str_array_length(args) < 3)
+               i_fatal("Missing input fields");
+
+       i = 0;
+       env_put(t_strconcat("LOCAL_IP=", args[i++], NULL));
+       env_put(t_strconcat("IP=", args[i++], NULL));
+       username = args[i++];
+       env_put(t_strconcat("USER=", username, NULL));
+
+       for (; args[i] != '\0'; i++) {
+               arg = str_tabunescape(t_strdup_noconst(args[i]));
+               value = strchr(arg, '=');
+               if (value != NULL) {
+                       key = t_str_ucase(t_strdup_until(arg, value));
+                       env_put(t_strconcat(key, value, NULL));
+                       str_printfa(keys, "%s ", key);
+               }
+       }
+       env_put(t_strconcat(ENV_USERDB_KEYS"=", str_c(keys), NULL));
+
+       if (dup2(fd, SCRIPT_CLIENT_FD) < 0)
+               i_fatal("dup2() failed: %m");
+       if (conn->fd != SCRIPT_COMM_FD) {
+               if (dup2(conn->fd, SCRIPT_COMM_FD) < 0)
+                       i_fatal("dup2() failed: %m");
+       }
+
+       master_service_init_log(master_service,
+                               t_strdup_printf("script(%s): ", username));
+
+       (void)execvp(exec_args[0], exec_args);
+       i_fatal("execvp(%s) failed: %m", exec_args[0]);
+}
+
+static void script_execute_finish(void)
+{
+       const char *keys_str, *username, *const *keys, *value;
+       string_t *reply = t_str_new(512);
+       ssize_t ret;
+
+       keys_str = getenv(ENV_USERDB_KEYS);
+       if (keys_str == NULL)
+               i_fatal(ENV_USERDB_KEYS" environment missing");
+
+       username = getenv("USER");
+       if (username == NULL)
+               i_fatal("USER environment missing");
+       str_append(reply, username);
+
+       for (keys = t_strsplit_spaces(keys_str, " "); *keys != NULL; keys++) {
+               value = getenv(t_str_ucase(*keys));
+               if (value != NULL) {
+                       str_append_c(reply, '\t');
+                       str_tabescape_write(reply,
+                                           t_strconcat(t_str_lcase(*keys), "=",
+                                                       value, NULL));
+               }
+       }
+       str_append_c(reply, '\n');
+
+       ret = fd_send(SCRIPT_COMM_FD, SCRIPT_CLIENT_FD,
+                     str_data(reply), str_len(reply));
+       if (ret < 0)
+               i_fatal("fd_send() failed: %m");
+       else if (ret != (ssize_t)str_len(reply))
+               i_fatal("fd_send() sent partial output");
+}
+
+int main(int argc, char *argv[])
+{
+       enum master_service_flags flags = 0;
+       int i;
+
+       if (getenv(MASTER_UID_ENV) == NULL)
+               flags |= MASTER_SERVICE_FLAG_STANDALONE;
+
+       master_service = master_service_init("script", flags,
+                                            &argc, &argv, NULL);
+       if (master_getopt(master_service) > 0)
+               return FATAL_DEFAULT;
+
+       master_service_init_log(master_service, "script: ");
+       master_service_init_finish(master_service);
+       master_service_set_service_count(master_service, 1);
+
+       if ((flags & MASTER_SERVICE_FLAG_STANDALONE) != 0)
+               script_execute_finish();
+       else {
+               if (argv[1] == NULL)
+                       i_fatal("Missing script path");
+               exec_args = i_new(char *, argc + 1);
+               for (i = 1; i < argc; i++)
+                       exec_args[i-1] = argv[i];
+               exec_args[i-1] = PKG_LIBEXECDIR"/script";
+               exec_args[i] = NULL;
+
+               master_service_run(master_service, client_connected);
+       }
+       master_service_deinit(&master_service);
+        return 0;
+}