Mercurial > dovecot > core-2.2
changeset 9159:6324a79d3ee1 HEAD
Initial commit for v2.0 master rewrite. Several features are still missing.
line wrap: on
line diff
--- a/.hgignore Thu Apr 23 14:07:45 2009 -0400 +++ b/.hgignore Thu Apr 23 19:53:44 2009 -0400 @@ -65,6 +65,7 @@ src/lib-sql/sql-drivers-register.c src/lib-storage/register/mail-storage-register.c src/lib-storage/register/mailbox-list-register.c +src/log/log src/lmtp/lmtp src/master/dovecot src/master/ssl-build-param
--- a/TODO Thu Apr 23 14:07:45 2009 -0400 +++ b/TODO Thu Apr 23 19:53:44 2009 -0400 @@ -1,21 +1,30 @@ + - mail_max_userip_connections + - dovecot stop, dovecot reload + - make sure status/log messages which are important get through to the server + - log prefixes work in a weird way now. failures.c prefixes are used only + when not doing internal logging. that won't work in future.. + - library dependency tracking still broken. .la changes get noticed, + .libs/*.a changes not + - o_stream_set_file_path() - dict pooling - config rewrite - - go through mail-process.c. nfs test? - - support !include and !include_try - - add back all setting verification code from master - - master_user, acl_groups are now plugin envs.. is it correct? + - dovecot -o setting=something overriding + - fix dovecot -n, dovecot -a + - export only the stuff that's actually wanted by the service. requires + some kind of dependencies or something to get e.g. imap to load also mail. /* currently non-external transactions can be applied multiple times, causing multiple increments. */ //FIXME:i_assert((t->flags & MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL) != 0); ^ appears to work now though, probably because of the added syncing stuff.. + - dbox: keep track of total bytes in dbox storage in map header. also if + possible keep track of refcount=0 bytes. use these to optimize checks. - dbox: save some stuff to map index header so we don't need to keep retrying it. like when saving the lowest file_id which to bother checking. - transaction log corruption should make sure dovecot.index is rewritten and perhaps not delete the file. - dbox: test crash-fixing - - dbox: cleanup tool / remove automatic cleanup. resyncing tool. - use backup index in mail_index_fsck() - dbox: mail_index_fsck() should perhaps cause dbox to be resynced? @@ -46,7 +55,7 @@ - add anonymous environment for anon logins - fs quota: getquotaroot inbox vs. other-box should return different quotas if two quotas are defined - - deliver: log mailbox name using utf8, not mutf7 + - lda: log mailbox name using utf8, not mutf7 - new primes code: are hash tables now being resized too often? - auth_log_prefix setting similar to mail_log_prefix - LDAP attrs: uid=foo,uid=bar doesn't work @@ -117,7 +126,7 @@ - Deny deleting non-empty mailboxes - Disable IDLE "still here" notifications - - maildir+pop3/deliver fast updates: + - maildir+pop3 fast updates: - with locking enabled, pop3 could just keep the one and same sync lock and do the whole thing using sync transaction - don't update dovecot-uidlist if dovecot.index.cache doesn't exist / @@ -129,7 +138,7 @@ - maildir - don't allow more than 26 keywords - physical separator could be configurable - - deliver+maildir: if new mails are in new/ or cur/ they're not added to + - lda+maildir: if new mails are in new/ or cur/ they're not added to dovecot-uidlist but newly saved mails are, so UIDs will be in wrong order - maildir_copy_with_hardlinks: We're currently first hardlinking to tmp/ and then rename()ing. This wouldn't be necessary if uidlist syncing noticed @@ -150,7 +159,6 @@ http://www.dovecot.org/list/dovecot/2007-April/021532.html - EXPUNGE command in read-only mailbox should give an error message if there are messages marked as \Deleted? - - dovecot -o setting=something overriding - file_cache: we're growing the mmap in page size blocks, which is horribly slow if mremap() doesn't exist. - login_max_processes_count shouldn't count proxying processes
--- a/configure.in Thu Apr 23 14:07:45 2009 -0400 +++ b/configure.in Thu Apr 23 19:53:44 2009 -0400 @@ -1,5 +1,5 @@ AC_PREREQ([2.59]) -AC_INIT([Dovecot],[1.3.UNSTABLE],[dovecot@dovecot.org]) +AC_INIT([Dovecot],[2.0.UNSTABLE],[dovecot@dovecot.org]) AC_CONFIG_SRCDIR([src]) AM_INIT_AUTOMAKE([foreign]) @@ -2200,7 +2200,7 @@ LIBDOVECOT_STORAGE='$(top_builddir)/src/lib-storage/libdovecot-storage.la' LIBDOVECOT_LOGIN='$(top_builddir)/src/login-common/libdovecot-login.la' else - LIBDOVECOT='$(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib/liblib.la $(LIBICONV)' + LIBDOVECOT='$(top_builddir)/src/lib-master/libmaster.la $(top_builddir)/src/lib-settings/libsettings.la $(top_builddir)/src/lib-dict/libdict.la $(top_builddir)/src/lib-imap/libimap.la $(top_builddir)/src/lib-mail/libmail.la $(top_builddir)/src/lib-auth/libauth.la $(top_builddir)/src/lib-charset/libcharset.la $(top_builddir)/src/lib/liblib.la $(LIBICONV)' LIBDOVECOT_STORAGE_LAST='$(top_builddir)/src/lib-storage/list/libstorage_list.la $(top_builddir)/src/lib-storage/index/libstorage_index.la $(top_builddir)/src/lib-storage/libstorage.la $(top_builddir)/src/lib-index/libindex.la' LIBDOVECOT_STORAGE_FIRST='$(top_builddir)/src/lib-storage/libstorage_service.la $(top_builddir)/src/lib-storage/register/libstorage_register.la' LIBDOVECOT_STORAGE="$LIBDOVECOT_STORAGE_FIRST $LINKED_STORAGE_LIBS $LIBDOVECOT_STORAGE_LAST" @@ -2376,6 +2376,7 @@ src/auth/Makefile src/config/Makefile src/lda/Makefile +src/log/Makefile src/lmtp/Makefile src/dict/Makefile src/imap/Makefile
--- a/src/Makefile.am Thu Apr 23 14:07:45 2009 -0400 +++ b/src/Makefile.am Thu Apr 23 19:53:44 2009 -0400 @@ -27,6 +27,7 @@ pop3 \ lda \ lmtp \ + log \ config \ tests \ util \
--- a/src/auth/Makefile.am Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/Makefile.am Thu Apr 23 19:53:44 2009 -0400 @@ -23,6 +23,7 @@ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-ntlm \ -I$(top_srcdir)/src/lib-otp \ + -I$(top_srcdir)/src/lib-master \ -DAUTH_MODULE_DIR=\""$(auth_moduledir)"\" \ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ -DPKG_RUNDIR=\""$(rundir)"\" \ @@ -44,7 +45,7 @@ $(LIBDOVECOT_SQL) \ $(LIBDOVECOT) -dovecot_auth_LDADD = $(dovecot_auth_libs) $(MODULE_LIBS) $(AUTH_LIBS) +dovecot_auth_LDADD = $(dovecot_auth_libs) $(AUTH_LIBS) $(MODULE_LIBS) dovecot_auth_DEPENDENCIES = $(dovecot_auth_libs) ldap_sources = db-ldap.c passdb-ldap.c userdb-ldap.c @@ -54,7 +55,6 @@ auth-cache.c \ auth-client-connection.c \ auth-master-connection.c \ - auth-master-listener.c \ auth-request.c \ auth-request-handler.c \ auth-settings.c \ @@ -111,7 +111,6 @@ auth-client-interface.h \ auth-master-interface.h \ auth-master-connection.h \ - auth-master-listener.h \ auth-request.h \ auth-request-handler.h \ auth-settings.h \
--- a/src/auth/auth-client-connection.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/auth-client-connection.c Thu Apr 23 19:53:44 2009 -0400 @@ -1,11 +1,12 @@ /* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ #include "common.h" +#include "array.h" #include "ioloop.h" #include "istream.h" #include "ostream.h" #include "network.h" -#include "array.h" +#include "hostpid.h" #include "str.h" #include "str-sanitize.h" #include "safe-memset.h" @@ -13,15 +14,16 @@ #include "auth-request-handler.h" #include "auth-client-interface.h" #include "auth-client-connection.h" -#include "auth-master-listener.h" #include "auth-master-connection.h" #include <stdlib.h> #define OUTBUF_THROTTLE_SIZE (1024*50) +static ARRAY_DEFINE(auth_client_connections, struct auth_client_connection *); +static struct timeout *to_clients; + static void auth_client_connection_unref(struct auth_client_connection **_conn); - static void auth_client_input(struct auth_client_connection *conn); static const char *reply_line_hide_pass(const char *line) @@ -90,7 +92,7 @@ return FALSE; } - old = auth_client_connection_lookup(conn->listener, pid); + old = auth_client_connection_lookup(pid); if (old != NULL) { /* already exists. it's possible that it just reconnected, see if the old connection is still there. */ @@ -112,7 +114,7 @@ conn->request_handler = auth_request_handler_create(conn->auth, auth_callback, conn, - array_count(&conn->listener->masters) != 0 ? + array_count(&auth_master_connections) != 0 ? auth_master_request_callback : NULL); auth_request_handler_set(conn->request_handler, conn->connect_uid, pid); @@ -261,7 +263,7 @@ } struct auth_client_connection * -auth_client_connection_create(struct auth_master_listener *listener, int fd) +auth_client_connection_create(struct auth *auth, int fd) { static unsigned int connect_uid_counter = 0; struct auth_client_connection *conn; @@ -269,8 +271,7 @@ string_t *str; conn = i_new(struct auth_client_connection, 1); - conn->auth = listener->auth; - conn->listener = listener; + conn->auth = auth; conn->refcount = 1; conn->connect_uid = ++connect_uid_counter; @@ -281,13 +282,13 @@ o_stream_set_flush_callback(conn->output, auth_client_output, conn); conn->io = io_add(fd, IO_READ, auth_client_input, conn); - array_append(&listener->clients, &conn, 1); + array_append(&auth_client_connections, &conn, 1); str = t_str_new(128); - str_printfa(str, "VERSION\t%u\t%u\nSPID\t%u\nCUID\t%u\nDONE\n", + str_printfa(str, "VERSION\t%u\t%u\nSPID\t%s\nCUID\t%u\nDONE\n", AUTH_CLIENT_PROTOCOL_MAJOR_VERSION, AUTH_CLIENT_PROTOCOL_MINOR_VERSION, - listener->pid, conn->connect_uid); + my_pid, conn->connect_uid); iov[0].iov_base = str_data(conn->auth->mech_handshake); iov[0].iov_len = str_len(conn->auth->mech_handshake); @@ -310,10 +311,10 @@ if (conn->fd == -1) return; - clients = array_get(&conn->listener->clients, &count); + clients = array_get(&auth_client_connections, &count); for (i = 0; i < count; i++) { if (clients[i] == conn) { - array_delete(&conn->listener->clients, i, 1); + array_delete(&auth_client_connections, i, 1); break; } } @@ -347,13 +348,12 @@ } struct auth_client_connection * -auth_client_connection_lookup(struct auth_master_listener *listener, - unsigned int pid) +auth_client_connection_lookup(unsigned int pid) { struct auth_client_connection *const *clients; unsigned int i, count; - clients = array_get(&listener->clients, &count); + clients = array_get(&auth_client_connections, &count); for (i = 0; i < count; i++) { if (clients[i]->pid == pid) return clients[i]; @@ -362,12 +362,12 @@ return NULL; } -static void request_timeout(struct auth_master_listener *listener) +static void request_timeout(void *context ATTR_UNUSED) { struct auth_client_connection *const *clients; unsigned int i, count; - clients = array_get(&listener->clients, &count); + clients = array_get(&auth_client_connections, &count); for (i = 0; i < count; i++) { if (clients[i]->request_handler != NULL) { auth_request_handler_check_timeouts( @@ -376,13 +376,22 @@ } } -void auth_client_connections_init(struct auth_master_listener *listener) +void auth_client_connections_init(void) { - listener->to_clients = timeout_add(5000, request_timeout, listener); + i_array_init(&auth_client_connections, 16); + to_clients = timeout_add(5000, request_timeout, NULL); } -void auth_client_connections_deinit(struct auth_master_listener *listener) +void auth_client_connections_deinit(void) { - if (listener->to_clients != NULL) - timeout_remove(&listener->to_clients); + struct auth_client_connection **clients; + unsigned int i, count; + + if (to_clients != NULL) + timeout_remove(&to_clients); + + clients = array_get_modifiable(&auth_client_connections, &count); + for (i = count; i > 0; i--) + auth_client_connection_destroy(&clients[i-1]); + array_free(&auth_client_connections); }
--- a/src/auth/auth-client-connection.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/auth-client-connection.h Thu Apr 23 19:53:44 2009 -0400 @@ -3,7 +3,6 @@ struct auth_client_connection { struct auth *auth; - struct auth_master_listener *listener; int refcount; int fd; @@ -19,14 +18,13 @@ }; struct auth_client_connection * -auth_client_connection_create(struct auth_master_listener *listener, int fd); +auth_client_connection_create(struct auth *auth, int fd); void auth_client_connection_destroy(struct auth_client_connection **conn); struct auth_client_connection * -auth_client_connection_lookup(struct auth_master_listener *listener, - unsigned int pid); +auth_client_connection_lookup(unsigned int pid); -void auth_client_connections_init(struct auth_master_listener *listener); -void auth_client_connections_deinit(struct auth_master_listener *listener); +void auth_client_connections_init(void); +void auth_client_connections_deinit(void); #endif
--- a/src/auth/auth-master-connection.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/auth-master-connection.c Thu Apr 23 19:53:44 2009 -0400 @@ -2,19 +2,19 @@ #include "common.h" #include "array.h" -#include "buffer.h" #include "hash.h" #include "str.h" +#include "hostpid.h" #include "str-sanitize.h" #include "ioloop.h" +#include "network.h" #include "istream.h" #include "ostream.h" -#include "network.h" +#include "master-service.h" #include "userdb.h" #include "auth-request-handler.h" #include "auth-master-interface.h" #include "auth-client-connection.h" -#include "auth-master-listener.h" #include "auth-master-connection.h" #include <unistd.h> @@ -29,6 +29,8 @@ struct auth_request *auth_request; }; +ARRAY_TYPE(auth_master_connections) auth_master_connections; + void auth_master_request_callback(struct auth_stream_reply *reply, void *context) { @@ -38,7 +40,7 @@ reply_str = auth_stream_reply_export(reply); - if (conn->listener->auth->set->debug) + if (conn->auth->set->debug) i_info("master out: %s", reply_str); iov[0].iov_base = reply_str; @@ -67,7 +69,7 @@ client_pid = (unsigned int)strtoul(list[1], NULL, 10); client_id = (unsigned int)strtoul(list[2], NULL, 10); - client_conn = auth_client_connection_lookup(conn->listener, client_pid); + client_conn = auth_client_connection_lookup(client_pid); if (client_conn == NULL) { i_error("Master requested auth for nonexisting client %u", client_pid); @@ -105,7 +107,7 @@ break; } - if (conn->listener->auth->set->debug) + if (conn->auth->set->debug) i_info("master out: %s", str_c(str)); str_append_c(str, '\n'); @@ -126,7 +128,7 @@ return FALSE; } - auth_request = auth_request_new_dummy(conn->listener->auth); + auth_request = auth_request_new_dummy(conn->auth); auth_request->id = (unsigned int)strtoul(list[0], NULL, 10); auth_request->context = conn; @@ -163,7 +165,7 @@ static bool auth_master_input_line(struct auth_master_connection *conn, const char *line) { - if (conn->listener->auth->set->debug) + if (conn->auth->set->debug) i_info("master in: %s", line); if (strncmp(line, "REQUEST\t", 8) == 0) @@ -248,41 +250,34 @@ } struct auth_master_connection * -auth_master_connection_create(struct auth_master_listener *listener, int fd) +auth_master_connection_create(struct auth *auth, int fd) { struct auth_master_connection *conn; + const char *line; conn = i_new(struct auth_master_connection, 1); - conn->listener = listener; conn->refcount = 1; conn->fd = fd; + conn->auth = auth; conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE); conn->output = o_stream_create_fd(fd, (size_t)-1, FALSE); o_stream_set_flush_callback(conn->output, master_output, conn); conn->io = io_add(fd, IO_READ, master_input, conn); - array_append(&listener->masters, &conn, 1); - return conn; -} - -void auth_master_connection_send_handshake(struct auth_master_connection *conn) -{ - const char *line; - - if (conn->output == NULL) - return; - - line = t_strdup_printf("VERSION\t%u\t%u\nSPID\t%u\n", + line = t_strdup_printf("VERSION\t%u\t%u\nSPID\t%s\n", AUTH_MASTER_PROTOCOL_MAJOR_VERSION, AUTH_MASTER_PROTOCOL_MINOR_VERSION, - conn->listener->pid); + my_pid); (void)o_stream_send_str(conn->output, line); + + array_append(&auth_master_connections, &conn, 1); + return conn; } void auth_master_connection_destroy(struct auth_master_connection **_conn) { struct auth_master_connection *conn = *_conn; - struct auth_master_connection *const *conns; + struct auth_master_connection *const *masters; unsigned int i, count; *_conn = NULL; @@ -290,6 +285,14 @@ return; conn->destroyed = TRUE; + masters = array_get(&auth_master_connections, &count); + for (i = 0; i < count; i++) { + if (masters[i] == conn) { + array_delete(&auth_master_connections, i, 1); + break; + } + } + if (conn->input != NULL) i_stream_close(conn->input); if (conn->output != NULL) @@ -302,16 +305,7 @@ conn->fd = -1; } - conns = array_get(&conn->listener->masters, &count); - for (i = 0; i < count; i++) { - if (conns[i] == conn) { - array_delete(&conn->listener->masters, i, 1); - break; - } - } - if (!standalone && auth_master_listeners_masters_left() == 0) - io_loop_stop(ioloop); - + master_service_client_connection_destroyed(service); auth_master_connection_unref(&conn); } @@ -339,3 +333,19 @@ i_free(conn); } + +void auth_master_connections_init(void) +{ + i_array_init(&auth_master_connections, 16); +} + +void auth_master_connections_deinit(void) +{ + struct auth_master_connection **masters; + unsigned int i, count; + + masters = array_get_modifiable(&auth_master_connections, &count); + for (i = count; i > 0; i--) + auth_master_connection_destroy(&masters[i-1]); + array_free(&auth_master_connections); +}
--- a/src/auth/auth-master-connection.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/auth-master-connection.h Thu Apr 23 19:53:44 2009 -0400 @@ -4,7 +4,7 @@ struct auth_stream_reply; struct auth_master_connection { - struct auth_master_listener *listener; + struct auth *auth; int refcount; int fd; @@ -15,18 +15,21 @@ unsigned int version_received:1; unsigned int destroyed:1; }; +ARRAY_DEFINE_TYPE(auth_master_connections, struct auth_master_connection *); + +extern ARRAY_TYPE(auth_master_connections) auth_master_connections; struct auth_master_connection * -auth_master_connection_create(struct auth_master_listener *listener, int fd); +auth_master_connection_create(struct auth *auth, int fd); void auth_master_connection_destroy(struct auth_master_connection **conn); void auth_master_connection_ref(struct auth_master_connection *conn); void auth_master_connection_unref(struct auth_master_connection **conn); -void auth_master_connection_send_handshake(struct auth_master_connection *conn); -void auth_master_connections_send_handshake(void); - void auth_master_request_callback(struct auth_stream_reply *reply, void *context); +void auth_master_connections_init(void); +void auth_master_connections_deinit(void); + #endif
--- a/src/auth/auth-master-interface.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/auth-master-interface.h Thu Apr 23 19:53:44 2009 -0400 @@ -1,6 +1,8 @@ #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
--- a/src/auth/auth-master-listener.c Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,179 +0,0 @@ -/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "array.h" -#include "ioloop.h" -#include "network.h" -#include "istream.h" -#include "ostream.h" -#include "auth-master-listener.h" -#include "auth-master-connection.h" -#include "auth-client-connection.h" - -#include <unistd.h> - -struct auth_master_listener_socket { - struct auth_master_listener *listener; - - enum listener_type type; - int fd; - char *path; - struct io *io; -}; - -static ARRAY_DEFINE(master_listeners, struct auth_master_listener *); - -struct auth_master_listener *auth_master_listener_create(struct auth *auth) -{ - struct auth_master_listener *listener; - - listener = i_new(struct auth_master_listener, 1); - listener->auth = auth; - listener->pid = (unsigned int)getpid(); - i_array_init(&listener->sockets, 16); - i_array_init(&listener->masters, 16); - i_array_init(&listener->clients, 16); - auth_client_connections_init(listener); - - array_append(&master_listeners, &listener, 1); - return listener; -} - -static void -auth_master_listener_socket_free(struct auth_master_listener_socket *s) -{ - if (s->path != NULL) { - (void)unlink(s->path); - i_free(s->path); - } - - io_remove(&s->io); - net_disconnect(s->fd); - i_free(s); -} - -void auth_master_listener_destroy(struct auth_master_listener *listener) -{ - struct auth_master_listener *const *listeners; - struct auth_master_listener_socket **sockets; - struct auth_master_connection **masters; - struct auth_client_connection **clients; - unsigned int i, count; - - listeners = array_get(&master_listeners, &count); - for (i = 0; i < count; i++) { - if (listeners[i] == listener) { - array_delete(&master_listeners, i, 1); - break; - } - } - - sockets = array_get_modifiable(&listener->sockets, &count); - for (i = count; i > 0; i--) - auth_master_listener_socket_free(sockets[i-1]); - - masters = array_get_modifiable(&listener->masters, &count); - for (i = count; i > 0; i--) - auth_master_connection_destroy(&masters[i-1]); - - clients = array_get_modifiable(&listener->clients, &count); - for (i = count; i > 0; i--) - auth_client_connection_destroy(&clients[i-1]); - - auth_client_connections_deinit(listener); - array_free(&listener->sockets); - array_free(&listener->masters); - array_free(&listener->clients); - i_free(listener); -} - -static void auth_master_listener_accept(struct auth_master_listener_socket *s) -{ - struct auth_master_connection *master; - int fd; - - fd = net_accept(s->fd, NULL, NULL); - if (fd < 0) { - if (fd < -1) - i_error("accept(type %d) failed: %m", s->type); - } else { - net_set_nonblock(fd, TRUE); - - switch (s->type) { - case LISTENER_CLIENT: - (void)auth_client_connection_create(s->listener, fd); - break; - case LISTENER_MASTER: - /* we'll just replace the previous master.. */ - master = auth_master_connection_create(s->listener, fd); - auth_master_connection_send_handshake(master); - break; - } - } -} - -void auth_master_listener_add(struct auth_master_listener *listener, - int fd, const char *path, - enum listener_type type) -{ - struct auth_master_listener_socket *s; - - s = i_new(struct auth_master_listener_socket, 1); - s->listener = listener; - s->fd = fd; - s->path = i_strdup(path); - s->type = type; - s->io = io_add(fd, IO_READ, auth_master_listener_accept, s); - - array_append(&listener->sockets, &s, 1); -} - -static void -auth_master_listener_send_handshakes(struct auth_master_listener *listener) -{ - struct auth_master_connection *const *masters; - unsigned int i, count; - - masters = array_get(&listener->masters, &count); - for (i = 0; i < count; i++) - auth_master_connection_send_handshake(masters[i]); -} - -void auth_master_listeners_send_handshake(void) -{ - struct auth_master_listener *const *listeners; - unsigned int i, count; - - listeners = array_get(&master_listeners, &count); - for (i = 0; i < count; i++) - auth_master_listener_send_handshakes(listeners[i]); -} - -bool auth_master_listeners_masters_left(void) -{ - struct auth_master_listener *const *listeners; - unsigned int i, count; - - listeners = array_get(&master_listeners, &count); - for (i = 0; i < count; i++) { - if (array_count(&listeners[i]->masters) > 0) - return TRUE; - } - return FALSE; -} - -void auth_master_listeners_init(void) -{ - i_array_init(&master_listeners, 2); -} - -void auth_master_listeners_deinit(void) -{ - struct auth_master_listener **listeners; - unsigned int i, count; - - listeners = array_get_modifiable(&master_listeners, &count); - for (i = count; i > 0; i--) - auth_master_listener_destroy(listeners[i-1]); - array_free(&master_listeners); -}
--- a/src/auth/auth-master-listener.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -#ifndef AUTH_MASTER_LISTENER_H -#define AUTH_MASTER_LISTENER_H - -enum listener_type { - LISTENER_MASTER, - LISTENER_CLIENT -}; - -struct auth_master_listener { - struct auth *auth; - unsigned int pid; - - ARRAY_DEFINE(sockets, struct auth_master_listener_socket *); - ARRAY_DEFINE(masters, struct auth_master_connection *); - ARRAY_DEFINE(clients, struct auth_client_connection *); - - struct timeout *to_clients; -}; - -struct auth_master_listener *auth_master_listener_create(struct auth *auth); -void auth_master_listener_destroy(struct auth_master_listener *listener); - -void auth_master_listener_add(struct auth_master_listener *listener, - int fd, const char *path, - enum listener_type type); - -void auth_master_listeners_send_handshake(void); -bool auth_master_listeners_masters_left(void); - -void auth_master_listeners_init(void); -void auth_master_listeners_deinit(void); - -#endif
--- a/src/auth/auth-settings.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/auth-settings.c Thu Apr 23 19:53:44 2009 -0400 @@ -2,114 +2,15 @@ #include "lib.h" #include "array.h" -#include "hostpid.h" #include "settings-parser.h" +#include "master-service-settings.h" #include "auth-settings.h" #include <stddef.h> -extern struct setting_parser_info auth_socket_setting_parser_info; extern struct setting_parser_info auth_setting_parser_info; extern struct setting_parser_info auth_root_setting_parser_info; -static bool auth_settings_check(void *_set, pool_t pool, const char **error_r); - -#undef DEF -#define DEF(type, name) \ - { type, #name, offsetof(struct auth_socket_unix_settings, name), NULL } - -static struct setting_define auth_socket_client_setting_defines[] = { - DEF(SET_STR, path), - DEF(SET_UINT, mode), - DEF(SET_STR, user), - DEF(SET_STR, group), - - SETTING_DEFINE_LIST_END -}; - -static struct auth_socket_unix_settings auth_socket_client_default_settings = { - MEMBER(path) "auth-client", - MEMBER(mode) 0660, - MEMBER(user) "", - MEMBER(group) "" -}; - -struct setting_parser_info auth_socket_client_setting_parser_info = { - MEMBER(defines) auth_socket_client_setting_defines, - MEMBER(defaults) &auth_socket_client_default_settings, - - MEMBER(parent) &auth_socket_setting_parser_info, - MEMBER(dynamic_parsers) NULL, - - MEMBER(parent_offset) (size_t)-1, - MEMBER(type_offset) (size_t)-1, - MEMBER(struct_size) sizeof(struct auth_socket_unix_settings) -}; - -#undef DEF -#define DEF(type, name) \ - { type, #name, offsetof(struct auth_socket_unix_settings, name), NULL } - -static struct setting_define auth_socket_master_setting_defines[] = { - DEF(SET_STR, path), - DEF(SET_UINT, mode), - DEF(SET_STR, user), - DEF(SET_STR, group), - - SETTING_DEFINE_LIST_END -}; - -static struct auth_socket_unix_settings auth_socket_master_default_settings = { - MEMBER(path) "auth-master", - MEMBER(mode) 0660, - MEMBER(user) "", - MEMBER(group) "" -}; - -struct setting_parser_info auth_socket_master_setting_parser_info = { - MEMBER(defines) auth_socket_master_setting_defines, - MEMBER(defaults) &auth_socket_master_default_settings, - - MEMBER(parent) &auth_socket_setting_parser_info, - MEMBER(dynamic_parsers) NULL, - - MEMBER(parent_offset) (size_t)-1, - MEMBER(type_offset) (size_t)-1, - MEMBER(struct_size) sizeof(struct auth_socket_unix_settings) -}; - -#undef DEF -#undef DEFLIST -#define DEF(type, name) \ - { type, #name, offsetof(struct auth_socket_settings, name), NULL } -#define DEFLIST(field, name, defines) \ - { SET_DEFLIST, name, offsetof(struct auth_socket_settings, field), defines } - -static struct setting_define auth_socket_setting_defines[] = { - DEF(SET_ENUM, type), - - DEFLIST(clients, "client", &auth_socket_client_setting_parser_info), - DEFLIST(masters, "master", &auth_socket_master_setting_parser_info), - - SETTING_DEFINE_LIST_END -}; - -static struct auth_socket_settings auth_socket_default_settings = { - MEMBER(type) "listen:connect" -}; - -struct setting_parser_info auth_socket_setting_parser_info = { - MEMBER(defines) auth_socket_setting_defines, - MEMBER(defaults) &auth_socket_default_settings, - - MEMBER(parent) &auth_setting_parser_info, - MEMBER(dynamic_parsers) NULL, - - MEMBER(parent_offset) (size_t)-1, - MEMBER(type_offset) offsetof(struct auth_socket_settings, type), - MEMBER(struct_size) sizeof(struct auth_socket_settings) -}; - #undef DEF #define DEF(type, name) \ { type, #name, offsetof(struct auth_passdb_settings, name), NULL } @@ -191,7 +92,6 @@ DEF(SET_UINT, worker_max_count), - DEFLIST(sockets, "socket", &auth_socket_setting_parser_info), DEFLIST(passdbs, "passdb", &auth_passdb_setting_parser_info), DEFLIST(userdbs, "userdb", &auth_userdb_setting_parser_info), @@ -227,7 +127,6 @@ MEMBER(worker_max_count) 30, - MEMBER(sockets) ARRAY_INIT, MEMBER(passdbs) ARRAY_INIT, MEMBER(userdbs) ARRAY_INIT }; @@ -242,7 +141,7 @@ MEMBER(parent_offset) offsetof(struct auth_settings, root), MEMBER(type_offset) offsetof(struct auth_settings, name), MEMBER(struct_size) sizeof(struct auth_settings), - MEMBER(check_func) auth_settings_check + MEMBER(check_func) NULL }; #undef DEF @@ -253,14 +152,12 @@ { SET_DEFLIST, name, offsetof(struct auth_root_settings, field), defines } static struct setting_define auth_root_setting_defines[] = { - DEF(SET_STR, base_dir), DEFLIST(auths, "auth", &auth_setting_parser_info), SETTING_DEFINE_LIST_END }; static struct auth_root_settings auth_root_default_settings = { - MEMBER(base_dir) PKG_RUNDIR, MEMBER(auths) ARRAY_INIT }; @@ -276,76 +173,25 @@ MEMBER(struct_size) sizeof(struct auth_root_settings) }; -static pool_t settings_pool = NULL; - -static void fix_base_path(struct auth_settings *set, const char **str) -{ - if (*str != NULL && **str != '\0' && **str != '/') { - *str = p_strconcat(settings_pool, - set->root->base_dir, "/", *str, NULL); - } -} - -/* <settings checks> */ -static bool auth_settings_check(void *_set ATTR_UNUSED, pool_t pool ATTR_UNUSED, - const char **error_r ATTR_UNUSED) +struct auth_settings * +auth_settings_read(struct master_service *service, const char *name) { -#ifndef CONFIG_BINARY - struct auth_settings *set = _set; - struct auth_socket_unix_settings *const *u; - struct auth_socket_settings *const *sockets; - unsigned int i, j, count, count2; - - if (!array_is_created(&set->sockets)) - return TRUE; - - sockets = array_get(&set->sockets, &count); - for (i = 0; i < count; i++) { - if (array_is_created(&sockets[i]->masters)) { - u = array_get(&sockets[i]->masters, &count2); - for (j = 0; j < count2; j++) - fix_base_path(set, &u[j]->path); - } - if (array_is_created(&sockets[i]->clients)) { - u = array_get(&sockets[i]->clients, &count2); - for (j = 0; j < count2; j++) - fix_base_path(set, &u[j]->path); - } - } -#endif - return TRUE; -} -/* </settings checks> */ - -struct auth_settings *auth_settings_read(const char *name) -{ - struct setting_parser_context *parser; + static const struct setting_parser_info *set_roots[] = { + &auth_root_setting_parser_info, + NULL + }; + const char *error; + void **sets; + struct auth_settings *const *auths; struct auth_root_settings *set; - struct auth_settings *const *auths; - const char *error; unsigned int i, count; - if (settings_pool == NULL) - settings_pool = pool_alloconly_create("auth settings", 1024); - else - p_clear(settings_pool); - - parser = settings_parser_init(settings_pool, - &auth_root_setting_parser_info, - SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS); - - auth_default_settings.gssapi_hostname = my_hostname; + if (master_service_settings_read(service, set_roots, NULL, FALSE, + &error) < 0) + i_fatal("Error reading configuration: %s", error); - if (settings_parse_environ(parser) < 0) { - i_fatal("Error reading configuration: %s", - settings_parser_get_error(parser)); - } - - if (settings_parser_check(parser, settings_pool, &error) < 0) - i_fatal("Invalid settings: %s", error); - - set = settings_parser_get(parser); - settings_parser_deinit(&parser); + sets = master_service_settings_get_others(service); + set = sets[0]; if (array_is_created(&set->auths)) { auths = array_get(&set->auths, &count); @@ -355,5 +201,4 @@ } } i_fatal("Error reading configuration: No auth section: %s", name); - return NULL; }
--- a/src/auth/auth-settings.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/auth-settings.h Thu Apr 23 19:53:44 2009 -0400 @@ -1,19 +1,7 @@ #ifndef AUTH_SETTINGS_H #define AUTH_SETTINGS_H -struct auth_socket_unix_settings { - const char *path; - unsigned int mode; - const char *user; - const char *group; -}; - -struct auth_socket_settings { - const char *type; - - ARRAY_DEFINE(clients, struct auth_socket_unix_settings *); - ARRAY_DEFINE(masters, struct auth_socket_unix_settings *); -}; +struct master_service; struct auth_passdb_settings { const char *driver; @@ -55,17 +43,15 @@ unsigned int worker_max_count; - ARRAY_DEFINE(sockets, struct auth_socket_settings *); ARRAY_DEFINE(passdbs, struct auth_passdb_settings *); ARRAY_DEFINE(userdbs, struct auth_userdb_settings *); }; struct auth_root_settings { - const char *base_dir; - ARRAY_DEFINE(auths, struct auth_settings *); }; -struct auth_settings *auth_settings_read(const char *name); +struct auth_settings * +auth_settings_read(struct master_service *service, const char *name); #endif
--- a/src/auth/auth-worker-client.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/auth-worker-client.c Thu Apr 23 19:53:44 2009 -0400 @@ -7,6 +7,7 @@ #include "istream.h" #include "ostream.h" #include "str.h" +#include "master-service.h" #include "auth-request.h" #include "auth-worker-client.h" @@ -504,7 +505,7 @@ net_disconnect(client->fd); client->fd = -1; - io_loop_stop(ioloop); + master_service_client_connection_destroyed(service); } void auth_worker_client_unref(struct auth_worker_client **_client)
--- a/src/auth/common.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/common.h Thu Apr 23 19:53:44 2009 -0400 @@ -4,12 +4,8 @@ #include "lib.h" #include "auth.h" -#define MASTER_SOCKET_FD 0 -#define CLIENT_LISTEN_FD 3 -#define WORKER_SERVER_FD 4 - -extern struct ioloop *ioloop; -extern bool standalone, worker, shutdown_request; +extern struct master_service *service; +extern bool worker, shutdown_request; extern time_t process_start_time; #endif
--- a/src/auth/db-ldap.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/db-ldap.c Thu Apr 23 19:53:44 2009 -0400 @@ -18,6 +18,7 @@ #include <stddef.h> #include <stdlib.h> +#include <unistd.h> #define HAVE_LDAP_SASL #ifdef HAVE_SASL_SASL_H @@ -657,7 +658,7 @@ i_fatal("LDAP: Can't get connection fd: %s", ldap_err2string(ret)); } - if (conn->fd <= CLIENT_LISTEN_FD) { + if (conn->fd <= STDERR_FILENO) { /* Solaris LDAP library seems to be broken */ i_fatal("LDAP: Buggy LDAP library returned wrong fd: %d", conn->fd);
--- a/src/auth/main.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/auth/main.c Thu Apr 23 19:53:44 2009 -0400 @@ -6,11 +6,11 @@ #include "network.h" #include "lib-signals.h" #include "restrict-access.h" -#include "fd-close-on-exec.h" #include "child-wait.h" #include "sql-api.h" #include "module-dir.h" #include "randgen.h" +#include "master-service.h" #include "password-scheme.h" #include "mech.h" #include "auth.h" @@ -18,202 +18,30 @@ #include "auth-worker-server.h" #include "auth-worker-client.h" #include "auth-master-interface.h" -#include "auth-master-listener.h" #include "auth-master-connection.h" #include "auth-client-connection.h" -#include <stdio.h> #include <stdlib.h> #include <unistd.h> -#include <syslog.h> -#include <pwd.h> -#include <grp.h> -#include <sys/stat.h> +#include <sys/un.h> -struct ioloop *ioloop; -bool standalone = FALSE, worker = FALSE, shutdown_request = FALSE; +enum auth_socket_type { + AUTH_SOCKET_UNKNOWN = 0, + AUTH_SOCKET_CLIENT, + AUTH_SOCKET_MASTER +}; + +struct master_service *service; +bool worker = FALSE, shutdown_request = FALSE; time_t process_start_time; static struct module *modules = NULL; static struct auth *auth; static struct auth_worker_client *worker_client; - -static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED) -{ - /* warn about being killed because of some signal, except SIGINT (^C) - which is too common at least while testing :) */ - if (si->si_signo != SIGINT) { - i_warning("Killed with signal %d (by pid=%s uid=%s code=%s)", - si->si_signo, dec2str(si->si_pid), - dec2str(si->si_uid), - lib_signal_code_to_str(si->si_signo, si->si_code)); - } - io_loop_stop(ioloop); -} - -static void open_logfile(void) -{ - const char *env; - - if (getenv("LOG_TO_MASTER") != NULL) { - i_set_failure_internal(); - return; - } - - if (getenv("USE_SYSLOG") != NULL) { - env = getenv("SYSLOG_FACILITY"); - i_set_failure_syslog("dovecot-auth", LOG_NDELAY, - env == NULL ? LOG_MAIL : atoi(env)); - } else { - /* log to file or stderr */ - i_set_failure_file(getenv("LOGFILE"), "dovecot-auth: "); - } - - if (getenv("INFOLOGFILE") != NULL) - i_set_info_file(getenv("INFOLOGFILE")); - - i_set_failure_timestamp_format(getenv("LOGSTAMP")); -} - -static uid_t get_uid(const char *user) -{ - struct passwd *pw; +static ARRAY_DEFINE(listen_fd_types, enum auth_socket_type); - if (*user == '\0') - return (uid_t)-1; - if (is_numeric(user, '\0')) - return strtoul(user, NULL, 10); - - errno = 0; - if ((pw = getpwnam(user)) == NULL) { - if (errno != 0) - i_fatal("User '%s' lookup failed: %m", user); - setpwent(); - if (getpwent() == NULL) { - if (errno != 0) - i_fatal("getpwent() failed: %m"); - i_fatal("getpwnam() failed for some reason. " - "Is auth_process_size set to too low?"); - } - i_fatal("User doesn't exist: %s", user); - } - return pw->pw_uid; -} - -static gid_t get_gid(const char *group) -{ - struct group *gr; - - if (*group == '\0') - return (gid_t)-1; - if (is_numeric(group, '\0')) - return strtoul(group, NULL, 10); - - errno = 0; - if ((gr = getgrnam(group)) == NULL) { - if (errno != 0) - i_fatal("Group '%s' lookup failed: %m", group); - else - i_fatal("Group doesn't exist: %s", group); - } - return gr->gr_gid; -} - -static int create_unix_listener(const struct auth_socket_unix_settings *set, - int backlog) +static void main_preinit(struct auth_settings *set) { - mode_t old_umask; - uid_t uid; - gid_t gid; - int fd; - - old_umask = umask((set->mode ^ 0777) & 0777); - fd = net_listen_unix_unlink_stale(set->path, backlog); - umask(old_umask); - if (fd == -1) { - if (errno == EADDRINUSE) - i_fatal("Socket already exists: %s", set->path); - else - i_fatal("net_listen_unix(%s) failed: %m", set->path); - } - - uid = get_uid(set->user); gid = get_gid(set->group); - if (chown(set->path, uid, gid) < 0) { - i_fatal("chown(%s, %s(%s), %s(%s)) failed: %m", - set->path, dec2str(uid), set->user, - dec2str(gid), set->group); - } - return fd; -} - -static void -add_extra_unix_listeners(struct auth_master_listener *listener, - struct auth_socket_unix_settings *const *sets, - unsigned int count, enum listener_type type) -{ - unsigned int i; - int fd; - - for (i = 0; i < count; i++) { - fd = create_unix_listener(sets[i], 128); - auth_master_listener_add(listener, fd, sets[i]->path, type); - } -} - -static void add_extra_listeners(struct auth *auth) -{ - struct auth_master_listener *listener; - struct auth_socket_settings *const *sockets; - struct auth_socket_unix_settings *const *unix_sockets; - unsigned int i, count, count2; - - if (!array_is_created(&auth->set->sockets)) - return; - - sockets = array_get(&auth->set->sockets, &count); - for (i = 0; i < count; i++) { - if (strcmp(sockets[i]->type, "listen") != 0) - continue; - - listener = auth_master_listener_create(auth); - - if (array_is_created(&sockets[i]->masters)) { - unix_sockets = array_get(&sockets[i]->masters, &count2); - add_extra_unix_listeners(listener, unix_sockets, count2, - LISTENER_MASTER); - } - if (array_is_created(&sockets[i]->clients)) { - unix_sockets = array_get(&sockets[i]->clients, &count2); - add_extra_unix_listeners(listener, unix_sockets, count2, - LISTENER_CLIENT); - } - } -} - -static void drop_privileges(void) -{ - const char *version, *name; - - version = getenv("DOVECOT_VERSION"); - if (version != NULL && strcmp(version, PACKAGE_VERSION) != 0) { - i_fatal("Dovecot version mismatch: " - "Master is v%s, dovecot-auth is v"PACKAGE_VERSION" " - "(if you don't care, set version_ignore=yes)", version); - } - - standalone = getenv("DOVECOT_MASTER") == NULL; - if (standalone && getenv("AUTH_1") == NULL) { - i_fatal("dovecot-auth is usually started through " - "dovecot master process. If you wish to run " - "it standalone, you'll need to set AUTH_* " - "environment variables (AUTH_1 isn't set)."); - } - name = getenv("AUTH_NAME"); - if (name == NULL) - i_fatal("Missing AUTH_NAME environment"); - - open_logfile(); - /* Open /dev/urandom before chrooting */ random_init(); @@ -225,30 +53,22 @@ only by root. Also load all modules here. */ passdbs_init(); userdbs_init(); - modules = module_dir_load(AUTH_MODULE_DIR, NULL, TRUE, version); + modules = module_dir_load(AUTH_MODULE_DIR, NULL, TRUE, + master_service_get_version_string(service)); module_dir_init(modules); - auth = auth_preinit(auth_settings_read(name)); - auth_master_listeners_init(); - if (!worker) - add_extra_listeners(auth); + auth = auth_preinit(set); /* Password lookups etc. may require roots, allow it. */ restrict_access_by_env(NULL, FALSE); restrict_access_allow_coredumps(TRUE); } -static void main_init(bool nodaemon) +static void main_init(void) { - struct auth_master_listener *listener; + i_array_init(&listen_fd_types, 8); process_start_time = ioloop_time; - lib_signals_init(); - lib_signals_set_handler(SIGINT, TRUE, sig_die, NULL); - lib_signals_set_handler(SIGTERM, TRUE, sig_die, NULL); - lib_signals_ignore(SIGPIPE, TRUE); - lib_signals_ignore(SIGALRM, FALSE); - /* If auth caches aren't used, just ignore these signals */ lib_signals_ignore(SIGHUP, TRUE); lib_signals_ignore(SIGUSR2, TRUE); @@ -258,40 +78,16 @@ password_schemes_init(); auth_init(auth); auth_request_handler_init(); + auth_master_connections_init(); + auth_client_connections_init(); if (worker) { - worker_client = - auth_worker_client_create(auth, WORKER_SERVER_FD); - return; + /* workers have only a single connection from the master + auth process */ + master_service_set_client_limit(service, 1); + } else if (getenv("MASTER_AUTH_FD") != NULL) { + (void)auth_master_connection_create(auth, MASTER_AUTH_FD); } - - if (getenv("DOVECOT_MASTER") == NULL) { - /* starting standalone */ - if (!nodaemon) { - switch (fork()) { - case -1: - i_fatal("fork() failed: %m"); - case 0: - break; - default: - exit(0); - } - - if (setsid() < 0) - i_fatal("setsid() failed: %m"); - - if (chdir("/") < 0) - i_fatal("chdir(/) failed: %m"); - } - } else { - listener = auth_master_listener_create(auth); - (void)auth_master_connection_create(listener, MASTER_SOCKET_FD); - auth_master_listener_add(listener, CLIENT_LISTEN_FD, - NULL, LISTENER_CLIENT); - } - - /* everything initialized, notify masters that all is well */ - auth_master_listeners_send_handshake(); } static void main_deinit(void) @@ -301,8 +97,9 @@ else auth_request_handler_flush_failures(TRUE); + auth_client_connections_deinit(); + auth_master_connections_deinit(); auth_worker_server_deinit(); - auth_master_listeners_deinit(); module_dir_unload(&modules); userdbs_deinit(); @@ -314,41 +111,88 @@ sql_drivers_deinit(); random_deinit(); - child_wait_deinit(); - lib_signals_deinit(); - closelog(); + array_free(&listen_fd_types); +} + +static void worker_connected(const struct master_service_connection *conn) +{ + auth_worker_client_create(auth, conn->fd); } -int main(int argc ATTR_UNUSED, char *argv[]) +static void client_connected(const struct master_service_connection *conn) { - bool foreground = FALSE; + enum auth_socket_type *type; + + if (worker) { + worker_client = auth_worker_client_create(auth, conn->fd); + return; + } -#ifdef DEBUG - if (getenv("GDB") == NULL) - fd_debug_verify_leaks(WORKER_SERVER_FD + 1, 1024); -#endif - /* NOTE: we start rooted, so keep the code minimal until - restrict_access_by_env() is called */ - lib_init(); - ioloop = io_loop_create(); + type = array_idx_modifiable(&listen_fd_types, conn->listen_fd); + if (*type == AUTH_SOCKET_UNKNOWN) { + /* figure out if this is a server or network socket by + checking the socket path name. */ + struct sockaddr_un sa; + socklen_t addrlen = sizeof(sa); + size_t len; - while (argv[1] != NULL) { - if (strcmp(argv[1], "-F") == 0) - foreground = TRUE; - else if (strcmp(argv[1], "-w") == 0) - worker = TRUE; - argv++; + if (getsockname(conn->listen_fd, (void *)&sa, &addrlen) < 0) + i_fatal("getsockname(%d) failed: %m", conn->listen_fd); + if (sa.sun_family != AF_UNIX) { + i_fatal("getsockname(%d) isn't UNIX socket", + conn->listen_fd); + } + + len = strlen(sa.sun_path); + if (len > 7 && strcmp(sa.sun_path + len - 7, "-master") == 0) + *type = AUTH_SOCKET_MASTER; + else + *type = AUTH_SOCKET_CLIENT; } - T_BEGIN { - drop_privileges(); - main_init(foreground); - } T_END; - io_loop_run(ioloop); + switch (*type) { + case AUTH_SOCKET_MASTER: + (void)auth_master_connection_create(auth, conn->fd); + break; + case AUTH_SOCKET_CLIENT: + (void)auth_client_connection_create(auth, conn->fd); + break; + default: + i_unreached(); + } +} + +int main(int argc, char *argv[]) +{ + const char *getopt_str, *auth_name = "default"; + int c; + + service = master_service_init("auth", 0, argc, argv); + master_service_init_log(service, "auth: ", 0); + + getopt_str = t_strconcat("w", master_service_getopt_string(), NULL); + while ((c = getopt(argc, argv, getopt_str)) > 0) { + switch (c) { + case 'a': + auth_name = optarg; + break; + case 'w': + worker = TRUE; + break; + default: + if (!master_service_parse_option(service, c, optarg)) + exit(FATAL_DEFAULT); + break; + } + } + + main_preinit(auth_settings_read(service, auth_name)); + + master_service_init_finish(service); + main_init(); + master_service_run(service, worker ? worker_connected : + client_connected); main_deinit(); - - io_loop_destroy(&ioloop); - lib_deinit(); - + master_service_deinit(&service); return 0; }
--- a/src/config/Makefile.am Thu Apr 23 14:07:45 2009 -0400 +++ b/src/config/Makefile.am Thu Apr 23 19:53:44 2009 -0400 @@ -1,8 +1,11 @@ +pkglibexecdir = $(libexecdir)/dovecot + bin_PROGRAMS = doveconf AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master \ -DSYSCONFDIR=\""$(sysconfdir)"\" \ -DPKG_RUNDIR=\""$(rundir)"\" \ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \
--- a/src/config/common.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/config/common.h Thu Apr 23 19:53:44 2009 -0400 @@ -3,7 +3,6 @@ #include "lib.h" -extern struct master_service *service; extern ARRAY_TYPE(const_string) config_strings; #endif
--- a/src/config/config-connection.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/config/config-connection.c Thu Apr 23 19:53:44 2009 -0400 @@ -3,6 +3,7 @@ #include "common.h" #include "array.h" #include "str.h" +#include "llist.h" #include "ioloop.h" #include "network.h" #include "istream.h" @@ -20,6 +21,8 @@ #define CONFIG_CLIENT_PROTOCOL_MINOR_VERSION 0 struct config_connection { + struct config_connection *prev, *next; + int fd; struct istream *input; struct ostream *output; @@ -29,6 +32,8 @@ unsigned int handshaked:1; }; +struct config_connection *config_connections = NULL; + static const char *const * config_connection_next_line(struct config_connection *conn) { @@ -93,14 +98,12 @@ conn->version_received = TRUE; } - t_push(); while ((args = config_connection_next_line(conn)) != NULL) { if (args[0] == NULL) continue; if (strcmp(args[0], "REQ") == 0) config_connection_request(conn, args + 1, 0); } - t_pop(); } struct config_connection *config_connection_create(int fd) @@ -112,11 +115,14 @@ conn->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE); conn->output = o_stream_create_fd(fd, (size_t)-1, FALSE); conn->io = io_add(fd, IO_READ, config_connection_input, conn); + DLLIST_PREPEND(&config_connections, conn); return conn; } void config_connection_destroy(struct config_connection *conn) { + DLLIST_REMOVE(&config_connections, conn); + io_remove(&conn->io); i_stream_destroy(&conn->input); o_stream_destroy(&conn->output); @@ -147,3 +153,9 @@ strings[i+1], NULL)); } T_END; } + +void config_connections_destroy_all(void) +{ + while (config_connections != NULL) + config_connection_destroy(config_connections); +}
--- a/src/config/config-connection.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/config/config-connection.h Thu Apr 23 19:53:44 2009 -0400 @@ -13,4 +13,6 @@ enum config_dump_flags flags); void config_connection_putenv(void); +void config_connections_destroy_all(void); + #endif
--- a/src/config/main.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/config/main.c Thu Apr 23 19:53:44 2009 -0400 @@ -1,11 +1,9 @@ /* Copyright (C) 2005-2009 Dovecot authors, see the included COPYING file */ #include "common.h" -#include "lib-signals.h" -#include "ioloop.h" #include "array.h" #include "env-util.h" -#include "str.h" +#include "master-service.h" #include "config-connection.h" #include "config-parser.h" @@ -14,59 +12,62 @@ ARRAY_TYPE(const_string) config_strings; -static const char *config_path = SYSCONFDIR "/" PACKAGE ".conf"; +static struct master_service *service; static pool_t config_pool; -static void main_init(const char *service) +static void main_init(const char *service_name) { - if (getenv("LOG_TO_MASTER") != NULL) - i_set_failure_internal(); - config_pool = pool_alloconly_create("config parser", 10240); p_array_init(&config_strings, config_pool, 256); - config_parse_file(config_pool, &config_strings, config_path, service); + config_parse_file(config_pool, &config_strings, + master_service_get_config_path(service), + service_name); +} + +static void client_connected(const struct master_service_connection *conn) +{ + config_connection_create(conn->fd); } int main(int argc, char *argv[]) { enum config_dump_flags flags = 0; - struct ioloop *ioloop; - const char *service = ""; + const char *getopt_str, *service_name = ""; char **exec_args = NULL; - int i; + int c; + + service = master_service_init("config", 0, argc, argv); - lib_init(); - - for (i = 1; i < argc; i++) { - if (strcmp(argv[i], "-a") == 0) { + getopt_str = t_strconcat("anp:e", master_service_getopt_string(), NULL); + while ((c = getopt(argc, argv, getopt_str)) > 0) { + if (c == 'e') + break; + switch (c) { + case 'a': flags |= CONFIG_DUMP_FLAG_HUMAN | CONFIG_DUMP_FLAG_DEFAULTS; - } else if (strcmp(argv[i], "-c") == 0) { - /* config file */ - i++; - if (i == argc) i_fatal("Missing config file argument"); - config_path = argv[i]; - } else if (strcmp(argv[i], "-n") == 0) { + break; + case 'n': flags |= CONFIG_DUMP_FLAG_HUMAN; - } else if (strcmp(argv[i], "-s") == 0) { - /* service */ - i++; - if (i == argc) i_fatal("Missing service argument"); - service = argv[i]; - } else if (strcmp(argv[i], "--exec") == 0) { - /* <command> [<args>] */ - i++; - if (i == argc) i_fatal("Missing exec binary argument"); - exec_args = &argv[i]; + break; + case 'p': + service_name = optarg; break; - } else { - i_fatal("Unknown parameter: %s", argv[i]); + default: + if (!master_service_parse_option(service, c, optarg)) + exit(FATAL_DEFAULT); } } + if (argv[optind] != NULL) + exec_args = &argv[optind]; - main_init(service); - ioloop = io_loop_create(); - if (exec_args == NULL) + master_service_init_log(service, "doveconf: ", 0); + master_service_init_finish(service); + main_init(service_name); + + if (master_service_get_socket_count(service) > 0) + master_service_run(service, client_connected); + else if (exec_args == NULL) config_connection_dump_request(STDOUT_FILENO, "master", flags); else { config_connection_putenv(); @@ -74,8 +75,8 @@ execvp(exec_args[0], exec_args); i_fatal("execvp(%s) failed: %m", exec_args[0]); } + config_connections_destroy_all(); pool_unref(&config_pool); - io_loop_destroy(&ioloop); - lib_deinit(); + master_service_deinit(&service); return 0; }
--- a/src/config/settings-get.pl Thu Apr 23 14:07:45 2009 -0400 +++ b/src/config/settings-get.pl Thu Apr 23 19:53:44 2009 -0400 @@ -38,7 +38,7 @@ $code .= $_; } - if (/#define.*DEF/ || /^#undef.*DEF/) { + if (/#define.*DEF/ || /^#undef.*DEF/ || /ARRAY_DEFINE_TYPE.*_settings/) { $write = 1; $state = 2 if (/\\$/); }
--- a/src/imap-login/Makefile.am Thu Apr 23 14:07:45 2009 -0400 +++ b/src/imap-login/Makefile.am Thu Apr 23 19:53:44 2009 -0400 @@ -6,6 +6,7 @@ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-auth \ -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-master \ -I$(top_srcdir)/src/login-common imap_login_LDADD = \
--- a/src/imap-login/client.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/imap-login/client.c Thu Apr 23 19:53:44 2009 -0400 @@ -11,6 +11,8 @@ #include "strescape.h" #include "imap-parser.h" #include "imap-id.h" +#include "master-service.h" +#include "master-auth.h" #include "client.h" #include "client-authenticate.h" #include "auth-client.h" @@ -42,10 +44,13 @@ # error client idle timeout must be smaller than authentication timeout #endif -#define AUTH_WAITING_MSG \ +#define AUTH_SERVER_WAITING_MSG \ "* OK Waiting for authentication process to respond.." +#define AUTH_MASTER_WAITING_MSG \ + "* OK Waiting for authentication master process to respond.." const char *login_protocol = "IMAP"; +const char *login_process_name = "imap-login"; static void client_set_title(struct imap_client *client) { @@ -119,7 +124,6 @@ int fd_ssl; client_ref(client); - connection_queue_add(1); if (!client_unref(client) || client->destroyed) return; @@ -132,6 +136,7 @@ return; } + client->common.proxying = TRUE; client->common.tls = TRUE; client->common.secured = TRUE; client_set_title(client); @@ -410,7 +415,7 @@ if (!auth_client_is_connected(auth_client)) { /* we're not yet connected to auth process - don't allow any commands */ - client_send_line(client, AUTH_WAITING_MSG); + client_send_line(client, AUTH_SERVER_WAITING_MSG); if (client->to_auth_waiting != NULL) timeout_remove(&client->to_auth_waiting); @@ -483,7 +488,8 @@ static void client_auth_waiting_timeout(struct imap_client *client) { - client_send_line(client, AUTH_WAITING_MSG); + client_send_line(client, client->common.master_tag == 0 ? + AUTH_SERVER_WAITING_MSG : AUTH_MASTER_WAITING_MSG); timeout_remove(&client->to_auth_waiting); } @@ -502,7 +508,11 @@ i_assert(fd != -1); - connection_queue_add(1); + if (clients_get_count() >= login_settings->login_max_connections) { + /* reached max. users count, kill few of the + oldest connections */ + client_destroy_oldest(); + } /* always use nonblocking I/O */ net_set_nonblock(fd, TRUE); @@ -524,8 +534,6 @@ client_link(&client->common); - main_ref(); - if (auth_client_is_connected(auth_client)) client_send_greeting(client); else @@ -559,10 +567,11 @@ if (client->output != NULL) o_stream_close(client->output); - if (client->common.master_tag != 0) - master_request_abort(&client->common); - - if (client->common.auth_request != NULL) { + if (client->common.master_tag != 0) { + i_assert(client->common.auth_request == NULL); + i_assert(client->common.authenticating); + master_auth_request_abort(service, client->common.master_tag); + } else if (client->common.auth_request != NULL) { i_assert(client->common.authenticating); sasl_server_auth_client_error(&client->common, NULL); } else { @@ -602,9 +611,6 @@ client->common.proxy = NULL; } client_unref(client); - - main_listen_start(); - main_unref(); } void client_destroy_success(struct imap_client *client, const char *reason) @@ -640,10 +646,14 @@ if (client->output != NULL) o_stream_unref(&client->output); + if (!client->common.proxying) { + i_assert(client->common.proxy == NULL); + master_service_client_connection_destroyed(service); + } + i_free(client->common.virtual_user); i_free(client->common.auth_mech_name); i_free(client); - return FALSE; }
--- a/src/imap-login/client.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/imap-login/client.h Thu Apr 23 19:53:44 2009 -0400 @@ -2,7 +2,6 @@ #define CLIENT_H #include "network.h" -#include "master.h" #include "client-common.h" /* Disconnect client after idling this many milliseconds */
--- a/src/imap-login/imap-proxy.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/imap-login/imap-proxy.c Thu Apr 23 19:53:44 2009 -0400 @@ -244,6 +244,7 @@ client->common.input = NULL; client->output = NULL; client->common.fd = -1; + client->common.proxying = TRUE; client_destroy_success(client, str_c(str)); return 1; } else if (strncmp(line, "L ", 2) == 0) { @@ -362,7 +363,6 @@ } i_assert(client->refcount > 1); - connection_queue_add(1); if (client->destroyed) { /* connection_queue_add() decided that we were the oldest
--- a/src/imap/main.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/imap/main.c Thu Apr 23 19:53:44 2009 -0400 @@ -19,7 +19,7 @@ #include <unistd.h> #define IS_STANDALONE() \ - (getenv("IMAPLOGINTAG") == NULL) + (getenv("CLIENT_INPUT") == NULL) struct client_workaround_list { const char *name; @@ -70,12 +70,44 @@ return client_workarounds; } +static void client_add_input(struct client *client, const char *input) +{ + buffer_t *buf; + const char *tag; + unsigned int data_pos; + + buf = input == NULL ? NULL : t_base64_decode_str(input); + if (buf != NULL && buf->used > 0) { + tag = t_strndup(buf->data, buf->used); + data_pos = strlen(tag) + 1; + if (data_pos > buf->used && + !i_stream_add_data(client->input, + CONST_PTR_OFFSET(buf->data, data_pos), + buf->used - data_pos)) + i_panic("Couldn't add client input to stream"); + } else { + /* IMAPLOGINTAG environment is compatible with mailfront */ + tag = getenv("IMAPLOGINTAG"); + } + + if (tag == NULL) { + client_send_line(client, t_strconcat( + "* PREAUTH [CAPABILITY ", + str_c(client->capability_string), "] " + "Logged in as ", client->user->username, NULL)); + } else { + client_send_line(client, t_strconcat( + tag, " OK [CAPABILITY ", + str_c(client->capability_string), "] Logged in", NULL)); + } + (void)client_handle_input(client); +} + static void main_init(const struct imap_settings *set, struct mail_user *user, bool dump_capability) { struct client *client; struct ostream *output; - const char *str, *tag; if (set->shutdown_clients && !dump_capability) { /* If master dies, the log fd gets closed and we'll quit */ @@ -96,29 +128,7 @@ output = client->output; o_stream_ref(output); o_stream_cork(output); - - /* IMAPLOGINTAG environment is compatible with mailfront */ - tag = getenv("IMAPLOGINTAG"); - if (tag == NULL) { - client_send_line(client, t_strconcat( - "* PREAUTH [CAPABILITY ", - str_c(client->capability_string), "] " - "Logged in as ", user->username, NULL)); - } else { - client_send_line(client, t_strconcat( - tag, " OK [CAPABILITY ", - str_c(client->capability_string), "] Logged in", NULL)); - } - 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"); - (void)client_handle_input(client); - } - } T_END; + client_add_input(client, getenv("CLIENT_INPUT")); o_stream_uncork(output); o_stream_unref(&output); } @@ -130,6 +140,12 @@ clients_deinit(); } +static void client_connected(const struct master_service_connection *conn) +{ + /* FIXME: we can't handle this yet */ + (void)close(conn->fd); +} + int main(int argc, char *argv[], char *envp[]) { const struct setting_parser_info *set_roots[] = { @@ -145,10 +161,6 @@ const char *value; int c; -#ifdef DEBUG - if (!IS_STANDALONE() && getenv("GDB") == NULL) - fd_debug_verify_leaks(3, 1024); -#endif if (IS_STANDALONE() && getuid() == 0 && net_getpeername(1, NULL, NULL) == 0) { printf("* BAD [ALERT] imap binary must not be started from " @@ -170,7 +182,7 @@ service = master_service_init("imap", service_flags, argc, argv); while ((c = getopt(argc, argv, master_service_getopt_string())) > 0) { if (!master_service_parse_option(service, c, optarg)) - i_fatal("Unknown argument: %c", c); + exit(FATAL_DEFAULT); } memset(&input, 0, sizeof(input)); @@ -200,9 +212,11 @@ while initializing */ io_loop_set_running(current_ioloop); - main_init(set, mail_user, dump_capability); + T_BEGIN { + main_init(set, mail_user, dump_capability); + } T_END; if (io_loop_is_running(current_ioloop)) - master_service_run(service); + master_service_run(service, client_connected); main_deinit(); mail_storage_service_deinit_user();
--- a/src/lda/main.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lda/main.c Thu Apr 23 19:53:44 2009 -0400 @@ -224,9 +224,6 @@ service = master_service_init("lda", MASTER_SERVICE_FLAG_STANDALONE, argc, argv); -#ifdef SIGXFSZ - lib_signals_ignore(SIGXFSZ, TRUE); -#endif memset(&ctx, 0, sizeof(ctx)); ctx.pool = pool_alloconly_create("mail deliver context", 256); @@ -282,8 +279,7 @@ default: if (!master_service_parse_option(service, c, optarg)) { print_help(); - i_fatal_status(EX_USAGE, - "Unknown argument: %c", c); + exit(EX_USAGE); } break; } @@ -325,6 +321,9 @@ ctx.dest_user = mail_storage_service_init_user(service, &service_input, set_roots, service_flags); +#ifdef SIGXFSZ + lib_signals_ignore(SIGXFSZ, TRUE); +#endif ctx.set = mail_storage_service_get_settings(service); duplicate_init(mail_user_set_get_storage_set(ctx.dest_user->set));
--- a/src/lib-master/Makefile.am Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-master/Makefile.am Thu Apr 23 19:53:44 2009 -0400 @@ -8,11 +8,14 @@ -DPKG_RUNDIR=\""$(rundir)"\" libmaster_la_SOURCES = \ + master-auth.c \ master-service.c \ master-service-settings.c \ syslog-util.c noinst_HEADERS = \ + master-auth.h \ + master-interface.h \ master-service.h \ master-service-private.h \ master-service-settings.h \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-master/master-auth.c Thu Apr 23 19:53:44 2009 -0400 @@ -0,0 +1,191 @@ +/* Copyright (C) 2005 Timo Sirainen */ + +#include "lib.h" +#include "ioloop.h" +#include "fdpass.h" +#include "buffer.h" +#include "hash.h" +#include "master-service-private.h" +#include "master-auth.h" + +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> + +struct master_auth { + struct master_service *service; + pool_t pool; + + 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; + +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) +{ + struct master_auth *auth = service->auth; + struct master_auth_request_node *node; + struct master_auth_request req; + buffer_t *buf; + struct stat st; + ssize_t ret; + + i_assert(request->auth_pid != 0); + + req = *request; + req.tag = ++auth->tag_counter; + if (req.tag == 0) + req.tag = ++auth->tag_counter; + + if (fstat(fd, &st) < 0) + i_fatal("fstat(auth dest fd) failed: %m"); + req.ino = st.st_ino; + + buf = buffer_create_dynamic(pool_datastack_create(), + sizeof(req) + req.data_size); + 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; + + 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 request_handle(struct master_auth *auth, + struct master_auth_reply *reply) +{ + struct master_auth_request_node *node; + + 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; +} + +void master_auth_init(struct master_service *service) +{ + 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"); + + 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; +} + +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; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-master/master-auth.h Thu Apr 23 19:53:44 2009 -0400 @@ -0,0 +1,26 @@ +#ifndef MASTER_AUTH_H +#define MASTER_AUTH_H + +struct master_service; +struct master_auth_request; +struct master_auth_reply; + +typedef void master_auth_callback_t(const struct master_auth_reply *reply, + void *context); + +/* 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); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-master/master-interface.h Thu Apr 23 19:53:44 2009 -0400 @@ -0,0 +1,104 @@ +#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. */ + +/* Child processes should send status updates whenever they accept a new + connection (decrease available_count) and when they close existing + connection (increase available_count). */ +struct master_status { + pid_t pid; + /* uid is used to check for old/invalid status messages */ + unsigned int uid; + /* number of new connections process is currently accepting */ + unsigned int available_count; +}; + +/* When connecting to log service, send this handshake first */ +struct log_service_handshake { + /* If magic is invalid, assume the data is already what we want + to log */ +#define MASTER_LOG_MAGIC 0x02ff03fe + unsigned int log_magic; + + /* If we're writing log lines more often than this, start throttling */ + unsigned int max_lines_per_sec; + + /* Add this previs to each logged line */ + unsigned int prefix_len; + /* 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 (4096*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 and authentication ID. */ + pid_t auth_pid; + unsigned int auth_id; + + /* 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, + /* user reached max. simultaneous connections */ + MASTER_AUTH_STATUS_MAX_CONNECTIONS +}; + +struct master_auth_reply { + unsigned int tag; + enum master_auth_status status; + /* PID of the post-login mail process handling this connection */ + pid_t mail_pid; +}; + +/* getenv(MASTER_UID_ENV) provides master_status.uid value */ +#define MASTER_UID_ENV "GENERATION" + +/* getenv(MASTER_CLIENT_LIMIT_ENV) provides maximum + master_status.available_count as specified in configuration file */ +#define MASTER_CLIENT_LIMIT_ENV "CLIENT_LIMIT" + +/* getenv(MASTER_CONFIG_FILE_ENV) provides path to configuration file/socket */ +#define MASTER_CONFIG_FILE_ENV "CONFIG_FILE" + +/* getenv(MASTER_DOVECOT_VERSION_ENV) provides master's version number */ +#define MASTER_DOVECOT_VERSION_ENV "DOVECOT_VERSION" + +/* points to /dev/null for now */ +#define MASTER_RESERVED_FD 3 + +/* Socket for sending master_auth_requests. Also used by auth server process + as a master socket. */ +#define MASTER_AUTH_FD 4 + +/* Shared pipe to master, used to send master_status reports */ +#define MASTER_STATUS_FD 5 +/* First file descriptor where process is expected to be listening. + The file descriptor count is given in -s parameter, defaulting to 1. + + master_status.available_count reports how many accept()s we're still + accepting. Once no children are listening, master will do it and create + new child processes when needed. */ +#define MASTER_LISTEN_FD_FIRST 6 + +#endif
--- a/src/lib-master/master-service-private.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-master/master-service-private.h Thu Apr 23 19:53:44 2009 -0400 @@ -1,8 +1,15 @@ #ifndef MASTER_SERVICE_PRIVATE_H #define MASTER_SERVICE_PRIVATE_H +#include "master-interface.h" #include "master-service.h" +struct master_service_listener { + struct master_service *service; + int fd; + struct io *io; +}; + struct master_service { struct ioloop *ioloop; @@ -16,12 +23,24 @@ const char *config_path; int syslog_facility; + unsigned int socket_count; + struct master_service_listener *listeners; + + struct io *io_status_write, *io_status_error; + unsigned int service_count_left; + unsigned int total_available_count; + struct master_status master_status; + + struct master_auth *auth; + master_service_connection_callback_t *callback; + pool_t set_pool; const struct master_service_settings *set; struct setting_parser_context *set_parser; unsigned int keep_environment:1; unsigned int log_directly:1; + unsigned int initial_status_sent:1; }; #endif
--- a/src/lib-master/master-service-settings.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-master/master-service-settings.c Thu Apr 23 19:53:44 2009 -0400 @@ -2,6 +2,8 @@ #include "lib.h" #include "array.h" +#include "istream.h" +#include "write-full.h" #include "settings-parser.h" #include "master-service-private.h" #include "master-service-settings.h" @@ -9,9 +11,13 @@ #include <stddef.h> #include <stdlib.h> #include <unistd.h> +#include <sys/stat.h> #define DOVECOT_CONFIG_BIN_PATH BINDIR"/doveconf" +#define CONFIG_HANDSHAKE "VERSION\t1\t0\n" +#define CONFIG_REQUEST_SERVICE "REQ\tservice=%s\n" + #undef DEF #define DEF(type, name) \ { type, #name, offsetof(struct master_service_settings, name), NULL } @@ -57,17 +63,49 @@ /* @UNSAFE */ conf_argv = t_new(const char *, 6 + (service->argc + 1) + 1); conf_argv[0] = DOVECOT_CONFIG_BIN_PATH; - conf_argv[1] = "-s"; + conf_argv[1] = "-p"; conf_argv[2] = service->name; conf_argv[3] = "-c"; conf_argv[4] = service->config_path; - conf_argv[5] = "--exec"; + conf_argv[5] = "-e"; memcpy(conf_argv+6, service->argv, (service->argc+1) * sizeof(conf_argv[0])); execv(conf_argv[0], (char **)conf_argv); i_fatal("execv(%s) failed: %m", conf_argv[0]); } +static int +master_service_read_config(struct master_service *service, bool preserve_home, + const char **error_r) +{ + const char *path, *str; + int fd; + + path = master_service_get_config_path(service); + fd = net_connect_unix(path); + if (fd < 0) { + struct stat st; + + *error_r = t_strdup_printf("net_connect_unix(%s) failed: %m", + path); + + if (stat(path, &st) == 0 && !S_ISFIFO(st.st_mode)) { + /* it's a file, not a socket */ + master_service_exec_config(service, preserve_home); + } + return -1; + } + net_set_nonblock(fd, FALSE); + + str = t_strdup_printf(CONFIG_HANDSHAKE CONFIG_REQUEST_SERVICE, + service->name); + if (write_full(fd, str, strlen(str)) < 0) { + *error_r = t_strdup_printf("write_full(%s) failed: %m", path); + return -1; + } + return fd; +} + int master_service_settings_read(struct master_service *service, const struct setting_parser_info *roots[], const struct dynamic_settings_parser *dyn_parsers, @@ -76,12 +114,18 @@ ARRAY_DEFINE(all_roots, const struct setting_parser_info *); const struct setting_parser_info *tmp_root; struct setting_parser_context *parser; + struct istream *input; const char *error; void **sets; unsigned int i; + int ret, fd = -1; - if (getenv("DOVECONF_ENV") == NULL) - master_service_exec_config(service, preserve_home); + if (getenv("DOVECONF_ENV") == NULL) { + fd = master_service_read_config(service, preserve_home, + error_r); + if (fd == -1) + return -1; + } if (service->set_pool != NULL) p_clear(service->set_pool); @@ -96,16 +140,29 @@ p_array_init(&all_roots, service->set_pool, 8); tmp_root = &master_service_setting_parser_info; array_append(&all_roots, &tmp_root, 1); - for (i = 0; roots[i] != NULL; i++) - array_append(&all_roots, &roots[i], 1); + if (roots != NULL) { + for (i = 0; roots[i] != NULL; i++) + array_append(&all_roots, &roots[i], 1); + } parser = settings_parser_init_list(service->set_pool, array_idx(&all_roots, 0), array_count(&all_roots), SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS); - if (settings_parse_environ(parser) < 0) { - *error_r = settings_parser_get_error(parser); - return -1; + if (fd != -1) { + input = i_stream_create_fd(fd, (size_t)-1, FALSE); + ret = settings_parse_stream_read(parser, input); + i_stream_unref(&input); + i_assert(ret <= 0); + if (ret < 0) { + *error_r = settings_parser_get_error(parser); + return -1; + } + } else { + if (settings_parse_environ(parser) < 0) { + *error_r = settings_parser_get_error(parser); + return -1; + } } if (settings_parser_check(parser, service->set_pool, &error) < 0) {
--- a/src/lib-master/master-service.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-master/master-service.c Thu Apr 23 19:53:44 2009 -0400 @@ -13,6 +13,7 @@ #include <stdlib.h> #include <unistd.h> +#include <sys/stat.h> #include <syslog.h> #define DEFAULT_CONFIG_FILE_PATH SYSCONFDIR"/dovecot.conf" @@ -23,9 +24,13 @@ /* getenv(MASTER_DOVECOT_VERSION_ENV) provides master's version number */ #define MASTER_DOVECOT_VERSION_ENV "DOVECOT_VERSION" +static void io_listeners_add(struct master_service *service); +static void io_listeners_remove(struct master_service *service); +static void master_status_update(struct master_service *service); + const char *master_service_getopt_string(void) { - return "c:Lk"; + return "c:ks:L"; } static void sig_die(const siginfo_t *si, void *context) @@ -69,7 +74,7 @@ is properly initialized */ i_set_failure_prefix(t_strdup_printf("%s(init): ", name)); - if (getenv("LOG_TO_MASTER") == NULL) + if (getenv(MASTER_UID_ENV) == NULL) flags |= MASTER_SERVICE_FLAG_STANDALONE; service = i_new(struct master_service, 1); @@ -78,18 +83,22 @@ service->name = i_strdup(name); service->flags = flags; service->ioloop = io_loop_create(); + service->service_count_left = (unsigned int)-1; + service->config_path = getenv(MASTER_CONFIG_FILE_ENV); if (service->config_path == NULL) service->config_path = DEFAULT_CONFIG_FILE_PATH; - if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) + if ((flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) { service->version_string = getenv(MASTER_DOVECOT_VERSION_ENV); - else + service->socket_count = 1; + } else { service->version_string = PACKAGE_VERSION; + } /* set up some kind of logging until we know exactly how and where we want to log */ - if (getenv("LOG_TO_MASTER") != NULL) + if (getenv("LOG_SERVICE") != NULL) i_set_failure_internal(); if (getenv("USER") != NULL) { i_set_failure_prefix(t_strdup_printf("%s(%s): ", @@ -98,18 +107,12 @@ i_set_failure_prefix(t_strdup_printf("%s: ", name)); } - /* set default signal handlers */ - lib_signals_init(); - lib_signals_ignore(SIGPIPE, TRUE); - lib_signals_ignore(SIGALRM, FALSE); - lib_signals_set_handler(SIGINT, TRUE, sig_die, service); - lib_signals_set_handler(SIGTERM, TRUE, sig_die, service); - master_service_verify_version(service); return service; } -void master_service_init_log(struct master_service *service, const char *prefix) +void master_service_init_log(struct master_service *service, const char *prefix, + unsigned int max_lines_per_sec) { const char *path; @@ -118,13 +121,18 @@ return; } - if (getenv("LOG_TO_MASTER") != NULL && !service->log_directly) { - /* logging via master process */ + if (getenv("LOG_SERVICE") != NULL && !service->log_directly) { + /* logging via log service */ i_set_failure_internal(); i_set_failure_prefix(prefix); return; } + if (service->set == NULL) { + i_set_failure_file("/dev/stderr", prefix); + return; + } + if (*service->set->log_path == '\0') { /* log to syslog */ int facility; @@ -149,6 +157,8 @@ bool master_service_parse_option(struct master_service *service, int opt, const char *arg) { + int i; + switch (opt) { case 'c': service->config_path = arg; @@ -156,6 +166,11 @@ case 'k': service->keep_environment = TRUE; break; + case 's': + if ((i = atoi(arg)) < 0) + i_fatal("Invalid socket count: %s", arg); + service->socket_count = i; + break; case 'L': service->log_directly = TRUE; break; @@ -165,6 +180,77 @@ return TRUE; } +static void master_status_error(void *context) +{ + struct master_service *service = context; + + /* status fd is a write-only pipe, so if we're here it means the + master wants us to die (or died itself). don't die until all + service connections are finished. */ + io_remove(&service->io_status_error); + + /* the log fd may also be closed already, don't die when trying to + log later */ + i_set_failure_ignore_errors(TRUE); + + if (service->master_status.available_count == + service->total_available_count) + master_service_stop(service); +} + +void master_service_init_finish(struct master_service *service) +{ + struct stat st; + const char *value; + unsigned int count; + + i_assert(service->total_available_count == 0); + i_assert(service->service_count_left == (unsigned int)-1); + + /* set default signal handlers */ + lib_signals_init(); + lib_signals_ignore(SIGPIPE, TRUE); + 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_STANDALONE) == 0) { + if (fstat(MASTER_STATUS_FD, &st) < 0 || !S_ISFIFO(st.st_mode)) + i_fatal("Must be started by dovecot master process"); + + /* initialize master_status structure */ + value = getenv(MASTER_UID_ENV); + if (value == NULL) + i_fatal(MASTER_UID_ENV" not set"); + service->master_status.pid = getpid(); + service->master_status.uid = + (unsigned int)strtoul(value, NULL, 10); + + /* set the default limit */ + value = getenv(MASTER_CLIENT_LIMIT_ENV); + count = value == NULL ? 0 : + (unsigned int)strtoul(value, NULL, 10); + if (count == 0) + i_fatal(MASTER_CLIENT_LIMIT_ENV" not set"); + master_service_set_client_limit(service, count); + + /* start listening errors for status fd, it means master died */ + service->io_status_error = io_add(MASTER_STATUS_FD, IO_ERROR, + master_status_error, service); + } else { + master_service_set_client_limit(service, 1); + master_service_set_service_count(service, 1); + } + + io_listeners_add(service); + + if ((service->flags & MASTER_SERVICE_FLAG_STD_CLIENT) != 0) { + /* we already have a connection to be served */ + service->master_status.available_count--; + } + master_status_update(service); +} + void master_service_env_clean(bool preserve_home) { const char *user, *tz, *home; @@ -189,6 +275,47 @@ if (home != NULL) env_put(home); } +void master_service_set_client_limit(struct master_service *service, + unsigned int client_limit) +{ + i_assert(service->master_status.available_count == + service->total_available_count); + + service->total_available_count = client_limit; + service->master_status.available_count = client_limit; +} + +unsigned int master_service_get_client_limit(struct master_service *service) +{ + return service->total_available_count; +} + +void master_service_set_service_count(struct master_service *service, + unsigned int count) +{ + unsigned int used; + + used = service->total_available_count - + service->master_status.available_count; + i_assert(count >= used); + + if (service->total_available_count > count) { + service->total_available_count = count; + service->master_status.available_count = count - used; + } + service->service_count_left = count; +} + +unsigned int master_service_get_service_count(struct master_service *service) +{ + return service->service_count_left; +} + +unsigned int master_service_get_socket_count(struct master_service *service) +{ + return service->socket_count; +} + const char *master_service_get_config_path(struct master_service *service) { return service->config_path; @@ -199,9 +326,12 @@ return service->version_string; } -void master_service_run(struct master_service *service) +void master_service_run(struct master_service *service, + master_service_connection_callback_t *callback) { + service->callback = callback; io_loop_run(service->ioloop); + service->callback = NULL; } void master_service_stop(struct master_service *service) @@ -209,11 +339,54 @@ io_loop_stop(service->ioloop); } +void master_service_client_connection_destroyed(struct master_service *service) +{ + if (service->listeners == NULL) { + /* we can listen again */ + io_listeners_add(service); + } + + i_assert(service->total_available_count > 0); + + if (service->service_count_left != service->total_available_count) { + i_assert(service->service_count_left == (unsigned int)-1); + service->master_status.available_count++; + } else { + /* we have only limited amount of service requests left */ + i_assert(service->service_count_left > 0); + service->service_count_left--; + service->total_available_count--; + + if (service->service_count_left == 0) { + i_assert(service->master_status.available_count == + service->total_available_count); + master_service_stop(service); + } + } + master_status_update(service); + + if (service->io_status_error == NULL && + service->master_status.available_count == + service->total_available_count) { + /* master has closed the connection and we have nothing else + to do anymore. */ + master_service_stop(service); + } +} + void master_service_deinit(struct master_service **_service) { struct master_service *service = *_service; *_service = NULL; + + io_listeners_remove(service); + + if (service->io_status_error != NULL) + io_remove(&service->io_status_error); + if (service->io_status_write != NULL) + io_remove(&service->io_status_write); + lib_signals_deinit(); io_loop_destroy(&service->ioloop); @@ -222,3 +395,115 @@ lib_deinit(); } + +static void master_service_listen(struct master_service_listener *l) +{ + struct master_service_connection conn; + + if (l->service->master_status.available_count == 0) { + /* we are full. stop listening for now. */ + io_listeners_remove(l->service); + return; + } + + memset(&conn, 0, sizeof(conn)); + conn.listen_fd = l->fd; + conn.fd = net_accept(l->fd, &conn.remote_ip, &conn.remote_port); + if (conn.fd < 0) { + if (conn.fd == -1) + return; + + if (errno != ENOTSOCK) { + i_error("net_accept() failed: %m"); + io_listeners_remove(l->service); + return; + } + /* it's not a socket. probably a fifo. use the "listener" + as the connection fd */ + io_remove(&l->io); + conn.fd = l->fd; + } + + l->service->master_status.available_count--; + master_status_update(l->service); + + l->service->callback(&conn); +} + +static void io_listeners_add(struct master_service *service) +{ + unsigned int i; + + if (service->socket_count == 0) + return; + + service->listeners = + i_new(struct master_service_listener, service->socket_count); + + for (i = 0; i < service->socket_count; i++) { + struct master_service_listener *l = &service->listeners[i]; + + l->service = service; + l->fd = MASTER_LISTEN_FD_FIRST + i; + l->io = io_add(MASTER_LISTEN_FD_FIRST + i, IO_READ, + master_service_listen, l); + } +} + +static void io_listeners_remove(struct master_service *service) +{ + unsigned int i; + + if (service->listeners != NULL) { + for (i = 0; i < service->socket_count; i++) { + if (service->listeners[i].io != NULL) + io_remove(&service->listeners[i].io); + } + i_free_and_null(service->listeners); + } +} + +static bool master_status_update_is_important(struct master_service *service) +{ + if (service->master_status.available_count == 0) + return TRUE; + if (!service->initial_status_sent) + return TRUE; + return FALSE; +} + +static void master_status_update(struct master_service *service) +{ + ssize_t ret; + + if (service->master_status.pid == 0) + return; /* closed */ + + ret = write(MASTER_STATUS_FD, &service->master_status, + sizeof(service->master_status)); + if (ret > 0) { + /* success */ + if (service->io_status_write != NULL) { + /* delayed important update sent successfully */ + io_remove(&service->io_status_write); + } + service->initial_status_sent = TRUE; + } else if (ret == 0) { + /* shouldn't happen? */ + i_error("write(master_status_fd) returned 0"); + service->master_status.pid = 0; + } else if (errno != EAGAIN) { + /* failure */ + if (errno != EPIPE) + i_error("write(master_status_fd) failed: %m"); + service->master_status.pid = 0; + } else if (master_status_update_is_important(service)) { + /* reader is busy, but it's important to get this notification + through. send it when possible. */ + if (service->io_status_write == NULL) { + service->io_status_write = + io_add(MASTER_STATUS_FD, IO_WRITE, + master_status_update, service); + } + } +}
--- a/src/lib-master/master-service.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-master/master-service.h Thu Apr 23 19:53:44 2009 -0400 @@ -12,6 +12,17 @@ MASTER_SERVICE_FLAG_LOG_TO_STDERR = 0x04 }; +struct master_service_connection { + int fd; + int listen_fd; + + struct ip_addr remote_ip; + unsigned int remote_port; +}; + +typedef void +master_service_connection_callback_t(const struct master_service_connection *conn); + struct master_service; const char *master_service_getopt_string(void); @@ -23,13 +34,32 @@ /* Parser command line option. Returns TRUE if processed. */ bool master_service_parse_option(struct master_service *service, int opt, const char *arg); +/* Finish service initialization. The caller should drop privileges + before calling this. */ +void master_service_init_finish(struct master_service *service); /* Clean environment from everything except TZ, USER and optionally HOME. */ void master_service_env_clean(bool preserve_home); /* Initialize logging. */ -void master_service_init_log(struct master_service *service, - const char *prefix); +void master_service_init_log(struct master_service *service, const char *prefix, + unsigned int max_lines_per_sec); + +/* Set maximum number of clients we can handle. Default is given by master. */ +void master_service_set_client_limit(struct master_service *service, + unsigned int client_limit); +/* Returns the maximum number of clients we can handle. */ +unsigned int master_service_get_client_limit(struct master_service *service); + +/* Set maximum number of client connections we will handle before shutting + down. */ +void master_service_set_service_count(struct master_service *service, + unsigned int count); +/* Returns the number of client connections we will handle before shutting + down. The value is decreased only after connection has been closed. */ +unsigned int master_service_get_service_count(struct master_service *service); +/* Return the number of listener sockets. */ +unsigned int master_service_get_socket_count(struct master_service *service); /* Returns configuration file path. */ const char *master_service_get_config_path(struct master_service *service); @@ -38,10 +68,14 @@ const char *master_service_get_version_string(struct master_service *service); /* Start the service. Blocks until finished */ -void master_service_run(struct master_service *service); +void master_service_run(struct master_service *service, + master_service_connection_callback_t *callback); /* Stop a running service. */ void master_service_stop(struct master_service *service); +/* Call whenever a client connection is destroyed. */ +void master_service_client_connection_destroyed(struct master_service *service); + /* Deinitialize the service. */ void master_service_deinit(struct master_service **service);
--- a/src/lib-settings/settings-parser.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-settings/settings-parser.c Thu Apr 23 19:53:44 2009 -0400 @@ -635,7 +635,7 @@ static const char *argv[] = { NULL, "-c", NULL, - "-s", NULL, + "-p", NULL, NULL }; argv[0] = bin_path;
--- a/src/lib-settings/settings-parser.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-settings/settings-parser.h Thu Apr 23 19:53:44 2009 -0400 @@ -108,7 +108,7 @@ int settings_parse_stream(struct setting_parser_context *ctx, struct istream *input); /* Read data from input stream and parser it. returns -1 = error, - 0 = eof/stream error, 1 = not finished yet (stream is non-blocking) */ + 0 = done, 1 = not finished yet (stream is non-blocking) */ int settings_parse_stream_read(struct setting_parser_context *ctx, struct istream *input); /* Open file and parse it. */
--- a/src/lib-storage/mail-storage-service.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-storage/mail-storage-service.c Thu Apr 23 19:53:44 2009 -0400 @@ -448,7 +448,8 @@ str = t_str_new(256); var_expand(str, user_set->mail_log_prefix, get_var_expand_table(service, input)); - master_service_init_log(service, str_c(str)); + master_service_init_log(service, str_c(str), + user_set->mail_log_max_lines_per_sec); } T_END; } @@ -474,9 +475,7 @@ if ((flags & MAIL_STORAGE_SERVICE_FLAG_DEBUG) != 0) set_keyval(service->set_parser, "mail_debug", "yes"); - /* now that we've read settings, we can set up logging */ mail_storage_service_init_log(service, &input); - set = master_service_settings_get(service); sets = master_service_settings_get_others(service); user_set = sets[0]; @@ -563,16 +562,16 @@ mail_storage_service_init_settings(service, set_roots, FALSE); - /* do all the global initialization. delay initializing plugins until - we drop privileges the first time. */ - master_service_init_log(service, - t_strdup_printf("%s: ", service->name)); - set = master_service_settings_get(service); sets = master_service_settings_get_others(service); user_set = sets[0]; mail_set = mail_user_set_get_storage_set(user_set); + /* do all the global initialization. delay initializing plugins until + we drop privileges the first time. */ + master_service_init_log(service, t_strconcat(service->name, ": ", NULL), + user_set->mail_log_max_lines_per_sec); + modules = *user_set->mail_plugins == '\0' ? NULL : module_dir_load(user_set->mail_plugin_dir, user_set->mail_plugins, TRUE,
--- a/src/lib-storage/mail-storage-settings.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-storage/mail-storage-settings.c Thu Apr 23 19:53:44 2009 -0400 @@ -145,6 +145,7 @@ DEF(SET_UINT, last_valid_uid), DEF(SET_UINT, first_valid_gid), DEF(SET_UINT, last_valid_gid), + DEF(SET_UINT, mail_log_max_lines_per_sec), DEF(SET_STR, mail_plugins), DEF(SET_STR, mail_plugin_dir), @@ -174,6 +175,8 @@ MEMBER(first_valid_gid) 1, MEMBER(last_valid_gid) 0, + MEMBER(mail_log_max_lines_per_sec) 10, + MEMBER(mail_plugins) "", MEMBER(mail_plugin_dir) MODULEDIR,
--- a/src/lib-storage/mail-storage-settings.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib-storage/mail-storage-settings.h Thu Apr 23 19:53:44 2009 -0400 @@ -54,6 +54,8 @@ unsigned int first_valid_uid, last_valid_uid; unsigned int first_valid_gid, last_valid_gid; + unsigned int mail_log_max_lines_per_sec; + const char *mail_plugins; const char *mail_plugin_dir;
--- a/src/lib/failures.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib/failures.c Thu Apr 23 19:53:44 2009 -0400 @@ -3,6 +3,7 @@ #include "lib.h" #include "ioloop.h" #include "str.h" +#include "hostpid.h" #include "network.h" #include "backtrace-string.h" #include "printf-format-fix.h" @@ -15,15 +16,13 @@ #include <syslog.h> #include <time.h> -const char *failure_log_type_prefixes[] = { +const char *failure_log_type_prefixes[LOG_TYPE_COUNT] = { "Info: ", "Warning: ", "Error: ", "Fatal: ", - "Panic: " -}; -static char log_type_internal_chars[] = { - 'I', 'W', 'E', 'F', 'P' + "Panic: ", + "Error: " }; /* Initialize working defaults */ @@ -37,6 +36,9 @@ static char *log_prefix = NULL, *log_stamp_format = NULL; static bool failure_ignore_errors = FALSE; +static void ATTR_FORMAT(2, 0) +i_internal_error_handler(enum log_type type, const char *fmt, va_list args); + /* kludgy .. we want to trust log_stamp_format with -Wformat-nonliteral */ static const char *get_log_stamp_format(const char *unused) ATTR_FORMAT_ARG(1); @@ -59,9 +61,6 @@ char buf[256]; time_t now; - if (log_prefix != NULL) - str_append(str, log_prefix); - if (log_stamp_format != NULL) { now = time(NULL); tm = localtime(&now); @@ -70,6 +69,8 @@ get_log_stamp_format("unused"), tm) > 0) str_append(str, buf); } + if (log_prefix != NULL) + str_append(str, log_prefix); } static void log_fd_flush_stop(struct ioloop *ioloop) @@ -193,6 +194,23 @@ va_list args; va_start(args, format); + + if (type == LOG_TYPE_ERROR_IGNORE_IF_SEEN_FATAL && + error_handler != i_internal_error_handler) { + /* this is handled specially only by internal logging. + skip over the pid. */ + T_BEGIN { + const char *str; + + str = t_strdup_vprintf(format, args); + while (*str >= '0' && *str <= '9') str++; + if (*str == ' ') str++; + + i_error("%s", str); + } T_END; + return; + } + if (type == LOG_TYPE_INFO) info_handler(type, format, args); else @@ -328,12 +346,16 @@ level = LOG_WARNING; break; case LOG_TYPE_ERROR: + case LOG_TYPE_ERROR_IGNORE_IF_SEEN_FATAL: level = LOG_ERR; break; case LOG_TYPE_FATAL: case LOG_TYPE_PANIC: level = LOG_CRIT; break; + case LOG_TYPE_COUNT: + case LOG_TYPE_OPTION: + i_unreached(); } if (syslog_handler(level, type, fmt, args) < 0) @@ -399,8 +421,28 @@ log_prefix = i_strdup(prefix); } +static int internal_send_split(string_t *full_str, unsigned int prefix_len) +{ + string_t *str; + unsigned int max_text_len, pos = prefix_len; + + str = t_str_new(PIPE_BUF); + str_append_n(str, str_c(full_str), prefix_len); + max_text_len = PIPE_BUF - prefix_len - 1; + + while (pos < str_len(full_str)) { + str_truncate(str, prefix_len); + str_append_n(str, str_c(full_str) + pos, max_text_len); + str_append_c(str, '\n'); + if (log_fd_write(2, str_data(str), str_len(str)) < 0) + return -1; + pos += max_text_len; + } + return 0; +} + static int ATTR_FORMAT(2, 0) -internal_handler(char log_type, const char *format, va_list args) +internal_handler(enum log_type log_type, const char *format, va_list args) { static int recursed = 0; int ret; @@ -414,13 +456,18 @@ recursed++; T_BEGIN { string_t *str; + unsigned int prefix_len; str = t_str_new(512); - str_append_c(str, 1); - str_append_c(str, log_type); + str_printfa(str, "\001%c%s ", log_type + 1, my_pid); + prefix_len = str_len(str); + str_vprintfa(str, format, args); str_append_c(str, '\n'); - ret = write_full(2, str_data(str), str_len(str)); + if (str_len(str) <= PIPE_BUF) + ret = log_fd_write(2, str_data(str), str_len(str)); + else + ret = internal_send_split(str, prefix_len); } T_END; if (ret < 0 && failure_ignore_errors) @@ -430,21 +477,62 @@ return ret; } +static bool line_is_ok(const char *line) +{ + if (*line != 1) + return FALSE; + + if (line[1] == '\0') { + i_warning("Broken log line follows (type=NUL)"); + return FALSE; + } + + if (line[1]-1 > LOG_TYPE_OPTION) { + i_warning("Broken log line follows (type=%d)", line[1]-1); + return FALSE; + } + return TRUE; +} + +void i_failure_parse_line(const char *line, struct failure_line *failure) +{ + memset(failure, 0, sizeof(*failure)); + if (!line_is_ok(line)) { + failure->log_type = LOG_TYPE_ERROR; + failure->text = line; + return; + } + failure->log_type = line[1] - 1; + + line += 2; + failure->text = line; + while (*line >= '0' && *line <= '9') { + failure->pid = failure->pid*10 + (*line - '0'); + line++; + } + if (*line != ' ') { + /* some old protocol? */ + failure->pid = 0; + return; + } + failure->text = line + 1; +} + static void ATTR_NORETURN ATTR_FORMAT(3, 0) i_internal_fatal_handler(enum log_type type, int status, const char *fmt, va_list args) { - if (internal_handler(log_type_internal_chars[type], fmt, args) < 0 && + if (internal_handler(type, fmt, args) < 0 && status == FATAL_DEFAULT) status = FATAL_LOGERROR; default_fatal_finish(type, status); } -static void ATTR_FORMAT(2, 0) +static void i_internal_error_handler(enum log_type type, const char *fmt, va_list args) { - if (internal_handler(log_type_internal_chars[type], fmt, args) < 0) + if (internal_handler(type, fmt, args) < 0) failure_exit(FATAL_LOGERROR); } @@ -480,7 +568,8 @@ const char *str; if (error_handler == i_internal_error_handler) { - str = t_strdup_printf("\x01Oip=%s\n", net_ip2addr(ip)); + str = t_strdup_printf("\001%c%s ip=%s\n", + LOG_TYPE_OPTION, my_pid, net_ip2addr(ip)); (void)write_full(2, str, strlen(str)); } }
--- a/src/lib/failures.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib/failures.h Thu Apr 23 19:53:44 2009 -0400 @@ -19,7 +19,21 @@ LOG_TYPE_WARNING, LOG_TYPE_ERROR, LOG_TYPE_FATAL, - LOG_TYPE_PANIC + LOG_TYPE_PANIC, + + /* Special message from master to log process: Log message begins with + "<pid> " and if <pid> has already logged a fatal/panic, this message + shouldn't be written to the log. Otherwise log as an error. */ + LOG_TYPE_ERROR_IGNORE_IF_SEEN_FATAL, + + LOG_TYPE_COUNT, + LOG_TYPE_OPTION +}; + +struct failure_line { + pid_t pid; + enum log_type log_type; + const char *text; }; #define DEFAULT_FAILURE_STAMP_FORMAT "%b %d %H:%M:%S " @@ -95,6 +109,9 @@ /* Call the exit callback and exit() */ void failure_exit(int status) ATTR_NORETURN; +/* Parse a line logged using internal failure handler */ +void i_failure_parse_line(const char *line, struct failure_line *failure); + void failures_deinit(void); #endif
--- a/src/lib/restrict-access.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib/restrict-access.c Thu Apr 23 19:53:44 2009 -0400 @@ -20,6 +20,7 @@ static gid_t process_primary_gid = (gid_t)-1; static gid_t process_privileged_gid = (gid_t)-1; static bool process_using_priv_gid = FALSE; +static char *chroot_dir = NULL; void restrict_access_init(struct restrict_access_settings *set) { @@ -273,6 +274,7 @@ if (chroot(set->chroot_dir) != 0) i_fatal("chroot(%s) failed: %m", set->chroot_dir); + chroot_dir = i_strdup(set->chroot_dir); if (home != NULL) { if (chdir(home) < 0) { @@ -407,6 +409,11 @@ env_remove("RESTRICT_CHROOT"); } +const char *restrict_access_get_current_chroot(void) +{ + return chroot_dir; +} + void restrict_access_allow_coredumps(bool allow ATTR_UNUSED) { #ifdef HAVE_PR_SET_DUMPABLE
--- a/src/lib/restrict-access.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lib/restrict-access.h Thu Apr 23 19:53:44 2009 -0400 @@ -37,6 +37,10 @@ environment settings. */ void restrict_access_by_env(const char *home, bool disallow_root); +/* Return the chrooted directory if restrict_access*() chrooted, + otherwise NULL. */ +const char *restrict_access_get_current_chroot(void); + /* Try to set up the process in a way that core dumps are still allowed after calling restrict_access_by_env(). */ void restrict_access_allow_coredumps(bool allow);
--- a/src/lmtp/client.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lmtp/client.c Thu Apr 23 19:53:44 2009 -0400 @@ -7,6 +7,7 @@ #include "istream.h" #include "ostream.h" #include "hostpid.h" +#include "master-service.h" #include "master-service-settings.h" #include "mail-namespace.h" #include "mail-storage.h" @@ -195,7 +196,7 @@ pool_unref(&client->state_pool); i_free(client); - listener_client_destroyed(); + master_service_client_connection_destroyed(service); } static const char *client_get_disconnect_reason(struct client *client)
--- a/src/lmtp/main.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/lmtp/main.c Thu Apr 23 19:53:44 2009 -0400 @@ -20,33 +20,17 @@ #define IS_STANDALONE() \ (getenv("MASTER_SERVICE") == NULL) -struct lmtp_listener { - int fd; - struct io *io; -}; - struct master_service *service; struct mail_storage_service_multi_ctx *multi_service; -static struct io *log_io = NULL; -static ARRAY_DEFINE(listeners, struct lmtp_listener *); - -static void log_error_callback(void *context ATTR_UNUSED) -{ - /* the log fd is closed, don't die when trying to log later */ - i_set_failure_ignore_errors(TRUE); - - master_service_stop(service); -} - -static void listen_connected(struct lmtp_listener *l) +static void client_connected(const struct master_service_connection *conn) { struct client *client; struct ip_addr remote_ip; unsigned int remote_port; int fd; - fd = net_accept(l->fd, &remote_ip, &remote_port); + fd = net_accept(conn->fd, &remote_ip, &remote_port); if (fd < 0) { if (fd < -1) i_error("accept() failed: %m"); @@ -59,80 +43,15 @@ (void)net_getsockname(fd, &client->local_ip, &client->local_port); } -static void listen_start(void) -{ - struct lmtp_listener *const *l; - unsigned int i, count; - - l = array_get(&listeners, &count); - for (i = 0; i < count; i++) { - i_assert(l[i]->io == NULL); - l[i]->io = io_add(l[i]->fd, IO_READ, listen_connected, l[i]); - } -} - -static void listen_stop(void) -{ - struct lmtp_listener *const *l; - unsigned int i, count; - - l = array_get(&listeners, &count); - for (i = 0; i < count; i++) { - i_assert(l[i]->io != NULL); - io_remove(&l[i]->io); - } -} - -static void listen_free(void) -{ - struct lmtp_listener **l; - unsigned int i, count; - - l = array_get_modifiable(&listeners, &count); - for (i = 0; i < count; i++) { - if (l[i]->io != NULL) - io_remove(&l[i]->io); - i_free(l[i]); - } - array_free(&listeners); -} - -void listener_client_destroyed(void) -{ - if (array_count(&listeners) == 0) - master_service_stop(service); -} - static void main_init(void) { - struct lmtp_listener *l; - const char *value; - unsigned int i, count; - - /* If master dies, the log fd gets closed and we'll quit */ - log_io = io_add(STDERR_FILENO, IO_ERROR, log_error_callback, NULL); - - value = getenv("LISTEN_FDS"); - count = value == NULL ? 0 : atoi(value); - i_array_init(&listeners, count + 1); - for (i = 0; i < count; i++) { - l = i_new(struct lmtp_listener, 1); - l->fd = LMTP_MASTER_FIRST_LISTEN_FD + i; - array_append(&listeners, &l, 1); - } - - if (count == 0) + if (IS_STANDALONE()) (void)client_create(STDIN_FILENO, STDOUT_FILENO); - else - listen_start(); } static void main_deinit(void) { - if (log_io != NULL) - io_remove(&log_io); clients_destroy(); - listen_free(); } int main(int argc, char *argv[], char *envp[]) @@ -157,13 +76,15 @@ } #endif - if (IS_STANDALONE()) - service_flags |= MASTER_SERVICE_FLAG_STANDALONE; + if (IS_STANDALONE()) { + service_flags |= MASTER_SERVICE_FLAG_STANDALONE | + MASTER_SERVICE_FLAG_STD_CLIENT; + } service = master_service_init("lmtp", service_flags, argc, argv); while ((c = getopt(argc, argv, master_service_getopt_string())) > 0) { if (!master_service_parse_option(service, c, optarg)) - i_fatal("Unknown argument: %c", c); + exit(FATAL_DEFAULT); } multi_service = mail_storage_service_multi_init(service, set_roots, @@ -173,7 +94,7 @@ process_title_init(argv, envp); main_init(); - master_service_run(service); + master_service_run(service, client_connected); main_deinit(); mail_storage_service_multi_deinit(&multi_service);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/log/Makefile.am Thu Apr 23 19:53:44 2009 -0400 @@ -0,0 +1,19 @@ +pkglibexecdir = $(libexecdir)/dovecot + +pkglibexec_PROGRAMS = log + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-master + +log_LDADD = $(LIBDOVECOT) +log_DEPENDENCIES = $(LIBDOVECOT) + +log_SOURCES = \ + log-connection.c \ + main.c + +noinst_HEADERS = \ + common.h \ + log-connection.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/log/common.h Thu Apr 23 19:53:44 2009 -0400 @@ -0,0 +1,9 @@ +#ifndef COMMON_H +#define COMMON_H + +#include "lib.h" + +extern struct master_service *service; +extern pid_t master_pid; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/log/log-connection.c Thu Apr 23 19:53:44 2009 -0400 @@ -0,0 +1,230 @@ +/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ + +#include "common.h" +#include "ioloop.h" +#include "llist.h" +#include "hash.h" +#include "master-interface.h" +#include "master-service.h" +#include "log-connection.h" + +#include <unistd.h> + +#define FATAL_QUEUE_TIMEOUT_MSECS 500 + +struct log_client { + struct ip_addr ip; + unsigned int fatal_logged:1; +}; + +struct log_connection { + struct log_connection *prev, *next; + + int fd; + struct io *io; + + char *prefix; + struct hash_table *clients; + + unsigned int handshaked:1; +}; + +static struct log_connection *log_connections = NULL; + +static struct log_client *log_client_get(struct log_connection *log, pid_t pid) +{ + struct log_client *client; + + client = hash_table_lookup(log->clients, POINTER_CAST(pid)); + if (client == NULL) { + client = i_new(struct log_client, 1); + hash_table_insert(log->clients, POINTER_CAST(pid), client); + } + return client; +} + +static void log_parse_ip(struct log_connection *log, + const struct failure_line *failure) +{ + struct log_client *client; + + client = log_client_get(log, failure->pid); + (void)net_addr2ip(failure->text + 3, &client->ip); +} + +static void log_remove_pid(struct log_connection *log, pid_t pid) +{ + struct log_client *client; + + client = hash_table_lookup(log->clients, POINTER_CAST(pid)); + if (client != NULL) { + hash_table_remove(log->clients, POINTER_CAST(pid)); + i_free(client); + } +} + +static void log_parse_option(struct log_connection *log, + const struct failure_line *failure) +{ + if (strncmp(failure->text, "ip=", 3) == 0) + log_parse_ip(log, failure); + else if (strcmp(failure->text, "bye") == 0) + log_remove_pid(log, failure->pid); +} + +static bool +log_handle_seen_fatal(struct log_connection *log, const char **_text) +{ + const char *text = *_text; + struct log_client *client; + pid_t pid = 0; + + while (*text >= '0' && *text <= '9') { + pid = pid*10 + (*text - '0'); + text++; + } + if (*text != ' ' || pid == 0) + return FALSE; + *_text = text; + + client = hash_table_lookup(log->clients, POINTER_CAST(pid)); + if (client != NULL && client->fatal_logged) { + log_remove_pid(log, pid); + return TRUE; + } + return FALSE; +} + +static void log_it(struct log_connection *log, const char *line) +{ + struct failure_line failure; + struct log_client *client; + + i_failure_parse_line(line, &failure); + switch (failure.log_type) { + case LOG_TYPE_FATAL: + case LOG_TYPE_PANIC: + client = log_client_get(log, failure.pid); + client->fatal_logged = TRUE; + break; + case LOG_TYPE_ERROR_IGNORE_IF_SEEN_FATAL: + /* Special case for master connection. If the following PID + has logged a fatal/panic, don't log this message. */ + failure.log_type = LOG_TYPE_ERROR; + if (failure.pid != master_pid) { + i_error("Non-master process %s " + "sent LOG_TYPE_ERROR_IGNORE_IF_SEEN_FATAL", + dec2str(failure.pid)); + break; + } + + if (log_handle_seen_fatal(log, &failure.text)) + return; + break; + case LOG_TYPE_OPTION: + log_parse_option(log, &failure); + return; + default: + break; + } + i_assert(failure.log_type < LOG_TYPE_COUNT); + + i_set_failure_prefix(log->prefix); + i_log_type(failure.log_type, "%s", failure.text); + i_set_failure_prefix("log: "); +} + +static bool log_connection_handshake(struct log_connection *log, + char **data, size_t size) +{ + struct log_service_handshake handshake; + + if (size < sizeof(handshake)) + return FALSE; + + memcpy(&handshake, *data, sizeof(handshake)); + if (handshake.log_magic != MASTER_LOG_MAGIC) + return FALSE; + + if (handshake.prefix_len <= size - sizeof(handshake)) { + log->prefix = i_strndup(*data + sizeof(handshake), + handshake.prefix_len); + *data += sizeof(handshake) + handshake.prefix_len; + } + log->handshaked = TRUE; + return TRUE; +} + +static void log_connection_input(struct log_connection *log) +{ + char data[PIPE_BUF+1], *p, *line; + ssize_t ret; + + ret = read(log->fd, data, sizeof(data)-1); + if (ret <= 0) { + if (ret < 0) + i_error("read(log pipe) failed: %m"); + log_connection_destroy(log); + return; + } + data[ret] = '\0'; + + line = data; + if (!log->handshaked) + log_connection_handshake(log, &line, ret); + + p = line; + while ((p = strchr(line, '\n')) != NULL) { + *p = '\0'; + log_it(log, line); + line = p + 1; + } + if (line - data != ret) { + i_error("Invalid log line follows: Missing LF"); + log_it(log, line); + } +} + +struct log_connection *log_connection_create(int fd) +{ + struct log_connection *log; + + log = i_new(struct log_connection, 1); + log->fd = fd; + log->io = io_add(fd, IO_READ, log_connection_input, log); + log->clients = hash_table_create(default_pool, default_pool, 0, + NULL, NULL); + + DLLIST_PREPEND(&log_connections, log); + log_connection_input(log); + return log; +} + +void log_connection_destroy(struct log_connection *log) +{ + struct hash_iterate_context *iter; + void *key, *value; + + DLLIST_REMOVE(&log_connections, log); + + iter = hash_table_iterate_init(log->clients); + while (hash_table_iterate(iter, &key, &value)) + i_free(value); + hash_table_iterate_deinit(&iter); + hash_table_destroy(&log->clients); + + if (log->io != NULL) + io_remove(&log->io); + i_free(log->prefix); + i_free(log); + + master_service_client_connection_destroyed(service); +} + +void log_connections_deinit(void) +{ + /* normally we don't exit until all log connections are gone, + but we could get here when we're being killed by a signal */ + while (log_connections != NULL) + log_connection_destroy(log_connections); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/log/log-connection.h Thu Apr 23 19:53:44 2009 -0400 @@ -0,0 +1,9 @@ +#ifndef LOG_CONNECTION_H +#define LOG_CONNECTION_H + +struct log_connection *log_connection_create(int fd); +void log_connection_destroy(struct log_connection *log); + +void log_connections_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/log/main.c Thu Apr 23 19:53:44 2009 -0400 @@ -0,0 +1,73 @@ +/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ + +#include "common.h" +#include "lib-signals.h" +#include "master-interface.h" +#include "master-service.h" +#include "master-service-settings.h" +#include "log-connection.h" + +#include <stdlib.h> +#include <unistd.h> + +struct master_service *service; +pid_t master_pid; + +static void +sig_reread_config(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) +{ + // FIXME +} + +static void +sig_reopen_logs(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) +{ + // FIXME +} + +static void main_init(void) +{ + lib_signals_set_handler(SIGHUP, TRUE, sig_reread_config, NULL); + lib_signals_set_handler(SIGUSR1, TRUE, sig_reopen_logs, NULL); + + master_pid = getppid(); +} + +static void main_deinit(void) +{ + log_connections_deinit(); +} + +static void client_connected(const struct master_service_connection *conn) +{ + log_connection_create(conn->fd); +} + +int main(int argc, char *argv[]) +{ + const char *error; + int c; + + service = master_service_init("log", 0, argc, argv); + + /* use log prefix and log to stderr until we've configured the real + logging */ + i_set_failure_file("/dev/stderr", "log: "); + + while ((c = getopt(argc, argv, master_service_getopt_string())) > 0) { + if (!master_service_parse_option(service, c, optarg)) + exit(FATAL_DEFAULT); + } + + if (master_service_settings_read(service, NULL, NULL, FALSE, + &error) < 0) + i_fatal("Error reading configuration: %s", error); + + master_service_init_log(service, "log: ", 0); + master_service_init_finish(service); + main_init(); + master_service_run(service, client_connected); + main_deinit(); + master_service_deinit(&service); + return 0; +}
--- a/src/login-common/Makefile.am Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/Makefile.am Thu Apr 23 19:53:44 2009 -0400 @@ -4,6 +4,7 @@ -I$(top_srcdir)/src/lib \ -I$(top_srcdir)/src/lib-settings \ -I$(top_srcdir)/src/lib-auth \ + -I$(top_srcdir)/src/lib-master \ -DPKG_RUNDIR=\""$(rundir)"\" \ -DPKG_STATEDIR=\""$(statedir)"\" \ -DSBINDIR=\""$(sbindir)"\" \ @@ -14,7 +15,6 @@ login-proxy.c \ login-settings.c \ main.c \ - master.c \ sasl-server.c \ ssl-proxy.c \ ssl-proxy-gnutls.c \ @@ -28,7 +28,6 @@ login-proxy.h \ login-settings.h \ common.h \ - master.h \ sasl-server.h \ ssl-proxy.h
--- a/src/login-common/client-common.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/client-common.h Thu Apr 23 19:53:44 2009 -0400 @@ -2,7 +2,6 @@ #define CLIENT_COMMON_H #include "network.h" -#include "master.h" #include "sasl-server.h" /* max. size of input buffer. this means: @@ -29,7 +28,6 @@ struct auth_request *auth_request; unsigned int master_tag; - master_callback_t *master_callback; sasl_server_callback_t *sasl_callback; unsigned int auth_attempts; @@ -39,6 +37,7 @@ unsigned int tls:1; unsigned int secured:1; unsigned int trusted:1; + unsigned int proxying:1; unsigned int authenticating:1; unsigned int auth_tried_disabled_plaintext:1; /* ... */
--- a/src/login-common/common.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/common.h Thu Apr 23 19:53:44 2009 -0400 @@ -12,20 +12,12 @@ #define AUTH_PLAINTEXT_DISABLED_MSG \ "Plaintext authentication disallowed on non-secure (SSL/TLS) connections." -extern const char *login_protocol; +extern const char *login_protocol, *login_process_name; extern struct auth_client *auth_client; extern bool closing_down; -extern unsigned int login_process_uid; +extern struct master_service *service; extern struct login_settings *login_settings; -void main_ref(void); -void main_unref(void); - -void main_listen_start(void); -void main_listen_stop(void); - -void connection_queue_add(unsigned int connection_count); - #endif
--- a/src/login-common/login-proxy.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/login-proxy.c Thu Apr 23 19:53:44 2009 -0400 @@ -6,6 +6,7 @@ #include "ostream.h" #include "llist.h" #include "str-sanitize.h" +#include "master-service.h" #include "client-common.h" #include "login-proxy.h" @@ -31,7 +32,6 @@ }; static struct login_proxy *login_proxies = NULL; -static unsigned int login_proxy_count = 0; static void server_input(struct login_proxy *proxy) { @@ -191,9 +191,7 @@ if (proxy->client_fd != -1) { /* detached proxy */ - main_unref(); DLLIST_REMOVE(&login_proxies, proxy); - login_proxy_count--; ipstr = net_ip2addr(&proxy->ip); i_info("proxy(%s): disconnecting %s", @@ -224,7 +222,7 @@ i_free(proxy->user); i_free(proxy); - main_listen_start(); + master_service_client_connection_destroyed(service); } bool login_proxy_is_ourself(const struct client *client, const char *host, @@ -253,11 +251,6 @@ return proxy->port; } -unsigned int login_proxy_get_count(void) -{ - return login_proxy_count; -} - void login_proxy_detach(struct login_proxy *proxy, struct istream *client_input, struct ostream *client_output) { @@ -291,9 +284,7 @@ proxy->callback = NULL; proxy->context = NULL; - login_proxy_count++; DLLIST_PREPEND(&login_proxies, proxy); - main_ref(); } void login_proxy_deinit(void)
--- a/src/login-common/login-proxy.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/login-proxy.h Thu Apr 23 19:53:44 2009 -0400 @@ -39,9 +39,6 @@ const char *login_proxy_get_host(const struct login_proxy *proxy) ATTR_PURE; unsigned int login_proxy_get_port(const struct login_proxy *proxy) ATTR_PURE; -/* Return number of active detached login proxies */ -unsigned int login_proxy_get_count(void) ATTR_PURE; - void login_proxy_deinit(void); #endif
--- a/src/login-common/login-settings.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/login-settings.c Thu Apr 23 19:53:44 2009 -0400 @@ -2,6 +2,7 @@ #include "lib.h" #include "settings-parser.h" +#include "master-service-settings.h" #include "login-settings.h" #include <stddef.h> @@ -92,8 +93,6 @@ MEMBER(check_func) login_settings_check }; -static pool_t settings_pool = NULL; - /* <settings checks> */ static int ssl_settings_check(void *_set ATTR_UNUSED, const char **error_r) { @@ -173,30 +172,19 @@ } /* </settings checks> */ -struct login_settings *login_settings_read(void) +struct login_settings *login_settings_read(struct master_service *service) { - struct setting_parser_context *parser; - struct login_settings *set; + static const struct setting_parser_info *set_roots[] = { + &login_setting_parser_info, + NULL + }; const char *error; - - if (settings_pool == NULL) - settings_pool = pool_alloconly_create("settings pool", 512); - else - p_clear(settings_pool); + void **sets; - parser = settings_parser_init(settings_pool, - &login_setting_parser_info, - SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS); + if (master_service_settings_read(service, set_roots, NULL, FALSE, + &error) < 0) + i_fatal("Error reading configuration: %s", error); - if (settings_parse_environ(parser) < 0) { - i_fatal("Error reading configuration: %s", - settings_parser_get_error(parser)); - } - - if (settings_parser_check(parser, settings_pool, &error) < 0) - i_fatal("Invalid settings: %s", error); - - set = settings_parser_get(parser); - settings_parser_deinit(&parser); - return set; + sets = master_service_settings_get_others(service); + return sets[0]; }
--- a/src/login-common/login-settings.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/login-settings.h Thu Apr 23 19:53:44 2009 -0400 @@ -1,6 +1,8 @@ #ifndef LOGIN_SETTINGS_H #define LOGIN_SETTINGS_H +struct master_service; + struct login_settings { const char *login_dir; bool login_chroot; @@ -35,6 +37,6 @@ const char *const *log_format_elements_split; }; -struct login_settings *login_settings_read(void); +struct login_settings *login_settings_read(struct master_service *service); #endif
--- a/src/login-common/main.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/main.c Thu Apr 23 19:53:44 2009 -0400 @@ -2,14 +2,13 @@ #include "common.h" #include "ioloop.h" -#include "array.h" -#include "lib-signals.h" #include "randgen.h" #include "restrict-access.h" #include "restrict-process-size.h" #include "process-title.h" -#include "fd-close-on-exec.h" -#include "master.h" +#include "master-auth.h" +#include "master-service.h" +#include "master-interface.h" #include "client-common.h" #include "auth-client.h" #include "ssl-proxy.h" @@ -19,231 +18,45 @@ #include <unistd.h> #include <syslog.h> -struct login_settings *login_settings; -unsigned int login_process_uid; struct auth_client *auth_client; bool closing_down; -static const char *process_name; -static struct ioloop *ioloop; -static int main_refcount; -static bool is_inetd, listening; - -static ARRAY_DEFINE(listen_ios, struct io *); -static unsigned int listen_count, ssl_listen_count; - -void main_ref(void) -{ - main_refcount++; -} +struct master_service *service; +struct login_settings *login_settings; -void main_unref(void) -{ - if (--main_refcount == 0) { - /* nothing to do, quit */ - io_loop_stop(ioloop); - } else if (closing_down && clients_get_count() == 0) { - /* last login finished, close all communications - to master process */ - master_close(); - /* we might still be proxying. close the connection to - dovecot-auth, since it's not needed anymore. */ - if (auth_client != NULL) - auth_client_free(&auth_client); - } else if (clients_get_count() == 0) { - /* make sure we clear all the memory used by the - authentication connections. also this makes sure that if - this connection's authentication was finished but the master - login wasn't, the next connection won't be able to log in - as this user by finishing the master login. */ - if (auth_client != NULL) - auth_client_reconnect(auth_client); - } -} +static bool ssl_connections = FALSE; -static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED) +static void client_connected(const struct master_service_connection *conn) { - /* warn about being killed because of some signal, except SIGINT (^C) - which is too common at least while testing :) */ - if (si->si_signo != SIGINT) { - i_warning("Killed with signal %d (by pid=%s uid=%s code=%s)", - si->si_signo, dec2str(si->si_pid), - dec2str(si->si_uid), - lib_signal_code_to_str(si->si_signo, si->si_code)); - } - io_loop_stop(ioloop); -} - -static void login_accept(void *context) -{ - int listen_fd = POINTER_CAST_TO(context, int); - struct ip_addr remote_ip, local_ip; - unsigned int remote_port, local_port; - struct client *client; - int fd; - - fd = net_accept(listen_fd, &remote_ip, &remote_port); - if (fd < 0) { - if (fd < -1) - i_error("accept() failed: %m"); - return; - } - i_set_failure_ip(&remote_ip); - - if (net_getsockname(fd, &local_ip, &local_port) < 0) { - memset(&local_ip, 0, sizeof(local_ip)); - local_port = 0; - } - - client = client_create(fd, FALSE, &local_ip, &remote_ip); - client->remote_port = remote_port; - client->local_port = local_port; - - if (login_settings->login_process_per_connection) { - closing_down = TRUE; - main_listen_stop(); - } -} - -static void login_accept_ssl(void *context) -{ - int listen_fd = POINTER_CAST_TO(context, int); - struct ip_addr remote_ip, local_ip; - unsigned int remote_port, local_port; struct client *client; struct ssl_proxy *proxy; - int fd, fd_ssl; + struct ip_addr local_ip; + unsigned int local_port; + int fd_ssl; - fd = net_accept(listen_fd, &remote_ip, &remote_port); - if (fd < 0) { - if (fd < -1) - i_error("accept() failed: %m"); - return; - } - i_set_failure_ip(&remote_ip); - - if (net_getsockname(fd, &local_ip, &local_port) < 0) { + if (net_getsockname(conn->fd, &local_ip, &local_port) < 0) { memset(&local_ip, 0, sizeof(local_ip)); local_port = 0; } - connection_queue_add(1); - - fd_ssl = ssl_proxy_new(fd, &remote_ip, &proxy); - if (fd_ssl == -1) - net_disconnect(fd); - else { - client = client_create(fd_ssl, TRUE, &local_ip, &remote_ip); - client->proxy = proxy; - client->remote_port = remote_port; - client->local_port = local_port; - } - - if (login_settings->login_process_per_connection) { - closing_down = TRUE; - main_listen_stop(); - } -} - -void main_listen_start(void) -{ - struct io *io; - unsigned int i, current_count; - int cur_fd; - - if (listening) - return; - if (closing_down) { - /* typically happens only with - login_process_per_connection=yes after client logs in */ - master_notify_state_change(LOGIN_STATE_FULL_LOGINS); - return; - } - - current_count = ssl_proxy_get_count() + login_proxy_get_count(); - if (current_count >= login_settings->login_max_connections) { - /* can't accept any more connections until existing proxies - get destroyed */ - return; - } - - cur_fd = LOGIN_MASTER_SOCKET_FD + 1; - i_array_init(&listen_ios, listen_count + ssl_listen_count); - for (i = 0; i < listen_count; i++, cur_fd++) { - io = io_add(cur_fd, IO_READ, login_accept, - POINTER_CAST(cur_fd)); - array_append(&listen_ios, &io, 1); - } - for (i = 0; i < ssl_listen_count; i++, cur_fd++) { - io = io_add(cur_fd, IO_READ, login_accept_ssl, - POINTER_CAST(cur_fd)); - array_append(&listen_ios, &io, 1); - } - listening = TRUE; - - /* the initial notification tells master that we're ok. if we die - before sending it, the master should shutdown itself. */ - master_notify_state_change(LOGIN_STATE_LISTENING); -} + // FIXME: a global ssl_connections isn't enough! + if (!ssl_connections) { + client = client_create(conn->fd, FALSE, &local_ip, + &conn->remote_ip); + } else { + fd_ssl = ssl_proxy_new(conn->fd, &conn->remote_ip, &proxy); + if (fd_ssl == -1) { + net_disconnect(conn->fd); + return; + } -void main_listen_stop(void) -{ - struct io **ios; - unsigned int i, count; - int cur_fd; - - if (!listening) - return; - - ios = array_get_modifiable(&listen_ios, &count); - for (i = 0; i < count; i++) - io_remove(&ios[i]); - array_free(&listen_ios); - - if (closing_down) { - cur_fd = LOGIN_MASTER_SOCKET_FD + 1; - for (i = 0; i < count; i++, cur_fd++) { - if (close(cur_fd) < 0) { - i_fatal("close(listener %d) failed: %m", - cur_fd); - } - } + client = client_create(fd_ssl, TRUE, + &local_ip, &conn->remote_ip); + client->proxying = TRUE; + client->proxy = proxy; } - - listening = FALSE; - if (io_loop_is_running(ioloop)) { - master_notify_state_change(clients_get_count() == 0 ? - LOGIN_STATE_FULL_LOGINS : - LOGIN_STATE_FULL_PRELOGINS); - } -} - -void connection_queue_add(unsigned int connection_count) -{ - unsigned int max_connections = login_settings->login_max_connections; - unsigned int current_count; - - if (login_settings->login_process_per_connection) - return; - - current_count = clients_get_count() + ssl_proxy_get_count() + - login_proxy_get_count(); - if (current_count + connection_count + 2 >= max_connections) { - /* after this client we've reached max users count, - so stop listening for more. reserve +2 extra for SSL with - login proxy connections. */ - main_listen_stop(); - - if (current_count >= max_connections) { - /* already reached max. users count, kill few of the - oldest connections. - - this happens when we've maxed out the login process - count and master has told us to start listening for - new connections even though we're full. */ - client_destroy_oldest(); - } - } + client->remote_port = conn->remote_port; + client->local_port = local_port; } static void auth_connect_notify(struct auth_client *client ATTR_UNUSED, @@ -253,211 +66,101 @@ clients_notify_auth_connected(); } -static void drop_privileges(unsigned int *max_fds_r) +static void main_preinit(void) { - const char *value; - - login_settings = login_settings_read(); + unsigned int max_fds; - if (!is_inetd) - i_set_failure_internal(); - else { - /* log to syslog */ - value = getenv("SYSLOG_FACILITY"); - i_set_failure_syslog(process_name, LOG_NDELAY, - value == NULL ? LOG_MAIL : atoi(value)); - } - - value = getenv("DOVECOT_VERSION"); - if (value != NULL && strcmp(value, PACKAGE_VERSION) != 0) { - i_fatal("Dovecot version mismatch: " - "Master is v%s, login is v"PACKAGE_VERSION" " - "(if you don't care, set version_ignore=yes)", value); - } - - value = getenv("LOGIN_DIR"); - if (value == NULL) - i_fatal("LOGIN_DIR environment missing"); - if (chdir(value) < 0) - i_error("chdir(%s) failed: %m", value); - + random_init(); /* Initialize SSL proxy so it can read certificate and private key file. */ - random_init(); ssl_proxy_init(); - value = getenv("LISTEN_FDS"); - listen_count = value == NULL ? 0 : atoi(value); - value = getenv("SSL_LISTEN_FDS"); - ssl_listen_count = value == NULL ? 0 : atoi(value); - /* set the number of fds we want to use. it may get increased or decreased. leave a couple of extra fds for auth sockets and such. normal connections each use one fd, but SSL connections use two */ - *max_fds_r = LOGIN_MASTER_SOCKET_FD + 16 + - listen_count + ssl_listen_count + + max_fds = MASTER_LISTEN_FD_FIRST + 16 + + master_service_get_socket_count(service) + login_settings->login_max_connections*2; - restrict_fd_limit(*max_fds_r); + restrict_fd_limit(max_fds); + io_loop_set_max_fd_count(current_ioloop, max_fds); - /* Refuse to run as root - we should never need it and it's - dangerous with SSL. */ + i_assert(strcmp(login_settings->ssl, "no") == 0 || ssl_initialized); + restrict_access_by_env(NULL, TRUE); - - /* make sure we can't fork() */ - restrict_process_size((unsigned int)-1, 1); } static void main_init(void) { - const char *value; - - lib_signals_init(); - lib_signals_set_handler(SIGINT, TRUE, sig_die, NULL); - lib_signals_set_handler(SIGTERM, TRUE, sig_die, NULL); - lib_signals_ignore(SIGPIPE, TRUE); + /* make sure we can't fork() */ + restrict_process_size((unsigned int)-1, 1); - value = getenv("PROCESS_UID"); - if (value == NULL) - i_fatal("BUG: PROCESS_UID environment not given"); - login_process_uid = strtoul(value, NULL, 10); - if (login_process_uid == 0) - i_fatal("BUG: PROCESS_UID environment is 0"); - - closing_down = FALSE; - main_refcount = 0; - - auth_client = auth_client_new(login_process_uid); - auth_client_set_connect_notify(auth_client, auth_connect_notify, NULL); - clients_init(); - - if (!ssl_initialized && ssl_listen_count > 0) { - /* this shouldn't happen, master should have - disabled the ssl socket.. */ - i_fatal("BUG: SSL initialization parameters not given " - "while they should have been"); + if (restrict_access_get_current_chroot() == NULL) { + if (chdir("login") < 0) + i_fatal("chdir(login) failed: %m"); } - if (!is_inetd) { - master_init(LOGIN_MASTER_SOCKET_FD); - main_listen_start(); - } + auth_client = auth_client_new((unsigned int)getpid()); + auth_client_set_connect_notify(auth_client, auth_connect_notify, NULL); + + clients_init(); + master_auth_init(service); } static void main_deinit(void) { - closing_down = TRUE; - main_listen_stop(); - ssl_proxy_deinit(); login_proxy_deinit(); if (auth_client != NULL) auth_client_free(&auth_client); clients_deinit(); - master_deinit(); - - lib_signals_deinit(); - closelog(); + master_auth_deinit(service); } -int main(int argc ATTR_UNUSED, char *argv[], char *envp[]) +int main(int argc, char *argv[], char *envp[]) { - const char *group_name; - struct ip_addr remote_ip, local_ip; - unsigned int remote_port, local_port, max_fds; - struct ssl_proxy *proxy = NULL; - struct client *client; - int i, fd = -1, master_fd = -1; - bool ssl = FALSE; + const char *getopt_str; + int c; - is_inetd = getenv("DOVECOT_MASTER") == NULL; + //FIXME:is_inetd = getenv("DOVECOT_MASTER") == NULL; + + service = master_service_init(login_process_name, 0, argc, argv); + master_service_init_log(service, t_strconcat(login_process_name, ": ", + NULL), 0); -#ifdef DEBUG - if (!is_inetd && getenv("GDB") == NULL) { - const char *env; + getopt_str = t_strconcat("DS", master_service_getopt_string(), NULL); + while ((c = getopt(argc, argv, getopt_str)) > 0) { + switch (c) { + case 'D': + restrict_access_allow_coredumps(TRUE); + break; + case 'S': + ssl_connections = TRUE; + break; + default: + if (!master_service_parse_option(service, c, optarg)) + exit(FATAL_DEFAULT); + break; + } + } - i = LOGIN_MASTER_SOCKET_FD + 1; - env = getenv("LISTEN_FDS"); - if (env != NULL) i += atoi(env); - env = getenv("SSL_LISTEN_FDS"); - if (env != NULL) i += atoi(env); - - fd_debug_verify_leaks(i, 1024); - } -#endif - /* NOTE: we start rooted, so keep the code minimal until - restrict_access_by_env() is called */ - lib_init(); - +#if 0 if (is_inetd) { /* running from inetd. create master process before dropping privileges. */ - process_name = strrchr(argv[0], '/'); - process_name = process_name == NULL ? argv[0] : process_name+1; - group_name = t_strcut(process_name, '-'); - - for (i = 1; i < argc; i++) { - if (strncmp(argv[i], "--group=", 8) == 0) { - group_name = argv[1]+8; - break; - } - } - - master_fd = master_connect(group_name); + master_fd = master_connect(t_strcut(login_process_name, '-')); } - - drop_privileges(&max_fds); - - if (argv[1] != NULL && strcmp(argv[1], "-D") == 0) - restrict_access_allow_coredumps(TRUE); +#endif process_title_init(argv, envp); - ioloop = io_loop_create(); - io_loop_set_max_fd_count(ioloop, max_fds); + login_settings = login_settings_read(service); + + main_preinit(); + master_service_init_finish(service); main_init(); - if (is_inetd) { - if (net_getpeername(1, &remote_ip, &remote_port) < 0) { - i_fatal("%s can be started only through dovecot " - "master process, inetd or equivalent", argv[0]); - } - if (net_getsockname(1, &local_ip, &local_port) < 0) { - memset(&local_ip, 0, sizeof(local_ip)); - local_port = 0; - } - - fd = 1; - for (i = 1; i < argc; i++) { - if (strcmp(argv[i], "--ssl") == 0) - ssl = TRUE; - else if (strncmp(argv[i], "--group=", 8) != 0) - i_fatal("Unknown parameter: %s", argv[i]); - } - - /* hardcoded imaps and pop3s ports to be SSL by default */ - if (local_port == 993 || local_port == 995 || ssl) { - ssl = TRUE; - fd = ssl_proxy_new(fd, &remote_ip, &proxy); - if (fd == -1) - return 1; - } - - master_init(master_fd); - closing_down = TRUE; - - if (fd != -1) { - client = client_create(fd, ssl, &local_ip, &remote_ip); - client->proxy = proxy; - client->remote_port = remote_port; - client->local_port = local_port; - } - } - - io_loop_run(ioloop); + master_service_run(service, client_connected); main_deinit(); - - io_loop_destroy(&ioloop); - lib_deinit(); - + master_service_deinit(&service); return 0; }
--- a/src/login-common/master.c Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,311 +0,0 @@ -/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "hash.h" -#include "buffer.h" -#include "ioloop.h" -#include "network.h" -#include "fdpass.h" -#include "istream.h" -#include "env-util.h" -#include "write-full.h" -#include "master.h" -#include "client-common.h" - -#include <unistd.h> - -static int master_fd; -static struct io *io_master; -static struct hash_table *master_requests; -static unsigned int master_tag_counter; - -static unsigned int master_pos; -static char master_buf[sizeof(struct master_login_reply)]; -static struct client destroyed_client; - -static void client_call_master_callback(struct client *client, - const struct master_login_reply *reply) -{ - master_callback_t *master_callback; - - master_callback = client->master_callback; - client->master_tag = 0; - client->master_callback = NULL; - - master_callback(client, reply); -} - -static void request_handle(struct master_login_reply *reply) -{ - struct client *client; - - if (reply->tag == 0 && !login_settings->login_process_per_connection) { - /* this means we have to start listening again. - we've reached maximum number of login processes. */ - main_listen_start(); - return; - } - - client = hash_table_lookup(master_requests, POINTER_CAST(reply->tag)); - if (client == NULL) - i_fatal("Master sent reply with unknown tag %u", reply->tag); - - hash_table_remove(master_requests, POINTER_CAST(reply->tag)); - if (client != &destroyed_client) { - client_call_master_callback(client, reply); - /* NOTE: client may be destroyed now */ - } -} - -void master_request_login(struct client *client, master_callback_t *callback, - unsigned int auth_pid, unsigned int auth_id) -{ - buffer_t *buf; - struct master_login_request *req; - struct stat st; - const unsigned char *data; - size_t size; - ssize_t ret; - unsigned int cmd_tag_size; - - i_assert(auth_pid != 0); - - if (master_fd == -1) { - struct master_login_reply reply; - - i_assert(closing_down); - memset(&reply, 0, sizeof(reply)); - reply.status = MASTER_LOGIN_STATUS_INTERNAL_ERROR; - callback(client, &reply); - return; - } - - data = i_stream_get_data(client->input, &size); - cmd_tag_size = client->auth_command_tag == NULL ? 0 : - strlen(client->auth_command_tag); - - buf = buffer_create_dynamic(pool_datastack_create(), - sizeof(*req) + size + cmd_tag_size); - buffer_write(buf, sizeof(*req), client->auth_command_tag, cmd_tag_size); - buffer_write(buf, sizeof(*req) + cmd_tag_size, data, size); - req = buffer_get_space_unsafe(buf, 0, sizeof(*req)); - req->version = MASTER_LOGIN_PROTOCOL_VERSION; - req->tag = ++master_tag_counter; - if (req->tag == 0) - req->tag = ++master_tag_counter; - req->auth_pid = auth_pid; - req->auth_id = auth_id; - req->local_ip = client->local_ip; - req->remote_ip = client->ip; - req->cmd_tag_size = cmd_tag_size; - req->data_size = req->cmd_tag_size + size; -#if (LOGIN_MAX_INBUF_SIZE*2) != MASTER_LOGIN_MAX_DATA_SIZE -# error buffer max sizes unsynced -#endif - i_assert(req->data_size <= LOGIN_MAX_INBUF_SIZE); - - if (fstat(client->fd, &st) < 0) - i_fatal("fstat(client) failed: %m"); - req->ino = st.st_ino; - - ret = fd_send(master_fd, client->fd, buf->data, buf->used); - if (ret < 0) - i_fatal("fd_send(%d) failed: %m", client->fd); - if ((size_t)ret != buf->used) { - i_fatal("fd_send() sent only %d of %d bytes", - (int)ret, (int)buf->used); - } - - client->master_tag = req->tag; - client->master_callback = callback; - - hash_table_insert(master_requests, POINTER_CAST(req->tag), client); -} - -void master_request_abort(struct client *client) -{ - struct master_login_reply reply; - - /* we're still going to get the reply from the master, so just - remember that we want to ignore it */ - hash_table_update(master_requests, POINTER_CAST(client->master_tag), - &destroyed_client); - - memset(&reply, 0, sizeof(reply)); - reply.status = MASTER_LOGIN_STATUS_INTERNAL_ERROR; - client_call_master_callback(client, &reply); -} - -void master_notify_state_change(enum master_login_state state) -{ - struct master_login_request req; - - if (io_master == NULL) - return; - - memset(&req, 0, sizeof(req)); - req.version = MASTER_LOGIN_PROTOCOL_VERSION; - req.tag = state; - req.ino = (ino_t)-1; - - /* sending -1 as fd does the notification */ - if (fd_send(master_fd, -1, &req, sizeof(req)) != sizeof(req)) - i_fatal("fd_send(-1) failed: %m"); -} - -void master_close(void) -{ - if (io_master == NULL) - return; - - io_remove(&io_master); - if (close(master_fd) < 0) - i_fatal("close(master) failed: %m"); - master_fd = -1; - - closing_down = TRUE; - main_listen_stop(); - main_unref(); - - /* may call this function again through main_unref() */ - clients_destroy_all(); -} - -static void master_exec(int fd) -{ - static char dovecot[] = "dovecot"; - char *argv[] = { dovecot, NULL }; - - switch (fork()) { - case -1: - i_fatal("fork() failed: %m"); - case 0: - if (dup2(fd, 0) < 0) - i_fatal("master_exec: dup2(%d, 0) failed: %m", fd); - (void)close(fd); - - if (setsid() < 0) - i_fatal("setsid() failed: %m"); - - env_put("DOVECOT_INETD=1"); - execv(SBINDIR"/dovecot", argv); - i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", - SBINDIR"/dovecot"); - default: - (void)close(fd); - } -} - -static void master_read_env(int fd) -{ - struct istream *input; - const char *line; - - env_clean(); - - /* read environment variable lines until empty line comes */ - input = i_stream_create_fd(fd, 8192, FALSE); - do { - switch (i_stream_read(input)) { - case -1: - i_fatal("EOF while reading environment from master"); - case -2: - i_fatal("Too large environment line from master"); - } - - while ((line = i_stream_next_line(input)) != NULL && - *line != '\0') - env_put(line); - } while (line == NULL); - - i_stream_destroy(&input); -} - -int master_connect(const char *group_name) -{ - const char *path = PKG_RUNDIR"/master"; - int i, fd = -1; - - for (i = 0; i < 5 && fd == -1; i++) { - fd = net_connect_unix(path); - if (fd != -1) - break; - - if (errno == ECONNREFUSED) { - if (unlink(path) < 0) - i_error("unlink(%s) failed: %m", path); - } else if (errno != ENOENT) { - i_fatal("Can't connect to master UNIX socket %s: %m", - path); - } - - /* need to create it */ - fd = net_listen_unix(path, 16); - if (fd != -1) { - master_exec(fd); - fd = -1; - } else if (errno != EADDRINUSE) { - i_fatal("Can't create master UNIX socket %s: %m", path); - } - } - - if (fd == -1) - i_fatal("Couldn't use/create UNIX socket %s", path); - - if (group_name[0] == '\0') - i_fatal("No login group name set"); - - if (strlen(group_name) >= 256) - i_fatal("Login group name too large: %s", group_name); - - /* group_name length is now guaranteed to be in range of 1..255 so we - can send <length byte><name> */ - group_name = t_strdup_printf("%c%s", (unsigned char)strlen(group_name), - group_name); - if (write_full(fd, group_name, strlen(group_name)) < 0) - i_fatal("write_full(master_fd) failed: %m"); - - master_read_env(fd); - return fd; -} - -static void master_input(void *context ATTR_UNUSED) -{ - int ret; - - ret = net_receive(master_fd, master_buf + master_pos, - sizeof(master_buf) - master_pos); - if (ret < 0) { - /* master died, kill all clients logging in */ - master_close(); - return; - } - - master_pos += ret; - if (master_pos < sizeof(master_buf)) - return; - - /* reply is now read */ - request_handle((void *)master_buf); - master_pos = 0; -} - -void master_init(int fd) -{ - main_ref(); - - master_fd = fd; - master_requests = hash_table_create(system_pool, system_pool, - 0, NULL, NULL); - - master_pos = 0; - io_master = io_add(master_fd, IO_READ, master_input, NULL); -} - -void master_deinit(void) -{ - hash_table_destroy(&master_requests); - - if (io_master != NULL) - io_remove(&io_master); -}
--- a/src/login-common/master.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -#ifndef MASTER_H -#define MASTER_H - -struct client; - -#include "../master/master-login-interface.h" - -typedef void master_callback_t(struct client *client, - const struct master_login_reply *reply); - -void master_request_login(struct client *client, master_callback_t *callback, - unsigned int auth_pid, unsigned int auth_id); -void master_request_abort(struct client *client); - -/* Notify master of a change in our state */ -void master_notify_state_change(enum master_login_state state); - -/* Close connection to master process */ -void master_close(void); - -/* inetd: Connect to existing master process, or create new one. */ -int master_connect(const char *group_name); - -void master_init(int fd); -void master_deinit(void); - -#endif
--- a/src/login-common/sasl-server.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/sasl-server.c Thu Apr 23 19:53:44 2009 -0400 @@ -3,11 +3,13 @@ #include "common.h" #include "base64.h" #include "buffer.h" +#include "istream.h" #include "str-sanitize.h" #include "auth-client.h" #include "ssl-proxy.h" +#include "master-interface.h" +#include "master-auth.h" #include "client-common.h" -#include "master.h" static enum auth_request_flags client_get_auth_flags(struct client *client) @@ -38,19 +40,21 @@ } static void -master_callback(struct client *client, const struct master_login_reply *reply) +master_auth_callback(const struct master_auth_reply *reply, void *context) { + struct client *client = context; enum sasl_server_reply sasl_reply = SASL_SERVER_REPLY_MASTER_FAILED; const char *data = NULL; + client->master_tag = 0; client->authenticating = FALSE; switch (reply->status) { - case MASTER_LOGIN_STATUS_OK: + case MASTER_AUTH_STATUS_OK: sasl_reply = SASL_SERVER_REPLY_SUCCESS; break; - case MASTER_LOGIN_STATUS_INTERNAL_ERROR: + case MASTER_AUTH_STATUS_INTERNAL_ERROR: break; - case MASTER_LOGIN_STATUS_MAX_CONNECTIONS: + case MASTER_AUTH_STATUS_MAX_CONNECTIONS: data = "Maximum number of connections from user+IP exceeded " "(mail_max_userip_connections)"; break; @@ -59,6 +63,35 @@ call_client_callback(client, sasl_reply, data, NULL); } +static void +master_send_request(struct client *client, struct auth_request *request) +{ + struct master_auth_request req; + const unsigned char *data; + size_t size; + buffer_t *buf; + + memset(&req, 0, sizeof(req)); + req.auth_pid = auth_client_request_get_server_pid(request); + req.auth_id = auth_client_request_get_id(request); + req.local_ip = client->local_ip; + req.remote_ip = client->ip; + + buf = buffer_create_dynamic(pool_datastack_create(), 256); + if (client->auth_command_tag != NULL) { + buffer_append(buf, client->auth_command_tag, + strlen(client->auth_command_tag)+1); + } + + data = i_stream_get_data(client->input, &size); + buffer_append(buf, data, size); + req.data_size = buf->used; + + client->master_tag = + master_auth_request(service, client->fd, &req, buf->data, + master_auth_callback, client); +} + static void authenticate_callback(struct auth_request *request, int status, const char *data_base64, const char *const *args, void *context) @@ -101,9 +134,7 @@ call_client_callback(client, SASL_SERVER_REPLY_SUCCESS, NULL, args); } else { - master_request_login(client, master_callback, - auth_client_request_get_server_pid(request), - auth_client_request_get_id(request)); + master_send_request(client, request); } break; case -1:
--- a/src/login-common/sasl-server.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/sasl-server.h Thu Apr 23 19:53:44 2009 -0400 @@ -1,6 +1,8 @@ #ifndef SASL_SERVER_H #define SASL_SERVER_H +struct client; + enum sasl_server_reply { SASL_SERVER_REPLY_SUCCESS, SASL_SERVER_REPLY_AUTH_FAILED,
--- a/src/login-common/ssl-proxy-openssl.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/ssl-proxy-openssl.c Thu Apr 23 19:53:44 2009 -0400 @@ -8,6 +8,7 @@ #include "read-full.h" #include "safe-memset.h" #include "llist.h" +#include "master-service.h" #include "ssl-proxy.h" #include <fcntl.h> @@ -473,7 +474,7 @@ ssl_proxy_unref(proxy); } -int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r) +int ssl_proxy_new(int fd, const struct ip_addr *ip, struct ssl_proxy **proxy_r) { struct ssl_proxy *proxy; SSL *ssl; @@ -524,7 +525,6 @@ DLLIST_PREPEND(&ssl_proxies, proxy); ssl_step(proxy); - main_ref(); *proxy_r = proxy; return sfd[1]; @@ -602,8 +602,6 @@ SSL_free(proxy->ssl); i_free(proxy); - - main_unref(); } static void ssl_proxy_destroy(struct ssl_proxy *proxy) @@ -631,7 +629,7 @@ ssl_proxy_unref(proxy); - main_listen_start(); + master_service_client_connection_destroyed(service); } static RSA *ssl_gen_rsa_key(SSL *ssl ATTR_UNUSED,
--- a/src/login-common/ssl-proxy.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/ssl-proxy.c Thu Apr 23 19:53:44 2009 -0400 @@ -9,7 +9,7 @@ /* no SSL support */ -int ssl_proxy_new(int fd ATTR_UNUSED, struct ip_addr *ip ATTR_UNUSED, +int ssl_proxy_new(int fd ATTR_UNUSED, const struct ip_addr *ip ATTR_UNUSED, struct ssl_proxy **proxy_r ATTR_UNUSED) { i_error("Dovecot wasn't built with SSL support");
--- a/src/login-common/ssl-proxy.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/login-common/ssl-proxy.h Thu Apr 23 19:53:44 2009 -0400 @@ -9,7 +9,7 @@ /* establish SSL connection with the given fd, returns a new fd which you must use from now on, or -1 if error occurred. Unless -1 is returned, the given fd must be simply forgotten. */ -int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r); +int ssl_proxy_new(int fd, const struct ip_addr *ip, struct ssl_proxy **proxy_r); bool ssl_proxy_has_valid_client_cert(const struct ssl_proxy *proxy) ATTR_PURE; bool ssl_proxy_has_broken_client_cert(struct ssl_proxy *proxy); const char *ssl_proxy_get_peer_name(struct ssl_proxy *proxy);
--- a/src/master/Makefile.am Thu Apr 23 14:07:45 2009 -0400 +++ b/src/master/Makefile.am Thu Apr 23 19:53:44 2009 -0400 @@ -1,7 +1,7 @@ pkglibexecdir = $(libexecdir)/dovecot sbin_PROGRAMS = dovecot -pkglibexec_PROGRAMS = ssl-build-param +#pkglibexec_PROGRAMS = ssl-build-param AM_CPPFLAGS = \ -I$(top_srcdir)/src/lib \ @@ -11,52 +11,37 @@ -DPKG_RUNDIR=\""$(rundir)"\" \ -DPKG_STATEDIR=\""$(statedir)"\" \ -DPKG_LIBEXECDIR=\""$(pkglibexecdir)"\" \ - -DBINDIR=\""$(bindir)"\" \ - -DSSLDIR=\""$(ssldir)\"" + -DBINDIR=\""$(bindir)"\" -dovecot_LDADD = \ +libs = \ $(LIBCAP) \ $(LIBDOVECOT) +dovecot_LDADD = $(libs) +dovecot_DEPENDENCIES = $(libs) + dovecot_SOURCES = \ - auth-process.c \ - askpass.c \ capabilities-posix.c \ - child-process.c \ - dict-process.c \ dup2-array.c \ - listener.c \ - log.c \ - login-process.c \ - mail-process.c \ + main.c \ master-settings.c \ - main.c \ - ssl-init.c \ - sysinfo-get.c + service-auth-server.c \ + service-auth-source.c \ + service-listen.c \ + service-log.c \ + service-monitor.c \ + service-process.c \ + service.c noinst_HEADERS = \ - auth-process.h \ - askpass.h \ capabilities.h \ - child-process.h \ - dict-process.h \ + common.h \ dup2-array.h \ - listener.h \ - common.h \ - log.h \ - login-process.h \ - mail-process.h \ - master-login-interface.h \ master-settings.h \ - ssl-init.h \ - sysinfo-get.h - -ssl_build_param_SOURCES = \ - ssl-init-main.c \ - ssl-init-openssl.c \ - ssl-init-gnutls.c - -ssl_build_param_LDADD = \ - $(LIBDOVECOT) \ - $(SSL_LIBS) -ssl_build_param_DEPENDENCIES = $(LIBDOVECOT) + service-auth-server.h \ + service-auth-source.h \ + service-listen.h \ + service-log.h \ + service-monitor.h \ + service-process.h \ + service.h
--- a/src/master/auth-process.c Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,760 +0,0 @@ -/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "array.h" -#include "hash.h" -#include "ioloop.h" -#include "env-util.h" -#include "fd-close-on-exec.h" -#include "unix-socket-create.h" -#include "network.h" -#include "istream.h" -#include "ostream.h" -#include "str.h" -#include "restrict-access.h" -#include "restrict-process-size.h" -#include "auth-process.h" -#include "child-process.h" -#include "../auth/auth-master-interface.h" -#include "log.h" - -#include <stdlib.h> -#include <unistd.h> -#include <fcntl.h> -#include <pwd.h> -#include <syslog.h> - -#define MAX_INBUF_SIZE 8192 -#define MAX_OUTBUF_SIZE 65536 - -struct auth_process_group { - struct auth_process_group *next; - - int listen_fd; - const struct master_settings *master_set; - const struct master_auth_settings *set; - - unsigned int process_count; - struct auth_process *processes; -}; - -struct auth_process { - struct auth_process *next; - - struct auth_process_group *group; - pid_t pid; - int fd; - struct io *io; - struct istream *input; - struct ostream *output; - - int worker_listen_fd; - struct io *worker_io; - - struct hash_table *requests; - - unsigned int external:1; - unsigned int version_received:1; - unsigned int initialized:1; - unsigned int in_auth_reply:1; -}; - -bool have_initialized_auth_processes = FALSE; - -static struct child_process auth_child_process = - { MEMBER(type) PROCESS_TYPE_AUTH }; -static struct child_process auth_worker_child_process = - { MEMBER(type) PROCESS_TYPE_AUTH_WORKER }; - -static struct timeout *to; -static unsigned int auth_tag; -static struct auth_process_group *process_groups; -static bool auth_stalled = FALSE; - -static void auth_process_destroy(struct auth_process *p); -static int create_auth_worker(struct auth_process *process, int fd); -static void auth_processes_start_missing(void *context); - -void auth_process_request(struct auth_process *process, unsigned int login_pid, - unsigned int login_id, - struct login_auth_request *request) -{ - string_t *str; - ssize_t ret; - - str = t_str_new(256); - str_printfa(str, "REQUEST\t%u\t%u\t%u\n", - ++auth_tag, login_pid, login_id); - - ret = o_stream_send(process->output, str_data(str), str_len(str)); - if (ret != (ssize_t)str_len(str)) { - if (ret >= 0) { - /* FIXME: well .. I'm not sure if it'd be better to - just block here. I don't think this condition should - happen often, so this could mean that the auth - process is stuck. Or that the computer is just - too heavily loaded. Possibility to block infinitely - is annoying though, so for now don't do it. */ - i_warning("Auth process %s transmit buffer full, " - "killing..", dec2str(process->pid)); - } - auth_process_destroy(process); - } else { - hash_table_insert(process->requests, - POINTER_CAST(auth_tag), request); - } -} - -static bool -auth_process_input_user(struct auth_process *process, const char *args) -{ - struct 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("BUG: Auth process %s sent corrupted USER line", - dec2str(process->pid)); - return FALSE; - } - id = (unsigned int)strtoul(list[0], NULL, 10); - - request = hash_table_lookup(process->requests, POINTER_CAST(id)); - if (request == NULL) { - i_error("BUG: Auth process %s sent unrequested reply with ID " - "%u", dec2str(process->pid), id); - return FALSE; - } - - if (!auth_success_written) { - int fd; - - 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; - } - - auth_master_callback(list[1], list + 2, request); - hash_table_remove(process->requests, POINTER_CAST(id)); - return TRUE; -} - -static bool -auth_process_input_notfound(struct auth_process *process, const char *args) -{ - struct login_auth_request *request; - unsigned int id; - - id = (unsigned int)strtoul(args, NULL, 10); - - request = hash_table_lookup(process->requests, POINTER_CAST(id)); - if (request == NULL) { - i_error("BUG: Auth process %s sent unrequested reply with ID " - "%u", dec2str(process->pid), id); - return FALSE; - } - - auth_master_callback(NULL, NULL, request); - hash_table_remove(process->requests, POINTER_CAST(id)); - return TRUE; -} - -static bool -auth_process_input_spid(struct auth_process *process, const char *args) -{ - unsigned int pid; - - if (process->initialized) { - i_error("BUG: Authentication server re-handshaking"); - return FALSE; - } - - pid = (unsigned int)strtoul(args, NULL, 10); - if (pid == 0) { - i_error("BUG: Authentication server said it's PID 0"); - return FALSE; - } - - if (process->pid != 0 && process->pid != (pid_t)pid) { - i_error("BUG: Authentication server sent invalid SPID " - "(%u != %s)", pid, dec2str(process->pid)); - return FALSE; - } - - process->pid = pid; - process->initialized = TRUE; - - have_initialized_auth_processes = TRUE; - return TRUE; -} - -static bool -auth_process_input_fail(struct auth_process *process, const char *args) -{ - struct 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 = hash_table_lookup(process->requests, POINTER_CAST(id)); - if (request == NULL) { - i_error("BUG: Auth process %s sent unrequested reply with ID " - "%u", dec2str(process->pid), id); - return FALSE; - } - - auth_master_callback(NULL, NULL, request); - hash_table_remove(process->requests, POINTER_CAST(id)); - return TRUE; -} - -static bool -auth_process_input_line(struct auth_process *process, const char *line) -{ - if (strncmp(line, "USER\t", 5) == 0) - return auth_process_input_user(process, line + 5); - else if (strncmp(line, "NOTFOUND\t", 9) == 0) - return auth_process_input_notfound(process, line + 9); - else if (strncmp(line, "FAIL\t", 5) == 0) - return auth_process_input_fail(process, line + 5); - else if (strncmp(line, "SPID\t", 5) == 0) - return auth_process_input_spid(process, line + 5); - else - return TRUE; -} - -static void auth_process_input(struct auth_process *process) -{ - const char *line; - bool ret; - - switch (i_stream_read(process->input)) { - case 0: - return; - case -1: - /* disconnected */ - auth_process_destroy(process); - return; - case -2: - /* buffer full */ - i_error("BUG: Auth process %s sent us more than %d " - "bytes of data", dec2str(process->pid), - (int)MAX_INBUF_SIZE); - auth_process_destroy(process); - return; - } - - if (!process->version_received) { - line = i_stream_next_line(process->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("Auth process %s not compatible with master " - "process (mixed old and new binaries?)", - dec2str(process->pid)); - auth_process_destroy(process); - return; - } - process->version_received = TRUE; - } - - while ((line = i_stream_next_line(process->input)) != NULL) { - T_BEGIN { - ret = auth_process_input_line(process, line); - } T_END; - if (!ret) { - auth_process_destroy(process); - break; - } - } -} - -static void auth_worker_input(struct auth_process *p) -{ - int fd; - - fd = net_accept(p->worker_listen_fd, NULL, NULL); - if (fd < 0) { - if (fd == -2) - i_error("accept(worker) failed: %m"); - return; - } - - net_set_nonblock(fd, TRUE); - fd_close_on_exec(fd, TRUE); - - create_auth_worker(p, fd); -} - -static struct auth_process * -auth_process_new(pid_t pid, int fd, struct auth_process_group *group) -{ - struct auth_process *p; - const char *path, *handshake; - - if (pid != 0) - child_process_add(pid, &auth_child_process); - - p = i_new(struct auth_process, 1); - p->group = group; - p->pid = pid; - p->fd = fd; - p->io = io_add(fd, IO_READ, auth_process_input, p); - p->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE); - p->output = o_stream_create_fd(fd, MAX_OUTBUF_SIZE, FALSE); - p->requests = hash_table_create(default_pool, default_pool, 0, - NULL, NULL); - - group->process_count++; - - path = t_strdup_printf("%s/auth-worker.%s", - *group->set->chroot != '\0' ? - group->set->chroot : - group->master_set->base_dir, - dec2str(pid)); - p->worker_listen_fd = - unix_socket_create(path, 0600, group->set->uid, - group->set->gid, 128); - if (p->worker_listen_fd == -1) - i_fatal("Couldn't create auth worker listener"); - - net_set_nonblock(p->worker_listen_fd, TRUE); - fd_close_on_exec(p->worker_listen_fd, TRUE); - p->worker_io = io_add(p->worker_listen_fd, IO_READ, - auth_worker_input, p); - - handshake = t_strdup_printf("VERSION\t%u\t%u\n", - AUTH_MASTER_PROTOCOL_MAJOR_VERSION, - AUTH_MASTER_PROTOCOL_MINOR_VERSION); - (void)o_stream_send_str(p->output, handshake); - - p->next = group->processes; - group->processes = p; - return p; -} - -static void auth_process_destroy(struct auth_process *p) -{ - struct hash_iterate_context *iter; - void *key, *value; - struct auth_process **pos; - const char *path; - - if (!p->initialized && io_loop_is_running(ioloop) && !p->external) { - /* log the process exit and kill ourself */ - child_processes_flush(); - log_deinit(); - i_fatal("Auth process died too early - shutting down"); - } - - for (pos = &p->group->processes; *pos != NULL; pos = &(*pos)->next) { - if (*pos == p) { - *pos = p->next; - break; - } - } - p->group->process_count--; - - path = t_strdup_printf("%s/auth-worker.%s", - *p->group->set->chroot != '\0' ? - p->group->set->chroot : - p->group->master_set->base_dir, - dec2str(p->pid)); - (void)unlink(path); - - io_remove(&p->worker_io); - if (close(p->worker_listen_fd) < 0) - i_error("close(worker_listen) failed: %m"); - - iter = hash_table_iterate_init(p->requests); - while (hash_table_iterate(iter, &key, &value)) - auth_master_callback(NULL, NULL, value); - hash_table_iterate_deinit(&iter); - hash_table_destroy(&p->requests); - - i_stream_destroy(&p->input); - o_stream_destroy(&p->output); - io_remove(&p->io); - if (close(p->fd) < 0) - i_error("close(auth) failed: %m"); - i_free(p); -} - -static int connect_auth_socket(struct auth_process_group *group, - const char *path) -{ - struct auth_process *auth; - int fd; - - fd = net_connect_unix(path); - if (fd == -1) { - i_error("net_connect_unix(%s) failed: %m", path); - return -1; - } - - net_set_nonblock(fd, TRUE); - fd_close_on_exec(fd, TRUE); - auth = auth_process_new(0, fd, group); - auth->external = TRUE; - return 0; -} - -static void auth_set_environment(const struct master_settings *master_set, - const struct master_auth_settings *set) -{ - struct restrict_access_settings rset; - - master_settings_export_to_env(master_set); - - /* setup access environment */ - restrict_access_init(&rset); - rset.system_groups_user = set->user; - rset.uid = set->uid; - rset.gid = set->gid; - rset.chroot_dir = set->chroot; - restrict_access_set_env(&rset); - - /* set other environment */ - env_put("DOVECOT_MASTER=1"); - env_put(t_strconcat("AUTH_NAME=", set->name, NULL)); - restrict_process_size(set->process_size, (unsigned int)-1); -} - -static const struct master_auth_socket_settings * -get_connect_socket(const struct master_auth_settings *auth_set) -{ - struct master_auth_socket_settings *const *as; - unsigned int count; - - if (!array_is_created(&auth_set->sockets)) - return NULL; - - as = array_get(&auth_set->sockets, &count); - if (count > 0 && strcmp(as[0]->type, "connect") == 0) - return as[0]; - else - return NULL; -} - -static int create_auth_process(struct auth_process_group *group) -{ - const struct master_auth_socket_settings *as; - struct master_auth_socket_unix_settings *const *masters; - const char *prefix, *executable; - struct log_io *log; - pid_t pid; - unsigned int count; - int fd[2], log_fd, i; - - /* see if this is a connect socket */ - as = get_connect_socket(group->set); - if (as != NULL) { - masters = array_get(&as->masters, &count); - if (count > 0) - return connect_auth_socket(group, masters[0]->path); - } - - /* create communication to process with a socket pair */ - if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { - i_error("socketpair() failed: %m"); - return -1; - } - - log_fd = log_create_pipe(&log, 0); - if (log_fd < 0) - pid = -1; - else { - pid = fork(); - if (pid < 0) - i_error("fork() failed: %m"); - } - - if (pid < 0) { - (void)close(fd[0]); - (void)close(fd[1]); - (void)close(log_fd); - return -1; - } - - if (pid != 0) { - /* master */ - prefix = t_strdup_printf("auth(%s): ", group->set->name); - log_set_prefix(log, prefix); - log_set_pid(log, pid); - - net_set_nonblock(fd[0], TRUE); - fd_close_on_exec(fd[0], TRUE); - auth_process_new(pid, fd[0], group); - (void)close(fd[1]); - (void)close(log_fd); - return 0; - } - - prefix = t_strdup_printf("master-auth(%s): ", group->set->name); - log_set_prefix(log, prefix); - - /* move master communication handle to 0 */ - if (dup2(fd[1], 0) < 0) - i_fatal("dup2(stdin) failed: %m"); - - (void)close(fd[0]); - (void)close(fd[1]); - - /* make sure we don't leak syslog fd. try to do it as late as possible, - but also before dup2()s in case syslog fd is one of them. */ - closelog(); - - /* set stdout to /dev/null, so anything written into it gets ignored. */ - if (dup2(null_fd, 1) < 0) - i_fatal("dup2(stdout) failed: %m"); - - if (dup2(log_fd, 2) < 0) - i_fatal("dup2(stderr) failed: %m"); - - child_process_init_env(group->master_set); - - if (group->listen_fd != 3) { - if (dup2(group->listen_fd, 3) < 0) - i_fatal("dup2() failed: %m"); - } - fd_close_on_exec(3, FALSE); - - for (i = 0; i <= 2; i++) - fd_close_on_exec(i, FALSE); - - auth_set_environment(group->master_set, group->set); - - env_put(t_strdup_printf("AUTH_WORKER_PATH=%s/auth-worker.%s", - *group->set->chroot != '\0' ? "" : - group->master_set->base_dir, - dec2str(getpid()))); - - executable = group->set->executable; - client_process_exec(executable, ""); - i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", executable); - return -1; -} - -static int create_auth_worker(struct auth_process *process, int fd) -{ - struct log_io *log; - const char *prefix, *executable; - pid_t pid; - int log_fd, i; - - log_fd = log_create_pipe(&log, 0); - if (log_fd < 0) - pid = -1; - else { - pid = fork(); - if (pid < 0) - i_error("fork() failed: %m"); - } - - if (pid < 0) { - (void)close(log_fd); - return -1; - } - - if (pid != 0) { - /* master */ - child_process_add(pid, &auth_worker_child_process); - prefix = t_strdup_printf("auth-worker(%s): ", - process->group->set->name); - log_set_prefix(log, prefix); - (void)close(fd); - (void)close(log_fd); - return 0; - } - - prefix = t_strdup_printf("master-auth-worker(%s): ", - process->group->set->name); - log_set_prefix(log, prefix); - - /* make sure we don't leak syslog fd. try to do it as late as possible, - but also before dup2()s in case syslog fd is one of them. */ - closelog(); - - /* set stdin and stdout to /dev/null, so anything written into it - gets ignored. */ - if (dup2(null_fd, 0) < 0) - i_fatal("dup2(stdin) failed: %m"); - if (dup2(null_fd, 1) < 0) - i_fatal("dup2(stdout) failed: %m"); - - if (dup2(log_fd, 2) < 0) - i_fatal("dup2(stderr) failed: %m"); - - if (dup2(fd, 4) < 0) - i_fatal("dup2(4) failed: %m"); - - for (i = 0; i <= 2; i++) - fd_close_on_exec(i, FALSE); - fd_close_on_exec(4, FALSE); - - child_process_init_env(process->group->master_set); - auth_set_environment(process->group->master_set, process->group->set); - - executable = t_strconcat(process->group->set->executable, " -w", NULL); - client_process_exec(executable, ""); - i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", executable); - return -1; -} - -struct auth_process *auth_process_find(unsigned int pid) -{ - struct auth_process_group *group; - struct auth_process *p; - - for (group = process_groups; group != NULL; group = group->next) { - for (p = group->processes; p != NULL; p = p->next) { - if ((unsigned int)p->pid == pid) - return p; - } - } - - return NULL; -} - -static void auth_process_group_create(struct master_settings *set, - struct master_auth_settings *auth_set) -{ - struct auth_process_group *group; - const char *path; - - group = i_new(struct auth_process_group, 1); - group->master_set = set; - group->set = auth_set; - - group->next = process_groups; - process_groups = group; - - if (get_connect_socket(auth_set) != NULL) - return; - - path = t_strconcat(set->login_dir, "/", auth_set->name, NULL); - group->listen_fd = unix_socket_create(path, 0660, master_uid, - set->server->login_gid, 128); - if (group->listen_fd == -1) - i_fatal("Couldn't create auth process listener"); - - net_set_nonblock(group->listen_fd, TRUE); - fd_close_on_exec(group->listen_fd, TRUE); -} - -static void auth_process_group_destroy(struct auth_process_group *group) -{ - struct auth_process *next; - const char *path; - - while (group->processes != NULL) { - next = group->processes->next; - auth_process_destroy(group->processes); - group->processes = next; - } - - path = t_strconcat(group->master_set->login_dir, "/", - group->set->name, NULL); - (void)unlink(path); - - if (close(group->listen_fd) < 0) - i_error("close(%s) failed: %m", path); - i_free(group); -} - -void auth_processes_destroy_all(void) -{ - struct auth_process_group *next; - - while (process_groups != NULL) { - next = process_groups->next; - auth_process_group_destroy(process_groups); - process_groups = next; - } - - have_initialized_auth_processes = FALSE; -} - -static void auth_process_groups_create(struct master_settings *set) -{ - struct master_auth_settings *const *auth_sets; - unsigned int i, count; - - auth_sets = array_get(&set->auths, &count); - for (i = 0; i < count; i++) - auth_process_group_create(set, auth_sets[i]); -} - -static void auth_processes_stall(void) -{ - if (auth_stalled) - return; - - i_error("Temporary failure in creating authentication processes, " - "slowing down for now"); - auth_stalled = TRUE; - - timeout_remove(&to); - to = timeout_add(60*1000, auth_processes_start_missing, NULL); -} - -static void -auth_processes_start_missing(void *context ATTR_UNUSED) -{ - struct auth_process_group *group; - unsigned int count; - - if (process_groups == NULL) { - /* first time here, create the groups */ - auth_process_groups_create(master_set->defaults); - } - - for (group = process_groups; group != NULL; group = group->next) { - count = group->process_count; - for (; count < group->set->count; count++) { - if (create_auth_process(group) < 0) { - auth_processes_stall(); - return; - } - } - } - - if (auth_stalled) { - /* processes were created successfully */ - i_info("Created authentication processes successfully, " - "unstalling"); - - auth_stalled = FALSE; - timeout_remove(&to); - to = timeout_add(1000, auth_processes_start_missing, NULL); - } -} - -void auth_processes_init(void) -{ - process_groups = NULL; - to = timeout_add(1000, auth_processes_start_missing, NULL); - - auth_processes_start_missing(NULL); -} - -void auth_processes_deinit(void) -{ - timeout_remove(&to); - auth_processes_destroy_all(); -}
--- a/src/master/auth-process.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -#ifndef AUTH_PROCESS_H -#define AUTH_PROCESS_H - -struct login_auth_request; - -extern bool have_initialized_auth_processes; - -void auth_master_callback(const char *user, const char *const *args, - struct login_auth_request *request); - -/* Find process for given id */ -struct auth_process *auth_process_find(unsigned int pid); - -/* Request information about given cookie */ -void auth_process_request(struct auth_process *process, unsigned int login_pid, - unsigned int login_id, - struct login_auth_request *request); - -/* Close any fds used by auth processes */ -void auth_processes_destroy_all(void); - -void auth_processes_init(void); -void auth_processes_deinit(void); - -#endif
--- a/src/master/child-process.c Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,290 +0,0 @@ -/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "lib-signals.h" -#include "array.h" -#include "hash.h" -#include "str.h" -#include "env-util.h" -#include "child-process.h" - -#include <unistd.h> -#include <syslog.h> -#include <sys/wait.h> - -const char *process_names[PROCESS_TYPE_MAX] = { - "unknown", - "auth", - "auth-worker", - "login", - "imap", - "pop3", - "ssl-build-param", - "dict" -}; - -struct hash_table *processes; -static child_process_destroy_callback_t *destroy_callbacks[PROCESS_TYPE_MAX]; - -struct child_process *child_process_lookup(pid_t pid) -{ - return hash_table_lookup(processes, POINTER_CAST(pid)); -} - -void child_process_add(pid_t pid, struct child_process *process) -{ - hash_table_insert(processes, POINTER_CAST(pid), process); -} - -void child_process_remove(pid_t pid) -{ - hash_table_remove(processes, POINTER_CAST(pid)); -} - -void child_process_init_env(const struct master_settings *set) -{ - /* remove all environment, we don't need them */ - env_clean(); - - /* we'll log through master process */ - env_put("LOG_TO_MASTER=1"); - env_put("DOVECONF_ENV=1"); - if (env_tz != NULL) - env_put(t_strconcat("TZ=", env_tz, NULL)); - - if (master_set != NULL && !set->version_ignore) - env_put("DOVECOT_VERSION="PACKAGE_VERSION); -#ifdef DEBUG - if (gdb) env_put("GDB=1"); -#endif -} - -void client_process_exec(const char *cmd, const char *title) -{ - const char **argv; - - /* very simple argument splitting. */ - if (*title == '\0') - argv = t_strsplit(cmd, " "); - else - argv = t_strsplit(t_strconcat(cmd, " ", title, NULL), " "); - - client_process_exec_argv(argv[0], argv); -} - -void client_process_exec_argv(const char *executable, const char **argv) -{ - const char *p; - - /* hide the path, it's ugly */ - p = strrchr(argv[0], '/'); - if (p != NULL) argv[0] = p+1; - - execv(executable, (char **)argv); -} - -static const char *get_exit_status_message(enum fatal_exit_status status, - enum process_type process_type) -{ - switch (status) { - case FATAL_LOGOPEN: - return "Can't open log file"; - case FATAL_LOGWRITE: - return "Can't write to log file"; - case FATAL_LOGERROR: - return "Internal logging error"; - case FATAL_OUTOFMEM: - switch (process_type) { - case PROCESS_TYPE_AUTH: - case PROCESS_TYPE_AUTH_WORKER: - return "Out of memory - see auth_process_size setting"; - case PROCESS_TYPE_LOGIN: - return "Out of memory - see login_process_size setting"; - case PROCESS_TYPE_IMAP: - case PROCESS_TYPE_POP3: - return "Out of memory - see mail_process_size setting"; - case PROCESS_TYPE_UNKNOWN: - case PROCESS_TYPE_SSL_PARAM: - case PROCESS_TYPE_DICT: - case PROCESS_TYPE_MAX: - break; - } - return "Out of memory"; - case FATAL_EXEC: - return "exec() failed"; - - case FATAL_DEFAULT: - return "Fatal failure"; - } - - return NULL; -} - -static void -log_coredump(string_t *str, enum process_type process_type, int status) -{ -#ifdef WCOREDUMP - struct master_auth_settings *const *auth_set; - int signum = WTERMSIG(status); - - if (WCOREDUMP(status)) { - str_append(str, " (core dumped)"); - return; - } - - if (signum != SIGABRT && signum != SIGSEGV && signum != SIGBUS) - return; - - /* let's try to figure out why we didn't get a core dump */ - if (core_dumps_disabled) { - str_printfa(str, " (core dumps disabled)"); - return; - } - - switch (process_type) { - case PROCESS_TYPE_LOGIN: -#ifdef HAVE_PR_SET_DUMPABLE - str_append(str, " (core not dumped - add -D to login_executable)"); - return; -#else - break; -#endif - case PROCESS_TYPE_IMAP: - case PROCESS_TYPE_POP3: -#ifndef HAVE_PR_SET_DUMPABLE - if (!master_set->defaults->mail_drop_priv_before_exec) { - str_append(str, " (core not dumped - set mail_drop_priv_before_exec=yes)"); - return; - } - if (*master_set->defaults->mail_privileged_group != '\0') { - str_append(str, " (core not dumped - mail_privileged_group prevented it)"); - return; - } -#endif - str_append(str, " (core not dumped - is home dir set?)"); - return; - case PROCESS_TYPE_AUTH: - case PROCESS_TYPE_AUTH_WORKER: - auth_set = array_idx(&master_set->defaults->auths, 0); - if (auth_set[0]->uid == 0) - break; -#ifdef HAVE_PR_SET_DUMPABLE - str_printfa(str, " (core not dumped - " - "no permissions for auth user %s in %s?)", - auth_set[0]->user, master_set->defaults->base_dir); -#else - str_append(str, " (core not dumped - auth user is not root)"); -#endif - return; - default: - break; - } - str_append(str, " (core not dumped)"); -#endif -} - -static void sigchld_handler(const siginfo_t *si ATTR_UNUSED, - void *context ATTR_UNUSED) -{ - struct child_process *process; - const char *process_type_name, *msg; - enum process_type process_type; - string_t *str; - pid_t pid; - int status; - bool abnormal_exit; - - str = t_str_new(128); - while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { - /* get the type and remove from hash */ - str_truncate(str, 0); - process = child_process_lookup(pid); - if (process == NULL) - process_type = PROCESS_TYPE_UNKNOWN; - else { - process_type = process->type; - child_process_remove(pid); - } - abnormal_exit = TRUE; - - /* write errors to syslog */ - process_type_name = process_names[process_type]; - if (WIFEXITED(status)) { - status = WEXITSTATUS(status); - if (status == 0) { - abnormal_exit = FALSE; - if (process_type == PROCESS_TYPE_UNKNOWN) { - i_error("unknown child %s exited " - "successfully", dec2str(pid)); - } - } else if (status == 1 && - process_type == PROCESS_TYPE_SSL_PARAM) { - /* kludgy. hide this failure. */ - } else if (status == FATAL_DEFAULT && - process->seen_fatal) { - /* the error was already logged. */ - } else { - msg = get_exit_status_message(status, - process_type); - msg = msg == NULL ? "" : - t_strconcat(" (", msg, ")", NULL); - str_printfa(str, - "child %s (%s) returned error %d%s", - dec2str(pid), process_type_name, - status, msg); - } - } else if (WIFSIGNALED(status)) { - str_printfa(str, "child %s (%s) killed with signal %d", - dec2str(pid), process_type_name, - WTERMSIG(status)); - log_coredump(str, process_type, status); - } - - if (str_len(str) > 0) { - if (process != NULL && process->ip.family != 0) { - if (!process->ip_changed) - str_append(str, " (ip="); - else - str_append(str, " (latest ip="); - str_printfa(str, "%s)", - net_ip2addr(&process->ip)); - } - i_error("%s", str_c(str)); - } - - if (destroy_callbacks[process_type] != NULL) { - destroy_callbacks[process_type](process, pid, - abnormal_exit); - } - } - - if (pid == -1 && errno != EINTR && errno != ECHILD) - i_warning("waitpid() failed: %m"); -} - -void child_process_set_destroy_callback(enum process_type type, - child_process_destroy_callback_t *cb) -{ - i_assert(type < PROCESS_TYPE_MAX); - - destroy_callbacks[type] = cb; -} - -void child_processes_init(void) -{ - processes = hash_table_create(default_pool, default_pool, 128, NULL, NULL); - lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, NULL); -} - -void child_processes_flush(void) -{ - /* make sure we log if child processes died unexpectedly */ - sigchld_handler(NULL, NULL); -} - -void child_processes_deinit(void) -{ - child_processes_flush(); - lib_signals_unset_handler(SIGCHLD, sigchld_handler, NULL); - hash_table_destroy(&processes); -}
--- a/src/master/child-process.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -#ifndef CHILD_PROCESS_H -#define CHILD_PROCESS_H - -enum process_type { - PROCESS_TYPE_UNKNOWN, - PROCESS_TYPE_AUTH, - PROCESS_TYPE_AUTH_WORKER, - PROCESS_TYPE_LOGIN, - PROCESS_TYPE_IMAP, - PROCESS_TYPE_POP3, - PROCESS_TYPE_SSL_PARAM, - PROCESS_TYPE_DICT, - - PROCESS_TYPE_MAX -}; - -struct child_process { - enum process_type type; - struct ip_addr ip; - unsigned int allow_change_ip:1; - unsigned int seen_fatal:1; - unsigned int ip_changed:1; -}; - -typedef void child_process_destroy_callback_t(struct child_process *process, - pid_t pid, bool abnormal_exit); - -extern const char *process_names[]; -extern struct hash_table *processes; - -struct child_process *child_process_lookup(pid_t pid); -void child_process_add(pid_t pid, struct child_process *process); -void child_process_remove(pid_t pid); - -void child_process_init_env(const struct master_settings *set); -void client_process_exec(const char *cmd, const char *title); -void client_process_exec_argv(const char *executable, const char **argv); - -void child_process_set_destroy_callback(enum process_type type, - child_process_destroy_callback_t *cb); - -void child_processes_init(void); -void child_processes_flush(void); -void child_processes_deinit(void); - -#endif
--- a/src/master/common.h Thu Apr 23 14:07:45 2009 -0400 +++ b/src/master/common.h Thu Apr 23 19:53:44 2009 -0400 @@ -1,26 +1,16 @@ #ifndef COMMON_H #define COMMON_H -struct ip_addr; - #include "lib.h" +#include "master-interface.h" #include "master-settings.h" -#define AUTH_SUCCESS_PATH PKG_STATEDIR"/auth-success" - -extern struct ioloop *ioloop; -extern int null_fd, inetd_login_fd; +extern struct master_service *master_service; extern uid_t master_uid; -extern char program_path[]; -extern char ssl_manual_key_password[]; -extern const char *env_tz; -extern bool auth_success_written; +extern gid_t master_gid; extern bool core_dumps_disabled; -#ifdef DEBUG -extern bool gdb; -#endif +extern int null_fd; -#define IS_INETD() \ - (inetd_login_fd != -1) +void process_exec(const char *cmd, const char *extra_args[]) ATTR_NORETURN; #endif
--- a/src/master/dict-process.c Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,241 +0,0 @@ -/* Copyright (c) 2006-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "array.h" -#include "ioloop.h" -#include "network.h" -#include "fd-close-on-exec.h" -#include "env-util.h" -#include "log.h" -#include "child-process.h" -#include "dict-process.h" - -#include <syslog.h> -#include <unistd.h> -#include <sys/stat.h> - -#define DICT_SERVER_SOCKET_NAME "dict-server" - -struct dict_listener { - char *path; - int fd; - struct io *io; - - struct dict_process *processes; - unsigned int destroyed:1; -}; - -struct dict_process { - struct child_process process; - struct dict_process *next; - - struct dict_listener *listener; - struct log_io *log; -}; - -static struct dict_listener *dict_listener; - -static int dict_process_create(struct dict_listener *listener) -{ - struct dict_process *process; - struct log_io *log; - const char *executable; - unsigned int i; - int log_fd; - pid_t pid; - - process = i_new(struct dict_process, 1); - process->process.type = PROCESS_TYPE_DICT; - process->listener = listener; - - log_fd = log_create_pipe(&log, 0); - if (log_fd < 0) - pid = -1; - else { - pid = fork(); - if (pid < 0) - i_error("fork() failed: %m"); - } - - if (pid < 0) { - (void)close(log_fd); - i_free(process); - return -1; - } - - if (pid != 0) { - /* master */ - process->next = process->listener->processes; - process->listener->processes = process; - - child_process_add(pid, &process->process); - log_set_prefix(log, "dict: "); - log_set_pid(log, pid); - (void)close(log_fd); - - process->log = log; - log_ref(process->log); - return 0; - } - log_set_prefix(log, "master-dict: "); - - /* make sure we don't leak syslog fd. try to do it as late as possible, - but also before dup2()s in case syslog fd is one of them. */ - closelog(); - - /* set stdin and stdout to /dev/null, so anything written into it - gets ignored. */ - if (dup2(null_fd, 0) < 0) - i_fatal("dup2(stdin) failed: %m"); - if (dup2(null_fd, 1) < 0) - i_fatal("dup2(stdout) failed: %m"); - - /* stderr = log, 3 = listener */ - if (dup2(log_fd, 2) < 0) - i_fatal("dup2(stderr) failed: %m"); - if (dup2(process->listener->fd, 3) < 0) - i_fatal("dup2(3) failed: %m"); - - for (i = 0; i <= 3; i++) - fd_close_on_exec(i, FALSE); - - child_process_init_env(master_set->defaults); - master_settings_export_to_env(master_set->defaults); - env_put(t_strconcat("DICT_LISTEN_FROM_FD=", - process->listener->path, NULL)); - - executable = PKG_LIBEXECDIR"/dict"; - client_process_exec(executable, ""); - i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", executable); - return -1; -} - -static void dict_listener_unref(struct dict_listener *listener) -{ - if (listener->processes == NULL && listener->destroyed) - i_free(listener); -} - -static void dict_process_deinit(struct dict_process *process) -{ - struct dict_listener *listener = process->listener; - struct dict_process **p; - - for (p = &listener->processes; *p != NULL; p = &(*p)->next) { - if (*p == process) { - *p = process->next; - break; - } - } - - if (process->log != NULL) - log_unref(process->log); - i_free(process); - - dict_listener_unref(listener); -} - -static void dict_listener_input(struct dict_listener *listener) -{ - unsigned int i = 0; - int fd; - - i_assert(listener->processes == NULL); - - for (i = 0; i < master_set->defaults->dict_process_count; i++) { - if (dict_process_create(listener) < 0) - break; - } - if (i > 0) - io_remove(&listener->io); - else { - /* failed to create dict process, so just reject this - connection and try again later */ - fd = net_accept(listener->fd, NULL, NULL); - if (fd >= 0) - (void)close(fd); - } -} - -static struct dict_listener *dict_listener_init(const char *path) -{ - struct dict_listener *listener; - mode_t old_umask; - - listener = i_new(struct dict_listener, 1); - listener->path = i_strdup(path); - old_umask = umask(0); - listener->fd = net_listen_unix_unlink_stale(path, 128); - umask(old_umask); - if (listener->fd == -1) { - if (errno == EADDRINUSE) - i_fatal("Socket already exists: %s", path); - else - i_fatal("net_listen_unix(%s) failed: %m", path); - } - fd_close_on_exec(listener->fd, TRUE); - listener->io = io_add(listener->fd, IO_READ, - dict_listener_input, listener); - return listener; -} - -static void dict_listener_deinit(struct dict_listener *listener) -{ - listener->destroyed = TRUE; - - if (listener->io != NULL) - io_remove(&listener->io); - if (close(listener->fd) < 0) - i_error("close(dict listener) failed: %m"); - listener->fd = -1; - - /* don't try to free the dict processes here, - let dict_process_destroyed() do it to avoid "unknown child exited" - errors. */ - dict_listener_unref(listener); -} - -static void -dict_process_destroyed(struct child_process *_process, - pid_t pid ATTR_UNUSED, bool abnormal_exit ATTR_UNUSED) -{ - struct dict_process *process = (struct dict_process *)_process; - struct dict_listener *listener = process->listener; - - if (listener->processes == NULL && listener->fd != -1) { - /* last listener died, create new ones */ - listener->io = io_add(listener->fd, IO_READ, - dict_listener_input, listener); - } - dict_process_deinit(process); -} - -void dict_processes_init(void) -{ - const char *path; - - path = t_strconcat(master_set->defaults->base_dir, - "/"DICT_SERVER_SOCKET_NAME, NULL); - dict_listener = dict_listener_init(path); - - child_process_set_destroy_callback(PROCESS_TYPE_DICT, - dict_process_destroyed); -} - -void dict_processes_deinit(void) -{ - dict_listener_deinit(dict_listener); -} - -void dict_processes_kill(void) -{ - struct dict_process *process; - - process = dict_listener->processes; - for (; process != NULL; process = process->next) { - if (process->log != NULL) { - log_unref(process->log); - process->log = NULL; - } - } -}
--- a/src/master/dict-process.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,8 +0,0 @@ -#ifndef DICT_PROCESS_H -#define DICT_PROCESS_H - -void dict_processes_init(void); -void dict_processes_deinit(void); -void dict_processes_kill(void); - -#endif
--- a/src/master/listener.c Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,350 +0,0 @@ -/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "array.h" -#include "ioloop.h" -#include "fd-close-on-exec.h" -#include "listener.h" - -#include <stdlib.h> -#include <unistd.h> - -static void resolve_ip(const char *set_name, const char *name, - struct ip_addr *ip, unsigned int *port) -{ - struct ip_addr *ip_list; - const char *p; - unsigned int ips_count; - int ret; - - if (*name == '\0') { - /* defaults to "*" or "[::]" */ - ip->family = 0; - return; - } - - if (name[0] == '[') { - /* IPv6 address */ - p = strchr(name, ']'); - if (p == NULL) { - i_fatal("%s: Missing ']' in address %s", - set_name, name); - } - name = t_strdup_until(name+1, p); - - p++; - if (*p == '\0') - p = NULL; - else if (*p != ':') { - i_fatal("%s: Invalid data after ']' in address %s", - set_name, name); - } - } else { - p = strrchr(name, ':'); - if (p != NULL) - name = t_strdup_until(name, p); - } - - if (p != NULL) { - if (!is_numeric(p+1, '\0')) { - i_fatal("%s: Invalid port in address %s", - set_name, name); - } - *port = atoi(p+1); - } - - if (strcmp(name, "*") == 0) { - /* IPv4 any */ - net_get_ip_any4(ip); - return; - } - - if (strcmp(name, "::") == 0) { - /* IPv6 any */ - net_get_ip_any6(ip); - return; - } - - /* Return the first IP if there happens to be multiple. */ - ret = net_gethostbyname(name, &ip_list, &ips_count); - if (ret != 0) { - i_fatal("%s: Can't resolve address %s: %s", - set_name, name, net_gethosterror(ret)); - } - - if (ips_count < 1) - i_fatal("%s: No IPs for address: %s", set_name, name); - - *ip = ip_list[0]; -} - -static void -check_conflicts_set(const struct master_settings *set, const struct ip_addr *ip, - unsigned int port, const char *name1, const char *name2) -{ - const struct listener *listens = NULL; - unsigned int i, count; - - if (array_is_created(&set->listens)) - listens = array_get(&set->listens, &count); - else - count = 0; - for (i = 0; i < count; i++) { - if (listens[i].fd <= 0 || listens[i].port != port || - !net_ip_compare(&listens[i].ip, ip)) - continue; - - i_fatal("Protocols %s and %s are listening in same ip/port", - name1, name2); - } - - if (array_is_created(&set->ssl_listens)) - listens = array_get(&set->ssl_listens, &count); - else - count = 0; - for (i = 0; i < count; i++) { - if (listens[i].fd <= 0 || listens[i].port != port || - !net_ip_compare(&listens[i].ip, ip)) - continue; - - i_fatal("Protocols %ss and %ss are listening in same ip/port", - name1, name2); - } -} - -static void check_conflicts(const struct ip_addr *ip, unsigned int port, - const char *proto) -{ - if (master_set->imap != NULL) { - check_conflicts_set(master_set->imap, ip, port, - "imap", proto); - } - if (master_set->pop3 != NULL) { - check_conflicts_set(master_set->pop3, ip, port, - "pop3", proto); - } -} - -static void -listener_init(const char *set_name, const char *listen_list, - unsigned int default_port, ARRAY_TYPE(listener) *listens_arr) -{ - const char *const *tmp; - struct listener l, *listens; - unsigned int i, count; - - if (!array_is_created(listens_arr)) - i_array_init(listens_arr, 4); - - listens = array_get_modifiable(listens_arr, &count); - for (i = 0; i < count; i++) - listens[i].wanted = FALSE; - - memset(&l, 0, sizeof(l)); - l.fd = -1; - l.wanted = TRUE; - - for (tmp = t_strsplit_spaces(listen_list, ", "); *tmp != NULL; tmp++) { - l.port = default_port; - resolve_ip(set_name, *tmp, &l.ip, &l.port); - - /* see if it already exists */ - for (i = 0; i < count; i++) { - if (listens[i].port == l.port && - net_ip_compare(&listens[i].ip, &l.ip)) { - listens[i].wanted = TRUE; - break; - } - } - - if (i == count) { - array_append(listens_arr, &l, 1); - listens = array_get_modifiable(listens_arr, &count); - } - } - - /* close unwanted fds */ - for (i = 0; i < count; ) { - if (listens[i].wanted) - i++; - else { - if (listens[i].fd > 0) { - if (close(listens[i].fd) < 0) - i_error("close(listener) failed: %m"); - } - array_delete(listens_arr, i, 1); - listens = array_get_modifiable(listens_arr, &count); - } - } -} - -static void listener_close_fds(ARRAY_TYPE(listener) *listens_arr) -{ - const struct listener *listens; - unsigned int i, count; - - if (!array_is_created(listens_arr)) - return; - - listens = array_get(listens_arr, &count); - for (i = 0; i < count; i++) { - if (listens[i].fd > 0) { - if (close(listens[i].fd) < 0) - i_error("close(listener) failed: %m"); - } - } - array_free(listens_arr); -} - -static void listen_parse_and_close_unneeded(struct master_settings *set) -{ - const char *const *proto; - unsigned int default_port; - bool nonssl_listen = FALSE, ssl_listen = FALSE; - - if (set == NULL) - return; - - /* register wanted protocols */ - proto = t_strsplit_spaces(set->protocols, " "); - for (; *proto != NULL; proto++) { - if (strcasecmp(*proto, "imap") == 0) { - if (set->protocol == MAIL_PROTOCOL_IMAP) - nonssl_listen = TRUE; - } else if (strcasecmp(*proto, "imaps") == 0) { - if (set->protocol == MAIL_PROTOCOL_IMAP && - strcmp(set->ssl, "no") != 0) - ssl_listen = TRUE; - } else if (strcasecmp(*proto, "pop3") == 0) { - if (set->protocol == MAIL_PROTOCOL_POP3) - nonssl_listen = TRUE; - } else if (strcasecmp(*proto, "pop3s") == 0) { - if (set->protocol == MAIL_PROTOCOL_POP3 && - strcmp(set->ssl, "no") != 0) - ssl_listen = TRUE; - } - } - - if (!nonssl_listen) - listener_close_fds(&set->listens); - else { - default_port = set->protocol == MAIL_PROTOCOL_IMAP ? 143 : 110; - listener_init("listen", set->listen, default_port, - &set->listens); - } - if (!ssl_listen) - listener_close_fds(&set->ssl_listens); - else { - default_port = set->protocol == MAIL_PROTOCOL_IMAP ? 993 : 995; - listener_init("ssl_listen", *set->ssl_listen != '\0' ? - set->ssl_listen : set->listen, default_port, - &set->ssl_listens); - } -} - -static void listen_copy_old(struct master_settings *old_set, - struct master_settings *new_set) -{ - if (old_set == NULL || new_set == NULL) { - if (old_set != NULL) { - listener_close_fds(&old_set->listens); - listener_close_fds(&old_set->ssl_listens); - } - return; - } - - i_assert(!array_is_created(&new_set->listens)); - i_assert(!array_is_created(&new_set->ssl_listens)); - - new_set->listens = old_set->listens; - new_set->ssl_listens = old_set->ssl_listens; - - old_set->listens.arr.buffer = NULL; - old_set->ssl_listens.arr.buffer = NULL; -} - -static void -listener_array_listen_missing(const char *proto, - ARRAY_TYPE(listener) *listens_arr, bool retry) -{ - struct listener *listens; - unsigned int i, j, count; - - if (!array_is_created(listens_arr)) - return; - - listens = array_get_modifiable(listens_arr, &count); - for (i = 0; i < count; i++) { - if (listens[i].fd > 0) - continue; - - for (j = 0; j < 10; j++) { - listens[i].fd = net_listen(&listens[i].ip, - &listens[i].port, 128); - if (listens[i].fd != -1) - break; - - if (errno == EADDRINUSE) { - /* retry */ - } else if (errno == EINTR && - io_loop_is_running(ioloop)) { - /* SIGHUPing sometimes gets us here. - we don't want to die. */ - } else { - /* error */ - break; - } - - check_conflicts(&listens[i].ip, listens[i].port, proto); - if (!retry) - break; - - /* wait a while and try again. we're SIGHUPing - so we most likely just closed it ourself.. */ - sleep(1); - } - - if (listens[i].fd == -1) { - i_fatal("listen(%s, %d) failed: %m", - net_ip2addr(&listens[i].ip), listens[i].port); - } - net_set_nonblock(listens[i].fd, TRUE); - fd_close_on_exec(listens[i].fd, TRUE); - } -} - -static void -listener_listen_missing(struct master_settings *set, - const char *proto, bool retry) -{ - listener_array_listen_missing(proto, &set->listens, retry); - listener_array_listen_missing(t_strconcat(proto, "s", NULL), - &set->ssl_listens, retry); -} - -void listeners_open_fds(struct master_server_settings *old_set, bool retry) -{ - if (old_set != NULL) { - listen_copy_old(old_set->imap, master_set->imap); - listen_copy_old(old_set->pop3, master_set->pop3); - } - listen_parse_and_close_unneeded(master_set->imap); - listen_parse_and_close_unneeded(master_set->pop3); - - if (master_set->imap != NULL) - listener_listen_missing(master_set->imap, "imap", retry); - if (master_set->pop3 != NULL) - listener_listen_missing(master_set->pop3, "pop3", retry); -} - -void listeners_close_fds(void) -{ - if (master_set->imap != NULL) { - listener_close_fds(&master_set->imap->listens); - listener_close_fds(&master_set->imap->ssl_listens); - } - if (master_set->pop3 != NULL) { - listener_close_fds(&master_set->pop3->listens); - listener_close_fds(&master_set->pop3->ssl_listens); - } -}
--- a/src/master/listener.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ -#ifndef LISTENER_H -#define LISTENER_H - -void listeners_open_fds(struct master_server_settings *old_set, bool retry); -void listeners_close_fds(void); - -#endif
--- a/src/master/log.c Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,338 +0,0 @@ -/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "ioloop.h" -#include "istream.h" -#include "fd-set-nonblock.h" -#include "fd-close-on-exec.h" -#include "child-process.h" -#include "log.h" - -#include <unistd.h> - -struct log_io { - struct log_io *prev, *next; - int refcount; - - struct io *io; - struct istream *stream; - pid_t pid; - struct ip_addr ip; - - time_t log_stamp; - unsigned int log_counter; - unsigned int max_lines_per_sec; - - char *prefix; - char next_log_type; - unsigned int throttle_msg:1; - unsigned int destroying:1; -}; - -static struct log_io *log_ios; -static struct timeout *to; -static unsigned int throttle_count; - -static int log_it(struct log_io *log_io, const char *line, bool continues); -static int log_read(struct log_io *log_io); -static void log_throttle_timeout(void *context); - -static bool log_write_pending(struct log_io *log_io) -{ - const char *line; - bool ret; - - if (log_io->log_stamp != ioloop_time) { - log_io->log_stamp = ioloop_time; - log_io->log_counter = 0; - } - - while ((line = i_stream_next_line(log_io->stream)) != NULL) { - T_BEGIN { - ret = log_it(log_io, line, FALSE); - } T_END; - if (!ret) - return FALSE; - } - - return TRUE; -} - -static void log_throttle(struct log_io *log_io) -{ - if (!log_io->throttle_msg) { - log_io->throttle_msg = TRUE; - log_it(log_io, "Sending log messages too fast, throttling..", - FALSE); - } - - if (log_io->io == NULL) { - i_assert(to != NULL); - return; - } - - io_remove(&log_io->io); - throttle_count++; - - if (to == NULL) - to = timeout_add(1000, log_throttle_timeout, NULL); -} - -static void log_read_callback(struct log_io *log_io) -{ - (void)log_read(log_io); -} - -static void log_unthrottle(struct log_io *log_io) -{ - if (log_io->io != NULL) - return; - - if (--throttle_count == 0 && to != NULL) - timeout_remove(&to); - log_io->io = io_add(i_stream_get_fd(log_io->stream), - IO_READ, log_read_callback, log_io); -} - -static int log_it(struct log_io *log_io, const char *line, bool continues) -{ - struct child_process *process; - const char *prefix; - enum log_type log_type; - - if (log_io->next_log_type == '\0') { - if (line[0] == 1 && line[1] != '\0') { - /* our internal protocol. - \001 + log_type */ - log_io->next_log_type = line[1]; - line += 2; - } else { - log_io->next_log_type = 'E'; - } - } - - prefix = log_io->prefix != NULL ? log_io->prefix : ""; - switch (log_io->next_log_type) { - case 'I': - log_type = LOG_TYPE_INFO; - break; - case 'W': - log_type = LOG_TYPE_WARNING; - break; - case 'E': - log_type = LOG_TYPE_ERROR; - break; - case 'F': - case 'P': - log_type = log_io->next_log_type == 'F' ? - LOG_TYPE_FATAL : LOG_TYPE_PANIC; - process = child_process_lookup(log_io->pid); - if (process != NULL) - process->seen_fatal = TRUE; - break; - case 'O': - /* logging option. ignore unknown ones. */ - if (strncmp(line, "ip=", 3) == 0) { - process = child_process_lookup(log_io->pid); - if (process != NULL && - (process->allow_change_ip || - process->ip.family == 0)) { - if (process->ip.family != 0) - process->ip_changed = TRUE; - net_addr2ip(line + 3, &process->ip); - } - } - log_io->next_log_type = '\0'; - return 1; - default: - log_type = LOG_TYPE_ERROR; - break; - } - i_log_type(log_type, "%s%s", prefix, line); - - if (!continues) - log_io->next_log_type = '\0'; - - if (++log_io->log_counter > log_io->max_lines_per_sec && - !log_io->destroying) { - log_throttle(log_io); - return 0; - } - return 1; -} - -static int log_read(struct log_io *log_io) -{ - const unsigned char *data; - const char *line; - size_t size; - int ret; - - if (!log_write_pending(log_io)) - return 0; - - ret = i_stream_read(log_io->stream); - if (ret < 0) { - if (ret == -1) { - /* closed */ - log_unref(log_io); - return -1; - } - - /* buffer full. treat it as one line */ - data = i_stream_get_data(log_io->stream, &size); - line = t_strndup(data, size); - i_stream_skip(log_io->stream, size); - - if (!log_it(log_io, line, TRUE)) - return 0; - } - - if (!log_write_pending(log_io)) - return 0; - - if (log_io->log_counter < log_io->max_lines_per_sec) - log_unthrottle(log_io); - return 0; -} - -int log_create_pipe(struct log_io **log_r, unsigned int max_lines_per_sec) -{ - struct log_io *log_io; - int fd[2]; - - if (pipe(fd) < 0) { - i_error("pipe() failed: %m"); - return -1; - } - - fd_set_nonblock(fd[0], TRUE); - fd_close_on_exec(fd[0], TRUE); - fd_close_on_exec(fd[1], TRUE); - - log_io = i_new(struct log_io, 1); - log_io->refcount = 1; - log_io->pid = (pid_t)-1; - log_io->stream = i_stream_create_fd(fd[0], 1024, TRUE); - log_io->max_lines_per_sec = - max_lines_per_sec != 0 ? max_lines_per_sec : (unsigned int)-1; - - throttle_count++; - log_unthrottle(log_io); - - if (log_ios != NULL) - log_ios->prev = log_io; - log_io->next = log_ios; - log_ios = log_io; - - if (log_r != NULL) - *log_r = log_io; - return fd[1]; -} - -void log_set_prefix(struct log_io *log, const char *prefix) -{ - i_free(log->prefix); - log->prefix = i_strdup(prefix); -} - -void log_set_pid(struct log_io *log, pid_t pid) -{ - log->pid = pid; -} - -void log_ref(struct log_io *log_io) -{ - log_io->refcount++; -} - -static void log_close(struct log_io *log_io) -{ - const unsigned char *data; - size_t size; - - if (log_io->destroying) - return; - - /* if there was something in buffer, write it */ - log_io->destroying = TRUE; - (void)log_write_pending(log_io); - - /* write partial data as well */ - data = i_stream_get_data(log_io->stream, &size); - if (size != 0) { - T_BEGIN { - log_it(log_io, t_strndup(data, size), TRUE); - } T_END; - } - - if (log_io == log_ios) - log_ios = log_io->next; - else - log_io->prev->next = log_io->next; - if (log_io->next != NULL) - log_io->next->prev = log_io->prev; - - if (log_io->io != NULL) - io_remove(&log_io->io); - else - throttle_count--; - i_stream_destroy(&log_io->stream); -} - -void log_unref(struct log_io *log_io) -{ - i_assert(log_io->refcount > 0); - - log_close(log_io); - - if (--log_io->refcount > 0) - return; - - i_free(log_io->prefix); - i_free(log_io); -} - -static void log_throttle_timeout(void *context ATTR_UNUSED) -{ - struct log_io *log, *next; - unsigned int left = throttle_count; - - i_assert(left > 0); - - for (log = log_ios; log != NULL; log = next) { - next = log->next; - - if (log->io == NULL) { - if (log_write_pending(log)) - log_unthrottle(log); - - if (--left == 0) - break; - } - } -} - -void log_init(void) -{ - log_ios = NULL; - throttle_count = 0; - to = NULL; -} - -void log_deinit(void) -{ - struct log_io *next; - - while (log_ios != NULL) { - next = log_ios->next; - /* do one final log read in case there's still something - waiting */ - if (log_read(log_ios) == 0) - log_unref(log_ios); - log_ios = next; - } - - if (to != NULL) - timeout_remove(&to); -}
--- a/src/master/log.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,16 +0,0 @@ -#ifndef LOG_H -#define LOG_H - -struct log_io; - -int log_create_pipe(struct log_io **log_r, unsigned int max_lines_per_sec); -void log_set_prefix(struct log_io *log, const char *prefix); -void log_set_pid(struct log_io *log, pid_t pid); - -void log_ref(struct log_io *log_io); -void log_unref(struct log_io *log_io); - -void log_init(void); -void log_deinit(void); - -#endif
--- a/src/master/login-process.c Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,893 +0,0 @@ -/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "array.h" -#include "ioloop.h" -#include "hash.h" -#include "network.h" -#include "ostream.h" -#include "fdpass.h" -#include "fd-close-on-exec.h" -#include "env-util.h" -#include "restrict-access.h" -#include "restrict-process-size.h" -#include "dup2-array.h" -#include "login-process.h" -#include "auth-process.h" -#include "mail-process.h" -#include "master-login-interface.h" -#include "master-settings.h" -#include "log.h" -#include "ssl-init.h" - -#include <unistd.h> -#include <syslog.h> -#include <sys/stat.h> - -#define LOGIN_LIMIT_WARNING_MIN_INTERVAL (60*5) - -struct login_process { - struct child_process process; - - struct login_group *group; - struct login_process *prev_prelogin, *next_prelogin; - int refcount; - - pid_t pid; - int fd; - struct io *io; - struct ostream *output; - enum master_login_state state; - - unsigned int initialized:1; - unsigned int destroyed:1; - unsigned int inetd_child:1; -}; - -struct login_auth_request { - struct login_process *process; - unsigned int tag; - unsigned int login_tag; - - struct mail_login_request mail_request; - unsigned char data[FLEXIBLE_ARRAY_MEMBER]; -}; - -static unsigned int auth_id_counter, login_pid_counter; -static struct timeout *to; -static struct io *io_listen; -static bool logins_stalled = FALSE; - -static struct login_group *login_groups; - -static void login_processes_stall(void); -static void login_process_destroy(struct login_process *p); -static void login_process_unref(struct login_process *p); -static bool login_process_init_group(struct login_process *p); -static void login_processes_start_missing(void *context); - -static void login_group_create(struct master_settings *set) -{ - struct login_group *group; - - group = i_new(struct login_group, 1); - group->refcount = 1; - group->set = set; - group->mail_process_type = set->protocol == MAIL_PROTOCOL_IMAP ? - PROCESS_TYPE_IMAP : PROCESS_TYPE_POP3; - - group->next = login_groups; - login_groups = group; -} - -static void login_group_unref(struct login_group *group) -{ - i_assert(group->refcount > 0); - - if (--group->refcount > 0) - return; - - i_free(group); -} - -void auth_master_callback(const char *user, const char *const *args, - struct login_auth_request *request) -{ - struct master_login_reply master_reply; - ssize_t ret; - - memset(&master_reply, 0, sizeof(master_reply)); - if (user == NULL) - master_reply.status = MASTER_LOGIN_STATUS_INTERNAL_ERROR; - else T_BEGIN { - struct login_group *group = request->process->group; - - master_reply.status = - create_mail_process(group->mail_process_type, - group->set, &request->mail_request, - user, args, request->data, FALSE, - &master_reply.mail_pid); - } T_END; - - /* reply to login */ - master_reply.tag = request->login_tag; - - ret = o_stream_send(request->process->output, &master_reply, - sizeof(master_reply)); - if (ret != sizeof(master_reply)) { - if (ret >= 0) { - i_warning("Login process %s transmit buffer full, " - "killing..", dec2str(request->process->pid)); - } - login_process_destroy(request->process); - } - - if (close(request->mail_request.fd) < 0) - i_error("close(mail client) failed: %m"); - login_process_unref(request->process); - i_free(request); -} - -static void process_remove_from_prelogin_lists(struct login_process *p) -{ - if (p->state != LOGIN_STATE_FULL_PRELOGINS) - return; - - if (p->prev_prelogin == NULL) - p->group->oldest_prelogin_process = p->next_prelogin; - else - p->prev_prelogin->next_prelogin = p->next_prelogin; - - if (p->next_prelogin == NULL) - p->group->newest_prelogin_process = p->prev_prelogin; - else - p->next_prelogin->prev_prelogin = p->prev_prelogin; - - p->prev_prelogin = p->next_prelogin = NULL; -} - -static void process_mark_nonlistening(struct login_process *p, - enum master_login_state new_state) -{ - if (p->group == NULL) - return; - - if (p->state == LOGIN_STATE_LISTENING) - p->group->listening_processes--; - - if (new_state == LOGIN_STATE_FULL_PRELOGINS) { - /* add to prelogin list */ - i_assert(p->state != new_state); - - p->prev_prelogin = p->group->newest_prelogin_process; - if (p->group->newest_prelogin_process == NULL) - p->group->oldest_prelogin_process = p; - else - p->group->newest_prelogin_process->next_prelogin = p; - p->group->newest_prelogin_process = p; - } else { - process_remove_from_prelogin_lists(p); - } -} - -static void process_mark_listening(struct login_process *p) -{ - if (p->group == NULL) - return; - - if (p->state != LOGIN_STATE_LISTENING) - p->group->listening_processes++; - - process_remove_from_prelogin_lists(p); -} - -static void login_process_set_initialized(struct login_process *p) -{ - p->initialized = TRUE; - - if (logins_stalled) { - /* processes were created successfully */ - i_info("Created login processes successfully, unstalling"); - - logins_stalled = FALSE; - timeout_remove(&to); - to = timeout_add(1000, login_processes_start_missing, NULL); - } -} - -static void -login_process_set_state(struct login_process *p, enum master_login_state state) -{ - if (state == p->state || state > LOGIN_STATE_COUNT || - (state < p->state && p->group->set->login_process_per_connection)) { - i_error("login: tried to change state %d -> %d " - "(if you can't login at all, see src/lib/fdpass.c)", - p->state, state); - login_process_destroy(p); - return; - } - - if (state == LOGIN_STATE_LISTENING) { - process_mark_listening(p); - } else { - process_mark_nonlistening(p, state); - } - - p->state = state; -} - -static void login_process_groups_create(void) -{ - if (master_set->imap != NULL) - login_group_create(master_set->imap); - if (master_set->pop3 != NULL) - login_group_create(master_set->pop3); -} - -static struct login_group * -login_group_process_find(enum mail_protocol protocol) -{ - struct login_group *group; - - if (login_groups == NULL) - login_process_groups_create(); - - for (group = login_groups; group != NULL; group = group->next) { - if (group->set->protocol == protocol) - return group; - } - - return NULL; -} - -static bool login_process_read_group(struct login_process *p) -{ - struct login_group *group; - const char *proto; - unsigned char buf[256]; - enum mail_protocol protocol; - unsigned int len; - ssize_t ret; - - /* read length */ - ret = read(p->fd, buf, 1); - if (ret != 1) - len = 0; - else { - len = buf[0]; - if (len >= sizeof(buf)) { - i_error("login: Server name length too large"); - return FALSE; - } - - ret = read(p->fd, buf, len); - } - - if (ret < 0) - i_error("login: read() failed: %m"); - else if (len == 0 || (size_t)ret != len) - i_error("login: Server name wasn't sent"); - else { - proto = t_strndup(buf, len); - if (strcmp(proto, "imap") == 0) - protocol = MAIL_PROTOCOL_IMAP; - else if (strcmp(proto, "pop3") == 0) - protocol = MAIL_PROTOCOL_POP3; - else { - i_error("login: Unknown protocol '%s'", proto); - return FALSE; - } - - group = login_group_process_find(protocol); - if (group == NULL) { - i_error("login: No group for protocol '%s'", proto); - return FALSE; - } - - p->group = group; - return login_process_init_group(p); - } - return FALSE; -} - -static int -login_read_request(struct login_process *p, struct master_login_request *req, - unsigned char data[MASTER_LOGIN_MAX_DATA_SIZE], - int *client_fd_r) -{ - struct stat st; - ssize_t ret; - - *client_fd_r = -1; - - ret = fd_read(p->fd, req, sizeof(*req), client_fd_r); - if (ret >= (ssize_t)sizeof(req->version) && - req->version != MASTER_LOGIN_PROTOCOL_VERSION) { - i_error("login: Protocol version mismatch " - "(mixed old and new binaries?)"); - return -1; - } - - if (ret != sizeof(*req)) { - if (ret == 0) { - /* disconnected, ie. the login process died */ - } else if (ret > 0) { - /* request wasn't fully read */ - i_error("login: fd_read() returned partial %d", - (int)ret); - } else { - if (errno == EAGAIN) - return 0; - - i_error("login: fd_read() failed: %m"); - } - return -1; - } - - if (req->ino == (ino_t)-1) { - if (*client_fd_r != -1) { - i_error("login: Notification request sent " - "a file descriptor"); - return -1; - } - return 1; - } - if (req->data_size != 0) { - if (req->data_size > MASTER_LOGIN_MAX_DATA_SIZE) { - i_error("login: Too large data_size sent"); - return -1; - } - /* @UNSAFE */ - ret = read(p->fd, data, req->data_size); - if (ret != req->data_size) { - if (ret == 0) { - /* disconnected */ - } else if (ret > 0) { - /* request wasn't fully read */ - i_error("login: Data read partially %d/%u", - (int)ret, req->data_size); - } else { - i_error("login: read(data) failed: %m"); - } - return -1; - } - } - - if (*client_fd_r == -1) { - i_error("login: Login request missing a file descriptor"); - return -1; - } - - if (fstat(*client_fd_r, &st) < 0) { - i_error("login: fstat(mail client) failed: %m"); - return -1; - } - if (st.st_ino != req->ino) { - i_error("login: Login request inode mismatch: %s != %s", - dec2str(st.st_ino), dec2str(req->ino)); - return -1; - } - return 1; -} - -static void login_process_input(struct login_process *p) -{ - struct auth_process *auth_process; - struct login_auth_request *authreq; - struct master_login_request req; - unsigned char data[MASTER_LOGIN_MAX_DATA_SIZE]; - int client_fd; - ssize_t ret; - - if (p->group == NULL) { - /* we want to read the group */ - if (!login_process_read_group(p)) - login_process_destroy(p); - return; - } - - ret = login_read_request(p, &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"); - } - login_process_destroy(p); - return; - } - - if (req.ino == (ino_t)-1) { - /* state notification */ - enum master_login_state state = req.tag; - - if (!p->initialized) { - /* initialization notify */ - login_process_set_initialized(p); - } else { - /* change "listening for new connections" status */ - login_process_set_state(p, state); - } - return; - } - - if (!p->initialized) { - i_error("login: trying to log in before initialization"); - login_process_destroy(p); - return; - } - - fd_close_on_exec(client_fd, TRUE); - - /* ask the cookie from the auth process */ - authreq = i_malloc(sizeof(*authreq) + req.data_size); - p->refcount++; - authreq->process = p; - authreq->tag = ++auth_id_counter; - authreq->login_tag = req.tag; - authreq->mail_request.fd = client_fd; - authreq->mail_request.local_ip = req.local_ip; - authreq->mail_request.remote_ip = req.remote_ip; - authreq->mail_request.cmd_tag_size = req.cmd_tag_size; - authreq->mail_request.data_size = req.data_size; - memcpy(authreq->data, data, req.data_size); - - auth_process = auth_process_find(req.auth_pid); - if (auth_process == NULL) { - i_error("login: Authentication process %u doesn't exist", - req.auth_pid); - auth_master_callback(NULL, NULL, authreq); - } else { - auth_process_request(auth_process, p->pid, - req.auth_id, authreq); - } -} - -static struct login_process * -login_process_new(struct login_group *group, pid_t pid, int fd, - bool inetd_child) -{ - struct login_process *p; - - i_assert(pid != 0); - - p = i_new(struct login_process, 1); - p->process.type = PROCESS_TYPE_LOGIN; - p->group = group; - p->refcount = 2; /* once for fd close, another for process exit */ - p->pid = pid; - p->fd = fd; - p->inetd_child = inetd_child; - p->io = io_add(fd, IO_READ, login_process_input, p); - p->output = o_stream_create_fd(fd, sizeof(struct master_login_reply)*10, - FALSE); - if (!inetd_child) { - if (!group->set->login_process_per_connection) - p->process.allow_change_ip = TRUE; - child_process_add(pid, &p->process); - } - - p->state = LOGIN_STATE_LISTENING; - - if (p->group != NULL) { - p->group->refcount++; - p->group->processes++; - p->group->listening_processes++; - } - return p; -} - -static void login_process_exited(struct login_process *p) -{ - if (p->group != NULL) - p->group->processes--; - - login_process_unref(p); -} - -static void login_process_destroy(struct login_process *p) -{ - if (p->destroyed) - return; - p->destroyed = TRUE; - - if (!p->initialized) - login_processes_stall(); - - o_stream_close(p->output); - io_remove(&p->io); - if (close(p->fd) < 0) - i_error("close(login) failed: %m"); - - process_mark_nonlistening(p, LOGIN_STATE_FULL_LOGINS); - - if (p->inetd_child) - login_process_exited(p); - login_process_unref(p); -} - -static void login_process_unref(struct login_process *p) -{ - i_assert(p->refcount > 0); - if (--p->refcount > 0) - return; - - if (p->group != NULL) - login_group_unref(p->group); - - o_stream_unref(&p->output); - i_free(p); -} - -static void login_process_init_env(struct login_group *group, pid_t pid) -{ - struct master_settings *set = group->set; - struct restrict_access_settings rset; - - child_process_init_env(group->set); - - /* setup access environment - needs to be done after - clean_child_process() since it clears environment. Don't set user - parameter since we don't want to call initgroups() for login - processes. */ - restrict_access_init(&rset); - rset.uid = set->login_uid; - rset.gid = set->server->login_gid; - if (set->login_chroot) - rset.chroot_dir = set->login_dir; - restrict_access_set_env(&rset); - - env_put("DOVECOT_MASTER=1"); - - master_settings_export_to_env(group->set); - if (ssl_manual_key_password != NULL) { - env_put(t_strconcat("SSL_MANUAL_KEY_PASSWORD=", - ssl_manual_key_password, NULL)); - } - - env_put(t_strconcat("PROCESS_UID=", dec2str(pid), NULL)); - if (group->mail_process_type == PROCESS_TYPE_IMAP && - set->imap_generated_capability != NULL) { - env_put(t_strconcat("CAPABILITY_STRING=", - set->imap_generated_capability, NULL)); - } - env_put(t_strconcat("LOGIN_DIR=", set->login_dir, NULL)); -} - -static pid_t create_login_process(struct login_group *group) -{ - struct log_io *log; - const struct listener *listens; - unsigned int max_log_lines_per_sec; - const char *prefix; - pid_t pid; - ARRAY_TYPE(dup2) dups; - unsigned int i, listen_count = 0, ssl_listen_count = 0; - int fd[2], log_fd, cur_fd, tmp_fd; - - if (group->set->login_uid == 0) - i_fatal("Login process must not run as root"); - - /* create communication to process with a socket pair */ - if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { - i_error("socketpair() failed: %m"); - return -1; - } - - max_log_lines_per_sec = - group->set->login_process_per_connection ? 10 : 0; - log_fd = log_create_pipe(&log, max_log_lines_per_sec); - if (log_fd < 0) - pid = -1; - else { - pid = fork(); - if (pid < 0) - i_error("fork() failed: %m"); - } - - if (pid < 0) { - (void)close(fd[0]); - (void)close(fd[1]); - (void)close(log_fd); - return -1; - } - - if (pid != 0) { - /* master */ - prefix = t_strdup_printf("%s-login: ", - process_names[group->mail_process_type]); - log_set_prefix(log, prefix); - log_set_pid(log, pid); - - net_set_nonblock(fd[0], TRUE); - fd_close_on_exec(fd[0], TRUE); - (void)login_process_new(group, pid, fd[0], FALSE); - (void)close(fd[1]); - (void)close(log_fd); - return pid; - } - - prefix = t_strdup_printf("master-%s-login: ", - process_names[group->mail_process_type]); - log_set_prefix(log, prefix); - - t_array_init(&dups, 16); - dup2_append(&dups, null_fd, STDIN_FILENO); - /* redirect writes to stdout also to error log. For example OpenSSL - can be made to log its debug messages to stdout. */ - dup2_append(&dups, log_fd, STDOUT_FILENO); - dup2_append(&dups, log_fd, STDERR_FILENO); - dup2_append(&dups, fd[1], LOGIN_MASTER_SOCKET_FD); - - /* redirect listener fds */ - cur_fd = LOGIN_MASTER_SOCKET_FD + 1; - if (array_is_created(&group->set->listens)) { - listens = array_get(&group->set->listens, &listen_count); - for (i = 0; i < listen_count; i++, cur_fd++) - dup2_append(&dups, listens[i].fd, cur_fd); - } - - if (array_is_created(&group->set->ssl_listens)) { - listens = array_get(&group->set->ssl_listens, - &ssl_listen_count); - for (i = 0; i < ssl_listen_count; i++, cur_fd++) - dup2_append(&dups, listens[i].fd, cur_fd); - } - - /* make sure we don't leak syslog fd. try to do it as late as possible, - but also before dup2()s in case syslog fd is one of them. */ - closelog(); - - if (dup2_array(&dups) < 0) - i_fatal("Failed to dup2() fds"); - - /* don't close any of these */ - for (tmp_fd = 0; tmp_fd < cur_fd; tmp_fd++) - fd_close_on_exec(tmp_fd, FALSE); - - (void)close(fd[0]); - (void)close(fd[1]); - - login_process_init_env(group, getpid()); - - env_put(t_strdup_printf("LISTEN_FDS=%u", listen_count)); - env_put(t_strdup_printf("SSL_LISTEN_FDS=%u", ssl_listen_count)); - - restrict_process_size(group->set->login_process_size, (unsigned int)-1); - - client_process_exec(group->set->login_executable, ""); - i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", - group->set->login_executable); - return -1; -} - -static void -login_process_destroyed(struct child_process *process, - pid_t pid ATTR_UNUSED, bool abnormal_exit) -{ - struct login_process *p = (struct login_process *)process; - - i_assert(!p->inetd_child); - - if (abnormal_exit) { - /* don't start raising the process count if they're dying all - the time */ - if (p->group != NULL) - p->group->wanted_processes_count = 0; - } - - login_process_exited(p); -} - -void login_processes_destroy_all(void) -{ - struct hash_iterate_context *iter; - void *key, *value; - - iter = hash_table_iterate_init(processes); - while (hash_table_iterate(iter, &key, &value)) { - struct login_process *p = value; - - if (p->process.type == PROCESS_TYPE_LOGIN) - login_process_destroy(p); - } - hash_table_iterate_deinit(&iter); - - while (login_groups != NULL) { - struct login_group *group = login_groups; - - login_groups = group->next; - login_group_unref(group); - } -} - -static void login_processes_notify_group(struct login_group *group) -{ - struct hash_iterate_context *iter; - struct master_login_reply reply; - void *key, *value; - - memset(&reply, 0, sizeof(reply)); - - iter = hash_table_iterate_init(processes); - while (hash_table_iterate(iter, &key, &value)) { - struct login_process *p = value; - - if (p->process.type == PROCESS_TYPE_LOGIN && p->group == group) - (void)o_stream_send(p->output, &reply, sizeof(reply)); - } - hash_table_iterate_deinit(&iter); -} - -static int login_group_start_missings(struct login_group *group) -{ - if (group->set->login_process_per_connection && - group->processes >= group->set->login_max_processes_count && - group->listening_processes == 0) { - /* destroy the oldest listening process. non-listening - processes are logged in users who we don't want to kick out - because someone's started flooding */ - if (group->oldest_prelogin_process != NULL && - group->oldest_prelogin_process->initialized) - login_process_destroy(group->oldest_prelogin_process); - else if (ioloop_time - group->last_limit_warning > - LOGIN_LIMIT_WARNING_MIN_INTERVAL) { - group->last_limit_warning = ioloop_time; - i_warning("All login processes are in use. You may " - "need to increase login_max_processes_count"); - } - } - - /* we want to respond fast when multiple clients are connecting - at once, but we also want to prevent fork-bombing. use the - same method as apache: check once a second if we need new - processes. if yes and we've used all the existing processes, - double their amount (unless we've hit the high limit). - Then for each second that didn't use all existing processes, - drop the max. process count by one. */ - if (group->wanted_processes_count < group->set->login_processes_count) { - group->wanted_processes_count = - group->set->login_processes_count; - } else if (group->listening_processes == 0) - group->wanted_processes_count *= 2; - else if (group->wanted_processes_count > - group->set->login_processes_count) - group->wanted_processes_count--; - - while (group->listening_processes < group->wanted_processes_count && - group->processes < group->set->login_max_processes_count) { - if (create_login_process(group) < 0) - return -1; - } - - if (group->listening_processes == 0 && - !group->set->login_process_per_connection) { - /* we've reached our limit. notify the processes to start - listening again which makes them kill some of their - oldest clients when accepting the next connection */ - login_processes_notify_group(group); - } - return 0; -} - -static void login_processes_stall(void) -{ - if (logins_stalled || IS_INETD()) - return; - - i_error("Temporary failure in creating login processes, " - "slowing down for now"); - logins_stalled = TRUE; - - timeout_remove(&to); - to = timeout_add(60*1000, login_processes_start_missing, NULL); -} - -static void -login_processes_start_missing(void *context ATTR_UNUSED) -{ - struct login_group *group; - - if (!have_initialized_auth_processes) { - /* don't create login processes before at least one auth - process has finished initializing */ - return; - } - - if (login_groups == NULL) - login_process_groups_create(); - - for (group = login_groups; group != NULL; group = group->next) { - if (login_group_start_missings(group) < 0) { - login_processes_stall(); - return; - } - } -} - -static int login_process_send_env(struct login_process *p) -{ - extern char **environ; - char **env; - ssize_t len; - int ret = 0; - - /* this will clear our environment. luckily we don't need it. */ - login_process_init_env(p->group, p->pid); - - for (env = environ; *env != NULL; env++) { - len = strlen(*env); - - if (o_stream_send(p->output, *env, len) != len || - o_stream_send(p->output, "\n", 1) != 1) { - ret = -1; - break; - } - } - - if (ret == 0 && o_stream_send(p->output, "\n", 1) != 1) - ret = -1; - - env_clean(); - return ret; -} - -static bool login_process_init_group(struct login_process *p) -{ - p->group->refcount++; - p->group->processes++; - p->group->listening_processes++; - - if (login_process_send_env(p) < 0) { - i_error("login: Couldn't send environment"); - return FALSE; - } - - return TRUE; -} - -static void inetd_login_accept(void *context ATTR_UNUSED) -{ - struct login_process *p; - int fd; - - fd = net_accept(inetd_login_fd, NULL, NULL); - if (fd < 0) { - if (fd < -1) - i_error("accept(inetd_login_fd) failed: %m"); - } else { - net_set_nonblock(fd, TRUE); - fd_close_on_exec(fd, TRUE); - - p = login_process_new(NULL, ++login_pid_counter, fd, TRUE); - p->initialized = TRUE; - } -} - -void login_processes_init(void) -{ - auth_id_counter = 0; - login_pid_counter = 0; - login_groups = NULL; - - child_process_set_destroy_callback(PROCESS_TYPE_LOGIN, - login_process_destroyed); - - if (!IS_INETD()) { - to = timeout_add(1000, login_processes_start_missing, NULL); - io_listen = NULL; - } else { - to = NULL; - io_listen = io_add(inetd_login_fd, IO_READ, - inetd_login_accept, NULL); - } -} - -void login_processes_deinit(void) -{ - if (to != NULL) - timeout_remove(&to); - if (io_listen != NULL) - io_remove(&io_listen); -}
--- a/src/master/login-process.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -#ifndef LOGIN_PROCESS_H -#define LOGIN_PROCESS_H - -#include "child-process.h" - -struct login_group { - struct login_group *next; - int refcount; - - enum process_type mail_process_type; - struct master_settings *set; - - unsigned int processes; - unsigned int listening_processes; - unsigned int wanted_processes_count; - time_t last_limit_warning; - - /* if login_process_per_connection=yes this contains the list of - processes that are in LOGIN_STATE_FULL_PRELOGINS state */ - struct login_process *oldest_prelogin_process; - struct login_process *newest_prelogin_process; -}; - -void login_processes_destroy_all(void); - -void login_processes_init(void); -void login_processes_deinit(void); - -#endif
--- a/src/master/mail-process.c Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,702 +0,0 @@ -/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ - -#include "common.h" -#include "array.h" -#include "hash.h" -#include "fd-close-on-exec.h" -#include "eacces-error.h" -#include "env-util.h" -#include "base64.h" -#include "str.h" -#include "network.h" -#include "mountpoint.h" -#include "restrict-access.h" -#include "restrict-process-size.h" -#include "home-expand.h" -#include "var-expand.h" -#include "settings-parser.h" -#include "mail-process.h" -#include "login-process.h" -#include "log.h" - -#include <stdlib.h> -#include <unistd.h> -#include <grp.h> -#include <syslog.h> -#include <sys/stat.h> - -#ifdef HAVE_SYS_RESOURCE_H -# include <sys/resource.h> -#endif - -/* Timeout chdir() completely after this many seconds */ -#define CHDIR_TIMEOUT 30 -/* Give a warning about chdir() taking a while if it took longer than this - many seconds to finish. */ -#define CHDIR_WARN_SECS 10 - -struct mail_process_group { - /* process.type + user + remote_ip identifies this process group */ - struct child_process process; - char *user; - struct ip_addr remote_ip; - - /* processes array acts also as refcount */ - ARRAY_DEFINE(processes, pid_t); -}; - -/* type+user -> struct mail_process_group */ -static struct hash_table *mail_process_groups; -static unsigned int mail_process_count = 0; - -static unsigned int mail_process_group_hash(const void *p) -{ - const struct mail_process_group *group = p; - - return str_hash(group->user) ^ group->process.type ^ - net_ip_hash(&group->remote_ip); -} - -static int mail_process_group_cmp(const void *p1, const void *p2) -{ - const struct mail_process_group *group1 = p1, *group2 = p2; - int ret; - - ret = strcmp(group1->user, group2->user); - if (ret == 0) - ret = group1->process.type - group2->process.type; - if (ret == 0 && !net_ip_compare(&group1->remote_ip, &group2->remote_ip)) - ret = -1; - return ret; -} - -static struct mail_process_group * -mail_process_group_lookup(enum process_type type, const char *user, - const struct ip_addr *ip) -{ - struct mail_process_group lookup_group; - - lookup_group.process.type = type; - lookup_group.user = t_strdup_noconst(user); - lookup_group.remote_ip = *ip; - - return hash_table_lookup(mail_process_groups, &lookup_group); -} - -static struct mail_process_group * -mail_process_group_create(enum process_type type, const char *user, - const struct ip_addr *ip) -{ - struct mail_process_group *group; - - group = i_new(struct mail_process_group, 1); - group->process.type = type; - group->user = i_strdup(user); - group->remote_ip = *ip; - - i_array_init(&group->processes, 10); - hash_table_insert(mail_process_groups, group, group); - return group; -} - -static void -mail_process_group_add(struct mail_process_group *group, pid_t pid) -{ - mail_process_count++; - array_append(&group->processes, &pid, 1); - child_process_add(pid, &group->process); -} - -static void mail_process_group_free(struct mail_process_group *group) -{ - array_free(&group->processes); - i_free(group->user); - i_free(group); -} - -static bool validate_uid_gid(struct master_settings *set, uid_t uid, gid_t gid, - const char *user) -{ - if (uid == 0) { - i_error("user %s: Logins with UID 0 not permitted", user); - return FALSE; - } - - if (set->login_uid == uid && master_uid != uid) { - i_error("user %s: Logins with login_user's UID %s " - "not permitted (see http://wiki.dovecot.org/UserIds).", - user, dec2str(uid)); - return FALSE; - } - - if (uid < (uid_t)set->first_valid_uid || - (set->last_valid_uid != 0 && uid > (uid_t)set->last_valid_uid)) { - i_error("user %s: Logins with UID %s not permitted " - "(see first_valid_uid in config file).", - user, dec2str(uid)); - return FALSE; - } - - if (gid < (gid_t)set->first_valid_gid || - (set->last_valid_gid != 0 && gid > (gid_t)set->last_valid_gid)) { - i_error("user %s: Logins for users with primary group ID %s " - "not permitted (see first_valid_gid in config file).", - user, dec2str(gid)); - return FALSE; - } - - return TRUE; -} - -static bool validate_chroot(struct master_settings *set, const char *dir) -{ - const char *const *chroot_dirs; - - if (*dir == '\0') - return FALSE; - - if (*set->valid_chroot_dirs == '\0') - return FALSE; - - chroot_dirs = t_strsplit(set->valid_chroot_dirs, ":"); - while (*chroot_dirs != NULL) { - if (**chroot_dirs != '\0' && - strncmp(dir, *chroot_dirs, strlen(*chroot_dirs)) == 0) - return TRUE; - chroot_dirs++; - } - - return FALSE; -} - -static const struct var_expand_table * -get_var_expand_table(const char *protocol, - const char *user, const char *home, - const char *local_ip, const char *remote_ip, - pid_t pid, uid_t uid) -{ -#define VAR_EXPAND_HOME_IDX 4 - static struct var_expand_table static_tab[] = { - { 'u', NULL, "user" }, - { 'n', NULL, "username" }, - { 'd', NULL, "domain" }, - { 's', NULL, "service" }, - { 'h', NULL, "home" }, - { 'l', NULL, "lip" }, - { 'r', NULL, "rip" }, - { 'p', NULL, "pid" }, - { 'i', NULL, "uid" }, - { '\0', NULL, NULL } - }; - struct var_expand_table *tab; - - tab = t_malloc(sizeof(static_tab)); - memcpy(tab, static_tab, sizeof(static_tab)); - - tab[0].value = user; - tab[1].value = user == NULL ? NULL : t_strcut(user, '@'); - tab[2].value = user == NULL ? NULL : strchr(user, '@'); - if (tab[2].value != NULL) tab[2].value++; - tab[3].value = t_str_ucase(protocol); - tab[VAR_EXPAND_HOME_IDX].value = home; - tab[5].value = local_ip; - tab[6].value = remote_ip; - tab[7].value = dec2str(pid); - tab[8].value = dec2str(uid); - - return tab; -} - -static void mail_process_set_environment(struct master_settings *set, - bool dump_capability) -{ - if (dump_capability) { - /* the only settings we need to have are what plugins to load - and from where. the rest will only make the dump-capability - more likely to fail. */ - const char *const *sets; - unsigned int i, count; - - env_put("MAIL=raw:/tmp"); - sets = array_get(&set->all_settings, &count); - for (i = 0; i < count; i++) { - if (strncmp(sets[i], "mail_plugin", 11) == 0) - env_put(sets[i]); - } - } else { - /* we don't know all the settings, so since we can't expand all - of them just let the mail process expand all of them - internally. */ - master_settings_export_to_env(set); - } -} - -void mail_process_exec(const char *protocol, const char **args) -{ - const struct var_expand_table *var_expand_table; - struct master_settings *set; - const char *executable; - - if (strcmp(protocol, "ext") == 0) { - /* external binary. section contains path for it. */ - if (*args == NULL) - i_fatal("External binary parameter not given"); - set = master_set->defaults; - executable = *args; - } else { - if (strcmp(protocol, "imap") == 0) - set = master_set->imap; - else if (strcmp(protocol, "pop3") == 0) - set = master_set->pop3; - else - i_fatal("Unknown protocol: '%s'", protocol); - executable = set->mail_executable; - args = NULL; - } - - var_expand_table = - get_var_expand_table(protocol, getenv("USER"), getenv("HOME"), - getenv("TCPLOCALIP"), - getenv("TCPREMOTEIP"), - getpid(), geteuid()); - - /* set up logging */ - env_put(t_strconcat("LOG_TIMESTAMP=", set->log_timestamp, NULL)); - if (*set->log_path == '\0') - env_put("USE_SYSLOG=1"); - else - env_put(t_strconcat("LOGFILE=", set->log_path, NULL)); - if (*set->info_log_path != '\0') - env_put(t_strconcat("INFOLOGFILE=", set->info_log_path, NULL)); - if (*set->mail_log_prefix != '\0') { - string_t *str = t_str_new(256); - - str_append(str, "LOG_PREFIX="); - var_expand(str, set->mail_log_prefix, var_expand_table); - env_put(str_c(str)); - } - - mail_process_set_environment(set, FALSE); - if (args == NULL) - client_process_exec(executable, ""); - else - client_process_exec_argv(executable, args); - - i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", executable); -} - -enum master_login_status -create_mail_process(enum process_type process_type, struct master_settings *set, - const struct mail_login_request *request, - const char *user, const char *const *args, - const unsigned char *data, bool dump_capability, - pid_t *pid_r) -{ - const struct var_expand_table *var_expand_table; - const char *p, *addr, *chroot_dir, *home_dir, *full_home_dir; - const char *system_groups_user, *master_user, *key; - struct mail_process_group *process_group; - struct restrict_access_settings rset; - char title[1024]; - struct log_io *log; - string_t *str, *expanded_vars; - pid_t pid; - uid_t uid; - gid_t gid; - ARRAY_DEFINE(extra_args, const char *); - unsigned int i, len, count, left, process_count, throttle; - int ret, log_fd, nice_value, chdir_errno; - bool home_given; - - i_assert(process_type == PROCESS_TYPE_IMAP || - process_type == PROCESS_TYPE_POP3); - - if (mail_process_count == set->max_mail_processes) { - i_error("Maximum number of mail processes exceeded " - "(see max_mail_processes setting)"); - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } - - t_array_init(&extra_args, 16); - home_dir = chroot_dir = system_groups_user = ""; - master_user = NULL; - uid = (uid_t)-1; gid = (gid_t)-1; nice_value = 0; - home_given = FALSE; - for (; *args != NULL; args++) { - if (strncmp(*args, "home=", 5) == 0) { - home_dir = *args + 5; - home_given = TRUE; - } else if (strncmp(*args, "mail=", 5) == 0) { - const char *arg; - - arg = t_strconcat("mail_location=", *args + 5, NULL); - array_append(&extra_args, &arg, 1); - } else if (strncmp(*args, "chroot=", 7) == 0) - chroot_dir = *args + 7; - else if (strncmp(*args, "nice=", 5) == 0) - nice_value = atoi(*args + 5); - else if (strncmp(*args, "system_groups_user=", 19) == 0) - system_groups_user = *args + 19; - else if (strncmp(*args, "uid=", 4) == 0) { - if (uid != (uid_t)-1) { - i_error("uid specified multiple times for %s", - user); - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } - uid = (uid_t)strtoul(*args + 4, NULL, 10); - } else if (strncmp(*args, "gid=", 4) == 0) { - gid = (gid_t)strtoul(*args + 4, NULL, 10); - } else if (strncmp(*args, "master_user=", 12) == 0) { - const char *arg = *args; - - master_user = arg + 12; - array_append(&extra_args, &arg, 1); - } else { - const char *arg = *args; - array_append(&extra_args, &arg, 1); - } - } - - /* check process limit for this user, but not if this is a master - user login. */ - process_group = dump_capability ? NULL : - mail_process_group_lookup(process_type, user, - &request->remote_ip); - process_count = process_group == NULL ? 0 : - array_count(&process_group->processes); - if (process_count >= set->mail_max_userip_connections && - set->mail_max_userip_connections != 0 && - master_user == NULL) - return MASTER_LOGIN_STATUS_MAX_CONNECTIONS; - - /* if uid/gid wasn't returned, use the defaults */ - if (uid == (uid_t)-1) { - uid = set->mail_uid_t; - if (uid == (uid_t)-1) { - i_error("User %s is missing UID (see mail_uid setting)", - user); - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } - } - if (gid == (gid_t)-1) { - gid = set->mail_gid_t; - if (gid == (gid_t)-1) { - i_error("User %s is missing GID (see mail_gid setting)", - user); - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } - } - - if (*chroot_dir == '\0' && *set->valid_chroot_dirs != '\0' && - (p = strstr(home_dir, "/./")) != NULL) { - /* wu-ftpd like <chroot>/./<home> - check only if there's even - a possibility of using them (non-empty valid_chroot_dirs)*/ - chroot_dir = t_strdup_until(home_dir, p); - home_dir = p + 2; - } else if (*chroot_dir != '\0' && *home_dir != '/') { - /* home directories should never be relative, but force this - with chroots. */ - home_dir = t_strconcat("/", home_dir, NULL); - } - - if (!dump_capability) { - if (!validate_uid_gid(set, uid, gid, user)) - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } - - if (*chroot_dir != '\0') { - if (!validate_chroot(set, chroot_dir)) { - i_error("Invalid chroot directory '%s' (user %s) " - "(see valid_chroot_dirs setting)", - chroot_dir, user); - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } - } else if (*set->mail_chroot != '\0') { - /* mail_chroot setting's value doesn't need to be in - valid_chroot_dirs. */ - chroot_dir = set->mail_chroot; - } - if (*chroot_dir != '\0' && set->mail_drop_priv_before_exec) { - i_error("Can't chroot to directory '%s' (user %s) " - "with mail_drop_priv_before_exec=yes", - chroot_dir, user); - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } - len = strlen(chroot_dir); - if (len > 2 && strcmp(chroot_dir + len - 2, "/.") == 0 && - strncmp(home_dir, chroot_dir, len - 2) == 0) { - /* strip chroot dir from home dir */ - home_dir += len - 2; - } - - if (!dump_capability) { - throttle = set->mail_debug ? 0 : - set->mail_log_max_lines_per_sec; - log_fd = log_create_pipe(&log, throttle); - if (log_fd == -1) - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } else { - log = NULL; - log_fd = dup(STDERR_FILENO); - if (log_fd == -1) { - i_error("dup() failed: %m"); - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } - fd_close_on_exec(log_fd, TRUE); - } - - pid = fork(); - if (pid < 0) { - i_error("fork() failed: %m"); - (void)close(log_fd); - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; - } - - var_expand_table = - get_var_expand_table(process_names[process_type], - user, home_given ? home_dir : NULL, - net_ip2addr(&request->local_ip), - net_ip2addr(&request->remote_ip), - pid != 0 ? pid : getpid(), uid); - str = t_str_new(128); - - if (pid != 0) { - /* master */ - var_expand(str, set->mail_log_prefix, var_expand_table); - - if (!dump_capability) { - log_set_prefix(log, str_c(str)); - log_set_pid(log, pid); - if (process_group == NULL) { - process_group = mail_process_group_create( - process_type, user, - &request->remote_ip); - } - mail_process_group_add(process_group, pid); - } - (void)close(log_fd); - *pid_r = pid; - return MASTER_LOGIN_STATUS_OK; - } - -#ifdef HAVE_SETPRIORITY - if (nice_value != 0) { - if (setpriority(PRIO_PROCESS, 0, nice_value) < 0) - i_error("setpriority(%d) failed: %m", nice_value); - } -#endif - - if (!dump_capability) { - str_append(str, "master-"); - var_expand(str, set->mail_log_prefix, var_expand_table); - log_set_prefix(log, str_c(str)); - } - - child_process_init_env(set); - - /* setup environment - set the most important environment first - (paranoia about filling up environment without noticing) */ - restrict_access_init(&rset); - rset.uid = uid; - rset.gid = gid; - rset.privileged_gid = set->mail_priv_gid_t; - rset.extra_groups = set->mail_access_groups; - rset.system_groups_user = system_groups_user; - rset.first_valid_gid = set->first_valid_gid; - rset.last_valid_gid = set->last_valid_gid; - rset.chroot_dir = dump_capability ? NULL : chroot_dir; - restrict_access_set_env(&rset); - - restrict_process_size(set->mail_process_size, (unsigned int)-1); - - if (dump_capability) - env_put("DUMP_CAPABILITY=1"); - - if ((*home_dir == '\0' && *chroot_dir == '\0') || dump_capability) { - full_home_dir = ""; - ret = -1; - } else { - full_home_dir = *chroot_dir == '\0' ? home_dir : - t_strconcat(chroot_dir, home_dir, NULL); - /* NOTE: 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 (uid != master_uid) { - if (setegid(gid) < 0) - i_fatal("setegid(%s) failed: %m", dec2str(gid)); - if (seteuid(uid) < 0) - i_fatal("seteuid(%s) failed: %m", dec2str(uid)); - } - - alarm(CHDIR_TIMEOUT); - ret = chdir(full_home_dir); - chdir_errno = errno; - if ((left = alarm(0)) < CHDIR_TIMEOUT - CHDIR_WARN_SECS) { - i_warning("chdir(%s) blocked for %u secs", - full_home_dir, CHDIR_TIMEOUT - left); - } - - /* Change UID back. No need to change GID back, it doesn't - really matter. */ - if (uid != master_uid && seteuid(master_uid) < 0) - i_fatal("seteuid(%s) failed: %m", dec2str(master_uid)); - - /* If user's home directory doesn't exist and we're not - trying to chroot anywhere, fallback to /tmp as the mails - could be stored elsewhere. The ENOTDIR check is mostly for - /dev/null home directory. */ - if (ret < 0 && (*chroot_dir != '\0' || - !(ENOTFOUND(chdir_errno) || - chdir_errno == EINTR))) { - errno = chdir_errno; - if (errno != EACCES) { - i_fatal("chdir(%s) failed with uid %s: %m", - full_home_dir, dec2str(uid)); - } else { - i_fatal("%s", eacces_error_get("chdir", - full_home_dir)); - } - } - } - 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"); - } - - mail_process_set_environment(set, dump_capability); - - /* extra args. uppercase key value. */ - expanded_vars = t_str_new(128); - str_append(expanded_vars, "VARS_EXPANDED="); - args = array_get(&extra_args, &count); - for (i = 0; i < count; i++) { - if (*args[i] == '=') { - /* Should be caught by dovecot-auth already */ - i_fatal("Userdb returned data with empty key (%s)", - args[i]); - } - p = strchr(args[i], '='); - if (p == NULL) { - /* boolean */ - key = args[i]; - env_put(t_strconcat(t_str_ucase(key), "=1", NULL)); - - } else { - /* key=value */ - key = t_strdup_until(args[i], p); - env_put(t_strconcat(t_str_ucase(key), p, NULL)); - } - str_append(expanded_vars, key); - str_append_c(expanded_vars, ' '); - } - env_put(str_c(expanded_vars)); - - env_put("LOGGED_IN=1"); - if (*home_dir != '\0') - env_put(t_strconcat("HOME=", home_dir, NULL)); - env_put(t_strconcat("USER=", user, NULL)); - - addr = net_ip2addr(&request->remote_ip); - env_put(t_strconcat("IP=", addr, NULL)); - env_put(t_strconcat("LOCAL_IP=", net_ip2addr(&request->local_ip), NULL)); - - i_assert(request->cmd_tag_size <= request->data_size); - if (request->cmd_tag_size > 0) { - env_put(t_strconcat("IMAPLOGINTAG=", - t_strndup(data, request->cmd_tag_size), NULL)); - } - - if (request->data_size > request->cmd_tag_size) { - str_truncate(str, 0); - str_append(str, "CLIENT_INPUT="); - base64_encode(data + request->cmd_tag_size, - request->data_size - request->cmd_tag_size, str); - env_put(str_c(str)); - } - - if (!set->verbose_proctitle) - title[0] = '\0'; - else { - if (addr == NULL) - addr = "??"; - - i_snprintf(title, sizeof(title), "[%s %s]", user, addr); - } - - /* make sure we don't leak syslog fd. try to do it as late as possible, - but also before dup2()s in case syslog fd is one of them. */ - closelog(); - - /* move the client socket into stdin and stdout fds, log to stderr */ - if (dup2(request->fd, 0) < 0) - i_fatal("dup2(stdin) failed: %m"); - if (dup2(request->fd, 1) < 0) - i_fatal("dup2(stdout) failed: %m"); - if (dup2(log_fd, 2) < 0) - i_fatal("dup2(stderr) failed: %m"); - - for (i = 0; i < 3; i++) - fd_close_on_exec(i, FALSE); - - if (set->mail_drop_priv_before_exec) { - restrict_access_by_env(home_dir, TRUE); - /* privileged GID is now only in saved-GID. if we want to - preserve it across exec, it needs to be temporarily - in effective gid. unfortunately this also causes kernel - to think we're a setgid-program. */ - restrict_access_use_priv_gid(); - } - - client_process_exec(set->mail_executable, title); - i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", - set->mail_executable); - - /* not reached */ - return MASTER_LOGIN_STATUS_INTERNAL_ERROR; -} - -static void -mail_process_destroyed(struct child_process *process, - pid_t pid, bool abnormal_exit ATTR_UNUSED) -{ - struct mail_process_group *group = (struct mail_process_group *)process; - const pid_t *pids; - unsigned int i, count; - - pids = array_get(&group->processes, &count); - if (count == 1) { - /* last process in this group */ - i_assert(pids[0] == pid); - hash_table_remove(mail_process_groups, group); - mail_process_group_free(group); - } else { - for (i = 0; i < count; i++) { - if (pids[i] == pid) - break; - } - i_assert(i != count); - array_delete(&group->processes, i, 1); - } - - mail_process_count--; -} - -void mail_processes_init(void) -{ - mail_process_groups = hash_table_create(default_pool, default_pool, 0, - mail_process_group_hash, - mail_process_group_cmp); - - child_process_set_destroy_callback(PROCESS_TYPE_IMAP, - mail_process_destroyed); - child_process_set_destroy_callback(PROCESS_TYPE_POP3, - mail_process_destroyed); -} - -void mail_processes_deinit(void) -{ - /* we may still end up in mail_process_destroyed(), so don't free - anything. This deinit code needs a redesign.. */ -}
--- a/src/master/mail-process.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -#ifndef MAIL_PROCESS_H -#define MAIL_PROCESS_H - -#include "master-login-interface.h" -#include "child-process.h" - -struct mail_login_request { - int fd; - unsigned int cmd_tag_size; - unsigned int data_size; - struct ip_addr local_ip, remote_ip; -}; - -struct login_group; -struct auth_master_reply; - -void mail_process_exec(const char *protocol, const char **args) ATTR_NORETURN; - -enum master_login_status -create_mail_process(enum process_type process_type, struct master_settings *set, - const struct mail_login_request *request, - const char *user, const char *const *args, - const unsigned char *data, bool dump_capability, - pid_t *pid_r); - -void mail_processes_init(void); -void mail_processes_deinit(void); - -#endif
--- a/src/master/main.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/master/main.c Thu Apr 23 19:53:44 2009 -0400 @@ -1,249 +1,67 @@ -/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ +/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ #include "common.h" -#include "array.h" -#include "ioloop.h" #include "lib-signals.h" -#include "network.h" -#include "env-util.h" #include "fd-close-on-exec.h" #include "write-full.h" +#include "env-util.h" +#include "hostpid.h" #include "restrict-process-size.h" - -#include "askpass.h" -#include "auth-process.h" +#include "master-service.h" +#include "master-service-settings.h" #include "capabilities.h" -#include "dict-process.h" -#include "login-process.h" -#include "mail-process.h" -#include "syslog-util.h" -#include "listener.h" -#include "ssl-init.h" -#include "log.h" -#include "sysinfo-get.h" -#include "hostpid.h" +#include "service.h" +#include "service-listen.h" +#include "service-monitor.h" +#include "service-log.h" #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> -#include <syslog.h> #include <sys/stat.h> -#define FATAL_FILENAME "master-fatal.lastlog" - -static const char *configfile = SYSCONFDIR "/" PACKAGE ".conf"; -static pid_t master_original_pid; - -struct ioloop *ioloop; -int null_fd = -1, inetd_login_fd; +struct master_service *master_service; uid_t master_uid; -char program_path[PATH_MAX]; -char ssl_manual_key_password[100]; -const char *env_tz; -bool auth_success_written; +gid_t master_gid; bool core_dumps_disabled; -#ifdef DEBUG -bool gdb; -#endif +int null_fd; + +static char *pidfile_path; +static struct service_list *services; -static void ATTR_NORETURN ATTR_FORMAT(3, 0) -master_fatal_callback(enum log_type type, int status, - const char *format, va_list args) +static const char *child_process_env[3]; /* @UNSAFE */ + +void process_exec(const char *cmd, const char *extra_args[]) { - const struct master_settings *set = master_set->defaults; - const char *path, *str; - va_list args2; - int fd; + const char *executable, *p, **argv; - /* if we already forked a child process, this isn't fatal for the - main process and there's no need to write the fatal file. */ - if (getpid() == master_original_pid) { - /* write the error message to a file */ - path = t_strconcat(set->base_dir, "/"FATAL_FILENAME, NULL); - fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); - if (fd != -1) { - VA_COPY(args2, args); - str = t_strdup_vprintf(format, args2); - write_full(fd, str, strlen(str)); - (void)close(fd); - } - } + argv = t_strsplit(cmd, " "); + executable = argv[0]; - /* write it to log as well */ - if (*set->log_path == '\0') - i_syslog_fatal_handler(type, status, format, args); - else - default_fatal_handler(type, status, format, args); -} + if (extra_args != NULL) { + unsigned int count1, count2; + const char **new_argv; -static void fatal_log_check(void) -{ - const struct master_settings *set = master_set->defaults; - const char *path; - char buf[1024]; - ssize_t ret; - int fd; - - path = t_strconcat(set->base_dir, "/"FATAL_FILENAME, NULL); - fd = open(path, O_RDONLY); - if (fd == -1) - return; - - ret = read(fd, buf, sizeof(buf)); - if (ret < 0) - i_error("read(%s) failed: %m", path); - else { - buf[ret] = '\0'; - i_warning("Last died with error (see error log for more " - "information): %s", buf); + /* @UNSAFE */ + count1 = str_array_length(argv); + count2 = str_array_length(extra_args); + new_argv = t_new(const char *, count1 + count2 + 1); + memcpy(new_argv, argv, sizeof(const char *) * count1); + memcpy(new_argv + count1, extra_args, + sizeof(const char *) * count2); + argv = new_argv; } - close(fd); - if (unlink(path) < 0) - i_error("unlink(%s) failed: %m", path); -} - -static void auth_warning_print(const struct master_server_settings *set) -{ - struct master_auth_settings *const *auths; - unsigned int count; - struct stat st; - - auth_success_written = stat(AUTH_SUCCESS_PATH, &st) == 0; - auths = array_get(&set->defaults->auths, &count); - if (!auth_success_written && count > 0 && !auths[0]->debug && - strcmp(set->defaults->protocols, "none") != 0) { - i_info("If you have trouble with authentication failures,\n" - "enable auth_debug setting. " - "See http://wiki.dovecot.org/WhyDoesItNotWork"); - - } -} - -static void set_logfile(struct master_settings *set) -{ - int facility; - - if (*set->log_path == '\0') { - if (!syslog_facility_find(set->syslog_facility, &facility)) - facility = LOG_MAIL; - - i_set_failure_syslog("dovecot", LOG_NDELAY, facility); - } else { - /* log to file or stderr */ - i_set_failure_file(set->log_path, "dovecot: "); - } - i_set_fatal_handler(master_fatal_callback); - - if (*set->info_log_path != '\0') - i_set_info_file(set->info_log_path); - - i_set_failure_timestamp_format(set->log_timestamp); -} - -static void settings_reload(void) -{ - /* this old_set wrapping works because master settings are - alternatingly read using two different pools */ - struct master_server_settings *set, *old_set = master_set; - - i_warning("SIGHUP received - reloading configuration"); - - /* restart auth and login processes */ - login_processes_destroy_all(); - auth_processes_destroy_all(); - dict_processes_kill(); - - /* see if hostname changed */ - hostpid_init(); - - if (master_settings_read(configfile, &set) < 0 || - !master_settings_check(set, FALSE, FALSE)) - i_warning("Invalid configuration, keeping old one"); - else { - if (!IS_INETD()) - listeners_open_fds(old_set, TRUE); - set_logfile(set->defaults); - } -} + /* hide the path, it's ugly */ + p = strrchr(argv[0], '/'); + if (p != NULL) argv[0] = p+1; -static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED) -{ - /* warn about being killed because of some signal, except SIGINT (^C) - which is too common at least while testing :) */ - if (si->si_signo != SIGINT) { - i_warning("Killed with signal %d (by pid=%s uid=%s code=%s)", - si->si_signo, dec2str(si->si_pid), - dec2str(si->si_uid), - lib_signal_code_to_str(si->si_signo, si->si_code)); - } - io_loop_stop(ioloop); -} - -static void sig_reload_settings(const siginfo_t *si ATTR_UNUSED, - void *context ATTR_UNUSED) -{ - settings_reload(); -} - -static void sig_reopen_logs(const siginfo_t *si ATTR_UNUSED, - void *context ATTR_UNUSED) -{ - set_logfile(master_set->defaults); -} - -static bool have_stderr_set(struct master_settings *set) -{ - if (*set->log_path != '\0' && - strcmp(set->log_path, "/dev/stderr") == 0) - return TRUE; - - if (*set->info_log_path != '\0' && - strcmp(set->info_log_path, "/dev/stderr") == 0) - return TRUE; - - return FALSE; -} + /* prefix with dovecot/ */ + argv[0] = t_strconcat(PACKAGE"/", argv[0], NULL); -static bool have_stderr(struct master_server_settings *server) -{ - if (server->imap != NULL && have_stderr_set(server->imap)) - return TRUE; - if (server->pop3 != NULL && have_stderr_set(server->pop3)) - return TRUE; - return FALSE; -} - -static void open_null_fd(void) -{ - null_fd = open("/dev/null", O_RDONLY); - if (null_fd == -1) - i_fatal("Can't open /dev/null: %m"); - fd_close_on_exec(null_fd, TRUE); -} - -static void open_fds(void) -{ - /* make sure all fds between 0..3 are used. */ - while (null_fd < 4) { - null_fd = dup(null_fd); - if (null_fd == -1) - i_fatal("dup(null_fd) failed: %m"); - fd_close_on_exec(null_fd, TRUE); - } - - if (!IS_INETD()) { - T_BEGIN { - listeners_open_fds(NULL, FALSE); - } T_END; - } - - /* close stdin and stdout. */ - if (dup2(null_fd, 0) < 0) - i_fatal("dup2(0) failed: %m"); - if (dup2(null_fd, 1) < 0) - i_fatal("dup2(1) failed: %m"); + execv(executable, (char **)argv); + i_fatal_status(FATAL_EXEC, "execvp(%s) failed: %m", executable); } static void create_pid_file(const char *path) @@ -261,42 +79,58 @@ (void)close(fd); } -static void pid_file_check_running(const struct master_settings *set) +static void +sig_settings_reload(const siginfo_t *si ATTR_UNUSED, + void *context ATTR_UNUSED) { - const char *path; - char buf[32]; - int fd; - ssize_t ret; + struct master_settings *new_set; + struct service_list *new_services; + const char *error; + pool_t pool; + + /* see if hostname changed */ + hostpid_init(); - path = t_strconcat(set->base_dir, "/master.pid", NULL); - fd = open(path, O_RDONLY); - if (fd == -1) { - if (errno == ENOENT) - return; - i_fatal("open(%s) failed: %m", path); +#if 0 // FIXME + /* FIXME: this loses process structures for existing processes. + figure out something. */ + new_set = master_settings_read(pool, config_binary, config_path); + new_services = new_set == NULL ? NULL : + services_create(new_set, child_process_env, &error); +#endif + if (new_services == NULL) { + /* new configuration is invalid, keep the old */ + i_error("Config reload failed: %s", error); + return; } - ret = read(fd, buf, sizeof(buf)); - if (ret <= 0) { - if (ret == 0) - i_error("Empty PID file in %s, overriding", path); - else - i_fatal("read(%s) failed: %m", path); - } else { - pid_t pid; + /* switch to new configuration. */ + (void)services_listen_using(new_services, services); + services_destroy(services); + services = new_services; + + services_monitor_start(services); +} + +static void +sig_log_reopen(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) +{ + service_signal(services->log, SIGUSR1); +} - if (buf[ret-1] == '\n') - ret--; - buf[ret] = '\0'; - pid = atoi(buf); - if (pid == getpid() || (kill(pid, 0) < 0 && errno == ESRCH)) { - /* doesn't exist */ - } else { - i_fatal("Dovecot is already running with PID %s " - "(read from %s)", buf, path); - } - } - (void)close(fd); +static void +sig_reap_children(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED) +{ + services_monitor_reap_children(services); +} + +static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED) +{ + i_warning("Killed with signal %d (by pid=%s uid=%s code=%s)", + si->si_signo, dec2str(si->si_pid), + dec2str(si->si_uid), + lib_signal_code_to_str(si->si_signo, si->si_code)); + master_service_stop(master_service); } static void main_log_startup(void) @@ -312,20 +146,13 @@ i_info(STARTUP_STRING); } -static void main_init(bool log_error) +static void main_init(const struct master_settings *set, bool log_error) { drop_capabilities(); /* deny file access from everyone else except owner */ (void)umask(0077); - set_logfile(master_set->defaults); - /* close stderr unless we're logging into /dev/stderr. */ - if (!have_stderr(master_set)) { - if (dup2(null_fd, 2) < 0) - i_fatal("dup2(2) failed: %m"); - } - if (log_error) { printf("Writing to error logs and killing myself..\n"); i_info("This is Dovecot's info log"); @@ -336,51 +163,44 @@ main_log_startup(); lib_signals_init(); - lib_signals_set_handler(SIGINT, TRUE, sig_die, NULL); - lib_signals_set_handler(SIGTERM, TRUE, sig_die, NULL); lib_signals_ignore(SIGPIPE, TRUE); lib_signals_ignore(SIGALRM, FALSE); - lib_signals_set_handler(SIGHUP, TRUE, sig_reload_settings, NULL); - lib_signals_set_handler(SIGUSR1, TRUE, sig_reopen_logs, NULL); + lib_signals_set_handler(SIGHUP, TRUE, sig_settings_reload, NULL); + lib_signals_set_handler(SIGUSR1, TRUE, sig_log_reopen, NULL); + lib_signals_set_handler(SIGCHLD, TRUE, sig_reap_children, NULL); + lib_signals_set_handler(SIGINT, TRUE, sig_die, NULL); + lib_signals_set_handler(SIGTERM, TRUE, sig_die, NULL); - child_processes_init(); - log_init(); - ssl_init(); - dict_processes_init(); - auth_processes_init(); - login_processes_init(); - mail_processes_init(); + pidfile_path = i_strconcat(set->base_dir, "/master.pid", NULL); + create_pid_file(pidfile_path); - create_pid_file(t_strconcat(master_set->defaults->base_dir, - "/master.pid", NULL)); + services_monitor_start(services); } static void main_deinit(void) { - (void)unlink(t_strconcat(master_set->defaults->base_dir, - "/master.pid", NULL)); - - login_processes_destroy_all(); - - mail_processes_deinit(); - login_processes_deinit(); - auth_processes_deinit(); - dict_processes_deinit(); - ssl_deinit(); + if (unlink(pidfile_path) < 0) + i_error("unlink(%s) failed: %m", pidfile_path); + i_free(pidfile_path); - listeners_close_fds(); - - if (close(null_fd) < 0) - i_error("close(null_fd) failed: %m"); - - log_deinit(); - /* log_deinit() may still want to look up child processes */ - child_processes_deinit(); - lib_signals_deinit(); - closelog(); + services_destroy(services); } -static void daemonize(struct master_settings *set) +static const char *get_full_config_path(struct service_list *list) +{ + const char *path; + char cwd[PATH_MAX]; + + path = master_service_get_config_path(master_service); + if (*path == '/') + return path; + + if (getcwd(cwd, sizeof(cwd)) == NULL) + i_fatal("getcwd() failed: %m"); + return p_strconcat(list->pool, cwd, "/", path, NULL); +} + +static void daemonize(void) { pid_t pid; @@ -394,21 +214,20 @@ if (setsid() < 0) i_fatal("setsid() failed: %m"); - if (chdir(set->base_dir) < 0) - i_fatal("chdir(%s) failed: %m", set->base_dir); + /* update my_pid */ + hostpid_init(); } static void print_help(void) { printf( "Usage: dovecot [-F] [-c <config file>] [-p] [-n] [-a]\n" -" [--version] [--build-options] [--exec-mail <protocol> [<args>]]\n"); +" [-cb <config binary path>] [--version] [--build-options]\n"); } static void print_build_options(void) { - static const char *build_options = - "Build options:" + printf("Build options:" #ifdef IOLOOP_EPOLL " ioloop=epoll" #endif @@ -439,8 +258,7 @@ #ifdef HAVE_OPENSSL " openssl" #endif - "\nMail storages: "MAIL_STORAGES"\n" - "SQL drivers:" + "\nSQL drivers:" #ifdef BUILD_MYSQL " mysql" #endif @@ -472,9 +290,6 @@ #ifdef PASSDB_SHADOW " shadow" #endif -#ifdef PASSDB_SIA - " sia" -#endif #ifdef PASSDB_SQL " sql" #endif @@ -482,8 +297,8 @@ " vpopmail" #endif "\nUserdb:" -#ifdef USERDB_NSS - " nss" +#ifdef USERDB_CHECKPASSWORD + " checkpassword" #endif #ifdef USERDB_LDAP " ldap" @@ -491,12 +306,12 @@ #ifdef USERDB_PASSWD " passwd" #endif +#ifdef USERDB_PREFETCH + " prefetch" +#endif #ifdef USERDB_PASSWD_FILE " passwd-file" #endif -#ifdef USERDB_PREFETCH - " prefetch" -#endif #ifdef USERDB_SQL " sql" #endif @@ -506,160 +321,139 @@ #ifdef USERDB_VPOPMAIL " vpopmail" #endif - "\n"; - puts(build_options); + "\n"); } int main(int argc, char *argv[]) { - /* parse arguments */ - struct master_server_settings *set; - const char *exec_protocol = NULL, **exec_args = NULL, *user, *home; + static const struct setting_parser_info *set_roots[] = { + &master_setting_parser_info, + NULL + }; + struct master_settings *set; + unsigned int child_process_env_idx = 0; + const char *getopt_str, *error, *env_tz; + void **sets; bool foreground = FALSE, ask_key_pass = FALSE, log_error = FALSE; - bool dump_config = FALSE, dump_config_nondefaults = FALSE; - int i; + int c; #ifdef DEBUG - gdb = getenv("GDB") != NULL; + if (getenv("GDB") == NULL) + fd_debug_verify_leaks(3, 1024); + else + child_process_env[child_process_env_idx++] = "GDB=1"; #endif - lib_init(); + master_service = master_service_init("master", 0, argc, argv); master_uid = geteuid(); - inetd_login_fd = -1; - for (i = 1; i < argc; i++) { - if (strcmp(argv[i], "-F") == 0) { - /* foreground */ + master_gid = getegid(); + + getopt_str = t_strconcat("Fp", master_service_getopt_string(), NULL); + while ((c = getopt(argc, argv, getopt_str)) > 0) { + switch (c) { + case 'F': foreground = TRUE; - } else if (strcmp(argv[i], "-a") == 0) { - dump_config = TRUE; - } else if (strcmp(argv[i], "-c") == 0) { - /* config file */ - i++; - if (i == argc) i_fatal("Missing config file argument"); - configfile = argv[i]; - } else if (strcmp(argv[i], "-n") == 0) { - dump_config_nondefaults = dump_config = TRUE; - } else if (strcmp(argv[i], "-p") == 0) { + break; + case 'p': /* Ask SSL private key password */ ask_key_pass = TRUE; - } else if (strcmp(argv[i], "--exec-mail") == 0) { - /* <protocol> [<args>] - read configuration and execute mail process */ - i++; - if (i == argc) i_fatal("Missing protocol argument"); - exec_protocol = argv[i]; - exec_args = (const char **)&argv[i+1]; break; - } else if (strcmp(argv[i], "--version") == 0) { + default: + if (!master_service_parse_option(master_service, + c, optarg)) { + print_help(); + exit(FATAL_DEFAULT); + } + break; + } + } + + if (optind < argc) { + if (strcmp(argv[optind], "--version") == 0) { printf("%s\n", VERSION); return 0; - } else if (strcmp(argv[i], "--build-options") == 0) { + } else if (strcmp(argv[optind], "--build-options") == 0) { print_build_options(); return 0; - } else if (strcmp(argv[i], "--log-error") == 0) { + } else if (strcmp(argv[optind], "--log-error") == 0) { log_error = TRUE; foreground = TRUE; } else { print_help(); - i_fatal("Unknown argument: %s", argv[1]); + i_fatal("Unknown argument: %s", argv[optind]); } } - /* need to have this open before reading settings */ - open_null_fd(); + do { + null_fd = open("/dev/null", O_WRONLY); + if (null_fd == -1) + i_fatal("Can't open /dev/null: %m"); + fd_close_on_exec(null_fd, TRUE); + } while (null_fd <= STDERR_FILENO); - if (getenv("DOVECOT_INETD") != NULL) { - /* starting through inetd. */ - inetd_login_fd = dup(0); - if (inetd_login_fd == -1) - i_fatal("dup(0) failed: %m"); - fd_close_on_exec(inetd_login_fd, TRUE); - foreground = TRUE; - } - - if (dump_config) { - - /* print the config file path before parsing it, so in case - of errors it's still shown */ - printf("# "VERSION": %s\n", configfile); - } + if (dup2(null_fd, STDIN_FILENO) < 0 || + dup2(null_fd, STDOUT_FILENO) < 0) + i_fatal("dup2(null_fd) failed: %m"); - /* read and verify settings before forking */ - T_BEGIN { - master_settings_init(); - if (master_settings_read(configfile, &set) < 0) - i_fatal("Invalid configuration in %s", configfile); - } T_END; - - if (!log_error && !dump_config && exec_protocol == NULL) - pid_file_check_running(set->defaults); - - if (!master_settings_check(set, exec_protocol != NULL, - dump_config || log_error)) - i_fatal("Invalid configuration in %s", configfile); + if (master_service_settings_read(master_service, set_roots, NULL, FALSE, + &error) < 0) + i_fatal("Error reading configuration: %s", error); + sets = master_service_settings_get_others(master_service); + set = sets[0]; - if (dump_config) { - const char *info; - - info = sysinfo_get(master_set->defaults->mail_location); - if (*info != '\0') - printf("# %s\n", info); + master_service_init_log(master_service, "dovecot: ", 0); + if (!log_error) + master_settings_do_fixes(set); - if (dump_config_nondefaults) - client_process_exec(DOVECOT_CONFIG_BIN_PATH" -a", ""); - else - client_process_exec(DOVECOT_CONFIG_BIN_PATH" -n", ""); - i_fatal("exec(%s) failed: %m", DOVECOT_CONFIG_BIN_PATH); - } - - if (ask_key_pass && !log_error) T_BEGIN { +#if 0 // FIXME + if (ask_key_pass) { const char *prompt; prompt = t_strdup_printf("Give the password for SSL key file " - "%s: ", - master_set->defaults->ssl_key_file); + "%s: ", set->ssl_key_file); askpass(prompt, ssl_manual_key_password, sizeof(ssl_manual_key_password)); - } T_END; + } +#endif /* save TZ environment. AIX depends on it to get the timezone correctly. */ env_tz = getenv("TZ"); - user = getenv("USER"); - home = getenv("HOME"); /* clean up the environment of everything */ env_clean(); /* put back the TZ */ - if (env_tz != NULL) - env_put(t_strconcat("TZ=", env_tz, NULL)); + if (env_tz != NULL) { + const char *env = t_strconcat("TZ=", env_tz, NULL); - if (exec_protocol != NULL) { - /* Put back user and home */ - env_put(t_strconcat("USER=", user, NULL)); - env_put(t_strconcat("HOME=", home, NULL)); - mail_process_exec(exec_protocol, exec_args); + env_put(env); + child_process_env[child_process_env_idx++] = env; } + i_assert(child_process_env_idx < + sizeof(child_process_env) / sizeof(child_process_env[0])); + child_process_env[child_process_env_idx++] = NULL; - if (!log_error) - open_fds(); + /* create service structures from settings. if there are any errors in + service configuration we'll catch it here. */ + services = services_create(set, child_process_env, &error); + if (services == NULL) + i_fatal("%s", error); - fatal_log_check(); - auth_warning_print(master_set); - if (!foreground) - daemonize(master_set->defaults); - master_original_pid = getpid(); + services->config->config_file_path = get_full_config_path(services); - ioloop = io_loop_create(); + /* if any listening fails, fail completely */ + if (services_listen(services) <= 0) + return FATAL_DEFAULT; - main_init(log_error); - io_loop_run(ioloop); - main_deinit(); + if (!foreground) + daemonize(); + if (chdir(set->base_dir) < 0) + i_fatal("chdir(%s) failed: %m", set->base_dir); - master_settings_deinit(); - io_loop_destroy(&ioloop); - lib_deinit(); - + main_init(set, log_error); + master_service_run(master_service, NULL); + main_deinit(); + master_service_deinit(&master_service); return 0; }
--- a/src/master/master-login-interface.h Thu Apr 23 14:07:45 2009 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,58 +0,0 @@ -#ifndef MASTER_LOGIN_INTERFACE_H -#define MASTER_LOGIN_INTERFACE_H - -#include "network.h" - -#define LOGIN_MASTER_SOCKET_FD 3 - -/* Increase the version number every time master_login_request - (or something else) is changed. */ -#define MASTER_LOGIN_PROTOCOL_VERSION 3 - -/* 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_LOGIN_MAX_DATA_SIZE (4096*2) - -enum master_login_state { - /* process is accepting new connections */ - LOGIN_STATE_LISTENING = 0, - /* process isn't accepting new connections, but it'd be able to kill - some connections which haven't logged in yet */ - LOGIN_STATE_FULL_PRELOGINS, - /* process is handling only logged in users */ - LOGIN_STATE_FULL_LOGINS, - - LOGIN_STATE_COUNT -}; - -struct master_login_request { - uint32_t version; - /* if fd == -1, tag is used as master_login_state */ - uint32_t tag; - - uint32_t auth_pid; - uint32_t auth_id; - /* request follows this many bytes of client input */ - uint16_t data_size; - uint16_t cmd_tag_size; - - ino_t ino; - - struct ip_addr local_ip, remote_ip; -}; - -enum master_login_status { - MASTER_LOGIN_STATUS_OK, - MASTER_LOGIN_STATUS_INTERNAL_ERROR, - /* user reached max. simultaneous connections */ - MASTER_LOGIN_STATUS_MAX_CONNECTIONS -}; - -struct master_login_reply { - unsigned int tag; - enum master_login_status status; - /* PID of the post-login mail process handling this connection */ - pid_t mail_pid; -}; - -#endif
--- a/src/master/master-settings.c Thu Apr 23 14:07:45 2009 -0400 +++ b/src/master/master-settings.c Thu Apr 23 19:53:44 2009 -0400 @@ -1,134 +1,145 @@ -/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */ +/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */ #include "common.h" #include "array.h" -#include "str.h" +#include "env-util.h" #include "istream.h" -#include "env-util.h" -#include "fd-close-on-exec.h" -#include "safe-mkdir.h" #include "mkdir-parents.h" -#include "unlink-directory.h" -#include "syslog-util.h" -#include "mail-process.h" -#include "master-login-interface.h" #include "settings-parser.h" +#include "master-settings.h" -#include <stdio.h> #include <stddef.h> -#include <stdlib.h> #include <dirent.h> #include <unistd.h> -#include <fcntl.h> -#include <ctype.h> -#include <signal.h> #include <sys/stat.h> #include <sys/wait.h> -#include <pwd.h> -#include <grp.h> -#ifdef HAVE_SYS_RESOURCE_H -# include <sys/resource.h> -#endif -extern struct setting_parser_info master_auth_setting_parser_info; -extern struct setting_parser_info master_setting_parser_info; -extern struct setting_parser_info master_auth_socket_setting_parser_info; +static bool master_settings_verify(void *_set, pool_t pool, + const char **error_r); + +extern struct setting_parser_info service_setting_parser_info; #undef DEF #define DEF(type, name) \ - { type, #name, offsetof(struct master_auth_socket_unix_settings, name), NULL } + { type, #name, offsetof(struct file_listener_settings, name), NULL } -static struct setting_define master_auth_socket_master_setting_defines[] = { +static struct setting_define file_listener_setting_defines[] = { DEF(SET_STR, path), + DEF(SET_UINT, mode), + DEF(SET_STR, user), + DEF(SET_STR, group), SETTING_DEFINE_LIST_END }; -static struct master_auth_socket_unix_settings master_auth_socket_master_default_settings = { - MEMBER(path) "auth-master" +static struct file_listener_settings file_listener_default_settings = { + MEMBER(path) "", + MEMBER(mode) 0600, + MEMBER(user) "", + MEMBER(group) "", +}; + +static struct setting_parser_info file_listener_setting_parser_info = { + MEMBER(defines) file_listener_setting_defines, + MEMBER(defaults) &file_listener_default_settings, + + MEMBER(parent) &service_setting_parser_info, + MEMBER(parent_offset) (size_t)-1, + MEMBER(struct_size) sizeof(struct file_listener_settings) }; -struct setting_parser_info master_auth_socket_master_setting_parser_info = { - MEMBER(defines) master_auth_socket_master_setting_defines, - MEMBER(defaults) &master_auth_socket_master_default_settings, +#undef DEF +#define DEF(type, name) \ + { type, #name, offsetof(struct inet_listener_settings, name), NULL } + +static struct setting_define inet_listener_setting_defines[] = { + DEF(SET_STR, address), + DEF(SET_UINT, port), + + SETTING_DEFINE_LIST_END +}; - MEMBER(parent) &master_auth_socket_setting_parser_info, - MEMBER(dynamic_parsers) NULL, +static struct inet_listener_settings inet_listener_default_settings = { + MEMBER(address) "*", + MEMBER(port) 0 +}; +static struct setting_parser_info inet_listener_setting_parser_info = { + MEMBER(defines) inet_listener_setting_defines, + MEMBER(defaults) &inet_listener_default_settings, + + MEMBER(parent) &service_setting_parser_info, MEMBER(parent_offset) (size_t)-1, - MEMBER(type_offset) (size_t)-1, - MEMBER(struct_size) sizeof(struct master_auth_socket_unix_settings) + MEMBER(struct_size) sizeof(struct inet_listener_settings) }; #undef DEF #undef DEFLIST #define DEF(type, name) \ - { type, #name, offsetof(struct master_auth_socket_settings, name), NULL } + { type, #name, offsetof(struct service_settings, name), NULL } #define DEFLIST(field, name, defines) \ - { SET_DEFLIST, name, offsetof(struct master_auth_socket_settings, field), defines } -static struct setting_define master_auth_socket_setting_defines[] = { + { SET_DEFLIST, name, offsetof(struct service_settings, field), defines } + +static struct setting_define service_setting_defines[] = { + DEF(SET_INTERNAL, master_set), DEF(SET_STR, type), - DEFLIST(masters, "master", &master_auth_socket_master_setting_parser_info), + DEF(SET_STR, executable), + DEF(SET_STR, user), + DEF(SET_STR, group), + 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), + + DEF(SET_UINT, process_limit), + DEF(SET_UINT, client_limit), + DEF(SET_UINT, vsz_limit), + + DEFLIST(unix_listeners, "unix_listener", + &file_listener_setting_parser_info), + DEFLIST(fifo_listeners, "fifo_listener", + &file_listener_setting_parser_info), + DEFLIST(inet_listeners, "inet_listener", + &inet_listener_setting_parser_info), SETTING_DEFINE_LIST_END }; -struct setting_parser_info master_auth_socket_setting_parser_info = { - MEMBER(defines) master_auth_socket_setting_defines, - MEMBER(defaults) NULL, +static struct service_settings service_default_settings = { + MEMBER(master_set) NULL, - MEMBER(parent) &master_auth_setting_parser_info, - MEMBER(dynamic_parsers) NULL, + MEMBER(type) "", + MEMBER(executable) "", + MEMBER(user) "", + MEMBER(group) "", + MEMBER(privileged_group) "", + MEMBER(extra_groups) "", + MEMBER(chroot) "", + MEMBER(auth_dest_service) "", - MEMBER(parent_offset) (size_t)-1, - MEMBER(type_offset) offsetof(struct master_auth_socket_settings, type), - MEMBER(struct_size) sizeof(struct master_auth_socket_settings) + MEMBER(drop_priv_before_exec) FALSE, + + MEMBER(process_limit) (unsigned int)-1, + MEMBER(client_limit) 0, + MEMBER(vsz_limit) 256, + + MEMBER(unix_listeners) ARRAY_INIT, + MEMBER(fifo_listeners) ARRAY_INIT, + MEMBER(inet_listeners) ARRAY_INIT }; -#undef DEF -#undef DEFLIST -#define DEF(type, name) \ - { type, #name, offsetof(struct master_auth_settings, name), NULL } -#define DEFLIST(field, name, defines) \ - { SET_DEFLIST, name, offsetof(struct master_auth_settings, field), defines } - -static struct setting_define master_auth_setting_defines[] = { - DEF(SET_STR, name), - DEF(SET_STR, executable), - DEF(SET_STR, user), - DEF(SET_STR, chroot), - DEF(SET_UINT, count), - DEF(SET_UINT, process_size), - DEF(SET_STR, mechanisms), - DEF(SET_BOOL, debug), - DEFLIST(sockets, "socket", &master_auth_socket_setting_parser_info), - - SETTING_DEFINE_LIST_END -}; - -static struct master_auth_settings master_auth_default_settings = { - MEMBER(name) "default", - MEMBER(executable) PKG_LIBEXECDIR"/dovecot-auth", - MEMBER(user) "root", - MEMBER(chroot) "", - MEMBER(count) 1, - MEMBER(process_size) 256, - MEMBER(mechanisms) "plain", - MEMBER(debug) FALSE - - /* .. */ -}; - -struct setting_parser_info master_auth_setting_parser_info = { - MEMBER(defines) master_auth_setting_defines, - MEMBER(defaults) &master_auth_default_settings, +struct setting_parser_info service_setting_parser_info = { + MEMBER(defines) service_setting_defines, + MEMBER(defaults) &service_default_settings, MEMBER(parent) &master_setting_parser_info, MEMBER(dynamic_parsers) NULL, - MEMBER(parent_offset) (size_t)-1, - MEMBER(type_offset) offsetof(struct master_auth_settings, name), - MEMBER(struct_size) sizeof(struct master_auth_settings) + MEMBER(parent_offset) offsetof(struct service_settings, master_set), + MEMBER(type_offset) (size_t)-1, + MEMBER(struct_size) sizeof(struct service_settings) }; #undef DEF @@ -139,135 +150,37 @@ { SET_DEFLIST, name, offsetof(struct master_settings, field), defines } static struct setting_define master_setting_defines[] = { - /* common */ DEF(SET_STR, base_dir), - DEF(SET_STR, log_path), - DEF(SET_STR, info_log_path), - DEF(SET_STR, log_timestamp), - DEF(SET_STR, syslog_facility), - - /* general */ - DEF(SET_STR, protocols), - DEF(SET_STR, listen), - DEF(SET_STR, ssl_listen), - - DEF(SET_STR, ssl), - DEF(SET_STR, ssl_key_file), - DEF(SET_UINT, ssl_parameters_regenerate), - DEF(SET_BOOL, version_ignore), + DEF(SET_STR, libexec_dir), + DEF(SET_UINT, default_process_limit), + DEF(SET_UINT, default_client_limit), - /* login */ - DEF(SET_STR, login_dir), - DEF(SET_STR, login_executable), - DEF(SET_STR, login_user), - - DEF(SET_BOOL, login_process_per_connection), - DEF(SET_BOOL, login_chroot), - DEF(SET_BOOL, disable_plaintext_auth), - - DEF(SET_UINT, login_process_size), - DEF(SET_UINT, login_processes_count), - DEF(SET_UINT, login_max_processes_count), - - /* mail */ - DEF(SET_STR, valid_chroot_dirs), - DEF(SET_STR, mail_chroot), - DEF(SET_UINT, max_mail_processes), - DEF(SET_UINT, mail_max_userip_connections), - DEF(SET_BOOL, verbose_proctitle), + DEF(SET_BOOL, version_ignore), DEF(SET_UINT, first_valid_uid), DEF(SET_UINT, last_valid_uid), DEF(SET_UINT, first_valid_gid), DEF(SET_UINT, last_valid_gid), - DEF(SET_STR, mail_access_groups), - DEF(SET_STR, mail_privileged_group), - DEF(SET_STR, mail_uid), - DEF(SET_STR, mail_gid), - DEF(SET_STR, mail_plugins), - DEF(SET_STR, imap_capability), - - DEF(SET_STR_VARS, mail_location), - DEF(SET_BOOL, mail_debug), - DEF(SET_BOOL, mail_drop_priv_before_exec), - - DEF(SET_STR, mail_executable), - DEF(SET_UINT, mail_process_size), - DEF(SET_STR, mail_log_prefix), - DEF(SET_UINT, mail_log_max_lines_per_sec), - - /* dict */ - DEF(SET_UINT, dict_process_count), - DEFLIST(auths, "auth", &master_auth_setting_parser_info), + DEFLIST(services, "service", &service_setting_parser_info), SETTING_DEFINE_LIST_END }; -struct master_settings master_default_settings = { - /* common */ +static struct master_settings master_default_settings = { MEMBER(base_dir) PKG_RUNDIR, - MEMBER(log_path) "", - MEMBER(info_log_path) "", - MEMBER(log_timestamp) DEFAULT_FAILURE_STAMP_FORMAT, - MEMBER(syslog_facility) "mail", - - /* general */ - MEMBER(protocols) "imap imaps", - MEMBER(listen) "*", - MEMBER(ssl_listen) "", - - MEMBER(ssl) "yes", - MEMBER(ssl_key_file) SSLDIR"/private/dovecot.pem", - MEMBER(ssl_parameters_regenerate) 168, - MEMBER(version_ignore) FALSE, + MEMBER(libexec_dir) PKG_LIBEXECDIR, + MEMBER(default_process_limit) 100, + MEMBER(default_client_limit) 1000, - /* login */ - MEMBER(login_dir) "login", - MEMBER(login_executable) NULL, - MEMBER(login_user) "dovecot", - - MEMBER(login_process_per_connection) TRUE, - MEMBER(login_chroot) TRUE, - MEMBER(disable_plaintext_auth) TRUE, - - MEMBER(login_process_size) 64, - MEMBER(login_processes_count) 3, - MEMBER(login_max_processes_count) 128, - - /* mail */ - MEMBER(valid_chroot_dirs) "", - MEMBER(mail_chroot) "", - MEMBER(max_mail_processes) 512, - MEMBER(mail_max_userip_connections) 10, - MEMBER(verbose_proctitle) FALSE, + MEMBER(version_ignore) FALSE, MEMBER(first_valid_uid) 500, MEMBER(last_valid_uid) 0, MEMBER(first_valid_gid) 1, MEMBER(last_valid_gid) 0, - MEMBER(mail_access_groups) "", - MEMBER(mail_privileged_group) "", - MEMBER(mail_uid) "", - MEMBER(mail_gid) "", - MEMBER(mail_plugins) "", - MEMBER(imap_capability) "", - MEMBER(mail_location) "", - MEMBER(mail_debug) FALSE, - MEMBER(maildir_very_dirty_syncs) FALSE, - MEMBER(dbox_purge_min_percentage) 0, - MEMBER(mail_drop_priv_before_exec) FALSE, - - MEMBER(mail_executable) NULL, - MEMBER(mail_process_size) 256, - MEMBER(mail_log_prefix) "%Us(%u): ", - MEMBER(mail_log_max_lines_per_sec) 10, - - /* dict */ - MEMBER(dict_process_count) 1 - - /* .. */ + MEMBER(services) ARRAY_INIT }; struct setting_parser_info master_setting_parser_info = { @@ -275,513 +188,73 @@ MEMBER(defaults) &master_default_settings, MEMBER(parent) NULL, - MEMBER(dynamic_parsers) NULL, - MEMBER(parent_offset) (size_t)-1, - MEMBER(type_offset) (size_t)-1, - MEMBER(struct_size) sizeof(struct master_settings) + MEMBER(struct_size) sizeof(struct master_settings), + MEMBER(check_func) master_settings_verify }; -static pool_t settings_pool, settings2_pool; -struct master_server_settings *master_set = NULL; - -#ifdef HAVE_MODULES -static const char * -get_process_capability(enum process_type ptype, struct master_settings *set) +/* <settings checks> */ +static void fix_file_listener_paths(ARRAY_TYPE(file_listener_settings) *l, + pool_t pool, const char *base_dir) { - /* FIXME: pretty ugly code just for getting the capability - automatically */ - static const char *args[] = { - "uid=65534", - "gid=65534", - "home=/tmp", - NULL - }; - const char *pname = process_names[ptype]; - enum master_login_status login_status; - struct mail_login_request request; - char buf[4096]; - int fd[2], status; - ssize_t ret; - unsigned int pos; - uid_t uid; - pid_t pid; - - uid = geteuid(); - if (uid != 0) { - /* use the current user */ - args[0] = t_strdup_printf("uid=%s", dec2str(uid)); - args[1] = t_strdup_printf("gid=%s", dec2str(getegid())); - } - - if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { - i_error("socketpair() failed: %m"); - return NULL; - } - fd_close_on_exec(fd[0], TRUE); - fd_close_on_exec(fd[1], TRUE); - - memset(&request, 0, sizeof(request)); - request.fd = fd[1]; - login_status = create_mail_process(ptype, set, &request, - "dump-capability", args, NULL, TRUE, - &pid); - if (login_status != MASTER_LOGIN_STATUS_OK) { - (void)close(fd[0]); - (void)close(fd[1]); - return NULL; - } - (void)close(fd[1]); - - alarm(5); - if (wait(&status) == -1) { - i_fatal("%s dump-capability process %d got stuck", - pname, (int)pid); - } - alarm(0); + struct file_listener_settings *const *sets; + unsigned int i, count; - if (status != 0) { - (void)close(fd[0]); - if (WIFSIGNALED(status)) { - i_error("%s dump-capability process " - "killed with signal %d", - pname, WTERMSIG(status)); - } else { - i_error("%s dump-capability process returned %d", - pname, WIFEXITED(status) ? WEXITSTATUS(status) : - status); - } - return NULL; - } - - pos = 0; - while ((ret = read(fd[0], buf + pos, sizeof(buf) - pos)) > 0) - pos += ret; - - if (ret < 0) { - i_error("read(%s dump-capability process) failed: %m", pname); - (void)close(fd[0]); - return NULL; - } - (void)close(fd[0]); - - if (pos == 0 || buf[pos-1] != '\n') { - i_error("%s dump-capability: Couldn't read capability " - "(got %u bytes)", pname, pos); - return NULL; - } - buf[pos-1] = '\0'; + if (!array_is_created(l)) + return; - return i_strdup(buf); -} - -static bool get_imap_capability(struct master_settings *set) -{ - static const char *generated_capability = NULL; - - if (generated_capability != NULL) { - /* Reloading configuration. Don't try to execute the imap - process again. Too risky and the wait() call below will - break it anyway. Just use the previous capability list we - already had generated. */ - set->imap_generated_capability = - p_strdup(settings_pool, generated_capability); - return TRUE; - } - - generated_capability = get_process_capability(PROCESS_TYPE_IMAP, set); - if (generated_capability == NULL) - return FALSE; - - set->imap_generated_capability = - p_strdup(settings_pool, generated_capability); - return TRUE; -} -#endif - -static void fix_base_path(struct master_settings *set, const char **str) -{ - if (*str != NULL && **str != '\0' && **str != '/') { - *str = p_strconcat(settings_pool, - set->base_dir, "/", *str, NULL); + sets = array_get(l, &count); + for (i = 0; i < count; i++) { + if (*sets[i]->path != '/') { + sets[i]->path = p_strconcat(pool, base_dir, "/", + sets[i]->path, NULL); + } } } -static bool parse_uid(const char *str, uid_t *uid_r) -{ - struct passwd *pw; - char *p; - - if (*str >= '0' && *str <= '9') { - *uid_r = (uid_t)strtoul(str, &p, 10); - if (*p == '\0') - return TRUE; - } - - pw = getpwnam(str); - if (pw == NULL) - return FALSE; - - *uid_r = pw->pw_uid; - return TRUE; -} - -static bool parse_gid(const char *str, gid_t *gid_r) -{ - struct group *gr; - char *p; - - if (*str >= '0' && *str <= '9') { - *gid_r = (gid_t)strtoul(str, &p, 10); - if (*p == '\0') - return TRUE; - } - - gr = getgrnam(str); - if (gr == NULL) - return FALSE; - - *gid_r = gr->gr_gid; - return TRUE; -} - -static bool get_login_uid(struct master_settings *set) -{ - struct passwd *pw; - - if ((pw = getpwnam(set->login_user)) == NULL) { - i_error("Login user doesn't exist: %s", set->login_user); - return FALSE; - } - - if (set->server->login_gid == 0) - set->server->login_gid = pw->pw_gid; - else if (set->server->login_gid != pw->pw_gid) { - i_error("All login process users must belong to same group " - "(%s vs %s)", dec2str(set->server->login_gid), - dec2str(pw->pw_gid)); - return FALSE; - } - set->login_uid = pw->pw_uid; - return TRUE; -} - -static bool auth_settings_verify(struct master_settings *set, - struct master_auth_settings *auth) -{ - struct passwd *pw; - struct master_auth_socket_settings *const *sockets; - unsigned int i, count; - - if ((pw = getpwnam(auth->user)) == NULL) { - i_error("Auth user doesn't exist: %s", auth->user); - return FALSE; - } - - if (set->login_uid == pw->pw_uid && master_uid != pw->pw_uid) { - i_error("login_user %s (uid %s) must not be same as auth_user", - auth->user, dec2str(pw->pw_uid)); - return FALSE; - } - auth->uid = pw->pw_uid; - auth->gid = pw->pw_gid; - - if (access(t_strcut(auth->executable, ' '), X_OK) < 0) { - i_error("auth_executable: Can't use %s: %m", - t_strcut(auth->executable, ' ')); - return FALSE; - } - - fix_base_path(set, &auth->chroot); - if (*auth->chroot != '\0' && access(auth->chroot, X_OK) < 0) { - i_error("Can't access auth chroot directory %s: %m", - auth->chroot);