From: Timo Sirainen Date: Fri, 11 Dec 2009 23:38:33 +0000 (-0500) Subject: imap, pop3: Added back ability to run post-login scripts. X-Git-Tag: 2.0.beta1~20 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=1d3b9fce06b466bcf64f9ab7b622f3a6e4e939ba;p=thirdparty%2Fdovecot%2Fcore.git imap, pop3: Added back ability to run post-login scripts. --HG-- branch : HEAD --- diff --git a/src/imap/main.c b/src/imap/main.c index 5d9710121d..ab97dbb7d4 100644 --- a/src/imap/main.c +++ b/src/imap/main.c @@ -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); } diff --git a/src/lib-master/master-login.c b/src/lib-master/master-login.c index ea4a664015..87bdff6bf4 100644 --- a/src/lib-master/master-login.c +++ b/src/lib-master/master-login.c @@ -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 #include +#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); + } } } diff --git a/src/lib-master/master-login.h b/src/lib-master/master-login.h index 815f29666b..4b44682263 100644 --- a/src/lib-master/master-login.h +++ b/src/lib-master/master-login.h @@ -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); diff --git a/src/pop3/main.c b/src/pop3/main.c index 71e4915b7c..2440567624 100644 --- a/src/pop3/main.c +++ b/src/pop3/main.c @@ -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); } diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 9b4b601fa9..395ff619fb 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -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 index 0000000000..06463a7ef6 --- /dev/null +++ b/src/util/script.c @@ -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 +#include + +#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; +}