doc/wiki/*.txt
doc/wiki/Makefile.am
+src/anvil/anvil
src/auth/checkpassword-reply
src/auth/dovecot-auth
src/config/all-settings.c
+src/config/config
src/config/doveconf
src/lda/dovecot-lda
src/dict/dict
- - mail_max_userip_connections
+ - move ssl proxying code to lib-master
+ - rawlog is broken because it can't get $HOME etc.
- 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,
src/lib-storage/index/raw/Makefile
src/lib-storage/index/shared/Makefile
src/lib-storage/register/Makefile
+src/anvil/Makefile
src/auth/Makefile
src/config/Makefile
src/lda/Makefile
type = config
executable = config
user = dovecot
- drop_priv_before_exec = yes
+
unix_listener {
path = config
+ mode = 0666
}
}
service log {
type = log
executable = log
+ process_limit = 1
+}
+
+service anvil {
+ type = anvil
+ executable = anvil
+ process_limit = 1
+ user = dovecot
+ chroot = empty
+
+ unix_listener {
+ path = anvil
+ }
}
service auth {
}
service imap {
- type = auth-destination
executable = imap
}
}
service pop3 {
- type = auth-destination
executable = pop3
}
--- /dev/null
+pkglibexecdir = $(libexecdir)/dovecot
+
+pkglibexec_PROGRAMS = anvil
+
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/lib \
+ -I$(top_srcdir)/src/lib-settings \
+ -I$(top_srcdir)/src/lib-master
+
+anvil_LDADD = \
+ $(LIBDOVECOT) \
+ $(MODULE_LIBS) \
+ $(RAND_LIBS)
+anvil_DEPENDENCIES = $(LIBDOVECOT)
+
+anvil_SOURCES = \
+ main.c \
+ anvil-connection.c \
+ connect-limit.c
+
+noinst_HEADERS = \
+ anvil-connection.h \
+ common.h \
+ connect-limit.h
--- /dev/null
+/* Copyright (C) 2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "llist.h"
+#include "istream.h"
+#include "ostream.h"
+#include "master-interface.h"
+#include "connect-limit.h"
+#include "anvil-connection.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define MAX_INBUF_SIZE 1024
+
+#define ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION 1
+#define ANVIL_CLIENT_PROTOCOL_MINOR_VERSION 0
+
+struct anvil_connection {
+ struct anvil_connection *prev, *next;
+
+ int fd;
+ struct istream *input;
+ struct ostream *output;
+ struct io *io;
+
+ unsigned int version_received:1;
+ unsigned int handshaked:1;
+};
+
+struct anvil_connection *anvil_connections = NULL;
+
+static const char *const *
+anvil_connection_next_line(struct anvil_connection *conn)
+{
+ const char *line;
+
+ line = i_stream_next_line(conn->input);
+ if (line == NULL)
+ return NULL;
+
+ return t_strsplit(line, "\t");
+}
+
+static int
+anvil_connection_request(struct anvil_connection *conn,
+ const char *const *args, const char **error_r)
+{
+ const char *cmd = args[0];
+ unsigned int count;
+ pid_t pid;
+
+ args++;
+ if (strcmp(cmd, "CONNECT") == 0) {
+ if (args[0] == NULL || args[1] == NULL) {
+ *error_r = "CONNECT: Not enough parameters";
+ return -1;
+ }
+ pid = strtol(args[0], NULL, 10);
+ connect_limit_connect(connect_limit, pid, args[1]);
+ return 0;
+ } else if (strcmp(cmd, "DISCONNECT") == 0) {
+ if (args[0] == NULL || args[1] == NULL) {
+ *error_r = "DISCONNECT: Not enough parameters";
+ return -1;
+ }
+ pid = strtol(args[0], NULL, 10);
+ connect_limit_disconnect(connect_limit, pid, args[1]);
+ return 0;
+ } else if (strcmp(cmd, "KILL") == 0) {
+ if (args[0] == NULL) {
+ *error_r = "KILL: Not enough parameters";
+ return -1;
+ }
+ if (conn->fd != MASTER_LISTEN_FD_FIRST) {
+ *error_r = "KILL sent by a non-master connection";
+ return -1;
+ }
+ pid = strtol(args[0], NULL, 10);
+ connect_limit_disconnect_pid(connect_limit, pid);
+ return 0;
+ } else if (strcmp(cmd, "LOOKUP") == 0) {
+ if (args[0] == NULL) {
+ *error_r = "LOOKUP: Not enough parameters";
+ return -1;
+ }
+ count = connect_limit_lookup(connect_limit, args[0]);
+ (void)o_stream_send_str(conn->output,
+ t_strdup_printf("%u\n", count));
+ return 0;
+ } else {
+ *error_r = t_strconcat("Unknown command: ", cmd, NULL);
+ return -1;
+ }
+}
+
+static void anvil_connection_input(void *context)
+{
+ struct anvil_connection *conn = context;
+ const char *const *args, *line, *error;
+
+ switch (i_stream_read(conn->input)) {
+ case -2:
+ i_error("BUG: Anvil client connection sent too much data");
+ anvil_connection_destroy(conn);
+ return;
+ case -1:
+ anvil_connection_destroy(conn);
+ return;
+ }
+
+ if (!conn->version_received) {
+ line = i_stream_next_line(conn->input);
+ if (line == NULL)
+ return;
+
+ if (strncmp(line, "VERSION\t", 8) != 0 ||
+ atoi(t_strcut(line + 8, '\t')) !=
+ ANVIL_CLIENT_PROTOCOL_MAJOR_VERSION) {
+ i_error("Anvil client not compatible with this server "
+ "(mixed old and new binaries?)");
+ anvil_connection_destroy(conn);
+ return;
+ }
+ conn->version_received = TRUE;
+ }
+
+ while ((args = anvil_connection_next_line(conn)) != NULL) {
+ if (args[0] != NULL) {
+ if (anvil_connection_request(conn, args, &error) < 0)
+ i_error("Anvil client input error: %s", error);
+ }
+ }
+}
+
+struct anvil_connection *anvil_connection_create(int fd)
+{
+ struct anvil_connection *conn;
+
+ conn = i_new(struct anvil_connection, 1);
+ conn->fd = 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, anvil_connection_input, conn);
+ DLLIST_PREPEND(&anvil_connections, conn);
+ return conn;
+}
+
+void anvil_connection_destroy(struct anvil_connection *conn)
+{
+ DLLIST_REMOVE(&anvil_connections, conn);
+
+ io_remove(&conn->io);
+ i_stream_destroy(&conn->input);
+ o_stream_destroy(&conn->output);
+ if (close(conn->fd) < 0)
+ i_error("close(anvil conn) failed: %m");
+ i_free(conn);
+}
+
+void anvil_connections_destroy_all(void)
+{
+ while (anvil_connections != NULL)
+ anvil_connection_destroy(anvil_connections);
+}
--- /dev/null
+#ifndef ANVIL_CONNECTION_H
+#define ANVIL_CONNECTION_H
+
+struct anvil_connection *anvil_connection_create(int fd);
+void anvil_connection_destroy(struct anvil_connection *conn);
+
+void anvil_connections_destroy_all(void);
+
+#endif
--- /dev/null
+#ifndef COMMON_H
+#define COMMON_H
+
+#include "lib.h"
+
+extern struct connect_limit *connect_limit;
+
+#endif
--- /dev/null
+/* Copyright (C) 2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "hash.h"
+#include "connect-limit.h"
+
+struct ident_pid {
+ /* ident string points to ident_hash keys */
+ const char *ident;
+ pid_t pid;
+ unsigned int refcount;
+};
+
+struct connect_limit {
+ /* ident => refcount */
+ struct hash_table *ident_hash;
+ /* struct ident_pid => struct ident_pid */
+ struct hash_table *ident_pid_hash;
+};
+
+static unsigned int ident_pid_hash(const void *p)
+{
+ const struct ident_pid *i = p;
+
+ return str_hash(i->ident) ^ i->pid;
+}
+
+static int ident_pid_cmp(const void *p1, const void *p2)
+{
+ const struct ident_pid *i1 = p1, *i2 = p2;
+
+ if (i1->pid < i2->pid)
+ return -1;
+ else if (i1->pid > i2->pid)
+ return 1;
+ else
+ return strcmp(i1->ident, i2->ident);
+}
+
+struct connect_limit *connect_limit_init(void)
+{
+ struct connect_limit *limit;
+
+ limit = i_new(struct connect_limit, 1);
+ limit->ident_hash =
+ hash_table_create(default_pool, default_pool, 0,
+ str_hash, (hash_cmp_callback_t *)strcmp);
+ limit->ident_pid_hash =
+ hash_table_create(default_pool, default_pool, 0,
+ ident_pid_hash, ident_pid_cmp);
+ return limit;
+}
+
+void connect_limit_deinit(struct connect_limit **_limit)
+{
+ struct connect_limit *limit = *_limit;
+
+ *_limit = NULL;
+ hash_table_destroy(&limit->ident_hash);
+ hash_table_destroy(&limit->ident_pid_hash);
+ i_free(limit);
+}
+
+unsigned int connect_limit_lookup(struct connect_limit *limit,
+ const char *ident)
+{
+ void *value;
+
+ value = hash_table_lookup(limit->ident_hash, ident);
+ if (value == NULL)
+ return 0;
+
+ return POINTER_CAST_TO(value, unsigned int);
+}
+
+void connect_limit_connect(struct connect_limit *limit, pid_t pid,
+ const char *ident)
+{
+ struct ident_pid *i, lookup_i;
+ void *key, *value;
+
+ if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value)) {
+ key = i_strdup(ident);
+ value = POINTER_CAST(1);
+ hash_table_insert(limit->ident_hash, key, value);
+ } else {
+ value = POINTER_CAST(POINTER_CAST_TO(value, unsigned int) + 1);
+ hash_table_update(limit->ident_hash, key, value);
+ }
+
+ lookup_i.ident = ident;
+ lookup_i.pid = pid;
+ i = hash_table_lookup(limit->ident_pid_hash, &lookup_i);
+ if (i == NULL) {
+ i = i_new(struct ident_pid, 1);
+ i->ident = key;
+ i->pid = pid;
+ i->refcount = 1;
+ hash_table_insert(limit->ident_pid_hash, i, i);
+ } else {
+ i->refcount++;
+ }
+}
+
+static void
+connect_limit_ident_hash_unref(struct connect_limit *limit, const char *ident)
+{
+ void *key, *value;
+ unsigned int new_refcount;
+
+ if (!hash_table_lookup_full(limit->ident_hash, ident, &key, &value))
+ i_panic("connect limit hash tables are inconsistent");
+
+ new_refcount = POINTER_CAST_TO(value, unsigned int) - 1;
+ if (new_refcount > 0) {
+ value = POINTER_CAST(new_refcount);
+ hash_table_update(limit->ident_hash, key, value);
+ } else {
+ hash_table_remove(limit->ident_hash, key);
+ i_free(key);
+ }
+}
+
+void connect_limit_disconnect(struct connect_limit *limit, pid_t pid,
+ const char *ident)
+{
+ struct ident_pid *i, lookup_i;
+
+ lookup_i.ident = ident;
+ lookup_i.pid = pid;
+
+ i = hash_table_lookup(limit->ident_pid_hash, &lookup_i);
+ if (i == NULL) {
+ i_error("connect limit: disconnection for unknown "
+ "pid %s + ident %s", dec2str(pid), ident);
+ return;
+ }
+
+ if (--i->refcount == 0) {
+ hash_table_remove(limit->ident_pid_hash, i);
+ i_free(i);
+ }
+
+ connect_limit_ident_hash_unref(limit, ident);
+}
+
+void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid)
+{
+ struct hash_iterate_context *iter;
+ struct ident_pid *i;
+ void *key, *value;
+
+ /* this should happen rarely (or never), so this slow implementation
+ should be fine. */
+ iter = hash_table_iterate_init(limit->ident_pid_hash);
+ while (hash_table_iterate(iter, &key, &value)) {
+ i = key;
+ if (i->pid == pid) {
+ hash_table_remove(limit->ident_pid_hash, i);
+ for (; i->refcount > 0; i->refcount--)
+ connect_limit_ident_hash_unref(limit, i->ident);
+ i_free(i);
+ }
+ }
+ hash_table_iterate_deinit(&iter);
+}
--- /dev/null
+#ifndef CONNECT_LIMIT_H
+#define CONNECT_LIMIT_H
+
+struct connect_limit *connect_limit_init(void);
+void connect_limit_deinit(struct connect_limit **limit);
+
+unsigned int connect_limit_lookup(struct connect_limit *limit,
+ const char *ident);
+void connect_limit_connect(struct connect_limit *limit, pid_t pid,
+ const char *ident);
+void connect_limit_disconnect(struct connect_limit *limit, pid_t pid,
+ const char *ident);
+void connect_limit_disconnect_pid(struct connect_limit *limit, pid_t pid);
+
+#endif
--- /dev/null
+/* Copyright (C) 2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "array.h"
+#include "env-util.h"
+#include "master-service.h"
+#include "connect-limit.h"
+#include "anvil-connection.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+struct connect_limit *connect_limit;
+
+static struct master_service *service;
+
+static void client_connected(const struct master_service_connection *conn)
+{
+ anvil_connection_create(conn->fd);
+}
+
+int main(int argc, char *argv[])
+{
+ int c;
+
+ service = master_service_init("anvil", 0, argc, argv);
+ while ((c = getopt(argc, argv, master_service_getopt_string())) > 0) {
+ if (!master_service_parse_option(service, c, optarg))
+ exit(FATAL_DEFAULT);
+ }
+
+ master_service_init_log(service, "anvil: ", 0);
+ master_service_init_finish(service);
+ connect_limit = connect_limit_init();
+
+ master_service_run(service, client_connected);
+
+ connect_limit_deinit(&connect_limit);
+ anvil_connections_destroy_all();
+ master_service_deinit(&service);
+ return 0;
+}
#define AUTH_MASTER_WAITING_MSG \
"* OK Waiting for authentication master process to respond.."
-const char *login_protocol = "IMAP";
+const char *login_protocol = "imap";
const char *login_process_name = "imap-login";
static void client_set_title(struct imap_client *client)
#include "ioloop.h"
#include "llist.h"
#include "str.h"
+#include "hostpid.h"
#include "network.h"
#include "istream.h"
#include "ostream.h"
{
struct client *client;
struct mail_namespace *ns;
+ const char *ident;
/* always use nonblocking I/O */
net_set_nonblock(fd_in, TRUE);
str_append(client->capability_string, *set->imap_capability != '\0' ?
set->imap_capability : CAPABILITY_STRING);
+ ident = mail_user_get_anvil_userip_ident(client->user);
+ if (ident != NULL) {
+ master_service_anvil_send(service, t_strconcat("CONNECT\t",
+ my_pid, "\t", ident, "/imap\n", NULL));
+ client->anvil_sent = TRUE;
+ }
+
i_assert(my_client == NULL);
my_client = client;
client_search_updates_free(client);
mailbox_close(&client->mailbox);
}
+ if (client->anvil_sent) {
+ master_service_anvil_send(service, t_strconcat("DISCONNECT\t",
+ my_pid, "\t",
+ mail_user_get_anvil_userip_ident(client->user), "/imap"
+ "\n", NULL));
+ }
mail_user_unref(&client->user);
if (client->free_parser != NULL)
unsigned int syncing:1;
unsigned int id_logged:1;
unsigned int mailbox_examined:1;
+ unsigned int anvil_sent:1;
unsigned int input_skip_line:1; /* skip all the data until we've
found a new line */
unsigned int modseqs_sent_since_sync:1;
enum master_auth_status {
MASTER_AUTH_STATUS_OK,
- MASTER_AUTH_STATUS_INTERNAL_ERROR,
- /* user reached max. simultaneous connections */
- MASTER_AUTH_STATUS_MAX_CONNECTIONS
+ MASTER_AUTH_STATUS_INTERNAL_ERROR
};
struct master_auth_reply {
/* 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
+/* Write pipe to anvil. Currently available only for auth destination
+ services, for others it's /dev/null. */
+#define MASTER_ANVIL_FD 3
/* Socket for sending master_auth_requests. Also used by auth server process
as a master socket. */
binary_path = service->argv[0];
path = getenv("PATH");
- if (*service->argv[0] != '/') {
+ if (*service->argv[0] != '/' && path != NULL) {
/* we have to find our executable from path */
paths = t_strsplit(path, ":");
for (; *paths != NULL; paths++) {
io_loop_stop(service->ioloop);
}
+void master_service_anvil_send(struct master_service *service, const char *cmd)
+{
+ ssize_t ret;
+
+ if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) != 0)
+ return;
+
+ ret = write(MASTER_ANVIL_FD, cmd, strlen(cmd));
+ if (ret < 0)
+ i_error("write(anvil) failed: %m");
+ else if (ret == 0)
+ i_error("write(anvil) failed: EOF");
+ else {
+ i_assert((size_t)ret == strlen(cmd));
+ }
+}
+
void master_service_client_connection_destroyed(struct master_service *service)
{
if (service->listeners == NULL) {
/* Stop a running service. */
void master_service_stop(struct master_service *service);
+/* Send command to anvil process, if we have fd to it. */
+void master_service_anvil_send(struct master_service *service, const char *cmd);
/* Call whenever a client connection is destroyed. */
void master_service_client_connection_destroyed(struct master_service *service);
#include "hostpid.h"
#include "network.h"
#include "str.h"
+#include "strescape.h"
#include "var-expand.h"
#include "settings-parser.h"
#include "auth-master.h"
return mail_storage_get_temp_prefix(ns->storage);
}
+const char *mail_user_get_anvil_userip_ident(struct mail_user *user)
+{
+ if (user->remote_ip == NULL)
+ return NULL;
+ return t_strconcat(net_ip2addr(user->remote_ip), "/",
+ str_tabescape(user->username), NULL);
+}
+
void mail_users_init(const char *auth_socket_path, bool debug)
{
auth_master_conn = auth_master_init(auth_socket_path, debug);
const char *mail_user_home_expand(struct mail_user *user, const char *path);
/* Returns 0 if ok, -1 if home directory isn't set. */
int mail_user_try_home_expand(struct mail_user *user, const char **path);
+/* Returns unique user+ip identifier for anvil. */
+const char *mail_user_get_anvil_userip_ident(struct mail_user *user);
#endif
extern struct auth_client *auth_client;
extern bool closing_down;
+extern int anvil_fd;
extern struct master_service *service;
extern struct login_settings *login_settings;
DEF(SET_BOOL, verbose_proctitle),
DEF(SET_UINT, login_max_connections),
+ DEF(SET_UINT, mail_max_userip_connections),
SETTING_DEFINE_LIST_END
};
MEMBER(auth_debug) FALSE,
MEMBER(verbose_proctitle) FALSE,
- MEMBER(login_max_connections) 256
+ MEMBER(login_max_connections) 256,
+ MEMBER(mail_max_userip_connections) 10
};
struct setting_parser_info login_setting_parser_info = {
bool verbose_proctitle;
unsigned int login_max_connections;
+ unsigned int mail_max_userip_connections;
/* generated: */
const char *const *log_format_elements_split;
struct auth_client *auth_client;
bool closing_down;
+int anvil_fd = -1;
struct master_service *service;
struct login_settings *login_settings;
clients_notify_auth_connected();
}
+static int anvil_connect(void)
+{
+#define ANVIL_HANDSHAKE "VERSION\t1\t0\n"
+ int fd;
+
+ fd = net_connect_unix("anvil");
+ if (fd < 0)
+ i_fatal("net_connect_unix(anvil) failed: %m");
+ net_set_nonblock(fd, FALSE);
+
+ if (write(fd, ANVIL_HANDSHAKE, strlen(ANVIL_HANDSHAKE)) < 0)
+ i_fatal("write(anvil) failed: %m");
+ return fd;
+}
+
static void main_preinit(void)
{
unsigned int max_fds;
i_assert(strcmp(login_settings->ssl, "no") == 0 || ssl_initialized);
+ if (login_settings->mail_max_userip_connections > 0)
+ anvil_fd = anvil_connect();
+
restrict_access_by_env(NULL, TRUE);
}
if (auth_client != NULL)
auth_client_free(&auth_client);
clients_deinit();
+
+ if (anvil_fd != -1) {
+ if (close(anvil_fd) < 0)
+ i_error("close(anvil) failed: %m");
+ }
master_auth_deinit(service);
}
#include "base64.h"
#include "buffer.h"
#include "istream.h"
+#include "write-full.h"
+#include "strescape.h"
#include "str-sanitize.h"
#include "auth-client.h"
#include "ssl-proxy.h"
#include "master-auth.h"
#include "client-common.h"
+#include <stdlib.h>
+#include <unistd.h>
+
+#define ERR_TOO_MANY_USERIP_CONNECTIONS \
+ "Maximum number of connections from user+IP exceeded " \
+ "(mail_max_userip_connections)"
+
static enum auth_request_flags
client_get_auth_flags(struct client *client)
{
break;
case MASTER_AUTH_STATUS_INTERNAL_ERROR:
break;
- case MASTER_AUTH_STATUS_MAX_CONNECTIONS:
- data = "Maximum number of connections from user+IP exceeded "
- "(mail_max_userip_connections)";
- break;
}
client->mail_pid = reply->mail_pid;
call_client_callback(client, sasl_reply, data, NULL);
master_auth_callback, client);
}
+static bool anvil_has_too_many_connections(struct client *client)
+{
+ const char *ident;
+ char buf[64];
+ ssize_t ret;
+
+ if (client->virtual_user == NULL)
+ return FALSE;
+ if (login_settings->mail_max_userip_connections == 0)
+ return FALSE;
+
+ ident = t_strconcat("LOOKUP\t", net_ip2addr(&client->ip), "/",
+ str_tabescape(client->virtual_user), "/",
+ login_protocol, "\n", NULL);
+ if (write_full(anvil_fd, ident, strlen(ident)) < 0)
+ i_fatal("write(anvil) failed: %m");
+ ret = read(anvil_fd, buf, sizeof(buf)-1);
+ if (ret < 0)
+ i_fatal("read(anvil) failed: %m");
+ else if (ret == 0)
+ i_fatal("read(anvil) failed: EOF");
+ if (buf[ret-1] != '\n')
+ i_fatal("anvil lookup failed: Invalid input in reply");
+ buf[ret-1] = '\0';
+
+ return strtoul(buf, NULL, 10) >=
+ login_settings->mail_max_userip_connections;
+}
+
static void authenticate_callback(struct auth_request *request, int status,
const char *data_base64,
const char *const *args, void *context)
client->authenticating = FALSE;
call_client_callback(client, SASL_SERVER_REPLY_SUCCESS,
NULL, args);
+ } else if (anvil_has_too_many_connections(client)) {
+ client->authenticating = FALSE;
+ call_client_callback(client,
+ SASL_SERVER_REPLY_MASTER_FAILED,
+ ERR_TOO_MANY_USERIP_CONNECTIONS, NULL);
} else {
master_send_request(client, request);
}
dup2-array.c \
main.c \
master-settings.c \
+ service-anvil.c \
service-auth-server.c \
service-auth-source.c \
service-listen.c \
service-log.c \
service-monitor.c \
service-process.c \
+ service-process-notify.c \
service.c
noinst_HEADERS = \
common.h \
dup2-array.h \
master-settings.h \
+ service-anvil.h \
service-auth-server.h \
service-auth-source.h \
service-listen.h \
service-log.h \
service-monitor.h \
service-process.h \
+ service-process-notify.h \
service.h
if (*services[i]->type != '\0' &&
strcmp(services[i]->type, "log") != 0 &&
strcmp(services[i]->type, "config") != 0 &&
+ strcmp(services[i]->type, "anvil") != 0 &&
strcmp(services[i]->type, "auth") != 0 &&
strcmp(services[i]->type, "auth-source") != 0) {
*error_r = t_strconcat("Unknown service type: ",
bool master_settings_do_fixes(const struct master_settings *set)
{
- const char *login_dir;
+ const char *login_dir, *empty_dir;
struct stat st;
gid_t gid;
return FALSE;
}
}
+
+ empty_dir = t_strconcat(set->base_dir, "/empty", NULL);
+ if (safe_mkdir(empty_dir, 0755, master_uid, getegid()) == 0) {
+ i_warning("Corrected permissions for empty directory "
+ "%s", empty_dir);
+ }
return TRUE;
}
--- /dev/null
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "fd-close-on-exec.h"
+#include "fd-set-nonblock.h"
+#include "service.h"
+#include "service-process.h"
+#include "service-process-notify.h"
+#include "service-anvil.h"
+
+#include <unistd.h>
+
+#define ANVIL_HANDSHAKE "VERSION\t1\t0\n"
+
+static int anvil_send_handshake(int fd, const char **error_r)
+{
+ ssize_t ret;
+
+ ret = write(fd, ANVIL_HANDSHAKE, strlen(ANVIL_HANDSHAKE));
+ if (ret < 0) {
+ *error_r = t_strdup_printf("write(anvil) failed: %m");
+ return -1;
+ }
+ if (ret == 0) {
+ *error_r = t_strdup_printf("write(anvil) returned EOF");
+ return -1;
+ }
+ /* this is a pipe, it either wrote all of it or nothing */
+ i_assert(ret == strlen(ANVIL_HANDSHAKE));
+ return 0;
+}
+
+static int
+service_process_write_anvil_kill(int fd, struct service_process *process)
+{
+ const char *data;
+
+ data = t_strdup_printf("KILL\t%s\n", dec2str(process->pid));
+ if (write(fd, data, strlen(data)) < 0) {
+ if (errno != EAGAIN)
+ i_error("write(anvil process) failed: %m");
+ return -1;
+ }
+ return 0;
+}
+
+int service_list_init_anvil(struct service_list *service_list,
+ const char **error_r)
+{
+ if (pipe(service_list->blocking_anvil_fd) < 0) {
+ *error_r = t_strdup_printf("pipe() failed: %m");
+ return -1;
+ }
+ if (pipe(service_list->nonblocking_anvil_fd) < 0) {
+ (void)close(service_list->blocking_anvil_fd[0]);
+ (void)close(service_list->blocking_anvil_fd[1]);
+ *error_r = t_strdup_printf("pipe() failed: %m");
+ return -1;
+ }
+ fd_set_nonblock(service_list->nonblocking_anvil_fd[1], TRUE);
+
+ fd_close_on_exec(service_list->blocking_anvil_fd[0], TRUE);
+ fd_close_on_exec(service_list->blocking_anvil_fd[1], TRUE);
+ fd_close_on_exec(service_list->nonblocking_anvil_fd[0], TRUE);
+ fd_close_on_exec(service_list->nonblocking_anvil_fd[1], TRUE);
+
+ if (anvil_send_handshake(service_list->blocking_anvil_fd[1],
+ error_r) < 0)
+ return -1;
+ if (anvil_send_handshake(service_list->nonblocking_anvil_fd[1],
+ error_r) < 0)
+ return -1;
+
+ i_assert(service_list->anvil_kills == NULL);
+ service_list->anvil_kills =
+ service_process_notify_init(service_list->nonblocking_anvil_fd[1],
+ service_process_write_anvil_kill);
+ return 0;
+}
+
+void service_list_deinit_anvil(struct service_list *service_list)
+{
+ service_process_notify_deinit(&service_list->anvil_kills);
+ if (close(service_list->blocking_anvil_fd[0]) < 0)
+ i_error("close(anvil) failed: %m");
+ if (close(service_list->blocking_anvil_fd[1]) < 0)
+ i_error("close(anvil) failed: %m");
+ if (close(service_list->nonblocking_anvil_fd[0]) < 0)
+ i_error("close(anvil) failed: %m");
+ if (close(service_list->nonblocking_anvil_fd[1]) < 0)
+ i_error("close(anvil) failed: %m");
+}
--- /dev/null
+#ifndef SERVICE_ANVIL_H
+#define SERVICE_ANVIL_H
+
+int service_list_init_anvil(struct service_list *service_list,
+ const char **error_r);
+void service_list_deinit_anvil(struct service_list *service_list);
+
+#endif
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);
+ request);
status = dest_process != NULL ?
MASTER_AUTH_STATUS_OK :
MASTER_AUTH_STATUS_INTERNAL_ERROR;
#include "fd-set-nonblock.h"
#include "service.h"
#include "service-process.h"
+#include "service-process-notify.h"
#include "service-log.h"
#include <unistd.h>
return 0;
}
+static int
+service_process_write_log_bye(int fd, struct service_process *process)
+{
+ const char *data;
+
+ data = t_strdup_printf("%d %s BYE\n",
+ process->service->log_process_internal_fd,
+ dec2str(process->pid));
+ if (write(fd, data, strlen(data)) < 0) {
+ if (errno != EAGAIN)
+ i_error("write(log process) failed: %m");
+ return -1;
+ }
+ return 0;
+}
+
int services_log_init(struct service_list *service_list)
{
struct service *const *services;
else
fd_set_nonblock(service_list->master_log_fd[1], TRUE);
+ i_assert(service_list->log_byes == NULL);
+ service_list->log_byes =
+ service_process_notify_init(service_list->master_log_fd[1],
+ service_process_write_log_bye);
+
n = 1;
for (i = 0; i < count; i++) {
if (services[i]->type == SERVICE_TYPE_LOG)
return 0;
}
-void services_log_clear_byes(struct service_list *service_list)
-{
- struct service_process *const *processes, *process;
- unsigned int i, count;
-
- if (service_list->io_log_write == NULL)
- return;
-
- processes = array_idx_modifiable(&service_list->bye_arr, 0);
- count = aqueue_count(service_list->bye_queue);
- for (i = 0; i < count; i++) {
- process = processes[aqueue_idx(service_list->bye_queue, i)];
- service_process_unref(process);
- }
- aqueue_clear(service_list->bye_queue);
- array_clear(&service_list->bye_arr);
-
- io_remove(&service_list->io_log_write);
-}
-
void services_log_deinit(struct service_list *service_list)
{
struct service *const *services;
services[i]->log_process_internal_fd = -1;
}
}
- services_log_clear_byes(service_list);
+ service_process_notify_deinit(&service_list->log_byes);
if (service_list->master_log_fd[0] != -1) {
if (close(service_list->master_log_fd[0]) < 0)
i_error("close(master log fd) failed: %m");
int services_log_init(struct service_list *service_list);
void services_log_deinit(struct service_list *service_list);
-void services_log_clear_byes(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);
#include "hash.h"
#include "service.h"
#include "service-process.h"
+#include "service-process-notify.h"
#include "service-log.h"
#include "service-monitor.h"
}
/* create a child process and let it accept() this connection */
- if (service_process_create(service, NULL, -1, NULL, 0) == NULL)
+ if (service_process_create(service, NULL, NULL) == NULL)
service_monitor_throttle(service);
else
service_monitor_listen_stop(service);
service_monitor_listen_start(services[i]);
}
- if (service_process_create(service_list->log, NULL, -1, NULL, 0) != NULL)
+ if (service_process_create(service_list->log, NULL, NULL) != NULL)
service_monitor_listen_stop(service_list->log);
- if (service_process_create(service_list->config, NULL, -1, NULL, 0) != NULL)
+ if (service_process_create(service_list->config, NULL, NULL) != NULL)
service_monitor_listen_stop(service_list->config);
}
services_log_deinit(service_list);
}
+static void service_process_failure(struct service_process *process, int status)
+{
+ struct service *service = process->service;
+
+ service_process_log_status_error(process, status);
+ if (process->total_count == 0)
+ service_monitor_throttle(service);
+
+ if (service->list->anvil_kills != NULL)
+ service_process_notify_add(service->list->anvil_kills, process);
+}
+
void services_monitor_reap_children(struct service_list *service_list)
{
struct service_process *process;
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_failure(process, status);
}
-
service_process_destroy(process);
if (service->process_avail == 0 && service->to_throttle == NULL)
--- /dev/null
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "aqueue.h"
+#include "ioloop.h"
+#include "service.h"
+#include "service-process.h"
+#include "service-process-notify.h"
+
+struct service_process_notify {
+ service_process_notify_callback_t *write_callback;
+
+ int fd;
+ struct io *io_write;
+ struct aqueue *process_queue;
+ ARRAY_DEFINE(processes, struct service_process *);
+};
+
+struct service_process_notify *
+service_process_notify_init(int fd,
+ service_process_notify_callback_t *write_callback)
+{
+ struct service_process_notify *notify;
+
+ notify = i_new(struct service_process_notify, 1);
+ notify->fd = fd;
+ notify->write_callback = write_callback;
+
+ i_array_init(¬ify->processes, 64);
+ notify->process_queue = aqueue_init(¬ify->processes.arr);
+ return notify;
+}
+
+static void service_process_notify_reset(struct service_process_notify *notify)
+{
+ struct service_process *const *processes, *process;
+ unsigned int i, count;
+
+ if (notify->io_write == NULL)
+ return;
+
+ processes = array_idx_modifiable(¬ify->processes, 0);
+ count = aqueue_count(notify->process_queue);
+ for (i = 0; i < count; i++) {
+ process = processes[aqueue_idx(notify->process_queue, i)];
+ service_process_unref(process);
+ }
+ aqueue_clear(notify->process_queue);
+ array_clear(¬ify->processes);
+
+ io_remove(¬ify->io_write);
+}
+
+static void notify_flush(struct service_process_notify *notify)
+{
+ struct service_process *const *processes, *process;
+
+ while (aqueue_count(notify->process_queue) > 0) {
+ processes = array_idx_modifiable(¬ify->processes, 0);
+ process = processes[aqueue_idx(notify->process_queue, 0)];
+
+ if (notify->write_callback(notify->fd, process) < 0) {
+ if (errno != EAGAIN)
+ service_process_notify_reset(notify);
+ return;
+ }
+ service_process_unref(process);
+ aqueue_delete_tail(notify->process_queue);
+ }
+ io_remove(¬ify->io_write);
+}
+
+void service_process_notify_deinit(struct service_process_notify **_notify)
+{
+ struct service_process_notify *notify = *_notify;
+
+ *_notify = NULL;
+
+ service_process_notify_reset(notify);
+ if (notify->io_write != NULL)
+ io_remove(¬ify->io_write);
+ aqueue_deinit(¬ify->process_queue);
+ array_free(¬ify->processes);
+ i_free(notify);
+}
+
+void service_process_notify_add(struct service_process_notify *notify,
+ struct service_process *process)
+{
+ if (notify->write_callback(notify->fd, process) < 0) {
+ if (errno != EAGAIN)
+ return;
+
+ if (notify->io_write == NULL) {
+ notify->io_write = io_add(notify->fd, IO_WRITE,
+ notify_flush, notify);
+ }
+ aqueue_append(notify->process_queue, &process);
+ service_process_ref(process);
+ }
+}
--- /dev/null
+#ifndef SERVICE_PROCESS_NOTIFY_H
+#define SERVICE_PROCESS_NOTIFY_H
+
+typedef int
+service_process_notify_callback_t(int fd, struct service_process *process);
+
+struct service_process_notify *
+service_process_notify_init(int fd,
+ service_process_notify_callback_t *write_callback);
+void service_process_notify_deinit(struct service_process_notify **notify);
+
+void service_process_notify_add(struct service_process_notify *notify,
+ struct service_process *process);
+
+#endif
#include "service-log.h"
#include "service-auth-server.h"
#include "service-auth-source.h"
+#include "service-process-notify.h"
#include "service-process.h"
#include <stdlib.h>
#include <sys/wait.h>
static void
-service_dup_fds(struct service *service, int auth_fd, int std_fd)
+service_dup_fds(struct service *service, int auth_fd, int std_fd,
+ bool give_anvil_fd)
{
struct service_listener *const *listeners;
ARRAY_TYPE(dup2) dups;
listeners = array_get(&service->listeners, &count);
t_array_init(&dups, count + 10);
- if (service->type == SERVICE_TYPE_LOG) {
+ switch (service->type) {
+ case SERVICE_TYPE_LOG:
i_assert(n == 0);
services_log_dup2(&dups, service->list, MASTER_LISTEN_FD_FIRST,
&socket_listener_count);
n += socket_listener_count;
+ break;
+ case SERVICE_TYPE_ANVIL:
+ /* nonblocking anvil fd must be the first one. anvil treats it
+ as the master's fd */
+ dup2_append(&dups, service->list->nonblocking_anvil_fd[0],
+ MASTER_LISTEN_FD_FIRST + n++);
+ dup2_append(&dups, service->list->blocking_anvil_fd[0],
+ MASTER_LISTEN_FD_FIRST + n++);
+ socket_listener_count += 2;
+ break;
+ default:
+ break;
}
/* first add non-ssl listeners */
}
}
- dup2_append(&dups, null_fd, MASTER_RESERVED_FD);
+ if (!give_anvil_fd)
+ dup2_append(&dups, null_fd, MASTER_ANVIL_FD);
+ else {
+ dup2_append(&dups, service->list->blocking_anvil_fd[1],
+ MASTER_ANVIL_FD);
+ }
dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
switch (service->type) {
timeout_remove(&process->to_status);
}
+static void
+handle_request(const struct service_process_auth_request *request)
+{
+ string_t *str;
+
+ if (request == NULL)
+ return;
+
+ if (request->data_size > 0) {
+ str = t_str_new(request->data_size*3);
+ str_append(str, "CLIENT_INPUT=");
+ base64_encode(request->data, request->data_size, str);
+ env_put(str_c(str));
+ }
+
+ env_put(t_strconcat("LOCAL_IP=", net_ip2addr(&request->local_ip), NULL));
+ env_put(t_strconcat("IP=", net_ip2addr(&request->remote_ip), NULL));
+}
+
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)
+ const struct service_process_auth_request *request)
{
static unsigned int uid_counter = 0;
struct service_process *process;
unsigned int uid = ++uid_counter;
- string_t *str;
int fd[2];
pid_t pid;
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));
- }
- service_dup_fds(service, fd[1], std_fd);
+ handle_request(request);
+ service_dup_fds(service, fd[1], request == NULL ? -1 :
+ request->fd, auth_args != NULL);
drop_privileges(service, auth_args);
process_exec(service->executable, NULL);
}
return process;
}
-static int service_process_write_bye(struct service_process *process)
-{
- const char *data;
-
- data = t_strdup_printf("%d %s BYE\n",
- process->service->log_process_internal_fd,
- dec2str(process->pid));
- if (write(process->service->list->master_log_fd[1],
- data, strlen(data)) < 0) {
- if (errno != EAGAIN)
- i_error("write(log process) failed: %m");
- return -1;
- }
- return 0;
-}
-
-static void service_list_log_flush_byes(struct service_list *service_list)
-{
- struct service_process *const *processes, *process;
-
- while (aqueue_count(service_list->bye_queue) > 0) {
- processes = array_idx_modifiable(&service_list->bye_arr, 0);
- process = processes[aqueue_idx(service_list->bye_queue, 0)];
-
- if (service_process_write_bye(process) < 0) {
- if (errno != EAGAIN)
- services_log_clear_byes(service_list);
- return;
- }
- service_process_unref(process);
- aqueue_delete_tail(service_list->bye_queue);
- }
- io_remove(&service_list->io_log_write);
-}
-
-static void service_process_log_bye(struct service_process *process)
-{
- struct service_list *service_list = process->service->list;
-
- if (process->service->log_fd[1] == -1) {
- /* stopping all services */
- return;
- }
-
- if (service_process_write_bye(process) < 0) {
- if (errno != EAGAIN)
- return;
-
- if (service_list->io_log_write == NULL) {
- service_list->io_log_write =
- io_add(service_list->master_log_fd[1], IO_WRITE,
- service_list_log_flush_byes,
- service_list);
- }
- aqueue_append(service_list->bye_queue, &process);
- service_process_ref(process);
- }
-}
-
void service_process_destroy(struct service_process *process)
{
struct service *service = process->service;
break;
}
- service_process_log_bye(process);
+ if (service->list->log_byes != NULL)
+ service_process_notify_add(service->list->log_byes, process);
process->destroyed = TRUE;
service_process_unref(process);
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);
+ const struct service_process_auth_request *request);
void service_process_destroy(struct service_process *process);
void service_process_ref(struct service_process *process);
#include "hash.h"
#include "str.h"
#include "service.h"
+#include "service-anvil.h"
#include "service-process.h"
#include "service-monitor.h"
service->type = SERVICE_TYPE_LOG;
else if (strcmp(set->type, "config") == 0)
service->type = SERVICE_TYPE_CONFIG;
+ else if (strcmp(set->type, "anvil") == 0)
+ service->type = SERVICE_TYPE_ANVIL;
else if (strcmp(set->type, "auth") == 0)
service->type = SERVICE_TYPE_AUTH_SERVER;
else if (strcmp(set->type, "auth-source") == 0)
return NULL;
}
+ if (service_list_init_anvil(service_list, error_r) < 0)
+ return NULL;
+
service_list->pids = hash_table_create(default_pool, pool, 0,
pid_hash, pid_hash_cmp);
- p_array_init(&service_list->bye_arr, pool, 64);
- service_list->bye_queue = aqueue_init(&service_list->bye_arr.arr);
return service_list;
}
service_process_destroy(value);
hash_table_iterate_deinit(&iter);
+ service_list_deinit_anvil(service_list);
hash_table_destroy(&service_list->pids);
- aqueue_deinit(&service_list->bye_queue);
pool_unref(&service_list->pool);
}
#include "network.h"
+struct master_settings;
+
/* If a service process doesn't send its first status notification in
this many seconds, kill the process */
#define SERVICE_FIRST_STATUS_TIMEOUT_SECS 30
enum service_type {
SERVICE_TYPE_UNKNOWN,
SERVICE_TYPE_LOG,
+ SERVICE_TYPE_ANVIL,
SERVICE_TYPE_CONFIG,
SERVICE_TYPE_AUTH_SERVER,
SERVICE_TYPE_AUTH_SOURCE
/* nonblocking log fds usd by master */
int master_log_fd[2];
- /* we're waiting to be able to send "bye" to log process */
- struct io *io_log_write;
- /* List of processes who are waiting for the "bye" */
- struct aqueue *bye_queue;
- ARRAY_DEFINE(bye_arr, struct service_process *);
+ struct service_process_notify *log_byes;
+
+ /* passed to auth destination processes */
+ int blocking_anvil_fd[2];
+ /* used by master process to notify about dying processes */
+ int nonblocking_anvil_fd[2];
+ struct service_process_notify *anvil_kills;
ARRAY_DEFINE(services, struct service *);
};
# error client idle timeout must be smaller than authentication timeout
#endif
-const char *login_protocol = "POP3";
+const char *login_protocol = "pop3";
const char *login_process_name = "pop3-login";
static void client_set_title(struct pop3_client *client)
#include "istream.h"
#include "ostream.h"
#include "str.h"
+#include "hostpid.h"
#include "var-expand.h"
#include "master-service.h"
#include "mail-storage.h"
const struct pop3_settings *set)
{
struct mail_storage *storage;
- const char *inbox;
+ const char *inbox, *ident;
struct client *client;
enum mailbox_open_flags flags;
const char *errmsg;
if (!set->pop3_no_flag_updates && client->messages_count > 0)
client->seen_bitmask = i_malloc(MSGS_BITMASK_SIZE(client));
+ ident = mail_user_get_anvil_userip_ident(client->user);
+ if (ident != NULL) {
+ master_service_anvil_send(service, t_strconcat("CONNECT\t",
+ my_pid, "\t", ident, "/pop3\n", NULL));
+ client->anvil_sent = TRUE;
+ }
+
i_assert(my_client == NULL);
my_client = client;
}
if (client->mailbox != NULL)
mailbox_close(&client->mailbox);
+ if (client->anvil_sent) {
+ master_service_anvil_send(service, t_strconcat("DISCONNECT\t",
+ my_pid, "\t",
+ mail_user_get_anvil_userip_ident(client->user), "/pop3"
+ "\n", NULL));
+ }
mail_user_unref(&client->user);
i_free(client->message_sizes);
unsigned int disconnected:1;
unsigned int deleted:1;
unsigned int waiting_input:1;
+ unsigned int anvil_sent:1;
};
/* Create new client with specified input/output handles. socket specifies