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
+ - 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?
- 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
- 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 /
- 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
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
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])
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"
src/auth/Makefile
src/config/Makefile
src/lda/Makefile
+src/log/Makefile
src/lmtp/Makefile
src/dict/Makefile
src/imap/Makefile
pop3 \
lda \
lmtp \
+ log \
config \
tests \
util \
-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)"\" \
$(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
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 \
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 \
/* 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"
#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 void auth_client_connection_unref(struct auth_client_connection **_conn);
+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)
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. */
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);
}
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;
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;
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);
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;
}
}
}
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];
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(
}
}
-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);
}
struct auth_client_connection {
struct auth *auth;
- struct auth_master_listener *listener;
int refcount;
int fd;
};
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
#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>
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)
{
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;
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);
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');
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;
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)
}
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;
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)
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);
}
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);
+}
struct auth_stream_reply;
struct auth_master_connection {
- struct auth_master_listener *listener;
+ struct auth *auth;
int refcount;
int fd;
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
#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
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-#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
#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 }
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),
MEMBER(worker_max_count) 30,
- MEMBER(sockets) ARRAY_INIT,
MEMBER(passdbs) ARRAY_INIT,
MEMBER(userdbs) ARRAY_INIT
};
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
{ 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
};
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)
-{
-#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 auth_settings *
+auth_settings_read(struct master_service *service, const char *name)
{
- struct setting_parser_context *parser;
- struct auth_root_settings *set;
- struct auth_settings *const *auths;
+ 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;
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 (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);
+ if (master_service_settings_read(service, set_roots, NULL, FALSE,
+ &error) < 0)
+ i_fatal("Error reading configuration: %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);
}
}
i_fatal("Error reading configuration: No auth section: %s", name);
- return NULL;
}
#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;
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
#include "istream.h"
#include "ostream.h"
#include "str.h"
+#include "master-service.h"
#include "auth-request.h"
#include "auth-worker-client.h"
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)
#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
#include <stddef.h>
#include <stdlib.h>
+#include <unistd.h>
#define HAVE_LDAP_SASL
#ifdef HAVE_SASL_SASL_H
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);
#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"
#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 ARRAY_DEFINE(listen_fd_types, enum auth_socket_type);
-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;
-
- 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)
-{
- 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)
+static void main_preinit(struct auth_settings *set)
{
- 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();
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);
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)
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();
sql_drivers_deinit();
random_deinit();
- child_wait_deinit();
- lib_signals_deinit();
- closelog();
+ array_free(&listen_fd_types);
}
-int main(int argc ATTR_UNUSED, char *argv[])
+static void worker_connected(const struct master_service_connection *conn)
{
- bool foreground = FALSE;
+ auth_worker_client_create(auth, conn->fd);
+}
-#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();
+static void client_connected(const struct master_service_connection *conn)
+{
+ enum auth_socket_type *type;
- while (argv[1] != NULL) {
- if (strcmp(argv[1], "-F") == 0)
- foreground = TRUE;
- else if (strcmp(argv[1], "-w") == 0)
- worker = TRUE;
- argv++;
+ if (worker) {
+ worker_client = auth_worker_client_create(auth, conn->fd);
+ return;
}
- T_BEGIN {
- drop_privileges();
- main_init(foreground);
- } T_END;
- io_loop_run(ioloop);
- main_deinit();
+ 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;
+
+ 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);
+ }
- io_loop_destroy(&ioloop);
- lib_deinit();
+ 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;
+ }
+ 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();
+ master_service_deinit(&service);
return 0;
}
+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)"\" \
#include "lib.h"
-extern struct master_service *service;
extern ARRAY_TYPE(const_string) config_strings;
#endif
#include "common.h"
#include "array.h"
#include "str.h"
+#include "llist.h"
#include "ioloop.h"
#include "network.h"
#include "istream.h"
#define CONFIG_CLIENT_PROTOCOL_MINOR_VERSION 0
struct config_connection {
+ struct config_connection *prev, *next;
+
int fd;
struct istream *input;
struct ostream *output;
unsigned int handshaked:1;
};
+struct config_connection *config_connections = NULL;
+
static const char *const *
config_connection_next_line(struct config_connection *conn)
{
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)
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);
strings[i+1], NULL));
} T_END;
}
+
+void config_connections_destroy_all(void)
+{
+ while (config_connections != NULL)
+ config_connection_destroy(config_connections);
+}
enum config_dump_flags flags);
void config_connection_putenv(void);
+void config_connections_destroy_all(void);
+
#endif
/* 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"
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;
- lib_init();
+ service = master_service_init("config", 0, argc, argv);
- 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;
- } else {
- i_fatal("Unknown parameter: %s", argv[i]);
+ case 'p':
+ service_name = optarg;
+ break;
+ default:
+ if (!master_service_parse_option(service, c, optarg))
+ exit(FATAL_DEFAULT);
}
}
+ if (argv[optind] != NULL)
+ exec_args = &argv[optind];
+
+ master_service_init_log(service, "doveconf: ", 0);
+ master_service_init_finish(service);
+ main_init(service_name);
- main_init(service);
- ioloop = io_loop_create();
- if (exec_args == NULL)
+ 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();
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;
}
$code .= $_;
}
- if (/#define.*DEF/ || /^#undef.*DEF/) {
+ if (/#define.*DEF/ || /^#undef.*DEF/ || /ARRAY_DEFINE_TYPE.*_settings/) {
$write = 1;
$state = 2 if (/\\$/);
}
-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 = \
#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"
# 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)
{
int fd_ssl;
client_ref(client);
- connection_queue_add(1);
if (!client_unref(client) || client->destroyed)
return;
return;
}
+ client->common.proxying = TRUE;
client->common.tls = TRUE;
client->common.secured = TRUE;
client_set_title(client);
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);
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);
}
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);
client_link(&client->common);
- main_ref();
-
if (auth_client_is_connected(auth_client))
client_send_greeting(client);
else
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 {
client->common.proxy = NULL;
}
client_unref(client);
-
- main_listen_start();
- main_unref();
}
void client_destroy_success(struct imap_client *client, const char *reason)
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;
}
#define CLIENT_H
#include "network.h"
-#include "master.h"
#include "client-common.h"
/* Disconnect client after idling this many milliseconds */
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) {
}
i_assert(client->refcount > 1);
- connection_queue_add(1);
if (client->destroyed) {
/* connection_queue_add() decided that we were the oldest
#include <unistd.h>
#define IS_STANDALONE() \
- (getenv("IMAPLOGINTAG") == NULL)
+ (getenv("CLIENT_INPUT") == NULL)
struct client_workaround_list {
const char *name;
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 */
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);
}
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[] = {
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 "
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));
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();
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);
default:
if (!master_service_parse_option(service, c, optarg)) {
print_help();
- i_fatal_status(EX_USAGE,
- "Unknown argument: %c", c);
+ exit(EX_USAGE);
}
break;
}
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));
-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
+/* 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
+#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
+#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
#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;
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
#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"
#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 }
/* @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,
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);
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) {
#include <stdlib.h>
#include <unistd.h>
+#include <sys/stat.h>
#include <syslog.h>
#define DEFAULT_CONFIG_FILE_PATH SYSCONFDIR"/dovecot.conf"
/* 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)
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);
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): ",
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;
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;
bool master_service_parse_option(struct master_service *service,
int opt, const char *arg)
{
+ int i;
+
switch (opt) {
case 'c':
service->config_path = arg;
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;
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;
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;
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)
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);
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);
+ }
+ }
+}
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);
/* 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);
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);
static const char *argv[] = {
NULL,
"-c", NULL,
- "-s", NULL,
+ "-p", NULL,
NULL
};
argv[0] = bin_path;
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. */
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;
}
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];
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,
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),
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,
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;
#include "lib.h"
#include "ioloop.h"
#include "str.h"
+#include "hostpid.h"
#include "network.h"
#include "backtrace-string.h"
#include "printf-format-fix.h"
#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 */
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);
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);
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)
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
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)
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;
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)
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);
}
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));
}
}
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 "
/* 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
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)
{
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) {
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
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);
#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"
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)
#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");
(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[])
}
#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,
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
+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
+#ifndef COMMON_H
+#define COMMON_H
+
+#include "lib.h"
+
+extern struct master_service *service;
+extern pid_t master_pid;
+
+#endif
--- /dev/null
+/* 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
+#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
+/* 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;
+}
-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)"\" \
login-proxy.c \
login-settings.c \
main.c \
- master.c \
sasl-server.c \
ssl-proxy.c \
ssl-proxy-gnutls.c \
login-proxy.h \
login-settings.h \
common.h \
- master.h \
sasl-server.h \
ssl-proxy.h
#define CLIENT_COMMON_H
#include "network.h"
-#include "master.h"
#include "sasl-server.h"
/* max. size of input buffer. this means:
struct auth_request *auth_request;
unsigned int master_tag;
- master_callback_t *master_callback;
sasl_server_callback_t *sasl_callback;
unsigned int auth_attempts;
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;
/* ... */
#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
#include "ostream.h"
#include "llist.h"
#include "str-sanitize.h"
+#include "master-service.h"
#include "client-common.h"
#include "login-proxy.h"
};
static struct login_proxy *login_proxies = NULL;
-static unsigned int login_proxy_count = 0;
static void server_input(struct login_proxy *proxy)
{
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",
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,
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)
{
proxy->callback = NULL;
proxy->context = NULL;
- login_proxy_count++;
DLLIST_PREPEND(&login_proxies, proxy);
- main_ref();
}
void login_proxy_deinit(void)
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
#include "lib.h"
#include "settings-parser.h"
+#include "master-service-settings.h"
#include "login-settings.h"
#include <stddef.h>
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)
{
}
/* </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;
+ void **sets;
- if (settings_pool == NULL)
- settings_pool = pool_alloconly_create("settings pool", 512);
- else
- p_clear(settings_pool);
-
- parser = settings_parser_init(settings_pool,
- &login_setting_parser_info,
- SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS);
-
- 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);
+ if (master_service_settings_read(service, set_roots, NULL, FALSE,
+ &error) < 0)
+ i_fatal("Error reading configuration: %s", error);
- set = settings_parser_get(parser);
- settings_parser_deinit(&parser);
- return set;
+ sets = master_service_settings_get_others(service);
+ return sets[0];
}
#ifndef LOGIN_SETTINGS_H
#define LOGIN_SETTINGS_H
+struct master_service;
+
struct login_settings {
const char *login_dir;
bool login_chroot;
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
#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"
#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++;
-}
-
-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 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 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;
- }
+struct master_service *service;
+struct login_settings *login_settings;
- client = client_create(fd, FALSE, &local_ip, &remote_ip);
- client->remote_port = remote_port;
- client->local_port = local_port;
+static bool ssl_connections = FALSE;
- if (login_settings->login_process_per_connection) {
- closing_down = TRUE;
- main_listen_stop();
- }
-}
-
-static void login_accept_ssl(void *context)
+static void client_connected(const struct master_service_connection *conn)
{
- 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;
-
- 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);
+ struct ip_addr local_ip;
+ unsigned int local_port;
+ int fd_ssl;
- 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);
-}
-
-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);
- }
+ // 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;
}
- }
-
- 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 = client_create(fd_ssl, TRUE,
+ &local_ip, &conn->remote_ip);
+ client->proxying = TRUE;
+ client->proxy = proxy;
}
+ client->remote_port = conn->remote_port;
+ client->local_port = local_port;
}
static void auth_connect_notify(struct auth_client *client ATTR_UNUSED,
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();
-
- 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);
+ unsigned int max_fds;
+ 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. */
- restrict_access_by_env(NULL, TRUE);
+ i_assert(strcmp(login_settings->ssl, "no") == 0 || ssl_initialized);
- /* make sure we can't fork() */
- restrict_process_size((unsigned int)-1, 1);
+ restrict_access_by_env(NULL, TRUE);
}
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);
-
- 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");
+ /* make sure we can't fork() */
+ restrict_process_size((unsigned int)-1, 1);
- closing_down = FALSE;
- main_refcount = 0;
+ if (restrict_access_get_current_chroot() == NULL) {
+ if (chdir("login") < 0)
+ i_fatal("chdir(login) failed: %m");
+ }
- auth_client = auth_client_new(login_process_uid);
+ auth_client = auth_client_new((unsigned int)getpid());
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 (!is_inetd) {
- master_init(LOGIN_MASTER_SOCKET_FD);
- main_listen_start();
- }
+ 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;
-
- is_inetd = getenv("DOVECOT_MASTER") == NULL;
-
-#ifdef DEBUG
- if (!is_inetd && getenv("GDB") == NULL) {
- const char *env;
-
- 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);
+ const char *getopt_str;
+ int c;
+
+ //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);
+
+ 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;
+ }
}
-#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);
- main_init();
+ login_settings = login_settings_read(service);
- 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;
- }
- }
+ main_preinit();
+ master_service_init_finish(service);
+ main_init();
- io_loop_run(ioloop);
+ master_service_run(service, client_connected);
main_deinit();
-
- io_loop_destroy(&ioloop);
- lib_deinit();
-
+ master_service_deinit(&service);
return 0;
}
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-#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
#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)
}
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;
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)
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:
#ifndef SASL_SERVER_H
#define SASL_SERVER_H
+struct client;
+
enum sasl_server_reply {
SASL_SERVER_REPLY_SUCCESS,
SASL_SERVER_REPLY_AUTH_FAILED,
#include "read-full.h"
#include "safe-memset.h"
#include "llist.h"
+#include "master-service.h"
#include "ssl-proxy.h"
#include <fcntl.h>
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;
DLLIST_PREPEND(&ssl_proxies, proxy);
ssl_step(proxy);
- main_ref();
*proxy_r = proxy;
return sfd[1];
SSL_free(proxy->ssl);
i_free(proxy);
-
- main_unref();
}
static void ssl_proxy_destroy(struct ssl_proxy *proxy)
ssl_proxy_unref(proxy);
- main_listen_start();
+ master_service_client_connection_destroyed(service);
}
static RSA *ssl_gen_rsa_key(SSL *ssl ATTR_UNUSED,
/* 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");
/* 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);
pkglibexecdir = $(libexecdir)/dovecot
sbin_PROGRAMS = dovecot
-pkglibexec_PROGRAMS = ssl-build-param
+#pkglibexec_PROGRAMS = ssl-build-param
AM_CPPFLAGS = \
-I$(top_srcdir)/src/lib \
-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 \
- master-settings.c \
main.c \
- ssl-init.c \
- sysinfo-get.c
+ master-settings.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 \
- dup2-array.h \
- listener.h \
common.h \
- log.h \
- login-process.h \
- mail-process.h \
- master-login-interface.h \
+ dup2-array.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
+++ /dev/null
-/* 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();
-}
+++ /dev/null
-#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
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-#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
#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
+++ /dev/null
-/* 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;
- }
- }
-}
+++ /dev/null
-#ifndef DICT_PROCESS_H
-#define DICT_PROCESS_H
-
-void dict_processes_init(void);
-void dict_processes_deinit(void);
-void dict_processes_kill(void);
-
-#endif
+++ /dev/null
-/* 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);
- }
-}
+++ /dev/null
-#ifndef LISTENER_H
-#define LISTENER_H
-
-void listeners_open_fds(struct master_server_settings *old_set, bool retry);
-void listeners_close_fds(void);
-
-#endif
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-#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
+++ /dev/null
-/* 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);
-}
+++ /dev/null
-#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
+++ /dev/null
-/* 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.. */
-}
+++ /dev/null
-#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
-/* 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 void ATTR_NORETURN ATTR_FORMAT(3, 0)
-master_fatal_callback(enum log_type type, int status,
- const char *format, va_list args)
-{
- const struct master_settings *set = master_set->defaults;
- const char *path, *str;
- va_list args2;
- int fd;
+static char *pidfile_path;
+static struct service_list *services;
- /* 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);
- }
- }
+static const char *child_process_env[3]; /* @UNSAFE */
- /* 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);
-}
-
-static void fatal_log_check(void)
+void process_exec(const char *cmd, const char *extra_args[])
{
- 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);
+ const char *executable, *p, **argv;
+
+ argv = t_strsplit(cmd, " ");
+ executable = argv[0];
+
+ if (extra_args != NULL) {
+ unsigned int count1, count2;
+ const char **new_argv;
+
+ /* @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);
-}
+ /* hide the path, it's ugly */
+ p = strrchr(argv[0], '/');
+ if (p != NULL) argv[0] = p+1;
-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");
+ /* prefix with dovecot/ */
+ argv[0] = t_strconcat(PACKAGE"/", argv[0], NULL);
- }
+ execv(executable, (char **)argv);
+ i_fatal_status(FATAL_EXEC, "execvp(%s) failed: %m", executable);
}
-static void set_logfile(struct master_settings *set)
+static void create_pid_file(const char *path)
{
- 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);
+ const char *pid;
+ int fd;
- if (*set->info_log_path != '\0')
- i_set_info_file(set->info_log_path);
+ pid = t_strconcat(dec2str(getpid()), "\n", NULL);
- i_set_failure_timestamp_format(set->log_timestamp);
+ fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (fd == -1)
+ i_fatal("open(%s) failed: %m", path);
+ if (write_full(fd, pid, strlen(pid)) < 0)
+ i_fatal("write() failed in %s: %m", path);
+ (void)close(fd);
}
-static void settings_reload(void)
+static void
+sig_settings_reload(const siginfo_t *si ATTR_UNUSED,
+ void *context ATTR_UNUSED)
{
- /* 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();
+ struct master_settings *new_set;
+ struct service_list *new_services;
+ const char *error;
+ pool_t pool;
/* 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);
- }
-}
-
-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));
+#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;
}
- 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;
+ /* switch to new configuration. */
+ (void)services_listen_using(new_services, services);
+ services_destroy(services);
+ services = new_services;
- return FALSE;
-}
-
-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;
+ services_monitor_start(services);
}
-static void open_null_fd(void)
+static void
+sig_log_reopen(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
{
- 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");
+ service_signal(services->log, SIGUSR1);
}
-static void create_pid_file(const char *path)
+static void
+sig_reap_children(const siginfo_t *si ATTR_UNUSED, void *context ATTR_UNUSED)
{
- const char *pid;
- int fd;
-
- pid = t_strconcat(dec2str(getpid()), "\n", NULL);
-
- fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644);
- if (fd == -1)
- i_fatal("open(%s) failed: %m", path);
- if (write_full(fd, pid, strlen(pid)) < 0)
- i_fatal("write() failed in %s: %m", path);
- (void)close(fd);
+ services_monitor_reap_children(services);
}
-static void pid_file_check_running(const struct master_settings *set)
+static void sig_die(const siginfo_t *si, void *context ATTR_UNUSED)
{
- const char *path;
- char buf[32];
- int fd;
- ssize_t ret;
-
- 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);
- }
-
- 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;
-
- 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);
+ 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)
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");
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);
-
- child_processes_init();
- log_init();
- ssl_init();
- dict_processes_init();
- auth_processes_init();
- login_processes_init();
- mail_processes_init();
-
- create_pid_file(t_strconcat(master_set->defaults->base_dir,
- "/master.pid", 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);
+
+ pidfile_path = i_strconcat(set->base_dir, "/master.pid", NULL);
+ create_pid_file(pidfile_path);
+
+ services_monitor_start(services);
}
static void main_deinit(void)
{
- (void)unlink(t_strconcat(master_set->defaults->base_dir,
- "/master.pid", NULL));
+ if (unlink(pidfile_path) < 0)
+ i_error("unlink(%s) failed: %m", pidfile_path);
+ i_free(pidfile_path);
- login_processes_destroy_all();
-
- mail_processes_deinit();
- login_processes_deinit();
- auth_processes_deinit();
- dict_processes_deinit();
- ssl_deinit();
+ services_destroy(services);
+}
- listeners_close_fds();
+static const char *get_full_config_path(struct service_list *list)
+{
+ const char *path;
+ char cwd[PATH_MAX];
- if (close(null_fd) < 0)
- i_error("close(null_fd) failed: %m");
+ path = master_service_get_config_path(master_service);
+ if (*path == '/')
+ return path;
- log_deinit();
- /* log_deinit() may still want to look up child processes */
- child_processes_deinit();
- lib_signals_deinit();
- closelog();
+ if (getcwd(cwd, sizeof(cwd)) == NULL)
+ i_fatal("getcwd() failed: %m");
+ return p_strconcat(list->pool, cwd, "/", path, NULL);
}
-static void daemonize(struct master_settings *set)
+static void daemonize(void)
{
pid_t pid;
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
#ifdef HAVE_OPENSSL
" openssl"
#endif
- "\nMail storages: "MAIL_STORAGES"\n"
- "SQL drivers:"
+ "\nSQL drivers:"
#ifdef BUILD_MYSQL
" mysql"
#endif
#ifdef PASSDB_SHADOW
" shadow"
#endif
-#ifdef PASSDB_SIA
- " sia"
-#endif
#ifdef PASSDB_SQL
" sql"
#endif
" vpopmail"
#endif
"\nUserdb:"
-#ifdef USERDB_NSS
- " nss"
+#ifdef USERDB_CHECKPASSWORD
+ " checkpassword"
#endif
#ifdef USERDB_LDAP
" ldap"
#ifdef USERDB_PASSWD
" passwd"
#endif
-#ifdef USERDB_PASSWD_FILE
- " passwd-file"
-#endif
#ifdef USERDB_PREFETCH
" prefetch"
#endif
+#ifdef USERDB_PASSWD_FILE
+ " passwd-file"
+#endif
#ifdef USERDB_SQL
" sql"
#endif
#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();
-
- 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);
- }
-
- /* 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);
+ 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 (dump_config) {
- const char *info;
+ if (dup2(null_fd, STDIN_FILENO) < 0 ||
+ dup2(null_fd, STDOUT_FILENO) < 0)
+ i_fatal("dup2(null_fd) failed: %m");
- info = sysinfo_get(master_set->defaults->mail_location);
- if (*info != '\0')
- printf("# %s\n", info);
+ 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_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);
- }
+ master_service_init_log(master_service, "dovecot: ", 0);
+ if (!log_error)
+ master_settings_do_fixes(set);
- 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 (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);
+ if (env_tz != NULL) {
+ const char *env = t_strconcat("TZ=", env_tz, NULL);
+
+ 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();
-
- main_init(log_error);
- io_loop_run(ioloop);
- main_deinit();
+ /* if any listening fails, fail completely */
+ if (services_listen(services) <= 0)
+ return FATAL_DEFAULT;
- master_settings_deinit();
- io_loop_destroy(&ioloop);
- lib_deinit();
+ if (!foreground)
+ daemonize();
+ if (chdir(set->base_dir) < 0)
+ i_fatal("chdir(%s) failed: %m", set->base_dir);
+ main_init(set, log_error);
+ master_service_run(master_service, NULL);
+ main_deinit();
+ master_service_deinit(&master_service);
return 0;
}
+++ /dev/null
-#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
-/* 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 "istream.h"
#include "env-util.h"
-#include "fd-close-on-exec.h"
-#include "safe-mkdir.h"
+#include "istream.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) "",
};
-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,
-
- MEMBER(parent) &master_auth_socket_setting_parser_info,
- MEMBER(dynamic_parsers) NULL,
+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(type_offset) (size_t)-1,
- MEMBER(struct_size) sizeof(struct master_auth_socket_unix_settings)
+ MEMBER(struct_size) sizeof(struct file_listener_settings)
};
#undef DEF
-#undef DEFLIST
#define DEF(type, name) \
- { type, #name, offsetof(struct master_auth_socket_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[] = {
- DEF(SET_STR, type),
- DEFLIST(masters, "master", &master_auth_socket_master_setting_parser_info),
+ { 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
};
-struct setting_parser_info master_auth_socket_setting_parser_info = {
- MEMBER(defines) master_auth_socket_setting_defines,
- MEMBER(defaults) NULL,
+static struct inet_listener_settings inet_listener_default_settings = {
+ MEMBER(address) "*",
+ MEMBER(port) 0
+};
- MEMBER(parent) &master_auth_setting_parser_info,
- MEMBER(dynamic_parsers) NULL,
+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) offsetof(struct master_auth_socket_settings, type),
- MEMBER(struct_size) sizeof(struct master_auth_socket_settings)
+ MEMBER(struct_size) sizeof(struct inet_listener_settings)
};
#undef DEF
#undef DEFLIST
#define DEF(type, name) \
- { type, #name, offsetof(struct master_auth_settings, name), NULL }
+ { type, #name, offsetof(struct service_settings, name), NULL }
#define DEFLIST(field, name, defines) \
- { SET_DEFLIST, name, offsetof(struct master_auth_settings, field), defines }
+ { SET_DEFLIST, name, offsetof(struct service_settings, field), defines }
-static struct setting_define master_auth_setting_defines[] = {
- DEF(SET_STR, name),
+static struct setting_define service_setting_defines[] = {
+ DEF(SET_INTERNAL, master_set),
+ DEF(SET_STR, type),
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_UINT, count),
- DEF(SET_UINT, process_size),
- DEF(SET_STR, mechanisms),
- DEF(SET_BOOL, debug),
- DEFLIST(sockets, "socket", &master_auth_socket_setting_parser_info),
+ 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
};
-static struct master_auth_settings master_auth_default_settings = {
- MEMBER(name) "default",
- MEMBER(executable) PKG_LIBEXECDIR"/dovecot-auth",
- MEMBER(user) "root",
+static struct service_settings service_default_settings = {
+ MEMBER(master_set) NULL,
+
+ MEMBER(type) "",
+ MEMBER(executable) "",
+ MEMBER(user) "",
+ MEMBER(group) "",
+ MEMBER(privileged_group) "",
+ MEMBER(extra_groups) "",
MEMBER(chroot) "",
- MEMBER(count) 1,
- MEMBER(process_size) 256,
- MEMBER(mechanisms) "plain",
- MEMBER(debug) FALSE
+ MEMBER(auth_dest_service) "",
+
+ 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
};
-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
{ 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),
-
- /* 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),
+ DEF(SET_STR, libexec_dir),
+ DEF(SET_UINT, default_process_limit),
+ DEF(SET_UINT, default_client_limit),
- /* 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 = {
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);
-
- 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';
-
- 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);
- }
-}
-
-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;
+ struct file_listener_settings *const *sets;
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);
- return FALSE;
- }
+ if (!array_is_created(l))
+ return;
- if (array_is_created(&auth->sockets))
- sockets = array_get(&auth->sockets, &count);
- else {
- sockets = NULL;
- count = 0;
- }
+ sets = array_get(l, &count);
for (i = 0; i < count; i++) {
- if (auth->count > 1 &&
- strcmp(sockets[i]->type, "listen") == 0) {
- i_error("Currently auth process count must be 1 if "
- "you're using auth socket listeners.");
- return FALSE;
+ if (*sets[i]->path != '/') {
+ sets[i]->path = p_strconcat(pool, base_dir, "/",
+ sets[i]->path, NULL);
}
}
- return TRUE;
}
-static const char *get_directory(const char *path)
-{
- char *str, *p;
-
- str = t_strdup_noconst(path);
- p = strrchr(str, '/');
- if (p == NULL)
- return ".";
- else {
- *p = '\0';
- return str;
- }
-}
-
-static bool settings_is_active(struct master_settings *set)
-{
- if (*set->protocols == '\0') {
- /* we're probably using this with --exec-mail */
- return TRUE;
- }
-
- if (set->protocol == MAIL_PROTOCOL_IMAP) {
- if (strstr(set->protocols, "imap") == NULL)
- return FALSE;
- } else {
- if (strstr(set->protocols, "pop3") == NULL)
- return FALSE;
- }
-
- return TRUE;
-}
-
-static bool settings_have_connect_sockets(struct master_settings *set)
-{
- struct master_auth_settings *const *auths;
- struct master_auth_socket_settings *const *sockets;
- unsigned int i, count, count2;
-
- auths = array_get(&set->auths, &count);
- for (i = 0; i < count; i++) {
- if (!array_is_created(&auths[i]->sockets))
- continue;
- sockets = array_get(&auths[i]->sockets, &count2);
- if (count2 > 0 && strcmp(sockets[0]->type, "connect") == 0)
- return TRUE;
- }
-
- return FALSE;
-}
-
-static bool settings_have_nonplaintext_auths(struct master_settings *set)
+static bool
+master_settings_verify(void *_set, pool_t pool, const char **error_r)
{
- struct master_auth_settings *const *auths;
- const char *const *tmp;
+ const struct master_settings *set = _set;
+ struct service_settings *const *services;
unsigned int i, count;
- auths = array_get(&set->auths, &count);
- for (i = 0; i < count; i++) {
- tmp = t_strsplit_spaces(auths[i]->mechanisms, " ");
- for (; *tmp != NULL; tmp++) {
- if (strcasecmp(*tmp, "PLAIN") != 0 &&
- strcasecmp(*tmp, "LOGIN") != 0)
- return TRUE;
- }
- }
-
- return FALSE;
-}
-
-static void unlink_auth_sockets(const char *path, const char *prefix)
-{
- DIR *dirp;
- struct dirent *dp;
- struct stat st;
- string_t *str;
- unsigned int prefix_len;
-
- dirp = opendir(path);
- if (dirp == NULL) {
- i_error("opendir(%s) failed: %m", path);
- return;
- }
-
- prefix_len = strlen(prefix);
- str = t_str_new(256);
- while ((dp = readdir(dirp)) != NULL) {
- if (dp->d_name[0] == '.')
- continue;
-
- if (strncmp(dp->d_name, prefix, prefix_len) != 0)
- continue;
-
- str_truncate(str, 0);
- str_printfa(str, "%s/%s", path, dp->d_name);
- if (lstat(str_c(str), &st) < 0) {
- if (errno != ENOENT)
- i_error("lstat(%s) failed: %m", str_c(str));
- continue;
- }
- if (!S_ISSOCK(st.st_mode))
- continue;
-
- /* try to avoid unlinking sockets if someone's already
- listening in them. do this only at startup, because
- when SIGHUPing a child process might catch the new
- connection before it notices that it's supposed
- to die. null_fd == -1 check is a bit kludgy, but works.. */
- if (null_fd == -1) {
- int fd = net_connect_unix(str_c(str));
- if (fd != -1 || errno != ECONNREFUSED) {
- i_fatal("Dovecot is already running? "
- "Socket already exists: %s",
- str_c(str));
- }
- }
-
- if (unlink(str_c(str)) < 0 && errno != ENOENT)
- i_error("unlink(%s) failed: %m", str_c(str));
- }
- (void)closedir(dirp);
-}
-
-static bool settings_verify(struct master_settings *set)
-{
- const char *dir;
- int facility;
-
- if (!get_login_uid(set))
- return FALSE;
-
- set->mail_uid_t = (uid_t)-1;
- set->mail_gid_t = (gid_t)-1;
- set->mail_priv_gid_t = (gid_t)-1;
-
- if (*set->mail_uid != '\0') {
- if (!parse_uid(set->mail_uid, &set->mail_uid_t)) {
- i_error("Non-existing mail_uid: %s", set->mail_uid);
- return FALSE;
- }
- }
- if (*set->mail_gid != '\0') {
- if (!parse_gid(set->mail_gid, &set->mail_gid_t)) {
- i_error("Non-existing mail_gid: %s", set->mail_uid);
- return FALSE;
- }
- }
- if (*set->mail_privileged_group != '\0') {
- if (!parse_gid(set->mail_privileged_group,
- &set->mail_priv_gid_t)) {
- i_error("Non-existing mail_privileged_group: %s",
- set->mail_privileged_group);
- return FALSE;
- }
- }
-
- if (set->protocol != MAIL_PROTOCOL_ANY &&
- access(t_strcut(set->mail_executable, ' '), X_OK) < 0) {
- i_error("mail_executable: Can't use %s: %m",
- t_strcut(set->mail_executable, ' '));
- return FALSE;
- }
-
- if (*set->log_path != '\0' && access(set->log_path, W_OK) < 0) {
- dir = get_directory(set->log_path);
- if (access(dir, W_OK) < 0) {
- i_error("log_path: Can't write to directory %s: %m",
- dir);
- return FALSE;
- }
- }
-
- if (*set->info_log_path != '\0' &&
- access(set->info_log_path, W_OK) < 0) {
- dir = get_directory(set->info_log_path);
- if (access(dir, W_OK) < 0) {
- i_error("info_log_path: Can't write to directory %s: %m",
- dir);
- return FALSE;
- }
- }
-
- if (!syslog_facility_find(set->syslog_facility, &facility)) {
- i_error("syslog_facility: Unknown value: %s",
- set->syslog_facility);
- return FALSE;
- }
-
-#ifndef HAVE_SSL
- if (strcmp(set->ssl, "no") != 0) {
- i_error("SSL support not compiled in but ssl=%s", set->ssl);
- return FALSE;
- }
-#endif
- if (strcmp(set->ssl, "no") == 0 && set->disable_plaintext_auth &&
- strncmp(set->listen, "127.", 4) != 0 &&
- strcmp(set->protocols, "none") != 0 &&
- !settings_have_nonplaintext_auths(set)) {
- i_warning("There is no way to login to this server: "
- "disable_plaintext_auth=yes, ssl=no, "
- "no non-plaintext auth mechanisms.");
- }
-
- if (set->max_mail_processes < 1) {
- i_error("max_mail_processes must be at least 1");
- return FALSE;
- }
- if (strcmp(set->login_dir, set->base_dir) == 0) {
- i_error("login_dir can't be the same as base_dir");
- return FALSE;
- }
-
if (set->last_valid_uid != 0 &&
set->first_valid_uid > set->last_valid_uid) {
- i_error("first_valid_uid can't be larger than last_valid_uid");
+ *error_r = "first_valid_uid can't be larger than last_valid_uid";
return FALSE;
}
if (set->last_valid_gid != 0 &&
set->first_valid_gid > set->last_valid_gid) {
- i_error("first_valid_gid can't be larger than last_valid_gid");
- return FALSE;
- }
- if (set->mail_drop_priv_before_exec && *set->mail_chroot != '\0') {
- i_error("mail_drop_priv_before_exec=yes and mail_chroot "
- "don't work together");
+ *error_r = "first_valid_gid can't be larger than last_valid_gid";
return FALSE;
}
- if (set->protocol != MAIL_PROTOCOL_ANY &&
- access(t_strcut(set->login_executable, ' '), X_OK) < 0) {
- i_error("login_executable: Can't use %s: %m",
- t_strcut(set->login_executable, ' '));
+ /* check that we have at least one service. the actual service
+ structure validity is checked later while creating them. */
+ services = array_get(&set->services, &count);
+ if (count == 0) {
+ *error_r = "No services defined";
return FALSE;
}
-
- if (set->login_processes_count < 1) {
- i_error("login_processes_count must be at least 1");
- return FALSE;
- }
-
-#ifndef HAVE_MODULES
- if (*set->mail_plugins != '\0') {
- i_error("mail_plugins: Plugin support wasn't built into Dovecot, "
- "can't load plugins: %s", set->mail_plugins);
- return FALSE;
+ for (i = 0; i < count; i++) {
+ if (*services[i]->executable != '/') {
+ services[i]->executable =
+ p_strconcat(pool, set->libexec_dir, "/",
+ services[i]->executable, NULL);
+ }
+ fix_file_listener_paths(&services[i]->unix_listeners,
+ pool, set->base_dir);
+ fix_file_listener_paths(&services[i]->fifo_listeners,
+ pool, set->base_dir);
}
-#endif
return TRUE;
}
+/* </settings checks> */
-static bool login_want_core_dumps(struct master_server_settings *set)
-{
- const char *p;
-
- p = set->pop3 == NULL ? NULL :
- strstr(set->pop3->login_executable, " -D");
- if (p != NULL && p[3] == '\0')
- return TRUE;
- p = set->imap == NULL ? NULL :
- strstr(set->imap->login_executable, " -D");
- if (p != NULL && p[3] == '\0')
- return TRUE;
- return FALSE;
-}
-
-static bool settings_do_fixes(struct master_settings *set)
+bool master_settings_do_fixes(const struct master_settings *set)
{
+ const char *login_dir;
struct stat st;
/* since base dir is under /var/run by default, it may have been
i_error("stat(%s) failed: %m", set->base_dir);
return FALSE;
}
+ if (!S_ISDIR(st.st_mode)) {
+ i_error("%s is not a directory", set->base_dir);
+ return FALSE;
+ }
- /* remove auth worker sockets left by unclean exits */
- unlink_auth_sockets(set->base_dir, "auth-worker.");
+ /* create login directory under base dir */
+ login_dir = t_strconcat(set->base_dir, "/login", NULL);
+ if (mkdir(login_dir, 0755) < 0 && errno != EEXIST) {
+ i_error("mkdir(%s) failed: %m", login_dir);
+ return FALSE;
+ }
/* Make sure our permanent state directory exists */
if (mkdir_parents(PKG_STATEDIR, 0750) < 0 && errno != EEXIST) {
i_error("mkdir(%s) failed: %m", PKG_STATEDIR);
return FALSE;
}
-
-#ifdef HAVE_MODULES
- if (*set->mail_plugins != '\0' && set->protocol == MAIL_PROTOCOL_IMAP &&
- *set->imap_capability == '\0') {
- if (!get_imap_capability(set))
- return FALSE;
- }
-#endif
return TRUE;
}
-static bool
-settings_fix(struct master_settings *set, bool nochecks, bool nofixes)
-{
- /* fix relative paths */
- fix_base_path(set, &set->login_dir);
-
- if (nochecks)
- return TRUE;
- if (!settings_verify(set))
- return FALSE;
- return nofixes ? TRUE : settings_do_fixes(set);
-}
-
-static void
-settings_warn_needed_fds(struct master_server_settings *server ATTR_UNUSED)
-{
-#ifdef HAVE_SETRLIMIT
- struct rlimit rlim;
- unsigned int fd_count = 0;
-
- if (getrlimit(RLIMIT_NOFILE, &rlim) < 0)
- return;
-
- /* count only log pipes needed for login and mail processes. we need
- more, but they're the ones that can use up most of the fds */
- if (server->imap != NULL)
- fd_count += server->imap->login_max_processes_count;
- if (server->pop3 != NULL)
- fd_count += server->pop3->login_max_processes_count;
- fd_count += server->defaults->max_mail_processes;
-
- if (rlim.rlim_cur < fd_count) {
- i_warning("fd limit %d is lower than what Dovecot can use under "
- "full load (more than %u). Either grow the limit or "
- "change login_max_processes_count and "
- "max_mail_processes master_settings",
- (int)rlim.rlim_cur, fd_count);
- }
-#endif
-}
-
-static void
-config_split_all_settings(struct master_settings *set, const char *input)
-{
- const char *p, *line;
- string_t *str;
-
- str = t_str_new(256);
- p_array_init(&set->all_settings, settings_pool, 256);
- for (p = input; *p != '\n'; p++) {
- str_truncate(str, 0);
- for (; *p != '='; p++) {
- i_assert(*p != '\n' && *p != '\0');
- str_append_c(str, i_toupper(*p));
- }
- for (; *p != '\n'; p++) {
- i_assert(*p != '\0');
- str_append_c(str, *p);
- }
- line = p_strdup(settings_pool, str_c(str));
- array_append(&set->all_settings, &line, 1);
- }
-}
-
-static int config_exec(const char *path, const char *service,
- struct master_settings **set_r)
+#if 0 //FIXME
+static struct master_settings *
+master_settings_read_fd(pool_t pool, int fd, const char **error_r)
{
struct setting_parser_context *parser;
- string_t *all_settings;
+ struct master_settings *set;
+ struct istream *input;
int ret;
- env_put("LOG_TO_MASTER=1");
-
- all_settings = str_new(default_pool, 10240);
- parser = settings_parser_init(settings_pool,
- &master_setting_parser_info,
+ parser = settings_parser_init(pool, &master_setting_parser_info,
SETTINGS_PARSER_FLAG_IGNORE_UNKNOWN_KEYS);
- settings_parse_save_input(parser, all_settings);
- if ((ret = settings_parse_exec(parser, DOVECOT_CONFIG_BIN_PATH,
- path, service)) == 0) {
- *set_r = settings_parser_get(parser);
- config_split_all_settings(*set_r, str_c(all_settings));
+ input = i_stream_create_fd(fd, 1024, FALSE);
+ ret = settings_parse_stream_read(parser, input);
+ i_stream_unref(&input);
+
+ if (ret == 0) {
+ *error_r = NULL;
+ set = settings_parser_get(parser);
+ } else {
+ *error_r = t_strdup_printf(
+ "master: Error reading configuration: %s",
+ settings_parser_get_error(parser));
+ set = NULL;
}
settings_parser_deinit(&parser);
- str_free(&all_settings);
- return ret;
-}
-
-int master_settings_read(const char *path,
- struct master_server_settings **set_r)
-{
- struct master_server_settings *set;
-
- p_clear(settings_pool);
- set = p_new(settings_pool, struct master_server_settings, 1);
-
- master_default_settings.mail_executable = NULL;
- master_default_settings.login_executable = NULL;
- if (config_exec(path, "", &set->defaults) < 0)
- return -1;
- set->defaults->protocol = MAIL_PROTOCOL_ANY;
- set->defaults->server = set;
-
- master_default_settings.mail_executable = PKG_LIBEXECDIR"/imap";
- master_default_settings.login_executable = PKG_LIBEXECDIR"/imap-login";
- if (config_exec(path, "imap", &set->imap) < 0)
- return -1;
- set->imap->protocol = MAIL_PROTOCOL_IMAP;
- set->imap->server = set;
-
- master_default_settings.mail_executable = PKG_LIBEXECDIR"/pop3";
- master_default_settings.login_executable = PKG_LIBEXECDIR"/pop3-login";
- if (config_exec(path, "pop3", &set->pop3) < 0)
- return -1;
- set->pop3->protocol = MAIL_PROTOCOL_POP3;
- set->pop3->server = set;
-
- *set_r = set;
- return 0;
-}
-static void settings_verify_master(struct master_server_settings *set)
-{
- if (!settings_have_connect_sockets(set->defaults)) {
- /* we are not using external authentication, so make sure the
- login directory exists with correct permissions and it's
- empty. with external auth we wouldn't want to delete
- existing sockets or break the permissions required by the
- auth server. */
- mode_t mode = login_want_core_dumps(set) ? 0770 : 0750;
- if (safe_mkdir(set->defaults->login_dir, mode,
- master_uid, set->login_gid) == 0) {
- i_warning("Corrected permissions for login directory "
- "%s", set->defaults->login_dir);
- }
-
- unlink_auth_sockets(set->defaults->login_dir, "");
- }
+ return set;
}
-bool master_settings_check(struct master_server_settings *set,
- bool nochecks, bool nofixes)
+struct master_settings *
+master_settings_read(pool_t pool, const char *config_binary,
+ const char *config_path)
{
- struct master_auth_settings *const *auths;
- unsigned int i, count;
- pool_t temp;
+ struct master_settings *set;
+ const char *error;
+ pid_t pid;
+ int fd[2], status;
- if ((*set->imap->protocols == '\0' ||
- *set->pop3->protocols == '\0') && !nochecks) {
- i_error("protocols: No protocols given in configuration file");
- return FALSE;
- }
- /* --exec-mail is used if nochecks=TRUE. Allow it regardless
- of what's in protocols setting. */
- if (!settings_is_active(set->imap) && !nochecks) {
- if (strcmp(set->imap->protocols, "none") == 0) {
- set->imap->protocol = MAIL_PROTOCOL_ANY;
- if (!settings_fix(set->imap, nochecks, nofixes))
- return FALSE;
- }
- set->imap = NULL;
- } else {
- if (!settings_fix(set->imap, nochecks, nofixes))
- return FALSE;
+ if (pipe(fd) < 0) {
+ i_error("pipe() failed: %m");
+ return NULL;
}
- if (!settings_is_active(set->pop3) && !nochecks)
- set->pop3 = NULL;
- else {
- if (!settings_fix(set->pop3, nochecks, nofixes))
- return FALSE;
+ pid = fork();
+ if (pid < 0) {
+ i_error("fork() failed: %m");
+ (void)close(fd[0]);
+ (void)close(fd[1]);
+ return NULL;
}
- if (!settings_fix(set->defaults, nochecks, nofixes))
- return FALSE;
+ if (pid == 0) {
+ /* child */
+ const char *argv[3] = { "-c", config_path, NULL };
- if (!nofixes) {
- settings_verify_master(set);
- auths = array_get(&set->defaults->auths, &count);
- if (count == 0) {
- i_error("Missing auth section");
- return FALSE;
- }
+ if (dup2(fd[1], STDOUT_FILENO) < 0)
+ i_fatal("dup2(stdout) failed: %m");
+ (void)close(fd[0]);
+ (void)close(fd[1]);
- for (i = 0; i < count; i++) {
- if (!auth_settings_verify(set->defaults, auths[i]))
- return FALSE;
- }
+ env_put("MASTER_INIT=1");
+ process_exec(config_binary, argv);
}
- if (!nochecks)
- settings_warn_needed_fds(set);
+ /* parent. */
+ (void)close(fd[1]);
- /* settings ok, swap them */
- temp = settings_pool;
- settings_pool = settings2_pool;
- settings2_pool = temp;
+ set = master_settings_read_fd(pool, fd[0], &error);
+ if (close(fd[0]) < 0)
+ i_error("close(setpipe) failed: %m");
- master_set = set;
- return TRUE;
-}
-
-void master_settings_export_to_env(const struct master_settings *set)
-{
- const char *const *sets;
- unsigned int i, count;
-
- sets = array_get(&set->all_settings, &count);
- for (i = 0; i < count; i++)
- env_put(sets[i]);
-}
+ /* if config process doesn't return successfully, assume the returned
+ configuration is invalid */
+ if (waitpid(pid, &status, 0) < 0) {
+ i_error("waitpid(config) failed: %m");
+ set = NULL;
+ } else if (status != 0) {
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != FATAL_DEFAULT)
+ process_log_status_error(pid, "config", status);
+ set = NULL;
+ } else if (set == NULL) {
+ i_error("%s", error);
+ }
-void master_settings_init(void)
-{
- settings_pool = pool_alloconly_create("settings", 4096);
- settings2_pool = pool_alloconly_create("settings2", 4096);
-}
+ if (set != NULL) {
+ if (!master_settings_verify(set))
+ set = NULL;
+ else if (!settings_do_fixes(set))
+ set = NULL;
+ }
-void master_settings_deinit(void)
-{
- pool_unref(&settings_pool);
- pool_unref(&settings2_pool);
+ return set;
}
+#endif
#ifndef MASTER_SETTINGS_H
#define MASTER_SETTINGS_H
-#include "network.h"
-
-#define DOVECOT_CONFIG_BIN_PATH BINDIR"/doveconf"
-
-enum mail_protocol {
- MAIL_PROTOCOL_ANY,
- MAIL_PROTOCOL_IMAP,
- MAIL_PROTOCOL_POP3,
- MAIL_PROTOCOL_LDA
+struct file_listener_settings {
+ const char *path;
+ unsigned int mode;
+ const char *user;
+ const char *group;
};
+ARRAY_DEFINE_TYPE(file_listener_settings, struct file_listener_settings *);
-struct listener {
- struct ip_addr ip;
+struct inet_listener_settings {
+ const char *address;
unsigned int port;
- int fd;
- bool wanted;
};
-ARRAY_DEFINE_TYPE(listener, struct listener);
-struct master_auth_socket_unix_settings {
- const char *path;
-};
+struct service_settings {
+ struct master_settings *master_set;
-struct master_auth_socket_settings {
const char *type;
-
- ARRAY_DEFINE(masters, struct master_auth_socket_unix_settings *);
-};
-
-struct master_auth_settings {
- const char *name;
const char *executable;
const char *user;
+ const char *group;
+ const char *privileged_group;
+ const char *extra_groups;
const char *chroot;
+ const char *auth_dest_service;
- unsigned int count;
- unsigned int process_size;
+ bool drop_priv_before_exec;
- const char *mechanisms;
- bool debug;
+ unsigned int process_limit;
+ unsigned int client_limit;
+ unsigned int vsz_limit;
- ARRAY_DEFINE(sockets, struct master_auth_socket_settings *);
-
- /* .. */
- uid_t uid;
- gid_t gid;
+ ARRAY_TYPE(file_listener_settings) unix_listeners;
+ ARRAY_TYPE(file_listener_settings) fifo_listeners;
+ ARRAY_DEFINE(inet_listeners, struct inet_listener_settings *);
};
struct master_settings {
- /* common */
const char *base_dir;
- const char *log_path;
- const char *info_log_path;
- const char *log_timestamp;
- const char *syslog_facility;
-
- /* general */
- const char *protocols;
- const char *listen;
- const char *ssl_listen;
+ const char *libexec_dir;
+ unsigned int default_process_limit;
+ unsigned int default_client_limit;
- const char *ssl;
- const char *ssl_key_file;
- unsigned int ssl_parameters_regenerate;
bool version_ignore;
- /* login */
- const char *login_dir;
- const char *login_executable;
- const char *login_user;
-
- bool login_process_per_connection;
- bool login_chroot;
- bool disable_plaintext_auth;
-
- unsigned int login_process_size;
- unsigned int login_processes_count;
- unsigned int login_max_processes_count;
-
- /* mail */
- const char *valid_chroot_dirs;
- const char *mail_chroot;
- unsigned int max_mail_processes;
- unsigned int mail_max_userip_connections;
- bool verbose_proctitle;
-
unsigned int first_valid_uid, last_valid_uid;
unsigned int first_valid_gid, last_valid_gid;
- const char *mail_access_groups;
- const char *mail_privileged_group;
- const char *mail_uid;
- const char *mail_gid;
-
- const char *mail_plugins;
- const char *imap_capability;
-
- const char *mail_location;
- bool mail_debug;
- bool maildir_very_dirty_syncs;
- unsigned int dbox_purge_min_percentage;
- bool mail_drop_priv_before_exec;
-
- const char *mail_executable;
- unsigned int mail_process_size;
- const char *mail_log_prefix;
- unsigned int mail_log_max_lines_per_sec;
-
- /* dict */
- unsigned int dict_process_count;
-
- ARRAY_DEFINE(auths, struct master_auth_settings *);
-
-#ifndef CONFIG_BINARY
- /* .. */
- struct master_server_settings *server;
- enum mail_protocol protocol;
- ARRAY_TYPE(listener) listens;
- ARRAY_TYPE(listener) ssl_listens;
-
- uid_t login_uid, mail_uid_t;
- gid_t mail_gid_t, mail_priv_gid_t;
-
- const char *imap_generated_capability;
- ARRAY_TYPE(const_string) all_settings;
-#endif
+ ARRAY_DEFINE(services, struct service_settings *);
};
-struct master_server_settings {
- struct master_settings *defaults;
- struct master_settings *imap;
- struct master_settings *pop3;
-
- gid_t login_gid;
-};
-
-extern struct master_server_settings *master_set;
extern struct setting_parser_info master_setting_parser_info;
-int master_settings_read(const char *path,
- struct master_server_settings **set_r);
-bool master_settings_check(struct master_server_settings *set,
- bool nochecks, bool nofixes);
-void master_settings_export_to_env(const struct master_settings *set);
-
-void master_settings_init(void);
-void master_settings_deinit(void);
+struct master_settings *
+master_settings_read(pool_t pool, const char *config_binary,
+ const char *config_path);
+bool master_settings_do_fixes(const struct master_settings *set);
#endif
--- /dev/null
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "hash.h"
+#include "service.h"
+#include "service-process.h"
+#include "service-auth-server.h"
+#include "service-auth-source.h"
+#include "../auth/auth-master-interface.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define AUTH_MAX_INBUF_SIZE 8192
+
+static void
+service_process_auth_request_free(struct service_process_auth_request *request)
+{
+ if (request->fd != -1) {
+ if (close(request->fd) < 0)
+ i_error("close(auth request fd) failed: %m");
+ }
+ i_free(request);
+}
+
+static void
+service_process_auth_server_close(struct service_process_auth_server *process)
+{
+ struct hash_iterate_context *iter;
+ void *key, *value;
+
+ if (process->auth_requests != NULL) {
+ iter = hash_table_iterate_init(process->auth_requests);
+ while (hash_table_iterate(iter, &key, &value)) {
+ struct service_process_auth_request *request = value;
+
+ service_process_unref(&request->process->process);
+ service_process_auth_request_free(request);
+ }
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&process->auth_requests);
+ }
+
+ if (process->auth_input != NULL)
+ i_stream_close(process->auth_input);
+ if (process->auth_output != NULL)
+ o_stream_close(process->auth_output);
+
+ if (process->io_auth != NULL)
+ io_remove(&process->io_auth);
+ if (process->auth_fd != -1) {
+ if (close(process->auth_fd) < 0)
+ i_error("close(auth_fd) failed: %m");
+ process->auth_fd = -1;
+ }
+}
+
+static struct service_process_auth_request *
+auth_process_lookup_request(struct service_process_auth_server *process,
+ unsigned int id)
+{
+ struct service_process_auth_request *request;
+
+ request = hash_table_lookup(process->auth_requests, POINTER_CAST(id));
+ if (request == NULL) {
+ i_error("service(%s): authentication service %s "
+ "sent reply with unknown ID %u",
+ process->process.service->name,
+ dec2str(process->process.pid), id);
+ return NULL;
+ }
+
+ hash_table_remove(process->auth_requests, POINTER_CAST(id));
+ if (!service_process_unref(&request->process->process)) {
+ /* process already died. */
+ service_process_auth_request_free(request);
+ return NULL;
+ }
+
+ return request;
+}
+
+static int
+auth_process_input_user(struct service_process_auth_server *process, const char *args)
+{
+ struct service_process_auth_request *request;
+ const char *const *list;
+ enum master_auth_status status;
+ unsigned int id;
+
+ /* <id> <userid> [..] */
+
+ list = t_strsplit(args, "\t");
+ if (list[0] == NULL || list[1] == NULL) {
+ i_error("BUG: Auth process %s sent corrupted USER line",
+ dec2str(process->process.pid));
+ return FALSE;
+ }
+ id = (unsigned int)strtoul(list[0], NULL, 10);
+
+ request = auth_process_lookup_request(process, id);
+ if (request != NULL) {
+ struct service *dest_service =
+ request->process->process.service->auth_dest_service;
+ struct service_process *dest_process;
+
+ /* FIXME: handle MASTER_AUTH_STATUS_MAX_CONNECTIONS */
+ dest_process = service_process_create(dest_service, list + 1,
+ request->fd,
+ request->data,
+ request->data_size);
+ status = dest_process != NULL ?
+ MASTER_AUTH_STATUS_OK :
+ MASTER_AUTH_STATUS_INTERNAL_ERROR;
+ service_process_auth_source_send_reply(request->process,
+ request->process_tag,
+ status);
+ service_process_auth_request_free(request);
+ }
+ return TRUE;
+}
+
+static int
+auth_process_input_notfound(struct service_process_auth_server *process,
+ const char *args)
+{
+ struct service_process_auth_request *request;
+ unsigned int id;
+
+ id = (unsigned int)strtoul(args, NULL, 10);
+
+ request = auth_process_lookup_request(process, id);
+ if (request != NULL) {
+ service_process_auth_source_send_reply(request->process,
+ request->process_tag,
+ FALSE);
+ service_process_auth_request_free(request);
+ }
+ return TRUE;
+}
+
+static int
+auth_process_input_fail(struct service_process_auth_server *process,
+ const char *args)
+{
+ struct service_process_auth_request *request;
+ const char *error;
+ unsigned int id;
+
+ error = strchr(args, '\t');
+ if (error != NULL)
+ error++;
+
+ id = (unsigned int)strtoul(args, NULL, 10);
+
+ request = auth_process_lookup_request(process, id);
+ if (request != NULL) {
+ service_process_auth_source_send_reply(request->process,
+ request->process_tag,
+ FALSE);
+ service_process_auth_request_free(request);
+ }
+ return TRUE;
+}
+
+static void
+service_process_auth_server_input(struct service_process_auth_server *process)
+{
+ const char *line;
+ int ret;
+
+ switch (i_stream_read(process->auth_input)) {
+ case 0:
+ return;
+ case -1:
+ /* disconnected */
+ service_process_auth_server_close(process);
+ return;
+ case -2:
+ /* buffer full */
+ i_error("service(%s): authentication server process %s "
+ "sent us too long line", process->process.service->name,
+ dec2str(process->process.pid));
+ service_process_auth_server_close(process);
+ return;
+ }
+
+ if (!process->auth_version_received) {
+ line = i_stream_next_line(process->auth_input);
+ if (line == NULL)
+ return;
+
+ /* make sure the major version matches */
+ if (strncmp(line, "VERSION\t", 8) != 0 ||
+ atoi(t_strcut(line + 8, '\t')) !=
+ AUTH_MASTER_PROTOCOL_MAJOR_VERSION) {
+ i_error("service(%s): authentication server process %s "
+ "not compatible with master process "
+ "(mixed old and new binaries?)",
+ process->process.service->name,
+ dec2str(process->process.pid));
+ service_process_auth_server_close(process);
+ return;
+ }
+ process->auth_version_received = TRUE;
+ }
+
+ while ((line = i_stream_next_line(process->auth_input)) != NULL) {
+ if (strncmp(line, "USER\t", 5) == 0)
+ ret = auth_process_input_user(process, line + 5);
+ else if (strncmp(line, "NOTFOUND\t", 9) == 0)
+ ret = auth_process_input_notfound(process, line + 9);
+ else if (strncmp(line, "FAIL\t", 5) == 0)
+ ret = auth_process_input_fail(process, line + 5);
+ else
+ ret = TRUE;
+
+ if (!ret) {
+ service_process_auth_server_close(process);
+ break;
+ }
+ }
+}
+
+void service_process_auth_server_init(struct service_process *_process, int fd)
+{
+ struct service_process_auth_server *process =
+ (struct service_process_auth_server *)_process;
+
+ i_assert(_process->service->type == SERVICE_TYPE_AUTH_SERVER);
+
+ process->auth_fd = fd;
+ process->auth_input = i_stream_create_fd(process->auth_fd,
+ AUTH_MAX_INBUF_SIZE, FALSE);
+ process->auth_output =
+ o_stream_create_fd(fd, (size_t)-1, FALSE);
+ process->io_auth =
+ io_add(process->auth_fd, IO_READ,
+ service_process_auth_server_input, process);
+ process->auth_requests =
+ hash_table_create(default_pool, default_pool, 0, NULL, NULL);
+}
+
+void service_process_auth_server_deinit(struct service_process *_process)
+{
+ struct service_process_auth_server *process =
+ (struct service_process_auth_server *)_process;
+
+ i_assert(_process->service->type == SERVICE_TYPE_AUTH_SERVER);
+
+ service_process_auth_server_close(process);
+}
--- /dev/null
+#ifndef SERVICE_AUTH_SERVER_H
+#define SERVICE_AUTH_SERVER_H
+
+struct service_process;
+
+void service_process_auth_server_init(struct service_process *process, int fd);
+void service_process_auth_server_deinit(struct service_process *process);
+
+#endif
--- /dev/null
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "hash.h"
+#include "str.h"
+#include "ioloop.h"
+#include "ostream.h"
+#include "fdpass.h"
+#include "fd-close-on-exec.h"
+#include "../auth/auth-master-interface.h"
+#include "service.h"
+#include "service-process.h"
+#include "service-auth-source.h"
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define AUTH_SOURCE_OUTBUF_THROTTLE_THRESHOLD (1024 - 256)
+#define AUTH_SERVER_MAX_OUTBUF_SIZE (1024*64)
+#define AUTH_BUSY_LOG_INTERVAL 30
+
+static void
+service_process_auth_source_input(struct service_process_auth_source *process);
+
+static void
+service_process_auth_source_close(struct service_process_auth_source *process)
+{
+ if (process->auth_output != NULL)
+ o_stream_close(process->auth_output);
+ if (process->io_auth != NULL)
+ io_remove(&process->io_auth);
+ if (process->auth_fd != -1) {
+ if (close(process->auth_fd) < 0)
+ i_error("close(auth_fd) failed: %m");
+ process->auth_fd = -1;
+ }
+}
+
+static int
+process_auth_source_output(struct service_process_auth_source *process)
+{
+ int ret;
+
+ if ((ret = o_stream_flush(process->auth_output)) < 0)
+ return -1;
+
+ if (process->io_auth == NULL &&
+ o_stream_get_buffer_used_size(process->auth_output) <
+ AUTH_SOURCE_OUTBUF_THROTTLE_THRESHOLD) {
+ /* enable parsing input again */
+ o_stream_unset_flush_callback(process->auth_output);
+ process->io_auth = io_add(process->auth_fd, IO_READ,
+ service_process_auth_source_input,
+ process);
+ }
+ return ret;
+}
+
+void service_process_auth_source_send_reply(struct service_process_auth_source *process,
+ unsigned int tag,
+ enum master_auth_status status)
+{
+ struct master_auth_reply reply;
+
+ if (o_stream_get_buffer_used_size(process->auth_output) >=
+ AUTH_SOURCE_OUTBUF_THROTTLE_THRESHOLD) {
+ /* not reading our output. stop parsing input until it will. */
+ if (process->io_auth != NULL) {
+ io_remove(&process->io_auth);
+
+ o_stream_set_flush_callback(process->auth_output,
+ process_auth_source_output,
+ process);
+ }
+ }
+
+ /* Reply to login process */
+ memset(&reply, 0, sizeof(reply));
+ reply.tag = tag;
+ reply.status = status;
+
+ o_stream_send(process->auth_output, &reply, sizeof(reply));
+}
+
+static unsigned int
+auth_server_send_request(struct service_process_auth_server *server_process,
+ struct service_process_auth_source *source_process,
+ unsigned int auth_id)
+{
+ unsigned int tag = 0;
+ string_t *str;
+
+ while (tag == 0)
+ tag = ++server_process->auth_tag_counter;
+
+ str = t_str_new(256);
+ if (!server_process->auth_version_sent) {
+ server_process->auth_version_sent = TRUE;
+ str_printfa(str, "VERSION\t%u\t%u\n",
+ AUTH_MASTER_PROTOCOL_MAJOR_VERSION,
+ AUTH_MASTER_PROTOCOL_MINOR_VERSION);
+ o_stream_send(server_process->auth_output,
+ str_data(str), str_len(str));
+ str_truncate(str, 0);
+ }
+
+ str_printfa(str, "REQUEST\t%u\t%s\t%u\n",
+ tag, dec2str(source_process->process.pid), auth_id);
+ o_stream_send(server_process->auth_output, str_data(str), str_len(str));
+ return tag;
+}
+
+static int
+auth_read_request(struct service_process_auth_source *process,
+ struct master_auth_request *req,
+ unsigned char data[MASTER_AUTH_MAX_DATA_SIZE],
+ int *client_fd_r)
+{
+ struct service *service = process->process.service;
+ struct stat st;
+ ssize_t ret;
+
+ *client_fd_r = -1;
+
+ ret = fd_read(process->auth_fd, req, sizeof(*req), client_fd_r);
+ if (ret != sizeof(*req)) {
+ if (ret == 0) {
+ /* disconnected */
+ } else if (ret > 0) {
+ /* request wasn't fully read */
+ i_error("service(%s): fd_read() partial input (%d/%d)",
+ service->name, (int)ret, (int)sizeof(*req));
+ } else {
+ if (errno == EAGAIN)
+ return 0;
+
+ i_error("service(%s): fd_read() failed: %m",
+ service->name);
+ }
+ return -1;
+ }
+
+ if (req->data_size != 0) {
+ if (req->data_size > MASTER_AUTH_MAX_DATA_SIZE) {
+ i_error("service(%s): Too large auth data_size sent",
+ service->name);
+ return -1;
+ }
+ /* @UNSAFE */
+ ret = read(process->auth_fd, data, req->data_size);
+ if (ret != (ssize_t)req->data_size) {
+ if (ret == 0) {
+ /* disconnected */
+ } else if (ret > 0) {
+ /* request wasn't fully read */
+ i_error("service(%s): Data read partially %d/%u",
+ service->name, (int)ret, req->data_size);
+ } else {
+ i_error("service(%s): read(data) failed: %m",
+ service->name);
+ }
+ return -1;
+ }
+ }
+
+ if (*client_fd_r == -1) {
+ i_error("service(%s): Auth request missing a file descriptor",
+ service->name);
+ return -1;
+ }
+
+ if (fstat(*client_fd_r, &st) < 0) {
+ i_error("service(%s): fstat(auth dest fd) failed: %m",
+ service->name);
+ return -1;
+ }
+ if (st.st_ino != req->ino) {
+ i_error("service(%s): Auth request inode mismatch: %s != %s",
+ service->name, dec2str(st.st_ino), dec2str(req->ino));
+ return -1;
+ }
+ return 1;
+}
+
+static void
+service_process_auth_source_input(struct service_process_auth_source *process)
+{
+ struct service *service = process->process.service;
+ struct service_process_auth_server *auth_process;
+ struct service_process_auth_request *auth_req;
+ struct master_auth_request req;
+ unsigned char data[MASTER_AUTH_MAX_DATA_SIZE];
+ unsigned int tag;
+ ssize_t ret;
+ int client_fd;
+
+ ret = auth_read_request(process, &req, data, &client_fd);
+ if (ret == 0)
+ return;
+ if (ret < 0) {
+ if (client_fd != -1) {
+ if (close(client_fd) < 0)
+ i_error("login: close(mail client) failed: %m");
+ }
+ service_process_auth_source_close(process);
+ return;
+ }
+ fd_close_on_exec(client_fd, TRUE);
+
+ /* we have a request. check its validity. */
+ auth_process = hash_table_lookup(service->list->pids, &req.auth_pid);
+ if (auth_process == NULL) {
+ i_error("service(%s): authentication request for unknown "
+ "auth server PID %s", service->name,
+ dec2str(req.auth_pid));
+ service_process_auth_source_send_reply(process, req.tag, FALSE);
+ (void)close(client_fd);
+ return;
+ }
+
+ if (o_stream_get_buffer_used_size(auth_process->auth_output) >=
+ AUTH_SERVER_MAX_OUTBUF_SIZE) {
+ if (auth_process->auth_busy_stamp <=
+ ioloop_time - AUTH_BUSY_LOG_INTERVAL) {
+ i_warning("service(%s): authentication server PID "
+ "%s too busy",
+ auth_process->process.service->name,
+ dec2str(req.auth_pid));
+ auth_process->auth_busy_stamp = ioloop_time;
+ }
+ service_process_auth_source_send_reply(process, req.tag, FALSE);
+ (void)close(client_fd);
+ return;
+ }
+
+ /* ok, we have a non-busy authentication server.
+ send a request to it. */
+ auth_req = i_malloc(sizeof(*auth_req) + req.data_size);
+ auth_req->process = process;
+ auth_req->process_tag = req.tag;
+ auth_req->fd = client_fd;
+ auth_req->local_ip = req.local_ip;
+ auth_req->remote_ip = req.remote_ip;
+ auth_req->data_size = req.data_size;
+ memcpy(auth_req->data, data, req.data_size);
+
+ tag = auth_server_send_request(auth_process, process, req.auth_id);
+
+ service_process_ref(&process->process);
+ hash_table_insert(auth_process->auth_requests,
+ POINTER_CAST(tag), auth_req);
+}
+
+void service_process_auth_source_init(struct service_process *_process, int fd)
+{
+ struct service_process_auth_source *process =
+ (struct service_process_auth_source *)_process;
+
+ i_assert(_process->service->type == SERVICE_TYPE_AUTH_SOURCE);
+
+ process->auth_fd = fd;
+ process->io_auth = io_add(process->auth_fd, IO_READ,
+ service_process_auth_source_input, process);
+ process->auth_output =
+ o_stream_create_fd(fd, (size_t)-1, FALSE);
+}
+
+void service_process_auth_source_deinit(struct service_process *_process)
+{
+ struct service_process_auth_source *process =
+ (struct service_process_auth_source *)_process;
+
+ i_assert(_process->service->type == SERVICE_TYPE_AUTH_SOURCE);
+
+ service_process_auth_source_close(process);
+}
--- /dev/null
+#ifndef SERVICE_AUTH_SOURCE_H
+#define SERVICE_AUTH_SOURCE_H
+
+struct service_process;
+struct service_process_auth_source;
+
+void service_process_auth_source_init(struct service_process *process, int fd);
+void service_process_auth_source_deinit(struct service_process *process);
+
+void service_process_auth_source_send_reply(struct service_process_auth_source *process,
+ unsigned int tag,
+ enum master_auth_status status);
+
+#endif
--- /dev/null
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "array.h"
+#include "fd-set-nonblock.h"
+#include "fd-close-on-exec.h"
+#include "network.h"
+#include "service.h"
+#include "service-listen.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+static int service_unix_listener_listen(struct service_listener *l)
+{
+ struct service *service = l->service;
+ const struct file_listener_settings *set = l->set.fileset.set;
+ mode_t old_umask;
+ int fd, i;
+
+ old_umask = umask((set->mode ^ 0777) & 0777);
+ for (i = 0;; i++) {
+ fd = net_listen_unix(set->path, service->process_limit);
+ if (fd != -1)
+ break;
+
+ if (errno == EISDIR || errno == ENOENT) {
+ /* looks like the path doesn't exist. */
+ return 0;
+ }
+
+ if (errno != EADDRINUSE) {
+ i_error("service(%s): net_listen_unix(%s) failed: %m",
+ service->name, set->path);
+ return -1;
+ }
+
+ /* already in use - see if it really exists.
+ after 3 times just fail here. */
+ fd = net_connect_unix(set->path);
+ if (fd != -1 || errno != ECONNREFUSED || i >= 3) {
+ if (fd != -1)
+ (void)close(fd);
+ i_error("service(%s): Socket already exists: %s",
+ service->name, set->path);
+ return 0;
+ }
+
+ /* delete and try again */
+ if (unlink(set->path) < 0 && errno != ENOENT) {
+ i_error("service(%s): unlink(%s) failed: %m",
+ service->name, set->path);
+ return -1;
+ }
+ }
+ umask(old_umask);
+
+ i_assert(fd != -1);
+
+ /* see if we need to change its owner/group */
+ if ((service->uid != (uid_t)-1 && service->uid != master_uid) ||
+ (service->gid != (gid_t)-1 && service->gid != master_gid)) {
+ if (chown(set->path, service->uid, service->gid) < 0) {
+ i_error("chown(%s, %s, %s) failed: %m", set->path,
+ dec2str(service->uid), dec2str(service->gid));
+ (void)close(fd);
+ return -1;
+ }
+ }
+
+ net_set_nonblock(fd, TRUE);
+ fd_close_on_exec(fd, TRUE);
+
+ l->fd = fd;
+ return 1;
+}
+
+static int service_fifo_listener_listen(struct service_listener *l)
+{
+ struct service *service = l->service;
+ const struct file_listener_settings *set = l->set.fileset.set;
+ mode_t old_umask;
+ int fd, ret;
+
+ old_umask = umask((set->mode ^ 0777) & 0777);
+ ret = mkfifo(set->path, set->mode);
+ umask(old_umask);
+
+ if (ret < 0 && errno != EEXIST) {
+ i_error("service(%s): mkfifo(%s) failed: %m",
+ service->name, set->path);
+ return -1;
+ }
+
+ fd = open(set->path, O_RDONLY);
+ if (fd == -1) {
+ i_error("service(%s): open(%s) failed: %m",
+ service->name, set->path);
+ return -1;
+ }
+
+ /* see if we need to change its owner/group */
+ if ((service->uid != (uid_t)-1 && service->uid != master_uid) ||
+ (service->gid != (gid_t)-1 && service->gid != master_gid)) {
+ if (chown(set->path, service->uid, service->gid) < 0) {
+ i_error("chown(%s, %s, %s) failed: %m", set->path,
+ dec2str(service->uid), dec2str(service->gid));
+ (void)close(fd);
+ return -1;
+ }
+ }
+
+ fd_set_nonblock(fd, TRUE);
+ fd_close_on_exec(fd, TRUE);
+
+ l->fd = fd;
+ return 1;
+}
+
+static int service_inet_listener_listen(struct service_listener *l)
+{
+ struct service *service = l->service;
+ const struct inet_listener_settings *set = l->set.inetset.set;
+ unsigned int port = set->port;
+ int fd;
+
+ fd = net_listen(&l->set.inetset.ip, &port, service->process_limit);
+ if (fd < 0) {
+ i_error("service(%s): listen(%s, %u) failed: %m",
+ service->name, set->address, set->port);
+ return errno == EADDRINUSE ? 0 : -1;
+ }
+ net_set_nonblock(fd, TRUE);
+ fd_close_on_exec(fd, TRUE);
+
+ l->fd = fd;
+ return 1;
+}
+
+static int service_listen(struct service *service)
+{
+ struct service_listener *const *listeners;
+ unsigned int i, count;
+ int ret = 1, ret2 = 0;
+
+ listeners = array_get(&service->listeners, &count);
+ for (i = 0; i < count; i++) {
+ if (listeners[i]->fd != -1)
+ continue;
+
+ switch (listeners[i]->type) {
+ case SERVICE_LISTENER_UNIX:
+ ret2 = service_unix_listener_listen(listeners[i]);
+ break;
+ case SERVICE_LISTENER_FIFO:
+ ret2 = service_fifo_listener_listen(listeners[i]);
+ break;
+ case SERVICE_LISTENER_INET:
+ ret2 = service_inet_listener_listen(listeners[i]);
+ break;
+ }
+
+ if (ret2 < ret)
+ ret = ret2;
+ }
+
+ return ret;
+}
+
+int services_listen(struct service_list *service_list)
+{
+ struct service *const *services;
+ unsigned int i, count;
+ int ret = 1, ret2;
+
+ services = array_get(&service_list->services, &count);
+ for (i = 0; i < count; i++) {
+ ret2 = service_listen(services[i]);
+ if (ret2 < ret)
+ ret = ret2;
+ }
+ return ret;
+}
+
+static int listener_equals(const struct service_listener *l1,
+ const struct service_listener *l2)
+{
+ if (l1->type != l2->type)
+ return FALSE;
+
+ switch (l1->type) {
+ case SERVICE_LISTENER_UNIX:
+ case SERVICE_LISTENER_FIFO:
+ if (strcmp(l1->set.fileset.set->path,
+ l2->set.fileset.set->path) != 0)
+ return FALSE;
+ if (l1->set.fileset.set->mode != l2->set.fileset.set->mode)
+ return FALSE;
+ if (l1->set.fileset.uid != l2->set.fileset.uid)
+ return FALSE;
+ if (l1->set.fileset.gid != l2->set.fileset.gid)
+ return FALSE;
+ return TRUE;
+ case SERVICE_LISTENER_INET:
+ if (memcmp(&l1->set.inetset.ip, &l2->set.inetset.ip,
+ sizeof(l1->set.inetset.ip)) != 0)
+ return FALSE;
+ if (l1->set.inetset.set->port != l2->set.inetset.set->port)
+ return FALSE;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int services_listen_using(struct service_list *new_service_list,
+ struct service_list *old_service_list)
+{
+ struct service *const *services;
+ ARRAY_DEFINE(new_listeners_arr, struct service_listener *);
+ ARRAY_DEFINE(old_listeners_arr, struct service_listener *);
+ struct service_listener *const *new_listeners, *const *old_listeners;
+ unsigned int i, j, count, new_count, old_count;
+
+ /* first create an arrays of all listeners to make things easier */
+ t_array_init(&new_listeners_arr, 64);
+ services = array_get(&new_service_list->services, &count);
+ for (i = 0; i < count; i++)
+ array_append_array(&new_listeners_arr, &services[i]->listeners);
+
+ t_array_init(&old_listeners_arr, 64);
+ services = array_get(&old_service_list->services, &count);
+ for (i = 0; i < count; i++)
+ array_append_array(&old_listeners_arr, &services[i]->listeners);
+
+ /* then start moving fds */
+ new_listeners = array_get(&new_listeners_arr, &new_count);
+ old_listeners = array_get(&old_listeners_arr, &old_count);
+
+ for (i = 0; i < new_count; i++) {
+ for (j = 0; j < old_count; j++) {
+ if (old_listeners[j]->fd != -1 &&
+ listener_equals(new_listeners[i],
+ old_listeners[j])) {
+ new_listeners[i]->fd = old_listeners[j]->fd;
+ old_listeners[j]->fd = -1;
+ break;
+ }
+ }
+ }
+
+ /* close what's left */
+ for (j = 0; j < old_count; j++) {
+ if (old_listeners[j]->fd != -1) {
+ if (close(old_listeners[j]->fd) < 0)
+ i_error("close(listener) failed: %m");
+ }
+ }
+
+ /* and let services_listen() deal with the remaining fds */
+ return services_listen(new_service_list);
+}
--- /dev/null
+#ifndef SERVICE_LISTEN_H
+#define SERVICE_LISTEN_H
+
+/* Start listening in all services. Returns -1 for fatal failures,
+ 0 if some of the addresses are already being used or path for
+ unix socket was lost, 1 if all is ok. It's safe to call this function
+ multiple times. */
+int services_listen(struct service_list *service_list);
+
+/* Move common listener fds from old_services to new_services, close those
+ that aren't needed anymore and finally call services_listen() to add
+ missing listeners. */
+int services_listen_using(struct service_list *new_service_list,
+ struct service_list *old_service_list);
+
+#endif
--- /dev/null
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "fd-close-on-exec.h"
+#include "array.h"
+#include "service.h"
+#include "service-log.h"
+
+#include <unistd.h>
+
+int services_log_init(struct service_list *service_list)
+{
+ struct log_service_handshake handshake;
+ struct service *const *services;
+ unsigned int i, count;
+ buffer_t *handshake_buf;
+ ssize_t ret = 0;
+
+ memset(&handshake, 0, sizeof(handshake));
+ handshake.log_magic = MASTER_LOG_MAGIC;
+
+ handshake_buf = buffer_create_dynamic(default_pool, 256);
+ services = array_get(&service_list->services, &count);
+ for (i = 0; i < count; i++) {
+ if (services[i]->type == SERVICE_TYPE_LOG)
+ continue;
+
+ i_assert(services[i]->log_fd[0] == -1);
+ if (pipe(services[i]->log_fd) < 0) {
+ i_error("pipe() failed: %m");
+ ret = -1;
+ break;
+ }
+ fd_close_on_exec(services[i]->log_fd[0], TRUE);
+ fd_close_on_exec(services[i]->log_fd[1], TRUE);
+
+ handshake.prefix_len = strlen(services[i]->name) + 2;
+
+ buffer_set_used_size(handshake_buf, 0);
+ buffer_append(handshake_buf, &handshake, sizeof(handshake));
+ buffer_append(handshake_buf, services[i]->name,
+ strlen(services[i]->name));
+ buffer_append(handshake_buf, ": ", 2);
+
+ ret = write(services[i]->log_fd[1],
+ handshake_buf->data, handshake_buf->used);
+ if (ret < 0) {
+ i_error("write(log handshake) failed: %m");
+ break;
+ }
+ if ((size_t)ret != handshake_buf->used) {
+ i_error("write(log handshake) didn't write everything");
+ ret = -1;
+ break;
+ }
+ }
+ buffer_free(&handshake_buf);
+ if (ret < 0) {
+ services_log_deinit(service_list);
+ return -1;
+ }
+ return 0;
+}
+
+void services_log_deinit(struct service_list *service_list)
+{
+ struct service *const *services;
+ unsigned int i, count;
+
+ services = array_get(&service_list->services, &count);
+ for (i = 0; i < count; i++) {
+ if (services[i]->log_fd[0] != -1) {
+ if (close(services[i]->log_fd[0]) < 0) {
+ i_error("service(%s): close(log_fd) failed: %m",
+ services[i]->name);
+ }
+ if (close(services[i]->log_fd[1]) < 0) {
+ i_error("service(%s): close(log_fd) failed: %m",
+ services[i]->name);
+ }
+ services[i]->log_fd[0] = -1;
+ services[i]->log_fd[1] = -1;
+ }
+ }
+}
+
+void services_log_dup2(ARRAY_TYPE(dup2) *dups,
+ struct service_list *service_list,
+ unsigned int first_fd, unsigned int *fd_count)
+{
+ struct service *const *services;
+ unsigned int i, n, count;
+
+ services = array_get(&service_list->services, &count);
+ for (i = n = 0; i < count; i++) {
+ if (services[i]->log_fd[1] != -1) {
+ dup2_append(dups, services[i]->log_fd[0], first_fd + n);
+ n++; *fd_count += 1;
+ }
+ }
+}
--- /dev/null
+#ifndef SERVICE_LOG_H
+#define SERVICE_LOG_H
+
+#include "dup2-array.h"
+
+int services_log_init(struct service_list *service_list);
+void services_log_deinit(struct service_list *service_list);
+
+void services_log_dup2(ARRAY_TYPE(dup2) *dups,
+ struct service_list *service_list,
+ unsigned int first_fd, unsigned int *fd_count);
+
+#endif
--- /dev/null
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "array.h"
+#include "ioloop.h"
+#include "fd-close-on-exec.h"
+#include "hash.h"
+#include "service.h"
+#include "service-process.h"
+#include "service-log.h"
+#include "service-monitor.h"
+
+#include <unistd.h>
+#include <sys/wait.h>
+#include <syslog.h>
+
+#define THROTTLE_TIMEOUT (1000*60)
+
+static void service_monitor_stop(struct service *service);
+static void service_monitor_listen_start(struct service *service);
+static void service_monitor_listen_stop(struct service *service);
+
+static void service_status_input(struct service *service)
+{
+ struct master_status status;
+ struct service_process *process;
+ ssize_t ret;
+
+ ret = read(service->status_fd[0], &status, sizeof(status));
+ switch (ret) {
+ case 0:
+ i_error("service(%s): read(status) failed: EOF", service->name);
+ service_monitor_stop(service);
+ return;
+ case -1:
+ i_error("service(%s): read(status) failed: %m", service->name);
+ service_monitor_stop(service);
+ return;
+ default:
+ i_error("service(%s): child %s sent partial status update "
+ "(%d bytes)", service->name,
+ dec2str(status.pid), (int)ret);
+ return;
+
+ case sizeof(status):
+ break;
+ }
+
+ process = hash_table_lookup(service->list->pids, &status.pid);
+ if (process == NULL) {
+ /* we've probably wait()ed it away already. ignore */
+ return;
+ }
+
+ if (process->uid != status.uid || process->service != service) {
+ /* a) Process was closed and another process was created with
+ the same PID, but we're still receiving status update from
+ the old process.
+
+ b) Some process is trying to corrupt our internal state by
+ trying to pretend to be someone else. We could use stronger
+ randomness here, but the worst they can do is DoS and there
+ are already more serious problems if someone is able to do
+ this.. */
+ i_error("service(%s): Ignoring invalid update from child %s "
+ "(UID=%u)", service->name, dec2str(status.pid),
+ status.uid);
+ return;
+ }
+
+ if (process->to_status != NULL) {
+ /* first status notification */
+ timeout_remove(&process->to_status);
+ }
+
+ if (process->available_count == status.available_count)
+ return;
+
+ if (process->available_count > status.available_count) {
+ /* process started servicing requests */
+ process->total_count +=
+ process->available_count - status.available_count;
+ if (status.available_count == 0) {
+ i_assert(service->process_avail > 0);
+ if (--service->process_avail == 0)
+ service_monitor_listen_start(service);
+ }
+ process->idle_start = 0;
+ } else {
+ /* process finished servicing requests */
+ if (process->available_count == 0) {
+ if (service->process_avail++ == 0)
+ service_monitor_listen_stop(service);
+ i_assert(service->process_avail <=
+ service->process_count);
+ }
+ if (status.available_count == service->set->client_limit)
+ process->idle_start = ioloop_time;
+ }
+ process->available_count = status.available_count;
+}
+
+static void service_throttle_timeout(struct service *service)
+{
+ timeout_remove(&service->to_throttle);
+ service_monitor_listen_start(service);
+}
+
+static void service_monitor_throttle(struct service *service)
+{
+ if (service->to_throttle != NULL)
+ return;
+
+ i_error("service(%s): command startup failed, throttling",
+ service->name);
+ service_monitor_listen_stop(service);
+
+ service->to_throttle = timeout_add(THROTTLE_TIMEOUT,
+ service_throttle_timeout, service);
+}
+
+static void service_accept(struct service *service)
+{
+ i_assert(service->process_avail == 0);
+
+ if (service->process_count == service->process_limit) {
+ /* we've reached our limits, new connections will have to
+ wait until there are more processes available */
+ service->listen_pending = TRUE;
+ service_monitor_listen_stop(service);
+ return;
+ }
+
+ /* create a child process and let it accept() this connection */
+ if (service_process_create(service, NULL, -1, NULL, 0) == NULL)
+ service_monitor_throttle(service);
+ else
+ service_monitor_listen_stop(service);
+}
+
+static void service_monitor_listen_start(struct service *service)
+{
+ struct service_listener *const *listeners;
+ unsigned int i, count;
+
+ service->listen_pending = FALSE;
+
+ listeners = array_get(&service->listeners, &count);
+ for (i = 0; i < count; i++) {
+ if (listeners[i]->io == NULL && listeners[i]->fd != -1) {
+ listeners[i]->io = io_add(listeners[i]->fd, IO_READ,
+ service_accept, service);
+ }
+ }
+}
+
+static void service_monitor_listen_stop(struct service *service)
+{
+ struct service_listener *const *listeners;
+ unsigned int i, count;
+
+ listeners = array_get(&service->listeners, &count);
+ for (i = 0; i < count; i++) {
+ struct service_listener *l = listeners[i];
+
+ if (l->io != NULL)
+ io_remove(&l->io);
+ }
+}
+
+void services_monitor_start(struct service_list *service_list)
+{
+ struct service *const *services;
+ unsigned int i, count;
+
+ services_log_init(service_list);
+
+ services = array_get(&service_list->services, &count);
+ for (i = 0; i < count; i++) {
+ if (services[i]->status_fd[0] == -1) {
+ /* we haven't yet created status pipe */
+ if (pipe(services[i]->status_fd) < 0) {
+ i_error("service(%s): pipe() failed: %m",
+ services[i]->name);
+ continue;
+ }
+
+ net_set_nonblock(services[i]->status_fd[0], TRUE);
+ fd_close_on_exec(services[i]->status_fd[0], TRUE);
+ net_set_nonblock(services[i]->status_fd[1], TRUE);
+ fd_close_on_exec(services[i]->status_fd[1], TRUE);
+
+ services[i]->io_status =
+ io_add(services[i]->status_fd[0], IO_READ,
+ service_status_input, services[i]);
+ }
+
+ if (services[i]->status_fd[0] != -1)
+ service_monitor_listen_start(services[i]);
+ }
+
+ if (service_process_create(service_list->log, NULL, -1, NULL, 0) != NULL)
+ service_monitor_listen_stop(service_list->log);
+ if (service_process_create(service_list->config, NULL, -1, NULL, 0) != NULL)
+ service_monitor_listen_stop(service_list->config);
+}
+
+static void service_monitor_stop(struct service *service)
+{
+ int i;
+
+ if (service->io_status != NULL)
+ io_remove(&service->io_status);
+
+ if (service->status_fd[0] != -1) {
+ for (i = 0; i < 2; i++) {
+ if (close(service->status_fd[i]) < 0) {
+ i_error("service(%s): close(%d) failed: %m",
+ service->name, i);
+ }
+ service->status_fd[i] = -1;
+ }
+ }
+ service_monitor_listen_stop(service);
+
+ if (service->to_throttle != NULL)
+ timeout_remove(&service->to_throttle);
+}
+
+void services_monitor_stop(struct service_list *service_list)
+{
+ struct service *const *services;
+ unsigned int i, count;
+
+ services = array_get(&service_list->services, &count);
+ for (i = 0; i < count; i++)
+ service_monitor_stop(services[i]);
+
+ services_log_deinit(service_list);
+}
+
+void services_monitor_reap_children(struct service_list *service_list)
+{
+ struct service_process *process;
+ struct service *service;
+ pid_t pid;
+ int status;
+
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ process = hash_table_lookup(service_list->pids, &pid);
+ if (process == NULL) {
+ i_error("waitpid() returned unknown PID %s",
+ dec2str(pid));
+ continue;
+ }
+
+ service = process->service;
+ if (status == 0) {
+ /* success */
+ if (service->listen_pending)
+ service_monitor_listen_start(service);
+ } else {
+ /* failure */
+ service_process_log_status_error(process, status);
+ if (process->total_count == 0)
+ service_monitor_throttle(service);
+ }
+
+ service_process_destroy(process);
+
+ if (service->process_avail == 0 && service->to_throttle == NULL)
+ service_monitor_listen_start(service);
+ }
+}
--- /dev/null
+#ifndef SERVICE_MONITOR_H
+#define SERVICE_MONITOR_H
+
+/* Start listening and monitoring services. */
+void services_monitor_start(struct service_list *service_list);
+
+/* Stop services. */
+void services_monitor_stop(struct service_list *service_list);
+
+/* Call after SIGCHLD has been detected */
+void services_monitor_reap_children(struct service_list *service_list);
+
+#endif
--- /dev/null
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "array.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "write-full.h"
+#include "base64.h"
+#include "hash.h"
+#include "str.h"
+#include "hostpid.h"
+#include "env-util.h"
+#include "fd-close-on-exec.h"
+#include "restrict-access.h"
+#include "master-service-settings.h"
+#include "dup2-array.h"
+#include "service.h"
+#include "service-log.h"
+#include "service-auth-server.h"
+#include "service-auth-source.h"
+#include "service-process.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+static const char **
+service_dup_fds(struct service *service, int auth_fd, int std_fd)
+{
+ struct service_listener *const *listeners;
+ ARRAY_TYPE(dup2) dups;
+ unsigned int i, count, n, socket_listener_count;
+
+ /* stdin/stdout is already redirected to /dev/null. Other master fds
+ should have been opened with fd_close_on_exec() so we don't have to
+ worry about them.
+
+ because the destination fd might be another one's source fd we have
+ to be careful not to overwrite anything. dup() the fd when needed */
+
+ socket_listener_count = 0;
+ listeners = array_get(&service->listeners, &count);
+ t_array_init(&dups, count + 4);
+ for (i = n = 0; i < count; i++) {
+ if (listeners[i]->fd == -1)
+ continue;
+
+ dup2_append(&dups, listeners[i]->fd,
+ MASTER_LISTEN_FD_FIRST + n);
+ n++; socket_listener_count++;
+ }
+
+ dup2_append(&dups, null_fd, MASTER_RESERVED_FD);
+ dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
+
+ switch (service->type) {
+ case SERVICE_TYPE_AUTH_SOURCE:
+ case SERVICE_TYPE_AUTH_SERVER:
+ i_assert(auth_fd != -1);
+ dup2_append(&dups, auth_fd, MASTER_AUTH_FD);
+ env_put(t_strdup_printf("MASTER_AUTH_FD=%d", MASTER_AUTH_FD));
+ break;
+ case SERVICE_TYPE_LOG:
+ services_log_dup2(&dups, service->list,
+ MASTER_LISTEN_FD_FIRST + n,
+ &socket_listener_count);
+ /* fall through */
+ default:
+ i_assert(auth_fd == -1);
+ dup2_append(&dups, null_fd, MASTER_AUTH_FD);
+ break;
+ }
+
+ if (std_fd != -1) {
+ dup2_append(&dups, std_fd, STDIN_FILENO);
+ dup2_append(&dups, std_fd, STDOUT_FILENO);
+ env_put("LOGGED_IN=1");
+ }
+
+ if (service->type != SERVICE_TYPE_LOG) {
+ /* set log file to stderr. dup2() here immediately so that
+ we can set up logging to it without causing any log messages
+ to be lost. */
+ i_assert(service->log_fd[1] != -1);
+
+ env_put("LOG_SERVICE=1");
+ if (dup2(service->log_fd[1], STDERR_FILENO) < 0)
+ i_fatal("dup2(log fd) failed: %m");
+ i_set_failure_internal();
+ } else {
+ dup2_append(&dups, null_fd, STDERR_FILENO);
+ }
+
+ /* 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("service(%s): dup2s failed", service->name);
+
+#ifdef DEBUG
+ env_put(t_strdup_printf("SOCKET_COUNT=%d", socket_listener_count));
+#endif
+
+ if (socket_listener_count == 1)
+ return NULL;
+ else {
+ const char **args = t_new(const char *, 3);
+ args[0] = "-s";
+ args[1] = dec2str(socket_listener_count);
+ return args;
+ }
+}
+
+static int validate_uid_gid(struct master_settings *set, uid_t uid, gid_t gid,
+ const char *user)
+{
+ if (uid == 0) {
+ i_error("Logins with UID 0 not permitted (user %s)", user);
+ return FALSE;
+ }
+
+ if (uid < (uid_t)set->first_valid_uid ||
+ (set->last_valid_uid != 0 && uid > (uid_t)set->last_valid_uid)) {
+ i_error("Logins with UID %s (user %s) not permitted "
+ "(see first_valid_uid in config file)",
+ dec2str(uid), user);
+ return FALSE;
+ }
+
+ if (gid < (gid_t)set->first_valid_gid ||
+ (set->last_valid_gid != 0 && gid > (gid_t)set->last_valid_gid)) {
+ i_error("Logins for users with primary group ID %s (user %s) "
+ "not permitted (see first_valid_gid in config file).",
+ dec2str(gid), user);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void auth_args_apply(const char *const *args,
+ struct restrict_access_settings *rset,
+ const char **home)
+{
+ for (; *args != NULL; args++) {
+ if (strncmp(*args, "uid=", 4) == 0)
+ rset->uid = (uid_t)strtoul(*args + 4, NULL, 10);
+ else if (strncmp(*args, "gid=", 4) == 0)
+ rset->gid = (gid_t)strtoul(*args + 4, NULL, 10);
+ else if (strncmp(*args, "home=", 5) == 0) {
+ *home = *args + 5;
+ env_put(t_strconcat("HOME=", *args + 5, NULL));
+ } else if (strncmp(*args, "chroot=", 7) == 0)
+ rset->chroot_dir = *args + 7;
+ else if (strncmp(*args, "system_groups_user=", 19) == 0)
+ rset->system_groups_user = *args + 19;
+ else if (strncmp(*args, "mail_access_groups=", 19) == 0) {
+ rset->extra_groups =
+ rset->extra_groups == NULL ? *args + 19 :
+ t_strconcat(*args + 19, ",",
+ rset->extra_groups, NULL);
+ } else {
+ /* unknown, set as environment */
+ //FIXME
+ env_put(t_strconcat("set_", *args, NULL));
+ }
+ }
+}
+
+static void drop_privileges(struct service *service,
+ const char *const *auth_args)
+{
+ struct master_settings *master_set = service->set->master_set;
+ struct restrict_access_settings rset;
+ const char *user, *home = NULL;
+
+ restrict_access_init(&rset);
+ rset.uid = service->uid;
+ rset.gid = service->gid;
+ rset.privileged_gid = service->privileged_gid;
+ rset.chroot_dir = *service->set->chroot == '\0' ? NULL :
+ service->set->chroot;
+ rset.extra_groups = service->extra_gids;
+
+ if (auth_args == NULL) {
+ /* non-authenticating service. don't use *_valid_gid checks */
+ } else {
+ i_assert(auth_args[0] != NULL);
+
+ rset.first_valid_gid = master_set->first_valid_gid;
+ rset.last_valid_gid = master_set->last_valid_gid;
+
+ user = auth_args[0];
+ env_put(t_strconcat("USER=", user, NULL));
+
+ auth_args_apply(auth_args + 1, &rset, &home);
+
+ if (!validate_uid_gid(master_set, rset.uid, rset.gid, user))
+ exit(FATAL_DEFAULT);
+ }
+
+ if (home != NULL) {
+ // FIXME: warn ENOENT if mail_debug=yes
+ if (chdir(home) < 0 && errno != ENOENT)
+ i_error("chdir(%s) failed: %m", home);
+ }
+
+ if (service->set->drop_priv_before_exec)
+ restrict_access(&rset, home, FALSE); //FIXME: disallow_root?
+ else
+ restrict_access_set_env(&rset);
+}
+
+static void
+service_process_setup_environment(struct service *service, unsigned int uid)
+{
+ const struct master_service_settings *set;
+ struct service_listener *const *listeners;
+ const char *const *p;
+ unsigned int limit, count;
+
+ /* remove all environment, and put back what we need */
+ env_clean();
+ for (p = service->list->child_process_env; *p != NULL; p++)
+ env_put(*p);
+
+ switch (service->type) {
+ case SERVICE_TYPE_CONFIG:
+ env_put(t_strconcat(MASTER_CONFIG_FILE_ENV"=",
+ service->config_file_path, NULL));
+ break;
+ case SERVICE_TYPE_LOG:
+ /* give the log's configuration directly, so it won't depend
+ on config process */
+ set = master_service_settings_get(master_service);
+ env_put("DOVECONF_ENV=1");
+ env_put(t_strconcat("LOG_PATH=", set->log_path, NULL));
+ env_put(t_strconcat("INFO_LOG_PATH=", set->info_log_path, NULL));
+ env_put(t_strconcat("LOG_TIMESTAMP=", set->log_timestamp, NULL));
+ env_put(t_strconcat("SYSLOG_FACILITY=", set->syslog_facility, NULL));
+ break;
+ default:
+ listeners = array_get(&service->list->config->listeners,
+ &count);
+ i_assert(count > 0);
+ env_put(t_strconcat(MASTER_CONFIG_FILE_ENV"=",
+ listeners[0]->set.fileset.set->path, NULL));
+ break;
+ }
+
+ limit = service->set->client_limit;
+ if (limit == 0) {
+ /* fallback to default limit */
+ limit = service->set->master_set->default_client_limit;
+ }
+
+ env_put(t_strdup_printf(MASTER_CLIENT_LIMIT_ENV"=%u", limit));
+ env_put(t_strdup_printf(MASTER_UID_ENV"=%u", uid));
+
+ if (!service->set->master_set->version_ignore)
+ env_put(MASTER_DOVECOT_VERSION_ENV"="PACKAGE_VERSION);
+}
+
+static void service_process_status_timeout(struct service_process *process)
+{
+ i_error("service(%s): Initial status notification not received in %d "
+ "seconds, killing the process", process->service->name,
+ SERVICE_FIRST_STATUS_TIMEOUT_SECS);
+ if (kill(process->pid, SIGKILL) < 0 && errno != ESRCH) {
+ i_error("service(%s): kill(%s, SIGKILL) failed: %m",
+ process->service->name, dec2str(process->pid));
+ }
+ timeout_remove(&process->to_status);
+}
+
+struct service_process *
+service_process_create(struct service *service, const char *const *auth_args,
+ int std_fd, const unsigned char *data, size_t data_size)
+{
+ static unsigned int uid_counter = 0;
+ struct service_process *process;
+ unsigned int uid = ++uid_counter;
+ string_t *str;
+ int fd[2];
+ pid_t pid;
+
+ switch (service->type) {
+ case SERVICE_TYPE_AUTH_SOURCE:
+ case SERVICE_TYPE_AUTH_SERVER:
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) {
+ i_error("service(%s): socketpair() failed: %m",
+ service->name);
+ return NULL;
+ }
+ fd_close_on_exec(fd[0], TRUE);
+ fd_close_on_exec(fd[1], TRUE);
+ break;
+ default:
+ fd[0] = fd[1] = -1;
+ break;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ i_error("service(%s): fork() failed: %m", service->name);
+ if (fd[0] != -1) {
+ (void)close(fd[0]);
+ (void)close(fd[1]);
+ }
+ return NULL;
+ }
+ if (pid == 0) {
+ /* child */
+ const char **args;
+
+ if (fd[0] != -1)
+ (void)close(fd[0]);
+ service_process_setup_environment(service, uid);
+ if (data_size > 0) {
+ str = t_str_new(data_size*3);
+ str_append(str, "CLIENT_INPUT=");
+ base64_encode(data, data_size, str);
+ env_put(str_c(str));
+ }
+ args = service_dup_fds(service, fd[1], std_fd);
+ drop_privileges(service, auth_args);
+ process_exec(service->executable, args);
+ }
+
+ switch (service->type) {
+ case SERVICE_TYPE_AUTH_SERVER:
+ process = i_malloc(sizeof(struct service_process_auth_server));
+ process->service = service;
+ service_process_auth_server_init(process, fd[0]);
+ (void)close(fd[1]);
+ break;
+ case SERVICE_TYPE_AUTH_SOURCE:
+ process = i_malloc(sizeof(struct service_process_auth_source));
+ process->service = service;
+ service_process_auth_source_init(process, fd[0]);
+ (void)close(fd[1]);
+ break;
+ default:
+ process = i_new(struct service_process, 1);
+ process->service = service;
+ i_assert(fd[0] == -1);
+ break;
+ }
+
+ process->refcount = 1;
+ process->pid = pid;
+ process->uid = uid;
+ process->to_status =
+ timeout_add(SERVICE_FIRST_STATUS_TIMEOUT_SECS * 1000,
+ service_process_status_timeout, process);
+
+ process->available_count = service->set->client_limit;
+ if (process->available_count == 0) {
+ /* fallback to default limit */
+ process->available_count =
+ service->set->master_set->default_client_limit;
+ }
+
+ service->process_count++;
+ service->process_avail++;
+
+ hash_table_insert(service->list->pids, &process->pid, process);
+ return process;
+}
+
+void service_process_destroy(struct service_process *process)
+{
+ struct service *service = process->service;
+ const char *data;
+
+ hash_table_remove(service->list->pids, &process->pid);
+
+ if (process->available_count > 0)
+ service->process_avail--;
+ service->process_count--;
+ i_assert(service->process_avail <= service->process_count);
+
+ if (process->to_status != NULL)
+ timeout_remove(&process->to_status);
+
+ switch (process->service->type) {
+ case SERVICE_TYPE_AUTH_SERVER:
+ service_process_auth_server_deinit(process);
+ break;
+ case SERVICE_TYPE_AUTH_SOURCE:
+ service_process_auth_source_deinit(process);
+ break;
+ default:
+ break;
+ }
+
+ data = t_strdup_printf("\001%c%s bye\n",
+ LOG_TYPE_OPTION+1, dec2str(process->pid));
+ if (write(process->service->log_fd[1], data, strlen(data)) < 0) {
+ if (errno != EAGAIN)
+ i_error("write(log process) failed: %m");
+ else {
+ //FIXME:process->io_log_write = io_add();
+ //return;
+ }
+ }
+
+ process->destroyed = TRUE;
+ service_process_unref(process);
+}
+
+void service_process_ref(struct service_process *process)
+{
+ i_assert(process->refcount > 0);
+
+ process->refcount++;
+}
+
+int service_process_unref(struct service_process *process)
+{
+ i_assert(process->refcount > 0);
+
+ if (--process->refcount > 0)
+ return TRUE;
+
+ i_assert(process->destroyed);
+
+ i_free(process);
+ return FALSE;
+}
+
+static const char *
+get_exit_status_message(struct service *service, enum fatal_exit_status status)
+{
+ 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:
+ if (service->set->vsz_limit == 0)
+ return "Out of memory";
+ return t_strdup_printf("Out of memory (vsz_limit=%u MB)",
+ service->set->vsz_limit);
+ case FATAL_EXEC:
+ return "exec() failed";
+
+ case FATAL_DEFAULT:
+ return "Fatal failure";
+ }
+
+ return NULL;
+}
+
+static void log_coredump(struct service *service, string_t *str, int status)
+{
+#ifdef WCOREDUMP
+ 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;
+ }
+
+#ifdef HAVE_PR_SET_DUMPABLE
+ if (!service->set->drop_priv_before_exec) {
+ str_append(str, " (core not dumped - set drop_priv_before_exec=yes)");
+ return;
+ }
+ if (*service->set->privileged_group != '\0') {
+ str_append(str, " (core not dumped - privileged_group prevented it)");
+ return;
+ }
+#endif
+
+ str_append(str, " (core not dumped)");
+#endif
+}
+
+static void
+service_process_get_status_error(string_t *str, struct service_process *process,
+ int status, enum log_type *type_r)
+{
+ struct service *service = process->service;
+ const char *msg;
+
+ *type_r = LOG_TYPE_ERROR;
+
+ str_printfa(str, "service(%s): child %s ", service->name,
+ dec2str(process->pid));
+ if (WIFSIGNALED(status)) {
+ str_printfa(str, "killed with signal %d", WTERMSIG(status));
+ log_coredump(service, str, status);
+ return;
+ }
+ if (!WIFEXITED(status)) {
+ str_printfa(str, "died with status %d", status);
+ return;
+ }
+
+ status = WEXITSTATUS(status);
+ if (status == 0) {
+ str_truncate(str, 0);
+ return;
+ }
+ str_printfa(str, "returned error %d", status);
+
+ msg = get_exit_status_message(service, status);
+ if (msg != NULL)
+ str_printfa(str, " (%s)", msg);
+
+ if (status == FATAL_DEFAULT)
+ *type_r = LOG_TYPE_ERROR_IGNORE_IF_SEEN_FATAL;
+}
+
+static void service_process_log(struct service_process *process,
+ enum log_type type, const char *str)
+{
+ const char *data;
+
+ if (type != LOG_TYPE_ERROR_IGNORE_IF_SEEN_FATAL ||
+ process->service->log_fd[1] == -1) {
+ i_log_type(type, "%s", str);
+ return;
+ }
+
+ /* log it via the log process in charge of handling
+ this process's logging */
+ data = t_strdup_printf("\001%c%s %s %s\n",
+ type+1, my_pid, dec2str(process->pid), str);
+ if (write_full(process->service->log_fd[1], data, strlen(data)) < 0) {
+ i_error("write(log process) failed: %m");
+ i_log_type(type, "%s", str);
+ }
+}
+
+void service_process_log_status_error(struct service_process *process,
+ int status)
+{
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ /* fast path */
+ return;
+ }
+ T_BEGIN {
+ string_t *str = t_str_new(256);
+ enum log_type type;
+
+ service_process_get_status_error(str, process, status, &type);
+ if (str_len(str) > 0)
+ service_process_log(process, type, str_c(str));
+ } T_END;
+}
--- /dev/null
+#ifndef SERVICE_PROCESS_H
+#define SERVICE_PROCESS_H
+
+struct service_process {
+ struct service *service;
+ int refcount;
+
+ 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;
+ /* number of connections process has ever accepted */
+ unsigned int total_count;
+
+ /* time when process started idling, or 0 if we're not idling */
+ time_t idle_start;
+
+ /* kill the process if it doesn't send initial status notification */
+ struct timeout *to_status;
+
+ unsigned int destroyed:1;
+};
+
+struct service_process_auth_server {
+ struct service_process process;
+
+ int auth_fd;
+ struct io *io_auth;
+ struct ostream *auth_output;
+ struct istream *auth_input;
+
+ /* pending authentication requests that are being verified from
+ auth server. */
+ struct hash_table *auth_requests;
+ /* Last time we wrote "authentication server is too busy" to log */
+ time_t auth_busy_stamp;
+ /* Tag counter for outgoing requests */
+ unsigned int auth_tag_counter;
+
+ unsigned int auth_version_sent:1;
+ unsigned int auth_version_received:1;
+};
+
+struct service_process_auth_source {
+ struct service_process process;
+
+ int auth_fd;
+ struct io *io_auth;
+ struct ostream *auth_output;
+};
+
+struct service_process_auth_request {
+ struct service_process_auth_source *process;
+
+ unsigned int process_tag;
+ int fd;
+
+ struct ip_addr local_ip, remote_ip;
+ unsigned int data_size;
+ unsigned char data[FLEXIBLE_ARRAY_MEMBER];
+};
+
+struct service_process *
+service_process_create(struct service *service, const char *const *auth_args,
+ int std_fd, const unsigned char *data, size_t data_size);
+void service_process_destroy(struct service_process *process);
+
+void service_process_ref(struct service_process *process);
+int service_process_unref(struct service_process *process);
+
+void service_process_log_status_error(struct service_process *process,
+ int status);
+
+#endif
--- /dev/null
+/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "array.h"
+#include "hash.h"
+#include "str.h"
+#include "service.h"
+#include "service-process.h"
+#include "service-monitor.h"
+
+#include <unistd.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+
+static int get_uid(const char *user, uid_t *uid_r, const char **error_r)
+{
+ struct passwd *pw;
+
+ if (*user == '\0') {
+ *uid_r = (uid_t)-1;
+ return 0;
+ }
+
+ if ((pw = getpwnam(user)) == NULL) {
+ *error_r = t_strdup_printf("User doesn't exist: %s", user);
+ return -1;
+ }
+
+ *uid_r = pw->pw_uid;
+ return 0;
+}
+
+static int get_gid(const char *group, gid_t *gid_r, const char **error_r)
+{
+ struct group *gr;
+
+ if (*group == '\0') {
+ *gid_r = (uid_t)-1;
+ return 0;
+ }
+
+ if ((gr = getgrnam(group)) == NULL) {
+ *error_r = t_strdup_printf("Group doesn't exist: %s", group);
+ return -1;
+ }
+
+ *gid_r = gr->gr_gid;
+ return 0;
+}
+
+static struct service_listener *
+service_create_file_listener(struct service *service,
+ enum service_listener_type type,
+ const struct file_listener_settings *set,
+ const char **error_r)
+{
+ struct service_listener *l;
+
+ l = p_new(service->list->pool, struct service_listener, 1);
+ l->service = service;
+ l->type = type;
+ l->fd = -1;
+ l->set.fileset.set = set;
+
+ if (get_uid(set->user, &l->set.fileset.uid, error_r) < 0)
+ return NULL;
+ if (get_gid(set->group, &l->set.fileset.gid, error_r) < 0)
+ return NULL;
+ return l;
+}
+
+static int
+resolve_ip(const char *address, struct ip_addr *ip_r, const char **error_r)
+{
+ struct ip_addr *ip_list;
+ unsigned int ips_count;
+ int ret;
+
+ if (address == NULL || strcmp(address, "*") == 0) {
+ /* IPv4 any */
+ net_get_ip_any4(ip_r);
+ return 0;
+ }
+
+ if (strcmp(address, "::") == 0) {
+ /* IPv6 any */
+ net_get_ip_any6(ip_r);
+ return 0;
+ }
+
+ /* Return the first IP if there happens to be multiple. */
+ ret = net_gethostbyname(address, &ip_list, &ips_count);
+ if (ret != 0) {
+ *error_r = t_strdup_printf("Can't resolve address %s: %s",
+ address, net_gethosterror(ret));
+ return -1;
+ }
+
+ if (ips_count < 1) {
+ *error_r = t_strdup_printf("No IPs for address: %s", address);
+ return -1;
+ }
+ if (ips_count > 1) {
+ *error_r = t_strdup_printf("Multiple IPs for address: %s",
+ address);
+ return -1;
+ }
+
+ *ip_r = ip_list[0];
+ return 0;
+}
+
+static struct service_listener *
+service_create_inet_listener(struct service *service,
+ const struct inet_listener_settings *set,
+ const char **error_r)
+{
+ struct service_listener *l;
+
+ l = p_new(service->list->pool, struct service_listener, 1);
+ l->service = service;
+ l->type = SERVICE_LISTENER_INET;
+ l->fd = -1;
+ l->set.inetset.set = set;
+
+ if (resolve_ip(set->address, &l->set.inetset.ip, error_r) < 0)
+ return NULL;
+
+ if (set->port == 0) {
+ *error_r = "Port not given";
+ return NULL;
+ }
+ if (set->port > 65535) {
+ *error_r = t_strdup_printf("Invalid port: %u", set->port);
+ return NULL;
+ }
+
+ return l;
+}
+
+static struct service *
+service_create(pool_t pool, const struct service_settings *set,
+ struct service_list *service_list, const char **error_r)
+{
+ struct file_listener_settings *const *unix_listeners;
+ struct file_listener_settings *const *fifo_listeners;
+ struct inet_listener_settings *const *inet_listeners;
+ struct service *service;
+ struct service_listener *l;
+ const char *p, *const *tmp;
+ string_t *str;
+ unsigned int i, unix_count, fifo_count, inet_count;
+
+ service = p_new(pool, struct service, 1);
+ service->list = service_list;
+ service->set = set;
+
+ service->type = SERVICE_TYPE_UNKNOWN;
+ if (*set->type != '\0') {
+ service->name = set->type;
+ if (strcmp(set->type, "log") == 0)
+ service->type = SERVICE_TYPE_LOG;
+ else if (strcmp(set->type, "config") == 0)
+ service->type = SERVICE_TYPE_CONFIG;
+ else if (strcmp(set->type, "auth") == 0)
+ service->type = SERVICE_TYPE_AUTH_SERVER;
+ else
+ service->name = NULL;
+ }
+
+ if (*set->auth_dest_service != '\0')
+ service->type = SERVICE_TYPE_AUTH_SOURCE;
+
+ if (set->process_limit == 0) {
+ /* unlimited */
+ service->process_limit = INT_MAX;
+ } else if (set->process_limit == (unsigned int)-1) {
+ /* use default */
+ service->process_limit =
+ set->master_set->default_process_limit;
+ } else {
+ service->process_limit = set->process_limit;
+ }
+
+ if (set->executable == NULL) {
+ *error_r = "executable not given";
+ return NULL;
+ }
+
+ /* get service name from some of the unique types, fallback to
+ executable name without path and parameters */
+ if (service->name == NULL) {
+ p = strrchr(set->executable, '/');
+ if (p == NULL)
+ service->name = t_strcut(set->executable, ' ');
+ else
+ service->name = t_strcut(p + 1, ' ');
+ service->name = p_strdup(pool, service->name);
+ }
+
+ if (get_uid(set->user, &service->uid, error_r) < 0)
+ return NULL;
+ if (get_gid(set->group, &service->gid, error_r) < 0)
+ return NULL;
+ if (get_gid(set->privileged_group, &service->privileged_gid,
+ error_r) < 0)
+ return NULL;
+
+ if (*set->extra_groups != '\0') {
+ str = t_str_new(64);
+ tmp = t_strsplit(set->extra_groups, ",");
+ for (; *tmp != NULL; tmp++) {
+ gid_t gid;
+
+ if (get_gid(*tmp, &gid, error_r) < 0)
+ return NULL;
+
+ if (str_len(str) > 0)
+ str_append_c(str, ',');
+ str_append(str, dec2str(gid));
+ }
+ service->extra_gids = p_strdup(pool, str_c(str));
+ }
+
+ if (*set->executable == '/')
+ service->executable = set->executable;
+ else {
+ service->executable =
+ p_strconcat(pool, set->master_set->libexec_dir, "/",
+ set->executable, NULL);
+ }
+ if (access(t_strcut(service->executable, ' '), X_OK) < 0) {
+ *error_r = t_strdup_printf("access(%s) failed: %m",
+ t_strcut(service->executable, ' '));
+ return NULL;
+ }
+
+ /* set these later, so if something fails we don't have to worry about
+ closing them */
+ service->log_fd[0] = -1;
+ service->log_fd[1] = -1;
+ service->status_fd[0] = -1;
+ service->status_fd[1] = -1;
+
+ if (array_is_created(&set->unix_listeners))
+ unix_listeners = array_get(&set->unix_listeners, &unix_count);
+ else {
+ unix_listeners = NULL;
+ unix_count = 0;
+ }
+ if (array_is_created(&set->fifo_listeners))
+ fifo_listeners = array_get(&set->unix_listeners, &fifo_count);
+ else {
+ fifo_listeners = NULL;
+ fifo_count = 0;
+ }
+ if (array_is_created(&set->inet_listeners))
+ inet_listeners = array_get(&set->inet_listeners, &inet_count);
+ else {
+ inet_listeners = NULL;
+ inet_count = 0;
+ }
+
+ if (unix_count == 0 && service->type == SERVICE_TYPE_CONFIG) {
+ *error_r = "Service must have unix listeners";
+ return NULL;
+ }
+
+ p_array_init(&service->listeners, pool,
+ unix_count + fifo_count + inet_count);
+
+ for (i = 0; i < unix_count; i++) {
+ l = service_create_file_listener(service, SERVICE_LISTENER_UNIX,
+ unix_listeners[i], error_r);
+ if (l == NULL)
+ return NULL;
+ array_append(&service->listeners, &l, 1);
+ }
+ for (i = 0; i < fifo_count; i++) {
+ l = service_create_file_listener(service, SERVICE_LISTENER_UNIX,
+ fifo_listeners[i], error_r);
+ if (l == NULL)
+ return NULL;
+ array_append(&service->listeners, &l, 1);
+ }
+ for (i = 0; i < inet_count; i++) {
+ l = service_create_inet_listener(service, inet_listeners[i],
+ error_r);
+ if (l == NULL)
+ return NULL;
+ array_append(&service->listeners, &l, 1);
+ }
+
+ return service;
+}
+
+static unsigned int pid_hash(const void *p)
+{
+ const pid_t *pid = p;
+
+ return (unsigned int)*pid;
+}
+
+static int pid_hash_cmp(const void *p1, const void *p2)
+{
+ const pid_t *pid1 = p1, *pid2 = p2;
+
+ return *pid1 < *pid2 ? -1 :
+ *pid1 > *pid2 ? 1 : 0;
+}
+
+static struct service *
+service_lookup(struct service_list *service_list, const char *name)
+{
+ struct service *const *services;
+ unsigned int i, count;
+
+ services = array_get(&service_list->services, &count);
+ for (i = 0; i < count; i++) {
+ if (strcmp(services[i]->name, name) == 0)
+ return services[i];
+ }
+ return NULL;
+}
+
+struct service_list *
+services_create(const struct master_settings *set,
+ const char *const *child_process_env, const char **error_r)
+{
+ struct service_list *service_list;
+ struct service *service, *const *services;
+ struct service_settings *const *service_settings;
+ pool_t pool;
+ const char *error;
+ unsigned int i, count;
+
+ pool = pool_alloconly_create("services pool", 4096);
+
+ service_list = p_new(pool, struct service_list, 1);
+ service_list->pool = pool;
+ service_list->child_process_env = child_process_env;
+
+ service_settings = array_get(&set->services, &count);
+ p_array_init(&service_list->services, pool, count);
+
+ for (i = 0; i < count; i++) {
+ service = service_create(pool, service_settings[i],
+ service_list, &error);
+ if (service == NULL) {
+ *error_r = t_strdup_printf("service(%s) %s",
+ service_settings[i]->type == NULL ? "unknown" :
+ service_settings[i]->type, error);
+ return NULL;
+ }
+
+ switch (service->type) {
+ case SERVICE_TYPE_LOG:
+ if (service_list->log != NULL) {
+ *error_r = "Multiple log services specified";
+ return NULL;
+ }
+ service_list->log = service;
+ break;
+ case SERVICE_TYPE_CONFIG:
+ if (service_list->config != NULL) {
+ *error_r = "Multiple config services specified";
+ return NULL;
+ }
+ service_list->config = service;
+ break;
+ default:
+ break;
+ }
+
+ array_append(&service_list->services, &service, 1);
+ }
+
+ /* resolve service dependencies */
+ services = array_get(&service_list->services, &count);
+ for (i = 0; i < count; i++) {
+ if (services[i]->type == SERVICE_TYPE_AUTH_SOURCE) {
+ const char *dest_service =
+ services[i]->set->auth_dest_service;
+ services[i]->auth_dest_service =
+ service_lookup(service_list, dest_service);
+ if (services[i]->auth_dest_service == NULL) {
+ *error_r = t_strdup_printf(
+ "auth_dest_service doesn't exist: %s",
+ dest_service);
+ return NULL;
+ }
+ }
+ }
+
+ if (service_list->log == NULL) {
+ *error_r = "log service not specified";
+ return NULL;
+ }
+
+ if (service_list->config == NULL) {
+ *error_r = "config process not specified";
+ return NULL;
+ }
+
+ service_list->pids = hash_table_create(default_pool, pool, 0,
+ pid_hash, pid_hash_cmp);
+ return service_list;
+}
+
+void service_signal(struct service *service, int signo)
+{
+ struct hash_iterate_context *iter;
+ void *key, *value;
+
+ iter = hash_table_iterate_init(service->list->pids);
+ while (hash_table_iterate(iter, &key, &value)) {
+ struct service_process *process = value;
+
+ if (process->service != service)
+ continue;
+
+ if (kill(process->pid, signo) < 0 && errno != ESRCH) {
+ i_error("service(%s): kill(%s, %d) failed: %m",
+ service->name, dec2str(process->pid), signo);
+ }
+ }
+ hash_table_iterate_deinit(&iter);
+}
+
+void services_destroy(struct service_list *service_list)
+{
+ struct hash_iterate_context *iter;
+ void *key, *value;
+
+ /* make sure we log if child processes died unexpectedly */
+ services_monitor_reap_children(service_list);
+
+ services_monitor_stop(service_list);
+
+ /* free all child process information */
+ iter = hash_table_iterate_init(service_list->pids);
+ while (hash_table_iterate(iter, &key, &value))
+ service_process_destroy(value);
+ hash_table_iterate_deinit(&iter);
+
+ hash_table_destroy(&service_list->pids);
+ pool_unref(&service_list->pool);
+}
--- /dev/null
+#ifndef SERVICE_H
+#define SERVICE_H
+
+#include "network.h"
+
+/* If a service process doesn't send its first status notification in
+ this many seconds, kill the process */
+#define SERVICE_FIRST_STATUS_TIMEOUT_SECS 30
+
+enum service_type {
+ SERVICE_TYPE_UNKNOWN,
+ SERVICE_TYPE_LOG,
+ SERVICE_TYPE_CONFIG,
+ SERVICE_TYPE_AUTH_SERVER,
+ SERVICE_TYPE_AUTH_SOURCE
+};
+
+enum service_listener_type {
+ SERVICE_LISTENER_UNIX,
+ SERVICE_LISTENER_FIFO,
+ SERVICE_LISTENER_INET
+};
+
+struct service_listener {
+ struct service *service;
+
+ enum service_listener_type type;
+ int fd; /* may be -1 */
+ struct io *io;
+
+ union {
+ struct {
+ const struct file_listener_settings *set;
+ uid_t uid;
+ gid_t gid;
+ } fileset;
+ struct {
+ const struct inet_listener_settings *set;
+ struct ip_addr ip;
+ } inetset;
+ } set;
+};
+
+struct service {
+ struct service_list *list;
+
+ enum service_type type;
+ const char *name;
+
+ const struct service_settings *set;
+ const char *config_file_path;
+
+ const char *executable;
+ uid_t uid;
+ gid_t gid;
+ gid_t privileged_gid;
+ const char *extra_gids; /* comma-separated list */
+
+ /* all listeners, even those that aren't currently listening */
+ ARRAY_DEFINE(listeners, struct service_listener *);
+
+ /* number of processes currently created for this service */
+ unsigned int process_count;
+ /* number of processes currently accepting new connections */
+ unsigned int process_avail;
+ /* max number of processes allowed */
+ unsigned int process_limit;
+
+ /* log process pipe file descriptors */
+ int log_fd[2];
+
+ /* status report pipe file descriptors */
+ int status_fd[2];
+ struct io *io_status;
+
+ /* if a process fails before servicing its first request, assume it's
+ broken and start throtting new process creations */
+ struct timeout *to_throttle;
+
+ /* SERVICE_TYPE_AUTH_SOURCE: Destination service to run after
+ successful authentication. */
+ struct service *auth_dest_service;
+
+ /* all processes are in use and new connections are coming */
+ unsigned int listen_pending:1;
+};
+
+struct service_list {
+ pool_t pool;
+
+ struct service *config;
+ struct service *log;
+ struct hash_table *pids;
+ const char *const *child_process_env;
+
+ ARRAY_DEFINE(services, struct service *);
+};
+
+/* Create all services from settings */
+struct service_list *
+services_create(const struct master_settings *set,
+ const char *const *child_process_env, const char **error_r);
+
+/* Destroy services */
+void services_destroy(struct service_list *service_list);
+
+/* Send a signal to all processes in a given service */
+void service_signal(struct service *service, int signo);
+
+#endif
+++ /dev/null
-/* Copyright (c) 2008-2009 Dovecot authors, see the included COPYING file */
-
-#include "lib.h"
-#include "mountpoint.h"
-#include "strescape.h"
-#include "sysinfo-get.h"
-
-#include <unistd.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#ifdef HAVE_SYS_UTSNAME_H
-# include <sys/utsname.h>
-#endif
-
-static bool readfile(const char *path, const char **data_r)
-{
- char buf[1024];
- int fd, ret;
-
- fd = open(path, O_RDONLY);
- if (fd == -1)
- return FALSE;
- ret = read(fd, buf, sizeof(buf));
- (void)close(fd);
- if (ret <= 0)
- return FALSE;
-
- *data_r = t_strndup(buf, ret);
- return TRUE;
-}
-
-static bool lsb_distro_get(const char *path, const char **name_r)
-{
- const char *data, *const *p, *str, *end;
-
- if (!readfile(path, &data))
- return FALSE;
-
- for (p = t_strsplit(data, "\n"); *p != '\0'; p++) {
- if (strncmp(*p, "DISTRIB_DESCRIPTION=", 20) == 0)
- break;
- }
- if (*p == '\0')
- return FALSE;
-
- str = t_strcut(*p + 20, '\n');
- if (*str != '"')
- *name_r = str;
- else {
- end = strrchr(++str, '"');
- *name_r = str_unescape(p_strdup_until(unsafe_data_stack_pool,
- str, end));
- }
- return TRUE;
-}
-
-static const char *distro_get(void)
-{
- static const char *files[] = {
- "", "/etc/redhat-release",
- "", "/etc/SuSE-release",
- "", "/etc/mandriva-release",
- "", "/etc/fedora-release",
- "", "/etc/sourcemage-release",
- "", "/etc/slackware-version",
- "", "/etc/gentoo-release",
- "Debian ", "/etc/debian_version",
- NULL
- };
- const char *name;
- unsigned int i;
-
- if (lsb_distro_get("/etc/lsb-release", &name))
- return name;
- for (i = 0; files[i] != NULL; i += 2) {
- if (readfile(files[i+1], &name)) {
- return t_strconcat(files[i], t_strcut(name, '\n'),
- NULL);
- }
- }
- return "";
-}
-
-static const char *filesystem_get(const char *mail_location)
-{
- struct mountpoint mp;
- const char *path;
-
- path = strchr(mail_location, ':');
- if (path == NULL)
- path = mail_location;
- else
- path = t_strcut(path + 1, ':');
- if (*path == '~') {
- /* we don't know where users' home dirs are */
- return "";
- }
- path = t_strcut(path, '%');
- if (strlen(path) <= 1)
- return "";
-
- /* all in all it seems we can support only /<path>/%u style location */
- if (mountpoint_get(path, pool_datastack_create(), &mp) < 0)
- return "";
- return mp.type == NULL ? "" : mp.type;
-}
-
-const char *sysinfo_get(const char *mail_location)
-{
- const char *distro = "", *fs, *uname_info = "";
-#ifdef HAVE_SYS_UTSNAME_H
- struct utsname u;
-
- if (uname(&u) < 0)
- i_error("uname() failed: %m");
- else {
- uname_info = t_strdup_printf("%s %s %s",
- u.sysname, u.release, u.machine);
- }
- if (strcmp(u.sysname, "Linux") == 0)
- distro = distro_get();
-#endif
- fs = filesystem_get(mail_location);
- if (*uname_info == '\0' && *distro == '\0' && *fs == '\0')
- return "";
- return t_strdup_printf("OS: %s %s %s %s %s", u.sysname, u.release, u.machine, distro, fs);
-}
+++ /dev/null
-#ifndef SYSINFO_GET_H
-#define SYSINFO_GET_H
-
-const char *sysinfo_get(const char *mail_location);
-
-#endif
input.username = argv[optind];
master_service_init_log(service,
- t_strdup_printf("convert-tool(%s): ", input.username));
+ t_strdup_printf("convert-tool(%s): ", input.username), 0);
user = mail_storage_service_init_user(service, &input, NULL, 0);
memset(&ns_set, 0, sizeof(ns_set));
AM_CPPFLAGS = \
-I$(top_srcdir)/src/lib \
-I$(top_srcdir)/src/lib-auth \
+ -I$(top_srcdir)/src/lib-master \
-I$(top_srcdir)/src/login-common
pop3_login_LDADD = \
#include "process-title.h"
#include "safe-memset.h"
#include "strescape.h"
+#include "master-service.h"
+#include "master-auth.h"
#include "client.h"
#include "client-authenticate.h"
#include "auth-client.h"
#endif
const char *login_protocol = "POP3";
+const char *login_process_name = "pop3-login";
static void client_set_title(struct pop3_client *client)
{
int fd_ssl;
client_ref(client);
- connection_queue_add(1);
if (!client_unref(client) || client->destroyed)
return;
return;
}
+ client->common.proxying = TRUE;
client->common.tls = TRUE;
client->common.secured = TRUE;
client_set_title(client);
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);
client_open_streams(client, fd);
client_link(&client->common);
- main_ref();
-
client->auth_connected = auth_client_is_connected(auth_client);
if (client->auth_connected)
client_auth_ready(client);
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 {
client->common.proxy = NULL;
}
client_unref(client);
-
- main_listen_start();
- main_unref();
}
void client_destroy_internal_failure(struct pop3_client *client)
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->last_user);
i_free(client->apop_challenge);
i_free(client->common.virtual_user);
i_free(client->common.auth_mech_name);
i_free(client);
-
return FALSE;
}
#define CLIENT_H
#include "network.h"
-#include "master.h"
#include "client-common.h"
#include "auth-client.h"
client->common.input = NULL;
client->output = NULL;
client->common.fd = -1;
+ client->common.proxying = TRUE;
client_destroy_success(client, str_c(str));
return 1;
}
}
i_assert(client->refcount > 1);
- connection_queue_add(1);
if (client->destroyed) {
/* connection_queue_add() decided that we were the oldest
clients_deinit();
}
+static void client_connected(const struct master_service_connection *conn)
+{
+ /* 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[] = {
service = master_service_init("pop3", 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));
io_loop_set_running(current_ioloop);
if (main_init(set, mail_user))
- master_service_run(service);
+ master_service_run(service, client_connected);
main_deinit();
mail_storage_service_deinit_user();