]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
Implemented anvil service, which is used to implement mail_max_userip_connections.
authorTimo Sirainen <tss@iki.fi>
Wed, 6 May 2009 01:28:34 +0000 (21:28 -0400)
committerTimo Sirainen <tss@iki.fi>
Wed, 6 May 2009 01:28:34 +0000 (21:28 -0400)
--HG--
branch : HEAD

42 files changed:
.hgignore
TODO
configure.in
dovecot-master-example.conf
src/anvil/Makefile.am [new file with mode: 0644]
src/anvil/anvil-connection.c [new file with mode: 0644]
src/anvil/anvil-connection.h [new file with mode: 0644]
src/anvil/common.h [new file with mode: 0644]
src/anvil/connect-limit.c [new file with mode: 0644]
src/anvil/connect-limit.h [new file with mode: 0644]
src/anvil/main.c [new file with mode: 0644]
src/imap-login/client.c
src/imap/imap-client.c
src/imap/imap-client.h
src/lib-master/master-interface.h
src/lib-master/master-service-settings.c
src/lib-master/master-service.c
src/lib-master/master-service.h
src/lib-storage/mail-user.c
src/lib-storage/mail-user.h
src/login-common/common.h
src/login-common/login-settings.c
src/login-common/login-settings.h
src/login-common/main.c
src/login-common/sasl-server.c
src/master/Makefile.am
src/master/master-settings.c
src/master/service-anvil.c [new file with mode: 0644]
src/master/service-anvil.h [new file with mode: 0644]
src/master/service-auth-server.c
src/master/service-log.c
src/master/service-log.h
src/master/service-monitor.c
src/master/service-process-notify.c [new file with mode: 0644]
src/master/service-process-notify.h [new file with mode: 0644]
src/master/service-process.c
src/master/service-process.h
src/master/service.c
src/master/service.h
src/pop3-login/client.c
src/pop3/pop3-client.c
src/pop3/pop3-client.h

index 093567bf54a5a5246ce62090c24c244027f2eb67..8c5340ced12c316c533fca0ed363f9d52487a93b 100644 (file)
--- a/.hgignore
+++ b/.hgignore
@@ -51,9 +51,11 @@ Makefile.in
 
 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
diff --git a/TODO b/TODO
index 5dfb99c9eb8758f6f04b9e760f13fde7b6320a31..3757b896ff582129a99d81794255a34c421c793b 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,6 +1,6 @@
- - 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,
index 04fdf348825d8e2197f783242a1840059eaea700..e3633990145b7d5b665753af6b70905690d95699 100644 (file)
@@ -2383,6 +2383,7 @@ src/lib-storage/index/cydir/Makefile
 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
index 5ec63b10b1706a609696b5af492734c04612aead..1a6cae0ee2dc5a1e62480654ca77ed806d0c0b28 100644 (file)
@@ -2,15 +2,29 @@ service config {
   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 {
@@ -56,7 +70,6 @@ service imap-login {
 }
 
 service imap {
-  type = auth-destination
   executable = imap
 }
 
@@ -80,7 +93,6 @@ service pop3-login {
 }
 
 service pop3 {
-  type = auth-destination
   executable = pop3
 }
 
diff --git a/src/anvil/Makefile.am b/src/anvil/Makefile.am
new file mode 100644 (file)
index 0000000..d252639
--- /dev/null
@@ -0,0 +1,24 @@
+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
diff --git a/src/anvil/anvil-connection.c b/src/anvil/anvil-connection.c
new file mode 100644 (file)
index 0000000..2eaf999
--- /dev/null
@@ -0,0 +1,165 @@
+/* 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);
+}
diff --git a/src/anvil/anvil-connection.h b/src/anvil/anvil-connection.h
new file mode 100644 (file)
index 0000000..1658b5b
--- /dev/null
@@ -0,0 +1,9 @@
+#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
diff --git a/src/anvil/common.h b/src/anvil/common.h
new file mode 100644 (file)
index 0000000..e6468df
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include "lib.h"
+
+extern struct connect_limit *connect_limit;
+
+#endif
diff --git a/src/anvil/connect-limit.c b/src/anvil/connect-limit.c
new file mode 100644 (file)
index 0000000..9d5f7bb
--- /dev/null
@@ -0,0 +1,166 @@
+/* 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);
+}
diff --git a/src/anvil/connect-limit.h b/src/anvil/connect-limit.h
new file mode 100644 (file)
index 0000000..2c44e8e
--- /dev/null
@@ -0,0 +1,15 @@
+#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
diff --git a/src/anvil/main.c b/src/anvil/main.c
new file mode 100644 (file)
index 0000000..e407ac7
--- /dev/null
@@ -0,0 +1,42 @@
+/* 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;
+}
index 6d0b9b6198b5fb0c0dc68cba37a65a19d4951b11..c38a8ce562afb1dcd99cfa835a5a6fc07a4fad63 100644 (file)
@@ -49,7 +49,7 @@
 #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)
index c7e89b8f6fe57e68ea361cd900eed8a6a9cb03da..14923e9a6e5bac935ccec3c0ce3a3859221def57 100644 (file)
@@ -4,6 +4,7 @@
 #include "ioloop.h"
 #include "llist.h"
 #include "str.h"
+#include "hostpid.h"
 #include "network.h"
 #include "istream.h"
 #include "ostream.h"
@@ -34,6 +35,7 @@ struct client *client_create(int fd_in, int fd_out, struct mail_user *user,
 {
        struct client *client;
        struct mail_namespace *ns;
+       const char *ident;
 
        /* always use nonblocking I/O */
        net_set_nonblock(fd_in, TRUE);
@@ -68,6 +70,13 @@ struct client *client_create(int fd_in, int fd_out, struct mail_user *user,
        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;
 
@@ -174,6 +183,12 @@ void client_destroy(struct client *client, const char *reason)
                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)
index 6a42be8c3ec31fe2041eb9086e4b81b2622b343e..4a94b963e65730b4861214cb29e41692993a0932 100644 (file)
@@ -139,6 +139,7 @@ struct client {
        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;
index 9334a34afd9c97553f010cdb0de7232ab9635ed7..42ce66602913dcb1ffd428ad2dbb21a8ae0cd0b0 100644 (file)
@@ -60,9 +60,7 @@ struct master_auth_request {
 
 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 {
@@ -85,8 +83,9 @@ 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. */
index 274828adc23e3db9dff9aaf0c7cfb3ccdac55b3b..350e70be54050e0d4f1730d0ad3fad3801e2b964 100644 (file)
@@ -59,7 +59,7 @@ master_service_exec_config(struct master_service *service, bool preserve_home)
 
        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++) {
index f4144c5c9ead01fb980532017a96809d16eec1cc..34fd7f8bd59930e759898af6e9c8956e1938979b 100644 (file)
@@ -356,6 +356,23 @@ void master_service_stop(struct master_service *service)
         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) {
index 69fc07e89d0623eea786bc9419d2de71a1aa53b8..129588148e5b4e99d79ae243b84103434e1859fd 100644 (file)
@@ -75,6 +75,8 @@ void master_service_run(struct master_service *service,
 /* 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);
 
index 28e80ed151166fd658f989f1f45c71116d85cf5c..97f318773c49b39aabab0a3c3d3d8cef50321a35 100644 (file)
@@ -5,6 +5,7 @@
 #include "hostpid.h"
 #include "network.h"
 #include "str.h"
+#include "strescape.h"
 #include "var-expand.h"
 #include "settings-parser.h"
 #include "auth-master.h"
@@ -317,6 +318,14 @@ const char *mail_user_get_temp_prefix(struct mail_user *user)
        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);
index 7dd9991ad5c0239d677a9bfeec2f2e1888d0e487..2315c9ac85cf3a8ae94e7eda68d98a495f6fcccf 100644 (file)
@@ -105,5 +105,7 @@ void mail_user_drop_useless_namespaces(struct mail_user *user);
 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
index 4e26e3e280373d4270a12b161611de9d5eb02781..0ae8a36acf7af3c1038e1678f4ee257f524dc2d4 100644 (file)
@@ -16,6 +16,7 @@ extern const char *login_protocol, *login_process_name;
 
 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;
index e29daa7c87420a7ffddc85f3c9824b06e85dce46..cf2711492c09abc5890e38621249543c5502c8ba 100644 (file)
@@ -43,6 +43,7 @@ static struct setting_define login_setting_defines[] = {
        DEF(SET_BOOL, verbose_proctitle),
 
        DEF(SET_UINT, login_max_connections),
+       DEF(SET_UINT, mail_max_userip_connections),
 
        SETTING_DEFINE_LIST_END
 };
@@ -75,7 +76,8 @@ static struct login_settings login_default_settings = {
        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 = {
index ba95f16d0332cd57ef4fe4b02df1b6b3d2f3afe0..a505692843baf5c2441066bb5136d075c00fd90f 100644 (file)
@@ -31,6 +31,7 @@ struct login_settings {
        bool verbose_proctitle;
 
        unsigned int login_max_connections;
+       unsigned int mail_max_userip_connections;
 
        /* generated: */
        const char *const *log_format_elements_split;
index 1ce6b8bc761143825f78b49a57e474ce1eaafa7f..886fde7064e0085b588db9dfbafa5683a8c0a4a0 100644 (file)
@@ -20,6 +20,7 @@
 
 struct auth_client *auth_client;
 bool closing_down;
+int anvil_fd = -1;
 
 struct master_service *service;
 struct login_settings *login_settings;
@@ -65,6 +66,21 @@ static void auth_connect_notify(struct auth_client *client ATTR_UNUSED,
                 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;
@@ -85,6 +101,9 @@ static void main_preinit(void)
 
        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);
 }
 
@@ -113,6 +132,11 @@ static void main_deinit(void)
        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);
 }
 
index daf8555e61b47a11e4df8eafcf846b5d61f95d0e..70808fe16290e968bd633206ed85fb4d74537a99 100644 (file)
@@ -4,6 +4,8 @@
 #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)
 {
@@ -54,10 +63,6 @@ master_auth_callback(const struct master_auth_reply *reply, void *context)
                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);
@@ -92,6 +97,35 @@ master_send_request(struct client *client, struct auth_request *request)
                                    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)
@@ -133,6 +167,11 @@ static void authenticate_callback(struct auth_request *request, int status,
                        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);
                }
index 1f462de68d347e9fdc46dea25eeb70107b6ce5f9..4545b73b8a54a00a912994026030ac0192b60ea2 100644 (file)
@@ -25,12 +25,14 @@ dovecot_SOURCES = \
        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 = \
@@ -38,10 +40,12 @@ 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
index 632a92a744ec4681c1cbf383f3c720390459f88d..014fdc64829476e4235fa0b1a88bca26e6890fff 100644 (file)
@@ -283,6 +283,7 @@ master_settings_verify(void *_set, pool_t pool, const char **error_r)
                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: ",
@@ -428,7 +429,7 @@ static void unlink_sockets(const char *path, const char *prefix)
 
 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;
 
@@ -479,5 +480,11 @@ bool master_settings_do_fixes(const struct master_settings *set)
                        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;
 }
diff --git a/src/master/service-anvil.c b/src/master/service-anvil.c
new file mode 100644 (file)
index 0000000..3d624da
--- /dev/null
@@ -0,0 +1,92 @@
+/* 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");
+}
diff --git a/src/master/service-anvil.h b/src/master/service-anvil.h
new file mode 100644 (file)
index 0000000..425a3be
--- /dev/null
@@ -0,0 +1,8 @@
+#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
index 0996bc9a8b0fa57af52be73d7b26ea3c48be5e03..f84d9de3ea4f9eafb6fb9335470c9941be48bee1 100644 (file)
@@ -107,11 +107,8 @@ auth_process_input_user(struct service_process_auth_server *process, const char
                        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;
index c8f4421f1b4efa8e2df871df1407385d4d24b7cf..b7a8294a650236a4ca9c61cd00b6dfe0d99c7434 100644 (file)
@@ -9,6 +9,7 @@
 #include "fd-set-nonblock.h"
 #include "service.h"
 #include "service-process.h"
+#include "service-process-notify.h"
 #include "service-log.h"
 
 #include <unistd.h>
@@ -48,6 +49,22 @@ static int service_log_fds_init(const char *log_prefix, int log_fd[2],
        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;
@@ -66,6 +83,11 @@ int services_log_init(struct service_list *service_list)
        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)
@@ -90,26 +112,6 @@ int services_log_init(struct service_list *service_list)
        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;
@@ -131,7 +133,7 @@ void services_log_deinit(struct service_list *service_list)
                        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");
index e6dca287eba33e0e1480a540cb7f40a7f232e3a7..475c91dd1484b01e76bde6b749781ad43a79cdee 100644 (file)
@@ -6,7 +6,6 @@
 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);
index d92db983bb7e2f600349c1943452006e517f8785..7b6eaf003c67e0229e952c953ca27ccbea3b7cbb 100644 (file)
@@ -7,6 +7,7 @@
 #include "hash.h"
 #include "service.h"
 #include "service-process.h"
+#include "service-process-notify.h"
 #include "service-log.h"
 #include "service-monitor.h"
 
@@ -129,7 +130,7 @@ static void service_accept(struct service *service)
        }
 
        /* 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);
@@ -195,9 +196,9 @@ void services_monitor_start(struct service_list *service_list)
                        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);
 }
 
@@ -235,6 +236,18 @@ void services_monitor_stop(struct service_list *service_list)
        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;
@@ -256,12 +269,8 @@ void services_monitor_reap_children(struct service_list *service_list)
                        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)
diff --git a/src/master/service-process-notify.c b/src/master/service-process-notify.c
new file mode 100644 (file)
index 0000000..ad4dc2f
--- /dev/null
@@ -0,0 +1,102 @@
+/* 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(&notify->processes, 64);
+       notify->process_queue = aqueue_init(&notify->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(&notify->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(&notify->processes);
+
+       io_remove(&notify->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(&notify->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(&notify->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(&notify->io_write);
+       aqueue_deinit(&notify->process_queue);
+       array_free(&notify->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);
+       }
+}
diff --git a/src/master/service-process-notify.h b/src/master/service-process-notify.h
new file mode 100644 (file)
index 0000000..04a17b0
--- /dev/null
@@ -0,0 +1,15 @@
+#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
index 3759e71026b2751b62d76bd22363570e97dd29d0..acddc2ab191b3fa824468b611812465c139f86fb 100644 (file)
@@ -20,6 +20,7 @@
 #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>
@@ -30,7 +31,8 @@
 #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;
@@ -47,11 +49,24 @@ service_dup_fds(struct service *service, int auth_fd, int std_fd)
        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 */
@@ -77,7 +92,12 @@ service_dup_fds(struct service *service, int auth_fd, int std_fd)
                }
        }
 
-       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) {
@@ -326,14 +346,32 @@ static void service_process_status_timeout(struct service_process *process)
        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;
 
@@ -366,13 +404,9 @@ service_process_create(struct service *service, const char *const *auth_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));
-               }
-               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);
        }
@@ -418,65 +452,6 @@ service_process_create(struct service *service, const char *const *auth_args,
        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;
@@ -502,7 +477,8 @@ void service_process_destroy(struct service_process *process)
                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);
index aca151efcee36c5d0802cf58954c8b2479b71b86..63897a813bed99246bf87428ea47da0e197925f6 100644 (file)
@@ -64,7 +64,7 @@ struct service_process_auth_request {
 
 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);
index 5b214c113b6158ed92d30ccd1c1e01bd4dc5a756..53a85a435639e0de54e4a8a9aceffb3866cb7171 100644 (file)
@@ -6,6 +6,7 @@
 #include "hash.h"
 #include "str.h"
 #include "service.h"
+#include "service-anvil.h"
 #include "service-process.h"
 #include "service-monitor.h"
 
@@ -136,6 +137,8 @@ service_create(pool_t pool, const struct service_settings *set,
                        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)
@@ -370,10 +373,11 @@ services_create(const struct master_settings *set,
                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;
 }
 
@@ -413,7 +417,7 @@ void services_destroy(struct service_list *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);
 }
index 3bde297203b598c5272181dedfe102b66121324d..4267d6f3b28842dcccdfb9525cb9ef85312e99b8 100644 (file)
@@ -3,6 +3,8 @@
 
 #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
@@ -10,6 +12,7 @@
 enum service_type {
        SERVICE_TYPE_UNKNOWN,
        SERVICE_TYPE_LOG,
+       SERVICE_TYPE_ANVIL,
        SERVICE_TYPE_CONFIG,
        SERVICE_TYPE_AUTH_SERVER,
        SERVICE_TYPE_AUTH_SOURCE
@@ -97,11 +100,13 @@ struct service_list {
 
        /* 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 *);
 };
index 2ffc0fee2854d618538fbd59fee34135e5f36969..082af0a3c914d6505fb99ccbdf70d5e04bf4a742 100644 (file)
@@ -35,7 +35,7 @@
 #  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)
index 3e32f52d01f0d61b1a62c23b1ba2a0097b531f55..65b0fc772a3bc5873d23ad5a343a2fc625db8803 100644 (file)
@@ -7,6 +7,7 @@
 #include "istream.h"
 #include "ostream.h"
 #include "str.h"
+#include "hostpid.h"
 #include "var-expand.h"
 #include "master-service.h"
 #include "mail-storage.h"
@@ -215,7 +216,7 @@ struct client *client_create(int fd_in, int fd_out, struct mail_user *user,
                             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;
@@ -286,6 +287,13 @@ struct client *client_create(int fd_in, int fd_out, struct mail_user *user,
        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;
 
@@ -364,6 +372,12 @@ void client_destroy(struct client *client, const char *reason)
        }
        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);
index a14dcbdacbdd37a5cec2a3ff8dc2a07eac459739..909d4575dc29c9da820a7c9dd26ee8d1618490a2 100644 (file)
@@ -56,6 +56,7 @@ struct client {
        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