Mercurial > dovecot > core-2.2
changeset 10101:4fe8c4382712 HEAD
Redesigned how login process passes connections to mail processes and changed related APIs.
Master process is no longer in the middle.
line wrap: on
line diff
--- a/doc/example-config/conf.d/master.conf Mon Oct 19 18:34:00 2009 -0400 +++ b/doc/example-config/conf.d/master.conf Mon Oct 19 21:42:09 2009 -0400 @@ -32,7 +32,6 @@ } service auth { - type = auth executable = dovecot-auth # default @@ -52,6 +51,11 @@ path = auth-userdb mode = 0600 } + + unix_listener { + path = auth-master + mode = 0600 + } } service auth-worker { @@ -65,9 +69,8 @@ service imap-login { protocol = imap - type = auth-source + type = login executable = imap-login - auth_dest_service = imap inet_listener { port = 143 @@ -104,13 +107,18 @@ # Most of the memory goes to mmap()ing files. You may need to increase this # limit if you have huge mailboxes. #vsz_limit = 256 + + service_count = 1 + unix_listener { + path = login/imap + mode = 0666 + } } service pop3-login { protocol = pop3 - type = auth-source + type = login executable = pop3-login - auth_dest_service = pop3 inet_listener { port = 110 @@ -130,6 +138,12 @@ service pop3 { protocol = pop3 executable = pop3 + + service_count = 1 + unix_listener { + path = login/pop3 + mode = 0666 + } } service lmtp {
--- a/src/auth/Makefile.am Mon Oct 19 18:34:00 2009 -0400 +++ b/src/auth/Makefile.am Mon Oct 19 21:42:09 2009 -0400 @@ -109,7 +109,6 @@ auth-cache.h \ auth-client-connection.h \ auth-common.h \ - auth-master-interface.h \ auth-master-connection.h \ mech-otp-skey-common.h \ mech-plain-common.h \
--- a/src/auth/auth-client-connection.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/auth/auth-client-connection.c Mon Oct 19 21:42:09 2009 -0400 @@ -116,9 +116,7 @@ conn->refcount++; conn->request_handler = auth_request_handler_create(conn->auth, - auth_callback, conn, - array_count(&auth_master_connections) != 0 ? - auth_master_request_callback : NULL); + auth_callback, conn, auth_master_request_callback); auth_request_handler_set(conn->request_handler, conn->connect_uid, pid); conn->pid = pid;
--- a/src/auth/auth-client-connection.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/auth/auth-client-connection.h Mon Oct 19 21:42:09 2009 -0400 @@ -1,7 +1,7 @@ #ifndef AUTH_CLIENT_CONNECTION_H #define AUTH_CLIENT_CONNECTION_H -#include "master-interface.h" +#include "master-auth.h" struct auth_client_connection { struct auth *auth;
--- a/src/auth/auth-master-connection.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/auth/auth-master-connection.c Mon Oct 19 21:42:09 2009 -0400 @@ -15,8 +15,8 @@ #include "master-service.h" #include "userdb.h" #include "userdb-blocking.h" +#include "master-interface.h" #include "auth-request-handler.h" -#include "auth-master-interface.h" #include "auth-client-connection.h" #include "auth-master-connection.h" @@ -502,7 +502,6 @@ struct auth_master_connection *conn = *_conn; struct auth_master_connection *const *masters; unsigned int i, count; - bool service_connection = conn->fd != MASTER_AUTH_FD; *_conn = NULL; if (conn->destroyed) @@ -529,8 +528,7 @@ conn->fd = -1; } - if (service_connection) - master_service_client_connection_destroyed(master_service); + master_service_client_connection_destroyed(master_service); auth_master_connection_unref(&conn); }
--- a/src/auth/auth-master-interface.h Mon Oct 19 18:34:00 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -#ifndef AUTH_MASTER_INTERFACE_H -#define AUTH_MASTER_INTERFACE_H - -#include "master-interface.h" - -/* Major version changes are not backwards compatible, - minor version numbers can be ignored. */ -#define AUTH_MASTER_PROTOCOL_MAJOR_VERSION 1 -#define AUTH_MASTER_PROTOCOL_MINOR_VERSION 1 - -#endif
--- a/src/auth/main.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/auth/main.c Mon Oct 19 21:42:09 2009 -0400 @@ -11,13 +11,13 @@ #include "module-dir.h" #include "randgen.h" #include "master-service.h" +#include "master-interface.h" #include "password-scheme.h" #include "mech.h" #include "auth.h" #include "auth-request-handler.h" #include "auth-worker-server.h" #include "auth-worker-client.h" -#include "auth-master-interface.h" #include "auth-master-connection.h" #include "auth-client-connection.h" @@ -86,9 +86,6 @@ /* workers have only a single connection from the master auth process */ master_service_set_client_limit(master_service, 1); - } else if (getenv("MASTER_AUTH_FD") != NULL) { - (void)auth_master_connection_create(auth, MASTER_AUTH_FD, - FALSE); } }
--- a/src/doveadm/doveadm.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/doveadm/doveadm.c Mon Oct 19 21:42:09 2009 -0400 @@ -98,6 +98,7 @@ argc -= optind; argv += optind; + master_service_init_finish(master_service); if (!doveadm_try_run(cmd_name, argc, argv) && !doveadm_mail_try_run(cmd_name, argc, argv)) usage();
--- a/src/dsync/dsync.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/dsync/dsync.c Mon Oct 19 21:42:09 2009 -0400 @@ -112,6 +112,7 @@ } if (optind != argc) usage(); + master_service_init_finish(master_service); memset(&input, 0, sizeof(input)); input.username = username;
--- a/src/imap/imap-client.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/imap/imap-client.c Mon Oct 19 21:42:09 2009 -0400 @@ -21,7 +21,7 @@ extern struct mail_storage_callbacks mail_storage_callbacks; struct imap_module_register imap_module_register = { 0 }; -static struct client *imap_clients = NULL; +struct client *imap_clients = NULL; static void client_idle_timeout(struct client *client) {
--- a/src/imap/imap-client.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/imap/imap-client.h Mon Oct 19 21:42:09 2009 -0400 @@ -146,6 +146,8 @@ unsigned int modseqs_sent_since_sync:1; }; +extern struct client *imap_clients; + /* Create new client with specified input/output handles. socket specifies if the handle is a socket. */ struct client *client_create(int fd_in, int fd_out, struct mail_user *user,
--- a/src/imap/main.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/imap/main.c Mon Oct 19 21:42:09 2009 -0400 @@ -9,8 +9,9 @@ #include "restrict-access.h" #include "fd-close-on-exec.h" #include "process-title.h" +#include "master-interface.h" #include "master-service.h" -#include "master-interface.h" +#include "master-login.h" #include "mail-user.h" #include "mail-storage-service.h" #include "imap-commands.h" @@ -21,18 +22,25 @@ #include <unistd.h> #define IS_STANDALONE() \ - (getenv("CLIENT_INPUT") == NULL) + (getenv(MASTER_UID_ENV) == NULL) + +static const struct setting_parser_info *set_roots[] = { + &imap_setting_parser_info, + NULL +}; +static struct master_login *master_login = NULL; +static enum mail_storage_service_flags storage_service_flags = 0; +static bool user_initialized = FALSE; void (*hook_client_created)(struct client **client) = NULL; -static void client_add_input(struct client *client, const char *input) +static void client_add_input(struct client *client, const buffer_t *buf) { - buffer_t *buf; + struct ostream *output; const char *tag; unsigned int data_pos; bool send_untagged_capability = FALSE; - buf = input == NULL ? NULL : t_base64_decode_str(input); if (buf != NULL && buf->used > 0) { tag = t_strndup(buf->data, buf->used); switch (*tag) { @@ -55,6 +63,9 @@ tag = getenv("IMAPLOGINTAG"); } + output = client->output; + o_stream_ref(output); + o_stream_cork(output); if (tag == NULL) { client_send_line(client, t_strconcat( "* PREAUTH [CAPABILITY ", @@ -63,68 +74,141 @@ } else if (send_untagged_capability) { /* client doesn't seem to understand tagged capabilities. send untagged instead and hope that it works. */ - o_stream_cork(client->output); client_send_line(client, t_strconcat("* CAPABILITY ", str_c(client->capability_string), NULL)); client_send_line(client, t_strconcat(tag, " OK Logged in", NULL)); - o_stream_uncork(client->output); } else { client_send_line(client, t_strconcat( tag, " OK [CAPABILITY ", str_c(client->capability_string), "] Logged in", NULL)); } (void)client_handle_input(client); + o_stream_uncork(output); + o_stream_unref(&output); } -static void main_init(const struct imap_settings *set, struct mail_user *user, - bool dump_capability) +static void +main_stdio_init_user(const struct imap_settings *set, struct mail_user *user) { struct client *client; - struct ostream *output; + buffer_t *input_buf; + const char *input_base64; + + input_base64 = getenv("CLIENT_INPUT"); + input_buf = input_base64 == NULL ? NULL : + t_base64_decode_str(input_base64); + + client = client_create(STDIN_FILENO, STDOUT_FILENO, user, set); + client_add_input(client, input_buf); +} + +static void +main_stdio_run(bool dump_capability) +{ + struct mail_storage_service_input input; + struct mail_user *mail_user; + const struct imap_settings *set; + const char *value; - if (set->shutdown_clients && !dump_capability) + memset(&input, 0, sizeof(input)); + input.module = input.service = "imap"; + input.username = getenv("USER"); + if (input.username == NULL && IS_STANDALONE()) + input.username = getlogin(); + if (input.username == NULL) + i_fatal("USER environment missing"); + if ((value = getenv("IP")) != NULL) + net_addr2ip(value, &input.remote_ip); + if ((value = getenv("LOCAL_IP")) != NULL) + net_addr2ip(value, &input.local_ip); + + user_initialized = TRUE; + mail_user = mail_storage_service_init_user(master_service, + &input, set_roots, + storage_service_flags); + set = mail_storage_service_get_settings(master_service); + restrict_access_allow_coredumps(TRUE); + if (set->shutdown_clients) master_service_set_die_with_master(master_service, TRUE); - client = client_create(0, 1, user, set); - if (dump_capability) { + struct client *client = client_create(0, 1, mail_user, set); printf("%s\n", str_c(client->capability_string)); exit(0); } - output = client->output; - o_stream_ref(output); - o_stream_cork(output); - client_add_input(client, getenv("CLIENT_INPUT")); - o_stream_uncork(output); - o_stream_unref(&output); + /* fake that we're running, so we know if client was destroyed + while handling its initial input */ + io_loop_set_running(current_ioloop); + main_stdio_init_user(set, mail_user); } -static void main_deinit(void) +static void +login_client_connected(const struct master_login_client *client, + const char *username, const char *const *extra_fields) { - clients_destroy_all(); + struct mail_storage_service_input input; + struct mail_user *mail_user; + struct client *imap_client; + const struct imap_settings *set; + buffer_t input_buf; + + if (imap_clients != NULL) { + i_error("Can't handle more than one connection currently"); + (void)close(client->fd); + return; + } + i_assert(!user_initialized); + + memset(&input, 0, sizeof(input)); + input.module = input.service = "imap"; + input.local_ip = client->auth_req.local_ip; + input.remote_ip = client->auth_req.remote_ip; + input.username = username; + input.userdb_fields = extra_fields; + + if (input.username == NULL) { + i_error("login client: Username missing from auth reply"); + (void)close(client->fd); + return; + } + user_initialized = TRUE; + master_login_deinit(&master_login); + + mail_user = mail_storage_service_init_user(master_service, + &input, set_roots, + storage_service_flags); + set = mail_storage_service_get_settings(master_service); + restrict_access_allow_coredumps(TRUE); + if (set->shutdown_clients) + master_service_set_die_with_master(master_service, TRUE); + + /* fake that we're running, so we know if client was destroyed + while handling its initial input */ + io_loop_set_running(current_ioloop); + + buffer_create_const_data(&input_buf, client->data, + client->auth_req.data_size); + imap_client = client_create(client->fd, client->fd, mail_user, set); + T_BEGIN { + client_add_input(imap_client, &input_buf); + } T_END; } static void client_connected(const struct master_service_connection *conn) { - /* FIXME: we can't handle this yet */ - (void)close(conn->fd); + if (master_login == NULL) { + /* running standalone, we shouldn't even get here */ + (void)close(conn->fd); + } else { + master_login_add(master_login, conn->fd); + } } int main(int argc, char *argv[], char *envp[]) { - const struct setting_parser_info *set_roots[] = { - &imap_setting_parser_info, - NULL - }; - enum master_service_flags service_flags = - MASTER_SERVICE_FLAG_STD_CLIENT; - enum mail_storage_service_flags storage_service_flags = 0; - struct mail_storage_service_input input; - struct mail_user *mail_user; - const struct imap_settings *set; + enum master_service_flags service_flags = 0; bool dump_capability; - const char *value; int c; if (IS_STANDALONE() && getuid() == 0 && @@ -134,12 +218,12 @@ return 1; } - if (IS_STANDALONE()) - service_flags |= MASTER_SERVICE_FLAG_STANDALONE; - else { + if (IS_STANDALONE()) { + service_flags |= MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_STD_CLIENT; + } else { storage_service_flags |= - MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT | - MAIL_STORAGE_SERVICE_FLAG_RESTRICT_BY_ENV; + MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT; } dump_capability = getenv("DUMP_CAPABILITY") != NULL; @@ -153,50 +237,31 @@ if (!master_service_parse_option(master_service, c, optarg)) exit(FATAL_DEFAULT); } - - memset(&input, 0, sizeof(input)); - input.module = "imap"; - input.service = "imap"; - input.username = getenv("USER"); - if (input.username == NULL && IS_STANDALONE()) - input.username = getlogin(); - if (input.username == NULL) { - if (getenv(MASTER_UID_ENV) == NULL) - i_fatal("USER environment missing"); - else { - i_fatal("login_executable setting must be imap-login, " - "not imap"); - } - } - if ((value = getenv("IP")) != NULL) - net_addr2ip(value, &input.remote_ip); - if ((value = getenv("LOCAL_IP")) != NULL) - net_addr2ip(value, &input.local_ip); + process_title_init(argv, envp); + master_service_init_finish(master_service); /* plugins may want to add commands, so this needs to be called early */ commands_init(); imap_fetch_handlers_init(); - mail_user = mail_storage_service_init_user(master_service, - &input, set_roots, - storage_service_flags); - set = mail_storage_service_get_settings(master_service); - restrict_access_allow_coredumps(TRUE); - - process_title_init(argv, envp); + if (IS_STANDALONE() || dump_capability) { + T_BEGIN { + main_stdio_run(dump_capability); + } T_END; + } else { + master_login = master_login_init("auth-master", + login_client_connected); + io_loop_set_running(current_ioloop); + } - /* fake that we're running, so we know if client was destroyed - while initializing */ - io_loop_set_running(current_ioloop); - - T_BEGIN { - main_init(set, mail_user, dump_capability); - } T_END; if (io_loop_is_running(current_ioloop)) master_service_run(master_service, client_connected); + clients_destroy_all(); - main_deinit(); - mail_storage_service_deinit_user(); + if (master_login != NULL) + master_login_deinit(&master_login); + if (user_initialized) + mail_storage_service_deinit_user(); imap_fetch_handlers_deinit(); commands_deinit();
--- a/src/lda/main.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lda/main.c Mon Oct 19 21:42:09 2009 -0400 @@ -378,6 +378,7 @@ i_fatal_status(EX_USAGE, "destination user parameter (-d user) not given"); } + master_service_init_finish(master_service); memset(&service_input, 0, sizeof(service_input)); service_input.module = "lda";
--- a/src/lib-master/Makefile.am Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lib-master/Makefile.am Mon Oct 19 21:42:09 2009 -0400 @@ -10,6 +10,8 @@ libmaster_la_SOURCES = \ master-auth.c \ + master-login.c \ + master-login-auth.c \ master-service.c \ master-service-settings.c \ syslog-util.c @@ -17,6 +19,8 @@ noinst_HEADERS = \ master-auth.h \ master-interface.h \ + master-login.h \ + master-login-auth.h \ master-service.h \ master-service-private.h \ master-service-settings.h \
--- a/src/lib-master/master-auth.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lib-master/master-auth.c Mon Oct 19 21:42:09 2009 -0400 @@ -12,45 +12,152 @@ #include <unistd.h> #include <sys/stat.h> -struct master_auth { - struct master_service *service; - pool_t pool; +struct master_auth_connection { + struct master_auth *auth; + unsigned int tag; int fd; struct io *io; - unsigned int tag_counter; - struct hash_table *requests; - char buf[sizeof(struct master_auth_reply)]; unsigned int buf_pos; - /* linked list, node->context is the next pointer */ - struct master_auth_request_node *free_nodes; -}; - -struct master_auth_request_node { master_auth_callback_t *callback; void *context; }; -static struct master_auth_request_node aborted_node; +struct master_auth { + struct master_service *service; + pool_t pool; + + const char *path; + + unsigned int tag_counter; + struct hash_table *connections; +}; + +struct master_auth * +master_auth_init(struct master_service *service, const char *path) +{ + struct master_auth *auth; + pool_t pool; + + pool = pool_alloconly_create("master auth", 1024); + auth = p_new(pool, struct master_auth, 1); + auth->pool = pool; + auth->service = service; + auth->path = p_strdup(pool, path); + auth->connections = hash_table_create(default_pool, pool, + 0, NULL, NULL); + return auth; +} + +static void +master_auth_connection_deinit(struct master_auth_connection **_conn) +{ + struct master_auth_connection *conn = *_conn; + struct master_auth_reply reply; + + *_conn = NULL; + + if (conn->tag != 0) { + hash_table_remove(conn->auth->connections, + POINTER_CAST(conn->tag)); + } + + if (conn->callback != NULL) { + memset(&reply, 0, sizeof(reply)); + reply.status = MASTER_AUTH_STATUS_INTERNAL_ERROR; + conn->callback(&reply, conn->context); + } + + if (conn->io != NULL) + io_remove(&conn->io); + if (conn->fd != -1) { + if (close(conn->fd) < 0) + i_fatal("close(master auth conn) failed: %m"); + conn->fd = -1; + } + i_free(conn); +} -unsigned int master_auth_request(struct master_service *service, int fd, - const struct master_auth_request *request, - const unsigned char *data, - master_auth_callback_t *callback, - void *context) +void master_auth_deinit(struct master_auth **_auth) +{ + struct master_auth *auth = *_auth; + struct hash_iterate_context *iter; + void *key, *value; + + *_auth = NULL; + + iter = hash_table_iterate_init(auth->connections); + while (hash_table_iterate(iter, &key, &value)) { + struct master_auth_connection *conn = value; + + conn->tag = 0; + master_auth_connection_deinit(&conn); + } + hash_table_iterate_deinit(&iter); + hash_table_destroy(&auth->connections); + pool_unref(&auth->pool); +} + +static void master_auth_connection_input(struct master_auth_connection *conn) { - struct master_auth *auth = service->auth; - struct master_auth_request_node *node; + const struct master_auth_reply *reply; + int ret; + + ret = read(conn->fd, conn->buf + conn->buf_pos, + sizeof(conn->buf) - conn->buf_pos); + if (ret <= 0) { + if (ret < 0) { + if (errno == EAGAIN) + return; + i_error("read(master auth conn) failed: %m"); + } else { + i_error("read(master auth conn) failed: " + "Remote closed connection"); + } + master_auth_connection_deinit(&conn); + return; + } + + conn->buf_pos += ret; + if (conn->buf_pos < sizeof(conn->buf)) + return; + + /* reply is now read */ + reply = (struct master_auth_reply *)conn->buf; + conn->buf_pos = 0; + + if (conn->tag != reply->tag) + i_error("Master sent reply with unknown tag %u", reply->tag); + else { + conn->callback(reply, conn->context); + conn->callback = NULL; + } + master_auth_connection_deinit(&conn); +} + +void master_auth_request(struct master_auth *auth, int fd, + const struct master_auth_request *request, + const unsigned char *data, + master_auth_callback_t *callback, + void *context, unsigned int *tag_r) +{ + struct master_auth_connection *conn; struct master_auth_request req; buffer_t *buf; struct stat st; ssize_t ret; + i_assert(request->client_pid != 0); i_assert(request->auth_pid != 0); + conn = i_new(struct master_auth_connection, 1); + conn->auth = auth; + conn->callback = callback; + conn->context = context; + req = *request; req.tag = ++auth->tag_counter; if (req.tag == 0) @@ -65,145 +172,38 @@ buffer_append(buf, &req, sizeof(req)); buffer_append(buf, data, req.data_size); - ret = fd_send(auth->fd, fd, buf->data, buf->used); - if (ret < 0) - i_fatal("fd_send(%d) failed: %m", fd); - if ((size_t)ret != buf->used) { - i_fatal("fd_send() sent only %d of %d bytes", - (int)ret, (int)buf->used); - } - - if (auth->free_nodes == NULL) - node = p_new(auth->pool, struct master_auth_request_node, 1); - else { - node = auth->free_nodes; - auth->free_nodes = node->context; - } - node->callback = callback; - node->context = context; - - hash_table_insert(auth->requests, POINTER_CAST(req.tag), node); - return req.tag; -} - -void master_auth_request_abort(struct master_service *service, unsigned int tag) -{ - struct master_auth *auth = service->auth; - struct master_auth_request_node *node; + conn->fd = net_connect_unix(auth->path); + if (conn->fd == -1) + i_error("net_connect_unix(%s) failed: %m", auth->path); - node = hash_table_lookup(auth->requests, POINTER_CAST(tag)); - if (node == NULL) - i_panic("master_auth_request_abort(): tag %u not found", tag); - - i_assert(node != &aborted_node); - hash_table_update(auth->requests, POINTER_CAST(tag), &aborted_node); - - node->callback = NULL; - node->context = auth->free_nodes; - auth->free_nodes = node; -} - -static void -master_notify_have_more_avail_processes(struct master_service *service, - bool have_more) -{ - if (!have_more) { - /* make sure we're listening for more connections */ - master_service_io_listeners_add(service); + ret = conn->fd == -1 ? -1 : + fd_send(conn->fd, fd, buf->data, buf->used); + if (ret < 0) + i_error("fd_send(%d) failed: %m", fd); + else if ((size_t)ret != buf->used) { + i_error("fd_send() sent only %d of %d bytes", + (int)ret, (int)buf->used); + ret = -1; } - service->call_avail_overflow = !have_more; -} - -static void request_handle(struct master_auth *auth, - struct master_auth_reply *reply) -{ - struct master_auth_request_node *node; - - if (reply->tag == 0) { - /* notification from master */ - master_notify_have_more_avail_processes(auth->service, - reply->status == 0); + if (ret < 0) { + master_auth_connection_deinit(&conn); return; } - node = hash_table_lookup(auth->requests, POINTER_CAST(reply->tag)); - if (node == NULL) - i_error("Master sent reply with unknown tag %u", reply->tag); - - if (node != &aborted_node) { - node->callback(reply, node->context); - - /* the callback may have called master_auth_request_abort(), - which would have put the node to free_nodes list already */ - if (node->callback != NULL) { - node->callback = NULL; - node->context = auth->free_nodes; - auth->free_nodes = node; - } - } - - hash_table_remove(auth->requests, POINTER_CAST(reply->tag)); -} - -static void master_auth_input(void *context) -{ - struct master_auth *auth = context; - int ret; - - ret = net_receive(auth->fd, auth->buf + auth->buf_pos, - sizeof(auth->buf) - auth->buf_pos); - if (ret < 0) { - /* master died, kill all clients logging in */ - master_service_stop(auth->service); - return; - } - - auth->buf_pos += ret; - if (auth->buf_pos < sizeof(auth->buf)) - return; - - /* reply is now read */ - request_handle(auth, (struct master_auth_reply *) auth->buf); - auth->buf_pos = 0; + conn->tag = req.tag; + conn->io = io_add(conn->fd, IO_READ, + master_auth_connection_input, conn); + hash_table_insert(auth->connections, POINTER_CAST(req.tag), conn); + *tag_r = req.tag; } -void master_auth_init(struct master_service *service) +void master_auth_request_abort(struct master_auth *auth, unsigned int tag) { - struct master_auth *auth; - struct ip_addr ip; - pool_t pool; - - i_assert(service->auth == NULL); - - if (getenv("MASTER_AUTH_FD") == NULL) - i_fatal("auth_dest_service setting not set"); - - if (net_getsockname(MASTER_AUTH_FD, &ip, NULL) < 0 || - ip.family != AF_UNIX) - i_fatal("MASTER_AUTH_FD not given"); + struct master_auth_connection *conn; - pool = pool_alloconly_create("master auth", 1024); - auth = p_new(pool, struct master_auth, 1); - auth->pool = pool; - auth->service = service; - auth->fd = MASTER_AUTH_FD; - auth->requests = hash_table_create(default_pool, pool, 0, NULL, NULL); - auth->io = io_add(auth->fd, IO_READ, master_auth_input, auth); - - service->auth = auth; -} + conn = hash_table_lookup(auth->connections, POINTER_CAST(tag)); + if (conn == NULL) + i_panic("master_auth_request_abort(): tag %u not found", tag); -void master_auth_deinit(struct master_service *service) -{ - struct master_auth *auth = service->auth; - - i_assert(service->auth != NULL); - - hash_table_destroy(&auth->requests); - if (auth->io != NULL) - io_remove(&auth->io); - if (close(auth->fd) < 0) - i_fatal("close(master auth) failed: %m"); - pool_unref(&auth->pool); - service->auth = NULL; + conn->callback = NULL; }
--- a/src/lib-master/master-auth.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lib-master/master-auth.h Mon Oct 19 21:42:09 2009 -0400 @@ -1,26 +1,74 @@ #ifndef MASTER_AUTH_H #define MASTER_AUTH_H +#include "network.h" + struct master_service; -struct master_auth_request; -struct master_auth_reply; + +/* Major version changes are not backwards compatible, + minor version numbers can be ignored. */ +#define AUTH_MASTER_PROTOCOL_MAJOR_VERSION 1 +#define AUTH_MASTER_PROTOCOL_MINOR_VERSION 1 + +/* Authentication client process's cookie size */ +#define MASTER_AUTH_COOKIE_SIZE (128/8) + +/* This should be kept in sync with LOGIN_MAX_INBUF_SIZE. Multiply it by two + to make sure there's space to transfer the command tag */ +#define MASTER_AUTH_MAX_DATA_SIZE (1024*2) + +/* Authentication request. File descriptor may be sent along with the + request. */ +struct master_auth_request { + /* Request tag. Reply is sent back using same tag. */ + unsigned int tag; + + /* Authentication process, authentication ID and auth cookie. */ + pid_t auth_pid; + unsigned int auth_id; + unsigned int client_pid; + uint8_t cookie[MASTER_AUTH_COOKIE_SIZE]; + + /* Local and remote IPs of the connection. The file descriptor + itself may be a local socketpair. */ + struct ip_addr local_ip, remote_ip; + + /* request follows this many bytes of client input */ + uint32_t data_size; + /* inode of the transferred fd. verified just to be sure that the + correct fd is mapped to the correct struct. */ + ino_t ino; +}; + +enum master_auth_status { + MASTER_AUTH_STATUS_OK, + MASTER_AUTH_STATUS_INTERNAL_ERROR +}; + +struct master_auth_reply { + /* tag=0 are notifications from master */ + unsigned int tag; + enum master_auth_status status; + /* PID of the post-login mail process handling this connection */ + pid_t mail_pid; +}; typedef void master_auth_callback_t(const struct master_auth_reply *reply, void *context); +struct master_auth * +master_auth_init(struct master_service *service, const char *path); +void master_auth_deinit(struct master_auth **auth); + /* Send an authentication request. The fd contains the file descriptor to transfer, or -1 if no fd is wanted to be transferred. Returns tag which can be used to abort the request (ie. ignore the reply from master). request->tag is ignored. */ -unsigned int master_auth_request(struct master_service *service, int fd, - const struct master_auth_request *request, - const unsigned char *data, - master_auth_callback_t *callback, - void *context); -void master_auth_request_abort(struct master_service *service, - unsigned int tag); - -void master_auth_init(struct master_service *service); -void master_auth_deinit(struct master_service *service); +void master_auth_request(struct master_auth *auth, int fd, + const struct master_auth_request *request, + const unsigned char *data, + master_auth_callback_t *callback, + void *context, unsigned int *tag_r); +void master_auth_request_abort(struct master_auth *auth, unsigned int tag); #endif
--- a/src/lib-master/master-interface.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lib-master/master-interface.h Mon Oct 19 21:42:09 2009 -0400 @@ -1,8 +1,6 @@ #ifndef MASTER_INTERFACE_H #define MASTER_INTERFACE_H -#include "network.h" - /* We are attempting semi-compatibility with Postfix's master process here. Whether this is useful or not remains to be seen. */ @@ -30,46 +28,9 @@ /* unsigned char prefix[]; */ }; -/* This should be kept in sync with LOGIN_MAX_INBUF_SIZE. Multiply it by two - to make sure there's space to transfer the command tag */ -#define MASTER_AUTH_MAX_DATA_SIZE (1024*2) - -/* Authentication client process's cookie size */ -#define MASTER_AUTH_COOKIE_SIZE (128/8) - -/* Authentication request. File descriptor may be sent along with the - request. */ -struct master_auth_request { - /* Request tag. Reply is sent back using same tag. */ - unsigned int tag; - - /* Authentication process, authentication ID and auth cookie. */ - pid_t auth_pid; - unsigned int auth_id; - uint8_t cookie[MASTER_AUTH_COOKIE_SIZE]; - - /* Local and remote IPs of the connection. The file descriptor - itself may be a local socketpair. */ - struct ip_addr local_ip, remote_ip; - - /* request follows this many bytes of client input */ - uint32_t data_size; - /* inode of the transferred fd. verified just to be sure that the - correct fd is mapped to the correct struct. */ - ino_t ino; -}; - -enum master_auth_status { - MASTER_AUTH_STATUS_OK, - MASTER_AUTH_STATUS_INTERNAL_ERROR -}; - -struct master_auth_reply { - /* tag=0 are notifications from master */ - unsigned int tag; - enum master_auth_status status; - /* PID of the post-login mail process handling this connection */ - pid_t mail_pid; +enum master_login_state { + MASTER_LOGIN_STATE_NONFULL = 0, + MASTER_LOGIN_STATE_FULL }; /* getenv(MASTER_UID_ENV) provides master_status.uid value */ @@ -93,13 +54,10 @@ if dovecot was started with -p parameter. */ #define MASTER_SSL_KEY_PASSWORD_ENV "SSL_KEY_PASSWORD" -/* Write pipe to anvil. Currently available only for auth destination - services, for others it's /dev/null. */ +/* Write pipe to anvil. */ #define MASTER_ANVIL_FD 3 - -/* Socket for sending master_auth_requests. Also used by auth server process - as a master socket. */ -#define MASTER_AUTH_FD 4 +/* Master's "all processes full" notification fd for login processes */ +#define MASTER_LOGIN_NOTIFY_FD 4 /* Shared pipe to master, used to send master_status reports */ #define MASTER_STATUS_FD 5
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-master/master-login-auth.c Mon Oct 19 21:42:09 2009 -0400 @@ -0,0 +1,290 @@ +/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "network.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "hex-binary.h" +#include "hash.h" +#include "str.h" +#include "master-auth.h" +#include "master-login-auth.h" + +#include <stdlib.h> + +#define AUTH_MAX_INBUF_SIZE 8192 + +struct master_login_auth_request { + master_login_auth_request_callback_t *callback; + void *context; +}; + +struct master_login_auth { + pool_t pool; + const char *auth_socket_path; + int refcount; + + int fd; + struct io *io; + struct istream *input; + struct ostream *output; + + unsigned int id_counter; + struct hash_table *requests; + + unsigned int version_received:1; +}; + +struct master_login_auth *master_login_auth_init(const char *auth_socket_path) +{ + struct master_login_auth *auth; + pool_t pool; + + pool = pool_alloconly_create("master login auth", 1024); + auth = p_new(pool, struct master_login_auth, 1); + auth->pool = pool; + auth->auth_socket_path = p_strdup(pool, auth_socket_path); + auth->refcount = 1; + auth->fd = -1; + auth->requests = hash_table_create(default_pool, pool, 0, NULL, NULL); + return auth; +} + +static void master_login_auth_disconnect(struct master_login_auth *auth) +{ + struct hash_iterate_context *iter; + void *key, *value; + + iter = hash_table_iterate_init(auth->requests); + while (hash_table_iterate(iter, &key, &value)) { + struct master_login_auth_request *request = value; + request->callback(NULL, request->context); + i_free(request); + } + hash_table_iterate_deinit(&iter); + hash_table_clear(auth->requests, FALSE); + + if (auth->io != NULL) + io_remove(&auth->io); + if (auth->fd != -1) { + i_stream_destroy(&auth->input); + o_stream_destroy(&auth->output); + + net_disconnect(auth->fd); + auth->fd = -1; + } + auth->version_received = FALSE; +} + +static void master_login_auth_unref(struct master_login_auth **_auth) +{ + struct master_login_auth *auth = *_auth; + + *_auth = NULL; + + i_assert(auth->refcount > 0); + if (--auth->refcount > 0) + return; + + hash_table_destroy(&auth->requests); + pool_unref(&auth->pool); +} + +void master_login_auth_deinit(struct master_login_auth **_auth) +{ + struct master_login_auth *auth = *_auth; + + *_auth = NULL; + + master_login_auth_disconnect(auth); + master_login_auth_unref(&auth); +} + +static struct master_login_auth_request * +master_login_auth_lookup_request(struct master_login_auth *auth, + unsigned int id) +{ + struct master_login_auth_request *request; + + request = hash_table_lookup(auth->requests, POINTER_CAST(id)); + if (request == NULL) { + i_error("Auth server sent reply with unknown ID %u", id); + return NULL; + } + + hash_table_remove(auth->requests, POINTER_CAST(id)); + return request; +} + +static bool +master_login_auth_input_user(struct master_login_auth *auth, const char *args) +{ + struct master_login_auth_request *request; + const char *const *list; + unsigned int id; + + /* <id> <userid> [..] */ + + list = t_strsplit(args, "\t"); + if (list[0] == NULL || list[1] == NULL) { + i_error("Auth server sent corrupted USER line"); + return FALSE; + } + id = (unsigned int)strtoul(list[0], NULL, 10); + + request = master_login_auth_lookup_request(auth, id); + if (request != NULL) { + request->callback(list + 1, request->context); + i_free(request); + } + return TRUE; +} + +static bool +master_login_auth_input_notfound(struct master_login_auth *auth, + const char *args) +{ + struct master_login_auth_request *request; + unsigned int id; + + id = (unsigned int)strtoul(args, NULL, 10); + request = master_login_auth_lookup_request(auth, id); + if (request != NULL) { + i_error("Auth request not found (timed out?): %u", id); + request->callback(NULL, request->context); + i_free(request); + } + return TRUE; +} + +static bool +master_login_auth_input_fail(struct master_login_auth *auth, const char *args) +{ + struct master_login_auth_request *request; + const char *error; + unsigned int id; + + error = strchr(args, '\t'); + if (error != NULL) + error++; + + id = (unsigned int)strtoul(args, NULL, 10); + request = master_login_auth_lookup_request(auth, id); + if (request != NULL) { + request->callback(NULL, request->context); + i_free(request); + } + return TRUE; +} + +static void master_login_auth_input(struct master_login_auth *auth) +{ + const char *line; + bool ret; + + switch (i_stream_read(auth->input)) { + case 0: + return; + case -1: + /* disconnected */ + master_login_auth_disconnect(auth); + return; + case -2: + /* buffer full */ + i_error("Auth server sent us too long line"); + master_login_auth_disconnect(auth); + return; + } + + if (!auth->version_received) { + line = i_stream_next_line(auth->input); + if (line == NULL) + return; + + /* make sure the major version matches */ + if (strncmp(line, "VERSION\t", 8) != 0 || + atoi(t_strcut(line + 8, '\t')) != + AUTH_MASTER_PROTOCOL_MAJOR_VERSION) { + i_error("Authentication server not compatible with " + "master process (mixed old and new binaries?)"); + master_login_auth_disconnect(auth); + return; + } + auth->version_received = TRUE; + } + + auth->refcount++; + while ((line = i_stream_next_line(auth->input)) != NULL) { + if (strncmp(line, "USER\t", 5) == 0) + ret = master_login_auth_input_user(auth, line + 5); + else if (strncmp(line, "NOTFOUND\t", 9) == 0) + ret = master_login_auth_input_notfound(auth, line + 9); + else if (strncmp(line, "FAIL\t", 5) == 0) + ret = master_login_auth_input_fail(auth, line + 5); + else + ret = TRUE; + + if (!ret || auth->input == NULL) { + master_login_auth_disconnect(auth); + break; + } + } + master_login_auth_unref(&auth); +} + +static int +master_login_auth_connect(struct master_login_auth *auth) +{ + int fd; + + i_assert(auth->fd == -1); + + fd = net_connect_unix(auth->auth_socket_path); + if (fd == -1) { + i_error("net_connect_unix(%s) failed: %m", + auth->auth_socket_path); + return -1; + } + auth->fd = fd; + auth->input = i_stream_create_fd(fd, AUTH_MAX_INBUF_SIZE, FALSE); + auth->output = o_stream_create_fd(fd, (size_t)-1, FALSE); + auth->io = io_add(fd, IO_READ, master_login_auth_input, auth); + return 0; +} + +void master_login_auth_request(struct master_login_auth *auth, + const struct master_auth_request *req, + master_login_auth_request_callback_t *callback, + void *context) +{ + struct master_login_auth_request *login_req; + unsigned int id; + string_t *str; + + str = t_str_new(128); + if (auth->fd == -1) { + if (master_login_auth_connect(auth) < 0) { + callback(NULL, context); + return; + } + str_printfa(str, "VERSION\t%u\t%u\n", + AUTH_MASTER_PROTOCOL_MAJOR_VERSION, + AUTH_MASTER_PROTOCOL_MINOR_VERSION); + } + + id = ++auth->id_counter; + if (id == 0) + id++; + + str_printfa(str, "REQUEST\t%u\t%u\t%u\t", id, + req->client_pid, req->auth_id); + binary_to_hex_append(str, req->cookie, sizeof(req->cookie)); + str_append_c(str, '\n'); + o_stream_send(auth->output, str_data(str), str_len(str)); + + login_req = i_new(struct master_login_auth_request, 1); + login_req->callback = callback; + login_req->context = context; + hash_table_insert(auth->requests, POINTER_CAST(id), login_req); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-master/master-login-auth.h Mon Oct 19 21:42:09 2009 -0400 @@ -0,0 +1,18 @@ +#ifndef MASTER_LOGIN_AUTH_H +#define MASTER_LOGIN_AUTH_H + +struct master_auth_request; + +typedef void +master_login_auth_request_callback_t(const char *const *auth_args, + void *context); + +struct master_login_auth *master_login_auth_init(const char *auth_socket_path); +void master_login_auth_deinit(struct master_login_auth **auth); + +void master_login_auth_request(struct master_login_auth *auth, + const struct master_auth_request *req, + master_login_auth_request_callback_t *callback, + void *context); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-master/master-login.c Mon Oct 19 21:42:09 2009 -0400 @@ -0,0 +1,205 @@ +/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "ostream.h" +#include "fdpass.h" +#include "fd-close-on-exec.h" +#include "llist.h" +#include "master-login.h" +#include "master-login-auth.h" + +#include <sys/stat.h> +#include <unistd.h> + +struct master_login_connection { + struct master_login_connection *prev, *next; + + struct master_login *login; + int fd; + struct io *io; + struct ostream *output; +}; + +struct master_login { + master_login_callback_t *callback; + struct master_login_connection *conns; + struct master_login_auth *auth; +}; + +static void master_login_conn_deinit(struct master_login_connection **_conn); + +struct master_login * +master_login_init(const char *auth_socket_path, + master_login_callback_t *callback) +{ + struct master_login *login; + + login = i_new(struct master_login, 1); + login->callback = callback; + login->auth = master_login_auth_init(auth_socket_path); + return login; +} + +void master_login_deinit(struct master_login **_login) +{ + struct master_login *login = *_login; + + *_login = NULL; + + master_login_auth_deinit(&login->auth); + while (login->conns != NULL) { + struct master_login_connection *conn = login->conns; + + master_login_conn_deinit(&conn); + } + i_free(login); +} + +static int +master_login_conn_read_request(struct master_login_connection *conn, + struct master_auth_request *req_r, + unsigned char data[MASTER_AUTH_MAX_DATA_SIZE], + int *client_fd_r) +{ + struct stat st; + ssize_t ret; + + *client_fd_r = -1; + + ret = fd_read(conn->fd, req_r, sizeof(*req_r), client_fd_r); + if (ret != sizeof(*req_r)) { + if (ret == 0) { + /* disconnected */ + } else if (ret > 0) { + /* request wasn't fully read */ + i_error("fd_read() partial input (%d/%d)", + (int)ret, (int)sizeof(*req_r)); + } else { + if (errno == EAGAIN) + return 0; + + i_error("fd_read() failed: %m"); + } + return -1; + } + + if (req_r->data_size != 0) { + if (req_r->data_size > MASTER_AUTH_MAX_DATA_SIZE) { + i_error("Too large auth data_size sent"); + return -1; + } + /* @UNSAFE */ + ret = read(conn->fd, data, req_r->data_size); + if (ret != (ssize_t)req_r->data_size) { + if (ret == 0) { + /* disconnected */ + } else if (ret > 0) { + /* request wasn't fully read */ + i_error("Data read partially %d/%u", + (int)ret, req_r->data_size); + } else { + i_error("read(data) failed: %m"); + } + return -1; + } + } + + if (*client_fd_r == -1) { + i_error("Auth request missing a file descriptor"); + return -1; + } + + if (fstat(*client_fd_r, &st) < 0) { + i_error("fstat(fd_recv client) failed: %m"); + return -1; + } + if (st.st_ino != req_r->ino) { + i_error("Auth request inode mismatch: %s != %s", + dec2str(st.st_ino), dec2str(req_r->ino)); + return -1; + } + return 1; +} + +static void +master_login_auth_callback(const char *const *auth_args, void *context) +{ + struct master_login_client *client = context; + struct master_auth_reply reply; + + 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(client->conn->output, &reply, sizeof(reply)); + + if (auth_args == NULL) { + if (close(client->fd) < 0) + i_error("close(fd_recv client) failed: %m"); + i_free(client); + return; + } + + client->conn->login->callback(client, auth_args[0], auth_args+1); + i_free(client); +} + +static void master_login_conn_input(struct master_login_connection *conn) +{ + struct master_auth_request req; + struct master_login_client *client; + unsigned char data[MASTER_AUTH_MAX_DATA_SIZE]; + int ret, client_fd; + + ret = master_login_conn_read_request(conn, &req, data, &client_fd); + if (ret <= 0) { + if (ret < 0) + master_login_conn_deinit(&conn); + if (client_fd != -1) { + if (close(client_fd) < 0) + i_error("close(fd_recv client) failed: %m"); + } + return; + } + fd_close_on_exec(client_fd, TRUE); + + /* @UNSAFE: we have a request. do userdb lookup for it. */ + client = i_malloc(sizeof(struct master_login_client) + req.data_size); + client->conn = conn; + client->fd = client_fd; + client->auth_req = req; + memcpy(client->data, data, req.data_size); + + master_login_auth_request(conn->login->auth, &req, + master_login_auth_callback, client); +} + +void master_login_add(struct master_login *login, int fd) +{ + struct master_login_connection *conn; + + conn = i_new(struct master_login_connection, 1); + conn->login = login; + conn->fd = fd; + conn->io = io_add(conn->fd, IO_READ, master_login_conn_input, conn); + conn->output = o_stream_create_fd(fd, (size_t)-1, FALSE); + + DLLIST_PREPEND(&login->conns, conn); +} + +static void master_login_conn_deinit(struct master_login_connection **_conn) +{ + struct master_login_connection *conn = *_conn; + + *_conn = NULL; + + DLLIST_REMOVE(&conn->login->conns, conn); + + io_remove(&conn->io); + o_stream_unref(&conn->output); + if (close(conn->fd) < 0) + i_error("close(master login) failed: %m"); + i_free(conn); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-master/master-login.h Mon Oct 19 21:42:09 2009 -0400 @@ -0,0 +1,25 @@ +#ifndef MASTER_LOGIN_H +#define MASTER_LOGIN_H + +#include "master-auth.h" + +struct master_login_client { + struct master_login_connection *conn; + int fd; + + struct master_auth_request auth_req; + unsigned char data[FLEXIBLE_ARRAY_MEMBER]; +}; + +typedef void +master_login_callback_t(const struct master_login_client *client, + const char *username, const char *const *extra_fields); + +struct master_login * +master_login_init(const char *auth_socket_path, + master_login_callback_t *callback); +void master_login_deinit(struct master_login **login); + +void master_login_add(struct master_login *login, int fd); + +#endif
--- a/src/lib-master/master-service-private.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lib-master/master-service-private.h Mon Oct 19 21:42:09 2009 -0400 @@ -35,8 +35,8 @@ struct master_status master_status; void (*avail_overflow_callback)(void); + struct timeout *to_overflow_state; - struct master_auth *auth; master_service_connection_callback_t *callback; pool_t set_pool; @@ -48,6 +48,7 @@ unsigned int initial_status_sent:1; unsigned int die_with_master:1; unsigned int call_avail_overflow:1; + unsigned int delay_status_updates:1; }; void master_service_io_listeners_add(struct master_service *service);
--- a/src/lib-master/master-service.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lib-master/master-service.c Mon Oct 19 21:42:09 2009 -0400 @@ -26,8 +26,14 @@ /* getenv(MASTER_DOVECOT_VERSION_ENV) provides master's version number */ #define MASTER_DOVECOT_VERSION_ENV "DOVECOT_VERSION" +/* when we're full of connections, how often to check if login state has + changed. we normally notice it immediately because of a signal, so this is + just a fallback against race conditions. */ +#define MASTER_SERVICE_STATE_CHECK_MSECS 1000 + struct master_service *master_service; +static void master_service_refresh_login_state(struct master_service *service); static void io_listeners_remove(struct master_service *service); static void master_status_update(struct master_service *service); @@ -51,6 +57,14 @@ io_loop_stop(service->ioloop); } +static void +sig_state_changed(const siginfo_t *si ATTR_UNUSED, void *context) +{ + struct master_service *service = context; + + master_service_refresh_login_state(service); +} + static void master_service_verify_version(struct master_service *service) { if (service->version_string != NULL && @@ -261,6 +275,10 @@ lib_signals_ignore(SIGALRM, FALSE); lib_signals_set_handler(SIGINT, TRUE, sig_die, service); lib_signals_set_handler(SIGTERM, TRUE, sig_die, service); + if ((service->flags & MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE) != 0) { + lib_signals_set_handler(SIGUSR1, TRUE, + sig_state_changed, service); + } if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) { if (fstat(MASTER_STATUS_FD, &st) < 0 || !S_ISFIFO(st.st_mode)) @@ -475,6 +493,47 @@ } } +static void master_service_set_login_state(struct master_service *service, + enum master_login_state state) +{ + if (service->to_overflow_state != NULL) + timeout_remove(&service->to_overflow_state); + + switch (state) { + case MASTER_LOGIN_STATE_NONFULL: + service->call_avail_overflow = FALSE; + if (service->master_status.available_count > 0) + return; + + /* some processes should now be able to handle new connections, + although we can't. but there may be race conditions, so + make sure that we'll check again soon if the state has + changed to "full" without our knowledge. */ + service->to_overflow_state = + timeout_add(MASTER_SERVICE_STATE_CHECK_MSECS, + master_service_refresh_login_state, + service); + return; + case MASTER_LOGIN_STATE_FULL: + /* make sure we're listening for more connections */ + service->call_avail_overflow = TRUE; + master_service_io_listeners_add(service); + return; + } + i_error("Invalid master login state: %d", state); +} + +static void master_service_refresh_login_state(struct master_service *service) +{ + int ret; + + ret = lseek(MASTER_LOGIN_NOTIFY_FD, 0, SEEK_CUR); + if (ret < 0) + i_error("lseek(login notify fd) failed: %m"); + else + master_service_set_login_state(service, ret); +} + static void master_service_close_config_fd(struct master_service *service) { if (service->config_fd != -1) { @@ -493,6 +552,8 @@ io_listeners_remove(service); master_service_close_config_fd(service); + if (service->to_overflow_state != NULL) + timeout_remove(&service->to_overflow_state); if (service->io_status_error != NULL) io_remove(&service->io_status_error); if (service->io_status_write != NULL) @@ -523,8 +584,11 @@ /* we are full. stop listening for now, unless overflow callback destroys one of the existing connections */ if (service->call_avail_overflow && - service->avail_overflow_callback != NULL) + service->avail_overflow_callback != NULL) { + service->delay_status_updates = TRUE; service->avail_overflow_callback(); + service->delay_status_updates = FALSE; + } if (service->master_status.available_count == 0) { io_listeners_remove(service); @@ -631,7 +695,7 @@ { ssize_t ret; - if (service->master_status.pid == 0) + if (service->master_status.pid == 0 || service->delay_status_updates) return; /* closed */ ret = write(MASTER_STATUS_FD, &service->master_status,
--- a/src/lib-master/master-service.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lib-master/master-service.h Mon Oct 19 21:42:09 2009 -0400 @@ -17,7 +17,9 @@ /* Don't read settings by executing config binary */ MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS = 0x10, /* Don't read settings from environment */ - MASTER_SERVICE_FLAG_NO_ENV_SETTINGS = 0x20 + MASTER_SERVICE_FLAG_NO_ENV_SETTINGS = 0x20, + /* Use MASTER_LOGIN_NOTIFY_FD to track login overflow state */ + MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE = 0x40 }; struct master_service_connection {
--- a/src/lib-storage/mail-storage-service.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lib-storage/mail-storage-service.c Mon Oct 19 21:42:09 2009 -0400 @@ -120,7 +120,7 @@ for (i = 0; i < count && ret == 0; i++) { line = str[i]; if (strncmp(line, "system_groups_user=", 19) == 0) - *system_groups_user_r = line + 19; + *system_groups_user_r = t_strdup(line + 19); else T_BEGIN { if (strncmp(line, "mail=", 5) == 0) { line = t_strconcat("mail_location=", @@ -149,19 +149,15 @@ static int service_auth_userdb_lookup(struct auth_master_connection *conn, - struct setting_parser_context *set_parser, const char *service_name, const struct mail_storage_service_input *input, const struct mail_user_settings *user_set, - const char **user, const char **system_groups_user_r, + pool_t pool, const char **user, + const char *const **fields_r, const char **error_r) { struct auth_user_info info; - struct auth_user_reply reply; - const char *system_groups_user, *orig_user = *user; - const char *new_username, *const *fields; - unsigned int len; - pool_t pool; + const char *new_username; int ret; memset(&info, 0, sizeof(info)); @@ -169,32 +165,19 @@ info.local_ip = input->local_ip; info.remote_ip = input->remote_ip; - pool = pool_alloconly_create("userdb lookup", 1024); ret = auth_master_user_lookup(conn, *user, &info, pool, - &new_username, &fields); + &new_username, fields_r); if (ret > 0) { - auth_user_fields_parse(fields, pool, &reply); - len = reply.chroot == NULL ? 0 : strlen(reply.chroot); - - *user = t_strdup(new_username); - if (user_reply_handle(set_parser, user_set, &reply, - &system_groups_user, error_r) < 0) - ret = -1; - *system_groups_user_r = t_strdup(system_groups_user); - } else { - if (ret == 0) - *error_r = "unknown user"; - else - *error_r = "userdb lookup failed"; - *system_groups_user_r = NULL; - } - - if (ret > 0 && strcmp(*user, orig_user) != 0) { - if (mail_user_set_get_storage_set(user_set)->mail_debug) - i_debug("changed username to %s", *user); - } - - pool_unref(&pool); + if (strcmp(*user, new_username) != 0) { + if (mail_user_set_get_storage_set(user_set)->mail_debug) + i_debug("changed username to %s", new_username); + *user = t_strdup(new_username); + } + *user = new_username; + } else if (ret == 0) + *error_r = "unknown user"; + else + *error_r = "userdb lookup failed"; return ret; } @@ -515,13 +498,14 @@ struct mail_user *mail_user; struct auth_master_connection *conn; void **sets; - const char *user, *orig_user, *home, *system_groups_user, *error; + const char *user, *orig_user, *home, *error; + const char *system_groups_user = NULL, *const *userdb_fields = NULL; unsigned int len; bool userdb_lookup; + pool_t userdb_pool = NULL; io_loop_set_time_moved_callback(current_ioloop, mail_storage_service_time_moved); - master_service_init_finish(service); userdb_lookup = (flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0; mail_storage_service_init_settings(service, &input, set_roots, @@ -542,25 +526,36 @@ orig_user = user = input.username; conn = auth_master_init(user_set->auth_socket_path, mail_set->mail_debug); - if (service_auth_userdb_lookup(conn, service->set_parser, - service->name, &input, - user_set, &user, - &system_groups_user, - &error) <= 0) + userdb_pool = pool_alloconly_create("userdb lookup", 1024); + if (service_auth_userdb_lookup(conn, service->name, &input, + user_set, userdb_pool, &user, + &userdb_fields, &error) <= 0) i_fatal("%s", error); auth_master_deinit(&conn); input.username = user; /* set up logging again in case username changed */ mail_storage_service_init_log(service, &input); + } else if (input.userdb_fields != NULL) { + userdb_fields = input.userdb_fields; + userdb_pool = pool_alloconly_create("userdb fields", 1024); } + if (userdb_fields != NULL) { + struct auth_user_reply reply; + + auth_user_fields_parse(userdb_fields, userdb_pool, &reply); + if (user_reply_handle(service->set_parser, user_set, &reply, + &system_groups_user, &error) < 0) + i_fatal("%s", error); + } + if (userdb_pool != NULL) + pool_unref(&userdb_pool); /* variable strings are expanded in mail_user_init(), but we need the home sooner so do it separately here. */ home = user_expand_varstr(service, &input, user_set->mail_home); if (!userdb_lookup) { - system_groups_user = NULL; if (*home == '\0' && getenv("HOME") != NULL) { home = getenv("HOME"); set_keyval(service->set_parser, "mail_home", home); @@ -638,7 +633,6 @@ io_loop_set_time_moved_callback(current_ioloop, mail_storage_service_time_moved); - master_service_init_finish(service); ctx = i_new(struct mail_storage_service_multi_ctx, 1); ctx->service = service; @@ -676,6 +670,33 @@ return ctx->conn; } +static int multi_userdb_lookup(struct mail_storage_service_multi_ctx *ctx, + struct mail_storage_service_multi_user *user, + pool_t userdb_pool, const char **error_r) +{ + struct auth_user_reply reply; + const char *username, *system_groups_user, *const *userdb_fields; + int ret; + + username = user->input.username; + ret = service_auth_userdb_lookup(ctx->conn, ctx->service->name, + &user->input, user->user_set, + userdb_pool, &username, + &userdb_fields, error_r); + if (ret <= 0) + return ret; + + auth_user_fields_parse(userdb_fields, userdb_pool, &reply); + ret = user_reply_handle(ctx->service->set_parser, user->user_set, + &reply, &system_groups_user, error_r); + if (ret <= 0) + return ret; + + user->input.username = p_strdup(user->pool, username); + user->system_groups_user = p_strdup(user->pool, system_groups_user); + return 1; +} + int mail_storage_service_multi_lookup(struct mail_storage_service_multi_ctx *ctx, const struct mail_storage_service_input *input, pool_t pool, @@ -683,9 +704,9 @@ const char **error_r) { struct mail_storage_service_multi_user *user; - const char *orig_user, *username; void **sets; - int ret; + pool_t userdb_pool; + int ret = 1; user = p_new(pool, struct mail_storage_service_multi_user, 1); memset(user_r, 0, sizeof(user_r)); @@ -698,15 +719,9 @@ user->user_set = sets[1]; if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0) { - orig_user = username = user->input.username; - ret = service_auth_userdb_lookup(ctx->conn, user->set_parser, - ctx->service->name, input, - user->user_set, &username, - &user->system_groups_user, - error_r); - if (ret <= 0) - return ret; - user->input.username = p_strdup(pool, username); + userdb_pool = pool_alloconly_create("userdb lookup", 1024); + ret = multi_userdb_lookup(ctx, user, userdb_pool, error_r); + pool_unref(&userdb_pool); } *user_r = user; return 1;
--- a/src/lib-storage/mail-storage-service.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/lib-storage/mail-storage-service.h Mon Oct 19 21:42:09 2009 -0400 @@ -25,6 +25,8 @@ const char *service; const char *username; struct ip_addr local_ip, remote_ip; + + const char *const *userdb_fields; }; struct setting_parser_info;
--- a/src/login-common/client-common.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/login-common/client-common.c Mon Oct 19 21:42:09 2009 -0400 @@ -118,7 +118,7 @@ i_assert(client->authenticating); i_assert(client->refcount > 1); client->authenticating = FALSE; - master_auth_request_abort(master_service, client->master_tag); + master_auth_request_abort(master_auth, client->master_tag); client->refcount--; } else if (client->auth_request != NULL) { i_assert(client->authenticating);
--- a/src/login-common/common.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/login-common/common.h Mon Oct 19 21:42:09 2009 -0400 @@ -16,6 +16,7 @@ extern unsigned int login_default_port; extern struct auth_client *auth_client; +extern struct master_auth *master_auth; extern bool closing_down; extern int anvil_fd;
--- a/src/login-common/main.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/login-common/main.c Mon Oct 19 21:42:09 2009 -0400 @@ -19,6 +19,7 @@ #include <syslog.h> struct auth_client *auth_client; +struct master_auth *master_auth; bool closing_down; int anvil_fd = -1; @@ -139,10 +140,10 @@ auth_client = auth_client_init("auth", (unsigned int)getpid(), FALSE); auth_client_set_connect_notify(auth_client, auth_connect_notify, NULL); + master_auth = master_auth_init(master_service, login_protocol); clients_init(); login_proxy_init(); - master_auth_init(master_service); } static void main_deinit(void) @@ -153,23 +154,25 @@ if (auth_client != NULL) auth_client_deinit(&auth_client); clients_deinit(); + master_auth_deinit(&master_auth); if (anvil_fd != -1) { if (close(anvil_fd) < 0) i_error("close(anvil) failed: %m"); } - master_auth_deinit(master_service); } int main(int argc, char *argv[], char *envp[]) { + enum master_service_flags service_flags = + MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN | + MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE; const char *getopt_str; pool_t set_pool; int c; - master_service = master_service_init(login_process_name, - MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN, - argc, argv); + master_service = master_service_init(login_process_name, service_flags, + argc, argv); master_service_init_log(master_service, t_strconcat( login_process_name, ": ", NULL));
--- a/src/login-common/sasl-server.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/login-common/sasl-server.c Mon Oct 19 21:42:09 2009 -0400 @@ -114,6 +114,7 @@ req.auth_id = auth_client_request_get_id(request); req.local_ip = client->local_ip; req.remote_ip = client->ip; + req.client_pid = getpid(); cookie = auth_client_get_cookie(auth_client); if (hex_to_binary(cookie, buf) == 0 && buf->used == sizeof(req.cookie)) @@ -127,9 +128,8 @@ buffer_append(buf, data, size); req.data_size = buf->used; - client->master_tag = - master_auth_request(master_service, client->fd, &req, buf->data, - master_auth_callback, client); + master_auth_request(master_auth, client->fd, &req, buf->data, + master_auth_callback, client, &client->master_tag); } static bool anvil_has_too_many_connections(struct client *client)
--- a/src/master/Makefile.am Mon Oct 19 18:34:00 2009 -0400 +++ b/src/master/Makefile.am Mon Oct 19 21:42:09 2009 -0400 @@ -22,8 +22,6 @@ main.c \ master-settings.c \ service-anvil.c \ - service-auth-server.c \ - service-auth-source.c \ service-listen.c \ service-log.c \ service-monitor.c \ @@ -37,8 +35,6 @@ dup2-array.h \ master-settings.h \ service-anvil.h \ - service-auth-server.h \ - service-auth-source.h \ service-listen.h \ service-log.h \ service-monitor.h \
--- a/src/master/main.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/master/main.c Mon Oct 19 21:42:09 2009 -0400 @@ -200,42 +200,12 @@ i_error("unlink(%s) failed: %m", path); } -static bool -services_has_name(const struct master_settings *set, const char *name) -{ - struct service_settings *const *services; - unsigned int i, count; - - services = array_get(&set->services, &count); - for (i = 0; i < count; i++) { - if (strcmp(services[i]->name, name) == 0) - return TRUE; - } - return FALSE; -} - -static bool services_have_auth_destinations(const struct master_settings *set) -{ - struct service_settings *const *services; - unsigned int i, count; - - services = array_get(&set->services, &count); - for (i = 0; i < count; i++) { - if (strcmp(services[i]->type, "auth-source") == 0) { - if (services_has_name(set, services[i]->auth_dest_service)) - return TRUE; - } - } - return FALSE; -} - static void auth_warning_print(const struct master_settings *set) { struct stat st; auth_success_written = stat(AUTH_SUCCESS_PATH, &st) == 0; - if (!auth_success_written && !set->auth_debug && - services_have_auth_destinations(set)) { + if (!auth_success_written && !set->auth_debug) { fprintf(stderr, "If you have trouble with authentication failures,\n" "enable auth_debug setting. See http://wiki.dovecot.org/WhyDoesItNotWork\n" @@ -334,8 +304,7 @@ if (services->config->process_avail == 0) { /* we can't reload config if there's no config process. */ - if (service_process_create(services->config, - NULL, NULL) == NULL) { + if (service_process_create(services->config) == NULL) { i_error("Can't reload configuration because " "we couldn't create a config process"); return;
--- a/src/master/master-settings.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/master/master-settings.c Mon Oct 19 21:42:09 2009 -0400 @@ -4,6 +4,7 @@ #include "array.h" #include "env-util.h" #include "istream.h" +#include "network.h" #include "str.h" #include "mkdir-parents.h" #include "safe-mkdir.h" @@ -96,7 +97,6 @@ DEF(SET_STR, privileged_group), DEF(SET_STR, extra_groups), DEF(SET_STR, chroot), - DEF(SET_STR, auth_dest_service), DEF(SET_BOOL, drop_priv_before_exec), @@ -128,7 +128,6 @@ MEMBER(privileged_group) "", MEMBER(extra_groups) "", MEMBER(chroot) "", - MEMBER(auth_dest_service) "", MEMBER(drop_priv_before_exec) FALSE, @@ -240,6 +239,27 @@ } } +static bool master_settings_parse_type(struct service_settings *set, + const char **error_r) +{ + if (*set->type == '\0') + set->parsed_type = SERVICE_TYPE_UNKNOWN; + else if (strcmp(set->type, "log") == 0) + set->parsed_type = SERVICE_TYPE_LOG; + else if (strcmp(set->type, "config") == 0) + set->parsed_type = SERVICE_TYPE_CONFIG; + else if (strcmp(set->type, "anvil") == 0) + set->parsed_type = SERVICE_TYPE_ANVIL; + else if (strcmp(set->type, "login") == 0) + set->parsed_type = SERVICE_TYPE_LOGIN; + else { + *error_r = t_strconcat("Unknown service type: ", + set->type, NULL); + return FALSE; + } + return TRUE; +} + static bool master_settings_verify(void *_set, pool_t pool, const char **error_r) { @@ -274,16 +294,8 @@ "Service #%d is missing name", i); return FALSE; } - if (*service->type != '\0' && - strcmp(service->type, "log") != 0 && - strcmp(service->type, "config") != 0 && - strcmp(service->type, "anvil") != 0 && - strcmp(service->type, "auth") != 0 && - strcmp(service->type, "auth-source") != 0) { - *error_r = t_strconcat("Unknown service type: ", - service->type, NULL); + if (!master_settings_parse_type(service, error_r)) return FALSE; - } for (j = 0; j < i; j++) { if (strcmp(service->name, services[j]->name) == 0) { *error_r = t_strdup_printf( @@ -342,8 +354,7 @@ services = array_get(&set->services, &count); for (i = 0; i < count; i++) { - if (strcmp(services[i]->type, "auth-source") == 0 && - strstr(services[i]->name, "-login") != NULL) { + if (strcmp(services[i]->type, "login") == 0) { if (strstr(services[i]->executable, " -D") != NULL) cores = TRUE; (void)get_uidgid(services[i]->user, &uid, gid_r, &error); @@ -364,8 +375,7 @@ services = array_get(&set->services, &count); for (i = 0; i < count; i++) { - if (strcmp(services[i]->type, "auth") == 0 && - array_is_created(&services[i]->unix_listeners)) { + if (array_is_created(&services[i]->unix_listeners)) { u = array_get(&services[i]->unix_listeners, &count2); for (j = 0; j < count2; j++) { if (strncmp(u[j]->path, dir, strlen(dir)) == 0)
--- a/src/master/master-settings.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/master/master-settings.h Mon Oct 19 21:42:09 2009 -0400 @@ -1,6 +1,16 @@ #ifndef MASTER_SETTINGS_H #define MASTER_SETTINGS_H +/* <settings checks> */ +enum service_type { + SERVICE_TYPE_UNKNOWN, + SERVICE_TYPE_LOG, + SERVICE_TYPE_ANVIL, + SERVICE_TYPE_CONFIG, + SERVICE_TYPE_LOGIN +}; +/* </settings checks> */ + struct file_listener_settings { const char *path; unsigned int mode; @@ -27,7 +37,6 @@ const char *privileged_group; const char *extra_groups; const char *chroot; - const char *auth_dest_service; bool drop_priv_before_exec; @@ -40,6 +49,8 @@ ARRAY_TYPE(file_listener_settings) unix_listeners; ARRAY_TYPE(file_listener_settings) fifo_listeners; ARRAY_DEFINE(inet_listeners, struct inet_listener_settings *); + + enum service_type parsed_type; }; struct master_settings {
--- a/src/master/service-auth-server.c Mon Oct 19 18:34:00 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,269 +0,0 @@ -/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "ioloop.h" -#include "istream.h" -#include "ostream.h" -#include "hash.h" -#include "service.h" -#include "service-process.h" -#include "service-auth-server.h" -#include "service-auth-source.h" -#include "../auth/auth-master-interface.h" - -#include <stdlib.h> -#include <unistd.h> - -#define AUTH_MAX_INBUF_SIZE 8192 - -static void -service_process_auth_request_free(struct service_process_auth_request *request) -{ - if (request->fd != -1) { - if (close(request->fd) < 0) - i_error("close(auth request fd) failed: %m"); - } - i_free(request); -} - -static void -service_process_auth_server_close(struct service_process_auth_server *process) -{ - struct hash_iterate_context *iter; - void *key, *value; - - if (process->auth_requests != NULL) { - iter = hash_table_iterate_init(process->auth_requests); - while (hash_table_iterate(iter, &key, &value)) { - struct service_process_auth_request *request = value; - - service_process_unref(&request->process->process); - service_process_auth_request_free(request); - } - hash_table_iterate_deinit(&iter); - hash_table_destroy(&process->auth_requests); - } - - if (process->auth_input != NULL) - i_stream_close(process->auth_input); - if (process->auth_output != NULL) - o_stream_close(process->auth_output); - - if (process->io_auth != NULL) - io_remove(&process->io_auth); - if (process->auth_fd != -1) { - if (close(process->auth_fd) < 0) - i_error("close(auth_fd) failed: %m"); - process->auth_fd = -1; - } -} - -static struct service_process_auth_request * -auth_process_lookup_request(struct service_process_auth_server *process, - unsigned int id) -{ - struct service_process_auth_request *request; - - request = hash_table_lookup(process->auth_requests, POINTER_CAST(id)); - if (request == NULL) { - service_error(process->process.service, - "authentication service %s " - "sent reply with unknown ID %u", - dec2str(process->process.pid), id); - return NULL; - } - - hash_table_remove(process->auth_requests, POINTER_CAST(id)); - if (!service_process_unref(&request->process->process)) { - /* process already died. */ - service_process_auth_request_free(request); - return NULL; - } - - return request; -} - -static struct service * -auth_process_get_dest_service(struct service_process_auth_source *process) -{ - struct service *service = process->process.service; - - if (!service->list->destroyed) - return service->auth_dest_service; - - service = service_lookup(services, service->set->auth_dest_service); - if (service == NULL) { - i_warning("service(%s): Lost destination service %s", - service->set->name, service->set->auth_dest_service); - } - return service; -} - -static int -auth_process_input_user(struct service_process_auth_server *process, const char *args) -{ - struct service_process_auth_request *request; - const char *const *list; - enum master_auth_status status; - unsigned int id; - - /* <id> <userid> [..] */ - - list = t_strsplit(args, "\t"); - if (list[0] == NULL || list[1] == NULL) { - i_error("BUG: Auth process %s sent corrupted USER line", - dec2str(process->process.pid)); - return FALSE; - } - id = (unsigned int)strtoul(list[0], NULL, 10); - - request = auth_process_lookup_request(process, id); - if (request != NULL) { - struct service *dest_service; - struct service_process *dest_process; - - dest_service = auth_process_get_dest_service(request->process); - dest_process = dest_service == NULL ? NULL : - service_process_create(dest_service, list + 1, request); - status = dest_process != NULL ? - MASTER_AUTH_STATUS_OK : - MASTER_AUTH_STATUS_INTERNAL_ERROR; - service_process_auth_source_send_reply(request->process, - request->process_tag, - status); - service_process_auth_request_free(request); - } - return TRUE; -} - -static int -auth_process_input_notfound(struct service_process_auth_server *process, - const char *args) -{ - struct service_process_auth_request *request; - unsigned int id; - - id = (unsigned int)strtoul(args, NULL, 10); - - request = auth_process_lookup_request(process, id); - if (request != NULL) { - service_process_auth_source_send_reply(request->process, - request->process_tag, - MASTER_AUTH_STATUS_INTERNAL_ERROR); - service_process_auth_request_free(request); - } - return TRUE; -} - -static int -auth_process_input_fail(struct service_process_auth_server *process, - const char *args) -{ - struct service_process_auth_request *request; - const char *error; - unsigned int id; - - error = strchr(args, '\t'); - if (error != NULL) - error++; - - id = (unsigned int)strtoul(args, NULL, 10); - - request = auth_process_lookup_request(process, id); - if (request != NULL) { - service_process_auth_source_send_reply(request->process, - request->process_tag, - MASTER_AUTH_STATUS_INTERNAL_ERROR); - service_process_auth_request_free(request); - } - return TRUE; -} - -static void -service_process_auth_server_input(struct service_process_auth_server *process) -{ - const char *line; - int ret; - - switch (i_stream_read(process->auth_input)) { - case 0: - return; - case -1: - /* disconnected */ - service_process_auth_server_close(process); - return; - case -2: - /* buffer full */ - service_error(process->process.service, - "authentication server process %s " - "sent us too long line", - dec2str(process->process.pid)); - service_process_auth_server_close(process); - return; - } - - if (!process->auth_version_received) { - line = i_stream_next_line(process->auth_input); - if (line == NULL) - return; - - /* make sure the major version matches */ - if (strncmp(line, "VERSION\t", 8) != 0 || - atoi(t_strcut(line + 8, '\t')) != - AUTH_MASTER_PROTOCOL_MAJOR_VERSION) { - service_error(process->process.service, - "authentication server process %s " - "not compatible with master process " - "(mixed old and new binaries?)", - dec2str(process->process.pid)); - service_process_auth_server_close(process); - return; - } - process->auth_version_received = TRUE; - } - - while ((line = i_stream_next_line(process->auth_input)) != NULL) { - if (strncmp(line, "USER\t", 5) == 0) - ret = auth_process_input_user(process, line + 5); - else if (strncmp(line, "NOTFOUND\t", 9) == 0) - ret = auth_process_input_notfound(process, line + 9); - else if (strncmp(line, "FAIL\t", 5) == 0) - ret = auth_process_input_fail(process, line + 5); - else - ret = TRUE; - - if (!ret) { - service_process_auth_server_close(process); - break; - } - } -} - -void service_process_auth_server_init(struct service_process *_process, int fd) -{ - struct service_process_auth_server *process = - (struct service_process_auth_server *)_process; - - i_assert(_process->service->type == SERVICE_TYPE_AUTH_SERVER); - - process->auth_fd = fd; - process->auth_input = i_stream_create_fd(process->auth_fd, - AUTH_MAX_INBUF_SIZE, FALSE); - process->auth_output = - o_stream_create_fd(fd, (size_t)-1, FALSE); - process->io_auth = - io_add(process->auth_fd, IO_READ, - service_process_auth_server_input, process); - process->auth_requests = - hash_table_create(default_pool, default_pool, 0, NULL, NULL); -} - -void service_process_auth_server_deinit(struct service_process *_process) -{ - struct service_process_auth_server *process = - (struct service_process_auth_server *)_process; - - i_assert(_process->service->type == SERVICE_TYPE_AUTH_SERVER); - - service_process_auth_server_close(process); -}
--- a/src/master/service-auth-server.h Mon Oct 19 18:34:00 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -#ifndef SERVICE_AUTH_SERVER_H -#define SERVICE_AUTH_SERVER_H - -struct service_process; - -void service_process_auth_server_init(struct service_process *process, int fd); -void service_process_auth_server_deinit(struct service_process *process); - -#endif
--- a/src/master/service-auth-source.c Mon Oct 19 18:34:00 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,308 +0,0 @@ -/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "hash.h" -#include "str.h" -#include "hex-binary.h" -#include "ioloop.h" -#include "ostream.h" -#include "fdpass.h" -#include "fd-close-on-exec.h" -#include "../auth/auth-master-interface.h" -#include "service.h" -#include "service-process.h" -#include "service-auth-source.h" - -#include <unistd.h> -#include <sys/stat.h> - -#define AUTH_SOURCE_OUTBUF_THROTTLE_THRESHOLD (1024 - 256) -#define AUTH_SERVER_MAX_OUTBUF_SIZE (1024*64) -#define AUTH_BUSY_LOG_INTERVAL 30 - -static void -service_process_auth_source_input(struct service_process_auth_source *process); - -static void -service_process_auth_source_close(struct service_process_auth_source *process) -{ - if (process->auth_output != NULL) - o_stream_close(process->auth_output); - if (process->io_auth != NULL) - io_remove(&process->io_auth); - if (process->auth_fd != -1) { - if (close(process->auth_fd) < 0) - i_error("close(auth_fd) failed: %m"); - process->auth_fd = -1; - } -} - -static int -process_auth_source_output(struct service_process_auth_source *process) -{ - int ret; - - if ((ret = o_stream_flush(process->auth_output)) < 0) - return -1; - - if (process->io_auth == NULL && - o_stream_get_buffer_used_size(process->auth_output) < - AUTH_SOURCE_OUTBUF_THROTTLE_THRESHOLD) { - /* enable parsing input again */ - o_stream_unset_flush_callback(process->auth_output); - process->io_auth = io_add(process->auth_fd, IO_READ, - service_process_auth_source_input, - process); - } - return ret; -} - -void service_process_auth_source_send_reply(struct service_process_auth_source *process, - unsigned int tag, - enum master_auth_status status) -{ - struct master_auth_reply reply; - - if (o_stream_get_buffer_used_size(process->auth_output) >= - AUTH_SOURCE_OUTBUF_THROTTLE_THRESHOLD) { - /* not reading our output. stop parsing input until it will. */ - if (process->io_auth != NULL) { - io_remove(&process->io_auth); - - o_stream_set_flush_callback(process->auth_output, - process_auth_source_output, - process); - } - } - - /* Reply to login process */ - memset(&reply, 0, sizeof(reply)); - reply.tag = tag; - reply.status = status; - - o_stream_send(process->auth_output, &reply, sizeof(reply)); -} - -static unsigned int -auth_server_send_request(struct service_process_auth_server *server_process, - struct service_process_auth_source *source_process, - unsigned int auth_id, - const uint8_t cookie[MASTER_AUTH_COOKIE_SIZE]) -{ - unsigned int tag = 0; - string_t *str; - - while (tag == 0) - tag = ++server_process->auth_tag_counter; - - str = t_str_new(256); - if (!server_process->auth_version_sent) { - server_process->auth_version_sent = TRUE; - str_printfa(str, "VERSION\t%u\t%u\n", - AUTH_MASTER_PROTOCOL_MAJOR_VERSION, - AUTH_MASTER_PROTOCOL_MINOR_VERSION); - o_stream_send(server_process->auth_output, - str_data(str), str_len(str)); - str_truncate(str, 0); - } - - str_printfa(str, "REQUEST\t%u\t%s\t%u\t", - tag, dec2str(source_process->process.pid), auth_id); - binary_to_hex_append(str, cookie, MASTER_AUTH_COOKIE_SIZE); - str_append_c(str, '\n'); - - o_stream_send(server_process->auth_output, str_data(str), str_len(str)); - return tag; -} - -static int -auth_read_request(struct service_process_auth_source *process, - struct master_auth_request *req, - unsigned char data[MASTER_AUTH_MAX_DATA_SIZE], - int *client_fd_r) -{ - struct service *service = process->process.service; - struct stat st; - ssize_t ret; - - *client_fd_r = -1; - - ret = fd_read(process->auth_fd, req, sizeof(*req), client_fd_r); - if (ret != sizeof(*req)) { - if (ret == 0) { - /* disconnected */ - } else if (ret > 0) { - /* request wasn't fully read */ - service_error(service, "fd_read() partial input (%d/%d)", - (int)ret, (int)sizeof(*req)); - } else { - if (errno == EAGAIN) - return 0; - - service_error(service, "fd_read() failed: %m"); - } - return -1; - } - - if (req->data_size != 0) { - if (req->data_size > MASTER_AUTH_MAX_DATA_SIZE) { - service_error(service, "Too large auth data_size sent"); - return -1; - } - /* @UNSAFE */ - ret = read(process->auth_fd, data, req->data_size); - if (ret != (ssize_t)req->data_size) { - if (ret == 0) { - /* disconnected */ - } else if (ret > 0) { - /* request wasn't fully read */ - service_error(service, - "Data read partially %d/%u", - (int)ret, req->data_size); - } else { - service_error(service, "read(data) failed: %m"); - } - return -1; - } - } - - if (*client_fd_r == -1) { - service_error(service, "Auth request missing a file descriptor"); - return -1; - } - - if (fstat(*client_fd_r, &st) < 0) { - service_error(service, "fstat(auth dest fd) failed: %m"); - return -1; - } - if (st.st_ino != req->ino) { - service_error(service, "Auth request inode mismatch: %s != %s", - dec2str(st.st_ino), dec2str(req->ino)); - return -1; - } - return 1; -} - -static void -service_process_auth_source_input(struct service_process_auth_source *process) -{ - struct service *service = process->process.service; - struct service_process_auth_server *auth_process; - struct service_process_auth_request *auth_req; - struct master_auth_request req; - unsigned char data[MASTER_AUTH_MAX_DATA_SIZE]; - unsigned int tag; - ssize_t ret; - int client_fd; - - ret = auth_read_request(process, &req, data, &client_fd); - if (ret == 0) - return; - if (ret < 0) { - if (client_fd != -1) { - if (close(client_fd) < 0) - i_error("login: close(mail client) failed: %m"); - } - service_process_auth_source_close(process); - return; - } - fd_close_on_exec(client_fd, TRUE); - - /* we have a request. check its validity. */ - auth_process = hash_table_lookup(service_pids, &req.auth_pid); - if (auth_process == NULL) { - service_error(service, "authentication request for unknown " - "auth server PID %s", dec2str(req.auth_pid)); - service_process_auth_source_send_reply(process, req.tag, - MASTER_AUTH_STATUS_INTERNAL_ERROR); - (void)close(client_fd); - return; - } - - if (o_stream_get_buffer_used_size(auth_process->auth_output) >= - AUTH_SERVER_MAX_OUTBUF_SIZE) { - if (auth_process->auth_busy_stamp <= - ioloop_time - AUTH_BUSY_LOG_INTERVAL) { - i_warning("service(%s): authentication server PID " - "%s too busy", - auth_process->process.service->set->name, - dec2str(req.auth_pid)); - auth_process->auth_busy_stamp = ioloop_time; - } - service_process_auth_source_send_reply(process, req.tag, - MASTER_AUTH_STATUS_INTERNAL_ERROR); - (void)close(client_fd); - return; - } - - /* ok, we have a non-busy authentication server. - send a request to it. */ - auth_req = i_malloc(sizeof(*auth_req) + req.data_size); - auth_req->process = process; - auth_req->process_tag = req.tag; - auth_req->fd = client_fd; - auth_req->local_ip = req.local_ip; - auth_req->remote_ip = req.remote_ip; - auth_req->data_size = req.data_size; - memcpy(auth_req->data, data, req.data_size); - - tag = auth_server_send_request(auth_process, process, req.auth_id, - req.cookie); - - service_process_ref(&process->process); - hash_table_insert(auth_process->auth_requests, - POINTER_CAST(tag), auth_req); -} - -void service_process_auth_source_init(struct service_process *_process, int fd) -{ - struct service_process_auth_source *process = - (struct service_process_auth_source *)_process; - - i_assert(_process->service->type == SERVICE_TYPE_AUTH_SOURCE); - - process->auth_fd = fd; - process->io_auth = io_add(process->auth_fd, IO_READ, - service_process_auth_source_input, process); - process->auth_output = - o_stream_create_fd(fd, (size_t)-1, FALSE); -} - -void service_process_auth_source_deinit(struct service_process *_process) -{ - struct service_process_auth_source *process = - (struct service_process_auth_source *)_process; - - i_assert(_process->service->type == SERVICE_TYPE_AUTH_SOURCE); - - service_process_auth_source_close(process); -} - -void service_processes_auth_source_notify(struct service *service, - bool all_processes_created) -{ - struct hash_iterate_context *iter; - void *key, *value; - enum master_auth_status status; - - i_assert(service->type == SERVICE_TYPE_AUTH_SOURCE); - - status = all_processes_created ? 1 : 0; - - iter = hash_table_iterate_init(service_pids); - while (hash_table_iterate(iter, &key, &value)) { - struct service_process *process = value; - struct service_process_auth_source *auth_process; - - if (process->service != service) - continue; - - auth_process = (struct service_process_auth_source *)process; - if (auth_process->last_notify_status != (int)status) { - auth_process->last_notify_status = (int)status; - service_process_auth_source_send_reply(auth_process, - 0, status); - } - } - hash_table_iterate_deinit(&iter); -}
--- a/src/master/service-auth-source.h Mon Oct 19 18:34:00 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -#ifndef SERVICE_AUTH_SOURCE_H -#define SERVICE_AUTH_SOURCE_H - -struct service_process; -struct service_process_auth_source; - -void service_process_auth_source_init(struct service_process *process, int fd); -void service_process_auth_source_deinit(struct service_process *process); - -void service_process_auth_source_send_reply(struct service_process_auth_source *process, - unsigned int tag, - enum master_auth_status status); - -void service_processes_auth_source_notify(struct service *service, - bool all_processes_created); - -#endif
--- a/src/master/service-monitor.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/master/service-monitor.c Mon Oct 19 21:42:09 2009 -0400 @@ -5,8 +5,9 @@ #include "ioloop.h" #include "fd-close-on-exec.h" #include "hash.h" +#include "str.h" +#include "safe-mkstemp.h" #include "service.h" -#include "service-auth-source.h" #include "service-process.h" #include "service-process-notify.h" #include "service-anvil.h" @@ -59,6 +60,11 @@ i_assert(service->process_avail > 0); service->process_avail--; + if (service->type == SERVICE_TYPE_LOGIN && + service->process_avail == 0 && + service->process_count == service->process_limit) + service_login_notify(service, TRUE); + /* we may need to start more */ service_monitor_start_extra_avail(service); service_monitor_listen_start(service); @@ -90,6 +96,8 @@ process); } } + if (service->type == SERVICE_TYPE_LOGIN) + service_login_notify(service, FALSE); } static void service_status_input(struct service *service) @@ -98,6 +106,7 @@ struct service_process *process; ssize_t ret; + status.pid = 0; ret = read(service->status_fd[0], &status, sizeof(status)); switch (ret) { case 0: @@ -177,11 +186,11 @@ service->listen_pending = TRUE; service_monitor_listen_stop(service); - if (service->type == SERVICE_TYPE_AUTH_SOURCE) { + if (service->type == SERVICE_TYPE_LOGIN) { /* reached process limit, notify processes that they need to start killing existing connections if they reach connection limit */ - service_processes_auth_source_notify(service, TRUE); + service_login_notify(service, TRUE); } } @@ -197,7 +206,7 @@ } /* create a child process and let it accept() this connection */ - if (service_process_create(service, NULL, NULL) == NULL) + if (service_process_create(service) == NULL) service_monitor_throttle(service); else service_monitor_listen_stop(service); @@ -215,7 +224,7 @@ count = service->process_limit - service->process_count; for (i = 0; i < count; i++) { - if (service_process_create(service, NULL, NULL) == NULL) { + if (service_process_create(service) == NULL) { service_monitor_throttle(service); break; } @@ -263,6 +272,38 @@ service->listening = FALSE; } +static int service_login_create_notify_fd(struct service *service) +{ + int fd; + + if (service->login_notify_fd != -1) + return 0; + + T_BEGIN { + string_t *prefix = t_str_new(128); + const char *path; + + str_append(prefix, "/tmp/dovecot-master"); + + fd = safe_mkstemp(prefix, 0600, (uid_t)-1, (gid_t)-1); + path = str_c(prefix); + + if (fd == -1) { + service_error(service, "safe_mkstemp(%s) failed: %m", + path); + } else if (unlink(path) < 0) { + service_error(service, "unlink(%s) failed: %m", path); + } else { + fd_close_on_exec(fd, TRUE); + service->login_notify_fd = fd; + } + } T_END; + + if (fd != service->login_notify_fd) + (void)close(fd); + return fd == -1 ? -1 : 0; +} + void services_monitor_start(struct service_list *service_list) { struct service *const *services; @@ -273,6 +314,10 @@ services = array_get(&service_list->services, &count); for (i = 0; i < count; i++) { + if (services[i]->type == SERVICE_TYPE_LOGIN) { + if (service_login_create_notify_fd(services[i]) < 0) + continue; + } if (services[i]->status_fd[0] == -1) { /* we haven't yet created status pipe */ if (pipe(services[i]->status_fd) < 0) { @@ -289,14 +334,13 @@ io_add(services[i]->status_fd[0], IO_READ, service_status_input, services[i]); } - if (services[i]->status_fd[0] != -1) { service_monitor_start_extra_avail(services[i]); service_monitor_listen_start(services[i]); } } - if (service_process_create(service_list->log, NULL, NULL) != NULL) + if (service_process_create(service_list->log) != NULL) service_monitor_listen_stop(service_list->log); } @@ -316,6 +360,15 @@ service->status_fd[i] = -1; } } + if (service->login_notify_fd != -1) { + if (close(service->login_notify_fd) < 0) { + service_error(service, + "close(login notify fd) failed: %m"); + } + service->login_notify_fd = -1; + } + if (service->to_login_notify != NULL) + timeout_remove(&service->to_login_notify); service_monitor_listen_stop(service); if (service->to_throttle != NULL)
--- a/src/master/service-process.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/master/service-process.c Mon Oct 19 21:42:09 2009 -0400 @@ -23,8 +23,6 @@ #include "service.h" #include "service-anvil.h" #include "service-log.h" -#include "service-auth-server.h" -#include "service-auth-source.h" #include "service-process-notify.h" #include "service-process.h" @@ -44,8 +42,7 @@ #define CHDIR_WARN_SECS 10 static void -service_dup_fds(struct service *service, int auth_fd, int std_fd, - bool give_anvil_fd) +service_dup_fds(struct service *service) { struct service_listener *const *listeners; ARRAY_TYPE(dup2) dups; @@ -105,33 +102,14 @@ } } - if (!give_anvil_fd) - dup2_append(&dups, null_fd, MASTER_ANVIL_FD); - else { - dup2_append(&dups, service->list->blocking_anvil_fd[1], - MASTER_ANVIL_FD); + if (service->login_notify_fd != -1) { + dup2_append(&dups, service->login_notify_fd, + MASTER_LOGIN_NOTIFY_FD); } + dup2_append(&dups, service->list->blocking_anvil_fd[1], + MASTER_ANVIL_FD); dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD); - switch (service->type) { - case SERVICE_TYPE_AUTH_SOURCE: - case SERVICE_TYPE_AUTH_SERVER: - i_assert(auth_fd != -1); - dup2_append(&dups, auth_fd, MASTER_AUTH_FD); - env_put(t_strdup_printf("MASTER_AUTH_FD=%d", MASTER_AUTH_FD)); - break; - default: - i_assert(auth_fd == -1); - dup2_append(&dups, null_fd, MASTER_AUTH_FD); - break; - } - - if (std_fd != -1) { - dup2_append(&dups, std_fd, STDIN_FILENO); - dup2_append(&dups, std_fd, STDOUT_FILENO); - env_put("LOGGED_IN=1"); - } - if (service->type != SERVICE_TYPE_LOG) { /* set log file to stderr. dup2() here immediately so that we can set up logging to it without causing any log messages @@ -158,194 +136,11 @@ } static void -validate_uid_gid(struct master_settings *set, - uid_t uid, gid_t gid, const char *user, - const struct service_process_auth_request *request) -{ - struct service_process *request_process = - request == NULL ? NULL : &request->process->process; - - if (uid == 0) { - i_fatal("User %s not allowed to log in using UNIX UID 0 " - "(root logins are never allowed)", user); - } - - if (request != NULL && request_process->service->uid == uid && - master_uid != uid) { - struct passwd *pw; - - pw = getpwuid(uid); - i_fatal("User %s not allowed to log in using %s's " - "UNIX UID %s%s (see http://wiki.dovecot.org/UserIds)", - user, request_process->service->set->name, - dec2str(uid), pw == NULL ? "" : - t_strdup_printf("(%s)", pw->pw_name)); - } - - if (uid < (uid_t)set->first_valid_uid || - (set->last_valid_uid != 0 && uid > (uid_t)set->last_valid_uid)) { - struct passwd *pw; - bool low = uid < (uid_t)set->first_valid_uid; - - pw = getpwuid(uid); - i_fatal("User %s not allowed to log in using too %s " - "UNIX UID %s%s (see %s in config file)", - user, low ? "low" : "high", - dec2str(uid), pw == NULL ? "" : - t_strdup_printf("(%s)", pw->pw_name), - low ? "first_valid_uid" : "last_valid_uid"); - } - - if (gid < (gid_t)set->first_valid_gid || - (set->last_valid_gid != 0 && gid > (gid_t)set->last_valid_gid)) { - struct group *gr; - bool low = gid < (gid_t)set->first_valid_gid; - - gr = getgrgid(gid); - i_fatal("User %s not allowed to log in using too %s primary " - "UNIX group ID %s%s (see %s in config file)", - user, low ? "low" : "high", - dec2str(gid), gr == NULL ? "" : - t_strdup_printf("(%s)", gr->gr_name), - low ? "first_valid_gid" : "last_valid_gid"); - } -} - -static void auth_args_apply(const char *const *args, - struct restrict_access_settings *rset, - const char **home) +drop_privileges(struct service *service) { - const char *key, *value; - string_t *expanded_vars; - - expanded_vars = t_str_new(128); - str_append(expanded_vars, "VARS_EXPANDED="); - for (; *args != NULL; args++) { - if (strncmp(*args, "uid=", 4) == 0) - rset->uid = (uid_t)strtoul(*args + 4, NULL, 10); - else if (strncmp(*args, "gid=", 4) == 0) - rset->gid = (gid_t)strtoul(*args + 4, NULL, 10); - else if (strncmp(*args, "home=", 5) == 0) { - *home = *args + 5; - env_put(t_strconcat("HOME=", *args + 5, NULL)); - } else if (strncmp(*args, "chroot=", 7) == 0) - rset->chroot_dir = *args + 7; - else if (strncmp(*args, "system_groups_user=", 19) == 0) - rset->system_groups_user = *args + 19; - else if (strncmp(*args, "mail_access_groups=", 19) == 0) { - rset->extra_groups = - rset->extra_groups == NULL ? *args + 19 : - t_strconcat(*args + 19, ",", - rset->extra_groups, NULL); - } else { - /* unknown, set as environment */ - value = strchr(*args, '='); - if (value == NULL) { - /* boolean */ - key = *args; - value = "=1"; - } else { - key = t_strdup_until(*args, value); - if (strcmp(key, "mail") == 0) { - /* FIXME: kind of ugly to have it - here.. */ - key = "mail_location"; - } - } - str_append(expanded_vars, key); - str_append_c(expanded_vars, ' '); - env_put(t_strconcat(t_str_ucase(key), value, NULL)); - } - } - env_put(str_c(expanded_vars)); -} - -static void auth_success_write(void) -{ - int fd; - - if (auth_success_written) - return; - - fd = creat(AUTH_SUCCESS_PATH, 0666); - if (fd == -1) - i_error("creat(%s) failed: %m", AUTH_SUCCESS_PATH); - else - (void)close(fd); - auth_success_written = TRUE; -} - -static void chdir_to_home(const struct restrict_access_settings *rset, - const char *user, const char *home) -{ - unsigned int left; - int ret, chdir_errno; - - if (*home != '/') { - i_fatal("user %s: Relative home directory paths not supported: " - "%s", user, home); - } - - /* if home directory is NFS-mounted, we might not have access to it as - root. Change the effective UID and GID temporarily to make it - work. */ - if (rset->uid != master_uid) { - if (setegid(rset->gid) < 0) - i_fatal("setegid(%s) failed: %m", dec2str(rset->gid)); - if (seteuid(rset->uid) < 0) - i_fatal("seteuid(%s) failed: %m", dec2str(rset->uid)); - } - - alarm(CHDIR_TIMEOUT); - ret = chdir(home); - chdir_errno = errno; - if ((left = alarm(0)) < CHDIR_TIMEOUT - CHDIR_WARN_SECS) { - i_warning("user %s: chdir(%s) blocked for %u secs", - user, home, CHDIR_TIMEOUT - left); - } - - errno = chdir_errno; - if (ret == 0) { - /* chdir succeeded */ - } else if ((errno == ENOENT || errno == ENOTDIR || errno == EINTR) && - rset->chroot_dir == NULL) { - /* Not chrooted, fallback to using /tmp. - - ENOENT: No home directory yet, but it might be automatically - created by the service process, so don't complain. - ENOTDIR: This check is mainly for /dev/null home directory. - EINTR: chdir() timed out. */ - } else if (errno == EACCES) { - i_fatal("user %s: %s", user, eacces_error_get("chdir", home)); - } else { - i_fatal("user %s: chdir(%s) failed with uid %s: %m", - user, home, dec2str(rset->uid)); - } - /* Change UID back. No need to change GID back, it doesn't - really matter. */ - if (rset->uid != master_uid && seteuid(master_uid) < 0) - i_fatal("seteuid(%s) failed: %m", dec2str(master_uid)); - - if (ret < 0) { - /* We still have to change to some directory where we have - rx-access. /tmp should exist everywhere. */ - if (chdir("/tmp") < 0) - i_fatal("chdir(/tmp) failed: %m"); - } -} - -static void -drop_privileges(struct service *service, const char *const *auth_args, - const struct service_process_auth_request *request) -{ - struct master_settings *master_set = service->set->master_set; struct restrict_access_settings rset; - const char *user, *home = NULL; bool disallow_root; - if (auth_args != NULL && service->set->master_set->mail_debug) - env_put("DEBUG=1"); - if (service->vsz_limit != 0) restrict_process_size(service->vsz_limit, -1U); @@ -357,31 +152,9 @@ service->set->chroot; rset.extra_groups = service->extra_gids; - if (auth_args == NULL) { - /* non-authenticating service. don't use *_valid_gid checks */ - } else { - i_assert(auth_args[0] != NULL); - - rset.first_valid_gid = master_set->first_valid_gid; - rset.last_valid_gid = master_set->last_valid_gid; - - user = auth_args[0]; - env_put(t_strconcat("USER=", user, NULL)); - - auth_success_write(); - auth_args_apply(auth_args + 1, &rset, &home); - - validate_uid_gid(master_set, rset.uid, rset.gid, user, - request); - } - - if (home != NULL) - chdir_to_home(&rset, user, home); - if (service->set->drop_priv_before_exec) { - disallow_root = service->type == SERVICE_TYPE_AUTH_SERVER || - service->type == SERVICE_TYPE_AUTH_SOURCE; - restrict_access(&rset, home, disallow_root); + disallow_root = service->type == SERVICE_TYPE_LOGIN; + restrict_access(&rset, NULL, disallow_root); } else { restrict_access_set_env(&rset); } @@ -451,120 +224,38 @@ timeout_remove(&process->to_status); } -static void -handle_request(const struct service_process_auth_request *request) -{ - string_t *str; - - if (request == NULL) - return; - - if (request->data_size > 0) { - str = t_str_new(request->data_size*3); - str_append(str, "CLIENT_INPUT="); - base64_encode(request->data, request->data_size, str); - env_put(str_c(str)); - } - - env_put(t_strconcat("LOCAL_IP=", net_ip2addr(&request->local_ip), NULL)); - env_put(t_strconcat("IP=", net_ip2addr(&request->remote_ip), NULL)); -} - -static const char ** -get_extra_args(struct service *service, - const struct service_process_auth_request *request, - const char *const *auth_args) -{ - const char **extra; - - if (!service->set->master_set->verbose_proctitle || request == NULL) - return NULL; - - extra = t_new(const char *, 2); - extra[0] = t_strdup_printf("[%s %s]", auth_args[0], - net_ip2addr(&request->remote_ip)); - return extra; -} - -struct service_process * -service_process_create(struct service *service, const char *const *auth_args, - const struct service_process_auth_request *request) +struct service_process *service_process_create(struct service *service) { static unsigned int uid_counter = 0; struct service_process *process; unsigned int uid = ++uid_counter; - int fd[2]; pid_t pid; if (service->to_throttle != NULL) { /* throttling service, don't create new processes */ return NULL; } - if (service->process_count >= service->process_limit) { - /* we should get here only with auth dest services */ - i_warning("service(%s): process_limit reached, " - "dropping this client connection", - service->set->name); - return NULL; - } - - switch (service->type) { - case SERVICE_TYPE_AUTH_SOURCE: - case SERVICE_TYPE_AUTH_SERVER: - if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { - service_error(service, "socketpair() failed: %m"); - return NULL; - } - fd_close_on_exec(fd[0], TRUE); - fd_close_on_exec(fd[1], TRUE); - break; - default: - fd[0] = fd[1] = -1; - break; - } pid = fork(); if (pid < 0) { service_error(service, "fork() failed: %m"); - if (fd[0] != -1) { - (void)close(fd[0]); - (void)close(fd[1]); - } return NULL; } if (pid == 0) { /* child */ - if (fd[0] != -1) - (void)close(fd[0]); service_process_setup_environment(service, uid); - handle_request(request); - service_dup_fds(service, fd[1], request == NULL ? -1 : - request->fd, auth_args != NULL); - drop_privileges(service, auth_args, request); - process_exec(service->executable, - get_extra_args(service, request, auth_args)); + service_dup_fds(service); + drop_privileges(service); + process_exec(service->executable, NULL); } switch (service->type) { - case SERVICE_TYPE_AUTH_SERVER: - process = i_malloc(sizeof(struct service_process_auth_server)); - process->service = service; - service_process_auth_server_init(process, fd[0]); - (void)close(fd[1]); - break; - case SERVICE_TYPE_AUTH_SOURCE: - process = i_malloc(sizeof(struct service_process_auth_source)); - process->service = service; - service_process_auth_source_init(process, fd[0]); - (void)close(fd[1]); - break; case SERVICE_TYPE_ANVIL: service_anvil_process_created(service); /* fall through */ default: process = i_new(struct service_process, 1); process->service = service; - i_assert(fd[0] == -1); break; } @@ -604,12 +295,6 @@ timeout_remove(&process->to_idle); switch (process->service->type) { - case SERVICE_TYPE_AUTH_SERVER: - service_process_auth_server_deinit(process); - break; - case SERVICE_TYPE_AUTH_SOURCE: - service_process_auth_source_deinit(process); - break; case SERVICE_TYPE_ANVIL: service_anvil_process_destroyed(service); break; @@ -624,8 +309,8 @@ service_process_unref(process); if (service->process_count < service->process_limit && - service->type == SERVICE_TYPE_AUTH_SOURCE) - service_processes_auth_source_notify(service, FALSE); + service->type == SERVICE_TYPE_LOGIN) + service_login_notify(service, FALSE); service_list_unref(service_list); }
--- a/src/master/service-process.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/master/service-process.h Mon Oct 19 21:42:09 2009 -0400 @@ -26,50 +26,10 @@ unsigned int destroyed:1; }; -struct service_process_auth_server { - struct service_process process; - - int auth_fd; - struct io *io_auth; - struct ostream *auth_output; - struct istream *auth_input; - - /* pending authentication requests that are being verified from - auth server. */ - struct hash_table *auth_requests; - /* Last time we wrote "authentication server is too busy" to log */ - time_t auth_busy_stamp; - /* Tag counter for outgoing requests */ - unsigned int auth_tag_counter; - - unsigned int auth_version_sent:1; - unsigned int auth_version_received:1; -}; +#define SERVICE_PROCESS_IS_INITIALIZED(process) \ + ((process)->to_status == NULL) -struct service_process_auth_source { - struct service_process process; - - int last_notify_status; - - int auth_fd; - struct io *io_auth; - struct ostream *auth_output; -}; - -struct service_process_auth_request { - struct service_process_auth_source *process; - - unsigned int process_tag; - int fd; - - struct ip_addr local_ip, remote_ip; - unsigned int data_size; - unsigned char data[FLEXIBLE_ARRAY_MEMBER]; -}; - -struct service_process * -service_process_create(struct service *service, const char *const *auth_args, - const struct service_process_auth_request *request); +struct service_process *service_process_create(struct service *service); void service_process_destroy(struct service_process *process); void service_process_ref(struct service_process *process);
--- a/src/master/service.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/master/service.c Mon Oct 19 21:42:09 2009 -0400 @@ -17,6 +17,7 @@ #include <signal.h> #define SERVICE_DIE_TIMEOUT_MSECS (1000*10) +#define SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS 2 struct hash_table *service_pids; @@ -177,23 +178,7 @@ service->vsz_limit = set->vsz_limit != 0 ? set->vsz_limit : set->master_set->default_vsz_limit; - - service->type = SERVICE_TYPE_UNKNOWN; - if (*set->type != '\0') { - if (strcmp(set->type, "log") == 0) - service->type = SERVICE_TYPE_LOG; - else if (strcmp(set->type, "config") == 0) - service->type = SERVICE_TYPE_CONFIG; - else if (strcmp(set->type, "anvil") == 0) - service->type = SERVICE_TYPE_ANVIL; - else if (strcmp(set->type, "auth") == 0) - service->type = SERVICE_TYPE_AUTH_SERVER; - else if (strcmp(set->type, "auth-source") == 0) - service->type = SERVICE_TYPE_AUTH_SOURCE; - } - - if (*set->auth_dest_service != '\0') - service->type = SERVICE_TYPE_AUTH_SOURCE; + service->type = service->set->parsed_type; if (set->process_limit == 0) { /* unlimited */ @@ -258,6 +243,7 @@ service->status_fd[0] = -1; service->status_fd[1] = -1; service->log_process_internal_fd = -1; + service->login_notify_fd = -1; if (array_is_created(&set->unix_listeners)) unix_listeners = array_get(&set->unix_listeners, &unix_count); @@ -358,7 +344,7 @@ struct service_list **services_r, const char **error_r) { struct service_list *service_list; - struct service *service, *const *services; + struct service *service; struct service_settings *const *service_settings; pool_t pool; const char *error; @@ -411,23 +397,6 @@ array_append(&service_list->services, &service, 1); } - /* resolve service dependencies */ - services = array_get(&service_list->services, &count); - for (i = 0; i < count; i++) { - if (services[i]->type == SERVICE_TYPE_AUTH_SOURCE) { - const char *dest_service = - services[i]->set->auth_dest_service; - services[i]->auth_dest_service = - service_lookup(service_list, dest_service); - if (services[i]->auth_dest_service == NULL) { - *error_r = t_strdup_printf( - "auth_dest_service doesn't exist: %s", - dest_service); - return -1; - } - } - } - if (service_list->log == NULL) { *error_r = "log service not specified"; return -1; @@ -452,6 +421,12 @@ for (; process != NULL; process = process->next) { i_assert(process->service == service); + if (!SERVICE_PROCESS_IS_INITIALIZED(process) && + signo != SIGKILL) { + /* too early to signal it */ + continue; + } + if (kill(process->pid, signo) < 0 && errno != ESRCH) { service_error(service, "kill(%s, %d) failed: %m", dec2str(process->pid), signo); @@ -459,6 +434,51 @@ } } +static void service_login_notify_send(struct service *service) +{ + service->last_login_notify_time = ioloop_time; + if (service->to_login_notify != NULL) + timeout_remove(&service->to_login_notify); + + service_signal(service, SIGUSR1); +} + +static void service_login_notify_timeout(struct service *service) +{ + service_login_notify_send(service); +} + +void service_login_notify(struct service *service, bool all_processes_full) +{ + enum master_login_state state; + int diff; + + if (service->last_login_full_notify == all_processes_full || + service->login_notify_fd == -1) + return; + + /* change the state always immediately. it's cheap. */ + service->last_login_full_notify = all_processes_full; + state = all_processes_full ? MASTER_LOGIN_STATE_FULL : + MASTER_LOGIN_STATE_NONFULL; + if (lseek(service->login_notify_fd, state, SEEK_SET) < 0) + service_error(service, "lseek(notify fd) failed: %m"); + + /* but don't send signal to processes too often */ + diff = ioloop_time - service->last_login_notify_time; + if (diff < SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS) { + if (service->to_login_notify != NULL) + return; + + diff = (SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS - diff) * 1000; + service->to_login_notify = + timeout_add(diff, service_login_notify_timeout, + service); + } else { + service_login_notify_send(service); + } +} + static void services_kill_timeout(struct service_list *service_list) { struct service *const *services, *log_service;
--- a/src/master/service.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/master/service.h Mon Oct 19 21:42:09 2009 -0400 @@ -2,22 +2,12 @@ #define SERVICE_H #include "network.h" - -struct master_settings; +#include "master-settings.h" /* If a service process doesn't send its first status notification in this many seconds, kill the process */ #define SERVICE_FIRST_STATUS_TIMEOUT_SECS 30 -enum service_type { - SERVICE_TYPE_UNKNOWN, - SERVICE_TYPE_LOG, - SERVICE_TYPE_ANVIL, - SERVICE_TYPE_CONFIG, - SERVICE_TYPE_AUTH_SERVER, - SERVICE_TYPE_AUTH_SOURCE -}; - enum service_listener_type { SERVICE_LISTENER_UNIX, SERVICE_LISTENER_FIFO, @@ -87,14 +77,16 @@ int status_fd[2]; struct io *io_status; + /* Login process's notify fd. We change its seek position to + communicate state to login processes. */ + int login_notify_fd; + time_t last_login_notify_time; + struct timeout *to_login_notify; + /* if a process fails before servicing its first request, assume it's broken and start throtting new process creations */ struct timeout *to_throttle; - /* SERVICE_TYPE_AUTH_SOURCE: Destination service to run after - successful authentication. */ - struct service *auth_dest_service; - /* Last time a "dropping client connections" warning was logged */ time_t last_drop_warning; @@ -104,6 +96,8 @@ unsigned int listening:1; /* TRUE if service has at least one inet_listener */ unsigned int have_inet_listeners:1; + /* service_login_notify()'s last notification state */ + unsigned int last_login_full_notify:1; }; struct service_list { @@ -122,7 +116,7 @@ int master_log_fd[2]; struct service_process_notify *log_byes; - /* passed to auth destination processes */ + /* passed to child processes */ int blocking_anvil_fd[2]; /* used by master process to notify about dying processes */ int nonblocking_anvil_fd[2]; @@ -154,6 +148,10 @@ /* Send a signal to all processes in a given service */ void service_signal(struct service *service, int signo); +/* Notify all processes (if necessary) that no more connections can be handled + by the service without killing existing connections (TRUE) or that they + can be (FALSE). */ +void service_login_notify(struct service *service, bool all_processes_full); /* Prevent service from launching new processes for a while. */ void service_throttle(struct service *service, unsigned int secs);
--- a/src/pop3/main.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/pop3/main.c Mon Oct 19 21:42:09 2009 -0400 @@ -2,12 +2,14 @@ #include "pop3-common.h" #include "ioloop.h" +#include "buffer.h" #include "istream.h" -#include "buffer.h" +#include "ostream.h" #include "base64.h" #include "restrict-access.h" #include "process-title.h" #include "master-service.h" +#include "master-login.h" #include "master-interface.h" #include "var-expand.h" #include "mail-storage-service.h" @@ -17,63 +19,151 @@ #include <unistd.h> #define IS_STANDALONE() \ - (getenv("LOGGED_IN") == NULL) + (getenv(MASTER_UID_ENV) == NULL) + +static const struct setting_parser_info *set_roots[] = { + &pop3_setting_parser_info, + NULL +}; +static struct master_login *master_login = NULL; +static enum mail_storage_service_flags storage_service_flags = 0; +static bool user_initialized = FALSE; void (*hook_client_created)(struct client **client) = NULL; -static bool main_init(const struct pop3_settings *set, struct mail_user *user) +static void client_add_input(struct client *client, const buffer_t *buf) +{ + struct ostream *output; + + if (buf != NULL && buf->used > 0) { + if (!i_stream_add_data(client->input, buf->data, buf->used)) + i_panic("Couldn't add client input to stream"); + } + + output = client->output; + o_stream_ref(output); + o_stream_cork(output); + if (!IS_STANDALONE()) + client_send_line(client, "+OK Logged in."); + (void)client_handle_input(client); + o_stream_uncork(output); + o_stream_unref(&output); +} + +static void +main_stdio_init_user(const struct pop3_settings *set, struct mail_user *user) { struct client *client; - const char *str; - bool ret = TRUE; + buffer_t *input_buf; + const char *input_base64; + + input_base64 = getenv("CLIENT_INPUT"); + input_buf = input_base64 == NULL ? NULL : + t_base64_decode_str(input_base64); + + client = client_create(STDIN_FILENO, STDOUT_FILENO, user, set); + client_add_input(client, input_buf); +} + +static void main_stdio_run(void) +{ + struct mail_storage_service_input input; + struct mail_user *mail_user; + const struct pop3_settings *set; + const char *value; + memset(&input, 0, sizeof(input)); + input.module = input.service = "pop3"; + input.username = getenv("USER"); + if (input.username == NULL && IS_STANDALONE()) + input.username = getlogin(); + if (input.username == NULL) + i_fatal("USER environment missing"); + if ((value = getenv("IP")) != NULL) + net_addr2ip(value, &input.remote_ip); + if ((value = getenv("LOCAL_IP")) != NULL) + net_addr2ip(value, &input.local_ip); + + user_initialized = TRUE; + mail_user = mail_storage_service_init_user(master_service, + &input, set_roots, + storage_service_flags); + set = mail_storage_service_get_settings(master_service); + restrict_access_allow_coredumps(TRUE); if (set->shutdown_clients) master_service_set_die_with_master(master_service, TRUE); - client = client_create(0, 1, user, set); - if (client == NULL) - return FALSE; - - if (!IS_STANDALONE()) - client_send_line(client, "+OK Logged in."); - - str = getenv("CLIENT_INPUT"); - if (str != NULL) T_BEGIN { - buffer_t *buf = t_base64_decode_str(str); - if (buf->used > 0) { - if (!i_stream_add_data(client->input, buf->data, - buf->used)) - i_panic("Couldn't add client input to stream"); - ret = client_handle_input(client); - } - } T_END; - return ret; + /* fake that we're running, so we know if client was destroyed + while handling its initial input */ + io_loop_set_running(current_ioloop); + main_stdio_init_user(set, mail_user); } -static void main_deinit(void) +static void +login_client_connected(const struct master_login_client *client, + const char *username, const char *const *extra_fields) { - clients_destroy_all(); + struct mail_storage_service_input input; + struct mail_user *mail_user; + struct client *pop3_client; + const struct pop3_settings *set; + buffer_t input_buf; + + if (pop3_clients != NULL) { + i_error("Can't handle more than one connection currently"); + (void)close(client->fd); + return; + } + i_assert(!user_initialized); + + memset(&input, 0, sizeof(input)); + input.module = input.service = "pop3"; + input.local_ip = client->auth_req.local_ip; + input.remote_ip = client->auth_req.remote_ip; + input.username = username; + input.userdb_fields = extra_fields; + + if (input.username == NULL) { + i_error("login client: Username missing from auth reply"); + (void)close(client->fd); + return; + } + user_initialized = TRUE; + master_login_deinit(&master_login); + + mail_user = mail_storage_service_init_user(master_service, + &input, set_roots, + storage_service_flags); + set = mail_storage_service_get_settings(master_service); + restrict_access_allow_coredumps(TRUE); + if (set->shutdown_clients) + master_service_set_die_with_master(master_service, TRUE); + + /* fake that we're running, so we know if client was destroyed + while handling its initial input */ + io_loop_set_running(current_ioloop); + + buffer_create_const_data(&input_buf, client->data, + client->auth_req.data_size); + pop3_client = client_create(client->fd, client->fd, mail_user, set); + T_BEGIN { + client_add_input(pop3_client, &input_buf); + } T_END; } static void client_connected(const struct master_service_connection *conn) { - /* we can't handle this yet */ - (void)close(conn->fd); + if (master_login == NULL) { + /* running standalone, we shouldn't even get here */ + (void)close(conn->fd); + } else { + master_login_add(master_login, conn->fd); + } } int main(int argc, char *argv[], char *envp[]) { - const struct setting_parser_info *set_roots[] = { - &pop3_setting_parser_info, - NULL - }; - enum master_service_flags service_flags = - MASTER_SERVICE_FLAG_STD_CLIENT; - enum mail_storage_service_flags storage_service_flags = 0; - struct mail_storage_service_input input; - struct mail_user *mail_user; - const struct pop3_settings *set; - const char *value; + enum master_service_flags service_flags = 0; int c; if (IS_STANDALONE() && getuid() == 0 && @@ -83,12 +173,12 @@ return 1; } - if (IS_STANDALONE()) - service_flags |= MASTER_SERVICE_FLAG_STANDALONE; - else { + if (IS_STANDALONE()) { + service_flags |= MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_STD_CLIENT; + } else { storage_service_flags |= - MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT | - MAIL_STORAGE_SERVICE_FLAG_RESTRICT_BY_ENV; + MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT; } master_service = master_service_init("pop3", service_flags, argc, argv); @@ -96,43 +186,27 @@ if (!master_service_parse_option(master_service, c, optarg)) exit(FATAL_DEFAULT); } + process_title_init(argv, envp); + master_service_init_finish(master_service); - memset(&input, 0, sizeof(input)); - input.module = "pop3"; - input.service = "pop3"; - input.username = getenv("USER"); - if (input.username == NULL && IS_STANDALONE()) - input.username = getlogin(); - if (input.username == NULL) { - if (getenv(MASTER_UID_ENV) == NULL) - i_fatal("USER environment missing"); - else { - i_fatal("login_executable setting must be pop3-login, " - "not pop3"); - } + if (IS_STANDALONE()) { + T_BEGIN { + main_stdio_run(); + } T_END; + } else { + master_login = master_login_init("auth-master", + login_client_connected); + io_loop_set_running(current_ioloop); } - if ((value = getenv("IP")) != NULL) - net_addr2ip(value, &input.remote_ip); - if ((value = getenv("LOCAL_IP")) != NULL) - net_addr2ip(value, &input.local_ip); - mail_user = mail_storage_service_init_user(master_service, - &input, set_roots, - storage_service_flags); - set = mail_storage_service_get_settings(master_service); - restrict_access_allow_coredumps(TRUE); + if (io_loop_is_running(current_ioloop)) + master_service_run(master_service, client_connected); + clients_destroy_all(); - process_title_init(argv, envp); - - /* fake that we're running, so we know if client was destroyed - while initializing */ - io_loop_set_running(current_ioloop); - - if (main_init(set, mail_user)) - master_service_run(master_service, client_connected); - - main_deinit(); - mail_storage_service_deinit_user(); + if (master_login != NULL) + master_login_deinit(&master_login); + if (user_initialized) + mail_storage_service_deinit_user(); master_service_deinit(&master_service); return 0; }
--- a/src/pop3/pop3-client.c Mon Oct 19 18:34:00 2009 -0400 +++ b/src/pop3/pop3-client.c Mon Oct 19 21:42:09 2009 -0400 @@ -35,7 +35,7 @@ transaction. This allows the mailbox to become unlocked. */ #define CLIENT_COMMIT_TIMEOUT_MSECS (10*1000) -static struct client *pop3_clients; +struct client *pop3_clients; static void client_input(struct client *client); static int client_output(struct client *client);
--- a/src/pop3/pop3-client.h Mon Oct 19 18:34:00 2009 -0400 +++ b/src/pop3/pop3-client.h Mon Oct 19 21:42:09 2009 -0400 @@ -60,6 +60,8 @@ unsigned int anvil_sent:1; }; +extern struct client *pop3_clients; + /* Create new client with specified input/output handles. socket specifies if the handle is a socket. */ struct client *client_create(int fd_in, int fd_out, struct mail_user *user,