changeset 5846:21e529b8a701 HEAD

Initial implementation for mail_max_user_connections setting.
author Timo Sirainen <tss@iki.fi>
date Sat, 30 Jun 2007 19:14:08 +0300
parents 9265c13c4103
children 55699bbeaec2
files dovecot-example.conf src/imap-login/client-authenticate.c src/login-common/master.c src/login-common/master.h src/login-common/sasl-server.c src/master/child-process.c src/master/child-process.h src/master/dict-process.c src/master/login-process.c src/master/mail-process.c src/master/mail-process.h src/master/main.c src/master/master-login-interface.h src/master/master-settings-defs.c src/master/master-settings.c src/master/master-settings.h src/master/ssl-init.c src/pop3-login/client-authenticate.c
diffstat 18 files changed, 228 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Sat Jun 30 19:01:06 2007 +0300
+++ b/dovecot-example.conf	Sat Jun 30 19:14:08 2007 +0300
@@ -334,6 +334,13 @@
 # new users aren't allowed to log in.
 #max_mail_processes = 1024
 
+# Maximum number of connections allowed for a user. The limits are enforced
+# separately for IMAP and POP3 connections, so you can move this setting
+# inside protocol {} to have separate settings for them. NOTE: The user names
+# are compared case-sensitively, so make sure your userdb returns usernames
+# always using the same casing so users can't bypass this limit!
+#mail_max_user_connections = 10
+
 # Set max. process size in megabytes. Most of the memory goes to mmap()ing
 # files, so it shouldn't harm much even if this limit is set pretty high.
 #mail_process_size = 256
--- a/src/imap-login/client-authenticate.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/imap-login/client-authenticate.c	Sat Jun 30 19:14:08 2007 +0300
@@ -222,7 +222,13 @@
 		}
 		break;
 	case SASL_SERVER_REPLY_MASTER_FAILED:
-		client_destroy_internal_failure(client);
+		if (data == NULL)
+			client_destroy_internal_failure(client);
+		else {
+			client_send_tagline(client,
+					    t_strconcat("NO ", data, NULL));
+			client_destroy(client, data);
+		}
 		break;
 	case SASL_SERVER_REPLY_CONTINUE:
 		data_len = strlen(data);
--- a/src/login-common/master.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/login-common/master.c	Sat Jun 30 19:14:08 2007 +0300
@@ -22,7 +22,8 @@
 static char master_buf[sizeof(struct master_login_reply)];
 static struct client destroyed_client;
 
-static void client_call_master_callback(struct client *client, bool success)
+static void client_call_master_callback(struct client *client,
+					enum master_login_status status)
 {
 	master_callback_t *master_callback;
 
@@ -30,7 +31,7 @@
 	client->master_tag = 0;
 	client->master_callback = NULL;
 
-	master_callback(client, success);
+	master_callback(client, status);
 }
 
 static void request_handle(struct master_login_reply *reply)
@@ -50,7 +51,7 @@
 
 	hash_remove(master_requests, POINTER_CAST(reply->tag));
 	if (client != &destroyed_client) {
-		client_call_master_callback(client, reply->success);
+		client_call_master_callback(client, reply->status);
 		/* NOTE: client may be destroyed now */
 	}
 }
--- a/src/login-common/master.h	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/login-common/master.h	Sat Jun 30 19:14:08 2007 +0300
@@ -5,7 +5,8 @@
 
 #include "../master/master-login-interface.h"
 
-typedef void master_callback_t(struct client *client, bool success);
+typedef void master_callback_t(struct client *client,
+			       enum master_login_status status);
 
 void master_request_login(struct client *client, master_callback_t *callback,
 			  unsigned int auth_pid, unsigned int auth_id);
--- a/src/login-common/sasl-server.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/login-common/sasl-server.c	Sat Jun 30 19:14:08 2007 +0300
@@ -37,11 +37,24 @@
 	/* NOTE: client may be destroyed now */
 }
 
-static void master_callback(struct client *client, bool success)
+static void
+master_callback(struct client *client, enum master_login_status status)
 {
+	enum sasl_server_reply reply = SASL_SERVER_REPLY_MASTER_FAILED;
+	const char *data = NULL;
+
 	client->authenticating = FALSE;
-	call_client_callback(client, success ? SASL_SERVER_REPLY_SUCCESS :
-			     SASL_SERVER_REPLY_MASTER_FAILED, NULL, NULL);
+	switch (status) {
+	case MASTER_LOGIN_STATUS_OK:
+		reply = SASL_SERVER_REPLY_SUCCESS;
+		break;
+	case MASTER_LOGIN_STATUS_INTERNAL_ERROR:
+		break;
+	case MASTER_LOGIN_STATUS_MAX_CONNECTIONS:
+		data = "Maximum number of connections exceeded";
+		break;
+	}
+	call_client_callback(client, reply, data, NULL);
 }
 
 static void authenticate_callback(struct auth_request *request, int status,
--- a/src/master/child-process.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/child-process.c	Sat Jun 30 19:14:08 2007 +0300
@@ -153,8 +153,10 @@
 				WTERMSIG(status));
 		}
 
-		if (destroy_callbacks[process_type] != NULL)
-			destroy_callbacks[process_type](process, abnormal_exit);
+		if (destroy_callbacks[process_type] != NULL) {
+			destroy_callbacks[process_type](process, pid,
+							abnormal_exit);
+		}
 	}
 
 	if (pid == -1 && errno != EINTR && errno != ECHILD)
--- a/src/master/child-process.h	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/child-process.h	Sat Jun 30 19:14:08 2007 +0300
@@ -18,8 +18,8 @@
 	enum process_type type;
 };
 
-typedef void child_process_destroy_callback_t(struct child_process *,
-					      bool abnormal_exit);
+typedef void child_process_destroy_callback_t(struct child_process *process,
+					      pid_t pid, bool abnormal_exit);
 
 extern const char *process_names[];
 extern struct hash_table *processes;
--- a/src/master/dict-process.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/dict-process.c	Sat Jun 30 19:14:08 2007 +0300
@@ -159,6 +159,7 @@
 
 static void
 dict_process_destroyed(struct child_process *process,
+		       pid_t pid __attr_unused__,
 		       bool abnormal_exit __attr_unused__)
 {
 	struct dict_process *p = (struct dict_process *)process;
--- a/src/master/login-process.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/login-process.c	Sat Jun 30 19:14:08 2007 +0300
@@ -94,12 +94,12 @@
 
 	memset(&master_reply, 0, sizeof(master_reply));
 	if (user == NULL)
-		master_reply.success = FALSE;
+		master_reply.status = MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 	else {
 		struct login_group *group = request->process->group;
 
 		t_push();
-		master_reply.success =
+		master_reply.status =
 			create_mail_process(group->mail_process_type,
 					    group->set,
 					    request->fd, &request->local_ip,
@@ -683,7 +683,8 @@
 }
 
 static void
-login_process_destroyed(struct child_process *process, bool abnormal_exit)
+login_process_destroyed(struct child_process *process,
+			pid_t pid __attr_unused__, bool abnormal_exit)
 {
 	struct login_process *p = (struct login_process *)process;
 
@@ -910,8 +911,6 @@
 
 void login_processes_deinit(void)
 {
-        login_processes_destroy_all();
-
 	if (to != NULL)
 		timeout_remove(&to);
 	if (io_listen != NULL)
--- a/src/master/mail-process.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/mail-process.c	Sat Jun 30 19:14:08 2007 +0300
@@ -1,7 +1,8 @@
-/* Copyright (C) 2002 Timo Sirainen */
+/* Copyright (C) 2002 Timo Sirainen */
 
 #include "common.h"
 #include "array.h"
+#include "hash.h"
 #include "fd-close-on-exec.h"
 #include "env-util.h"
 #include "str.h"
@@ -12,6 +13,7 @@
 #include "home-expand.h"
 #include "var-expand.h"
 #include "mail-process.h"
+#include "master-login-interface.h"
 #include "login-process.h"
 #include "log.h"
 
@@ -31,10 +33,76 @@
    many seconds to finish. */
 #define CHDIR_WARN_SECS 10
 
-static struct child_process imap_child_process = { PROCESS_TYPE_IMAP };
-static struct child_process pop3_child_process = { PROCESS_TYPE_POP3 };
+struct mail_process_group {
+	/* process.type / user identifies this process group */
+	struct child_process process;
+	char *user;
+
+	/* processes array acts also as refcount */
+	ARRAY_DEFINE(processes, pid_t);
+};
+
+/* type+user -> struct mail_process_group */
+static struct hash_table *mail_process_groups;
+static unsigned int mail_process_count = 0;
+
+static unsigned int mail_process_group_hash(const void *p)
+{
+	const struct mail_process_group *group = p;
+
+	return str_hash(group->user) ^ group->process.type;
+}
+
+static int mail_process_group_cmp(const void *p1, const void *p2)
+{
+	const struct mail_process_group *group1 = p1, *group2 = p2;
+	int ret;
+
+	ret = strcmp(group1->user, group2->user);
+	if (ret == 0)
+		ret = group1->process.type - group2->process.type;
+	return ret;
+}
 
-static unsigned int mail_process_count = 0;
+static struct mail_process_group *
+mail_process_group_lookup(enum process_type type, const char *user)
+{
+	struct mail_process_group lookup_group;
+
+	lookup_group.process.type = type;
+	lookup_group.user = t_strdup_noconst(user);
+
+	return hash_lookup(mail_process_groups, &lookup_group);
+}
+
+static struct mail_process_group *
+mail_process_group_create(enum process_type type, const char *user)
+{
+	struct mail_process_group *group;
+
+	group = i_new(struct mail_process_group, 1);
+	group->process.type = type;
+	group->user = i_strdup(user);
+
+	i_array_init(&group->processes, 10);
+	hash_insert(mail_process_groups, group, group);
+	return group;
+}
+
+static void
+mail_process_group_add(struct mail_process_group *group, pid_t pid)
+{
+	mail_process_count++;
+	array_append(&group->processes, &pid, 1);
+	child_process_add(pid, &group->process);
+}
+
+static void mail_process_group_free(struct mail_process_group *group)
+{
+	array_free(&group->processes);
+	i_free(group->user);
+	i_free(group);
+}
 
 static bool validate_uid_gid(struct settings *set, uid_t uid, gid_t gid,
 			     const char *user)
@@ -422,15 +490,17 @@
 		"If you're sure this check was wrong, set nfs_check=no.", path);
 }
 
-bool create_mail_process(enum process_type process_type, struct settings *set,
-			 int socket, const struct ip_addr *local_ip,
-			 const struct ip_addr *remote_ip,
-			 const char *user, const char *const *args,
-			 bool dump_capability)
+enum master_login_status
+create_mail_process(enum process_type process_type, struct settings *set,
+		    int socket, const struct ip_addr *local_ip,
+		    const struct ip_addr *remote_ip,
+		    const char *user, const char *const *args,
+		    bool dump_capability)
 {
 	const struct var_expand_table *var_expand_table;
 	const char *p, *addr, *mail, *chroot_dir, *home_dir, *full_home_dir;
 	const char *system_user;
+	struct mail_process_group *process_group;
 	char title[1024];
 	struct log_io *log;
 	string_t *str;
@@ -438,19 +508,26 @@
 	uid_t uid;
 	gid_t gid;
 	ARRAY_DEFINE(extra_args, const char *);
-	unsigned int i, count, left;
+	unsigned int i, count, left, process_count;
 	int ret, log_fd, nice, chdir_errno;
 	bool home_given, nfs_check;
 
 	i_assert(process_type == PROCESS_TYPE_IMAP ||
 		 process_type == PROCESS_TYPE_POP3);
 
-	// FIXME: per-group
 	if (mail_process_count == set->max_mail_processes) {
 		i_error("Maximum number of mail processes exceeded");
-		return FALSE;
+		return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 	}
 
+	/* check process limit for this user */
+	process_group = mail_process_group_lookup(process_type, user);
+	process_count = process_group == NULL ? 0 :
+		array_count(&process_group->processes);
+	if (process_count >= set->mail_max_user_connections &&
+	    set->mail_max_user_connections != 0)
+		return MASTER_LOGIN_STATUS_MAX_CONNECTIONS;
+
 	t_array_init(&extra_args, 16);
 	mail = home_dir = chroot_dir = system_user = "";
 	uid = gid = 0; nice = 0;
@@ -471,7 +548,7 @@
 			if (uid != 0) {
 				i_error("uid specified multiple times for %s",
 					user);
-				return FALSE;
+				return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 			}
 			uid = (uid_t)strtoul(*args + 4, NULL, 10);
 		} else if (strncmp(*args, "gid=", 4) == 0)
@@ -494,7 +571,7 @@
 
 	if (!dump_capability) {
 		if (!validate_uid_gid(set, uid, gid, user))
-			return FALSE;
+			return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 	}
 
 	if (*chroot_dir == '\0' && *set->mail_chroot != '\0')
@@ -505,26 +582,26 @@
 			i_error("Invalid chroot directory '%s' (user %s) "
 				"(see valid_chroot_dirs in config file)",
 				chroot_dir, user);
-			return FALSE;
+			return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 		}
 		if (set->mail_drop_priv_before_exec) {
 			i_error("Can't chroot to directory '%s' (user %s) "
 				"with mail_drop_priv_before_exec=yes",
 				chroot_dir, user);
-			return FALSE;
+			return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 		}
 	}
 
 	if (!dump_capability) {
 		log_fd = log_create_pipe(&log, set->mail_log_max_lines_per_sec);
 		if (log_fd == -1)
-			return FALSE;
+			return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 	} else {
 		log = NULL;
 		log_fd = dup(STDERR_FILENO);
 		if (log_fd == -1) {
 			i_error("dup() failed: %m");
-			return FALSE;
+			return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 		}
 		fd_close_on_exec(log_fd, TRUE);
 	}
@@ -542,7 +619,7 @@
 	if (pid < 0) {
 		i_error("fork() failed: %m");
 		(void)close(log_fd);
-		return FALSE;
+		return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 	}
 
 	var_expand_table =
@@ -557,16 +634,15 @@
 		/* master */
 		var_expand(str, set->mail_log_prefix, var_expand_table);
 
-		mail_process_count++;
-		if (!dump_capability) {
+		if (!dump_capability)
 			log_set_prefix(log, str_c(str));
-			child_process_add(pid,
-					  process_type == PROCESS_TYPE_IMAP ?
-					  &imap_child_process :
-					  &pop3_child_process);
+		if (process_group == NULL) {
+			process_group = mail_process_group_create(process_type,
+								  user);
 		}
+		mail_process_group_add(process_group, pid);
 		(void)close(log_fd);
-		return TRUE;
+		return MASTER_LOGIN_STATUS_OK;
 	}
 
 #ifdef HAVE_SETPRIORITY
@@ -715,20 +791,58 @@
 		       set->mail_executable);
 
 	/* not reached */
-	return FALSE;
+	return MASTER_LOGIN_STATUS_INTERNAL_ERROR;
 }
 
 static void
-mail_process_destroyed(struct child_process *process __attr_unused__,
-		       bool abnormal_exit __attr_unused__)
+mail_process_destroyed(struct child_process *process,
+		       pid_t pid, bool abnormal_exit __attr_unused__)
 {
+	struct mail_process_group *group = (struct mail_process_group *)process;
+	const pid_t *pids;
+	unsigned int i, count;
+
+	pids = array_get(&group->processes, &count);
+	if (count == 1) {
+		/* last process in this group */
+		i_assert(pids[0] == pid);
+		hash_remove(mail_process_groups, group);
+		mail_process_group_free(group);
+	} else {
+		for (i = 0; i < count; i++) {
+			if (pids[i] == pid)
+				break;
+		}
+		i_assert(i != count);
+		array_delete(&group->processes, i, 1);
+	}
+
 	mail_process_count--;
 }
 
 void mail_processes_init(void)
 {
+	mail_process_groups = hash_create(default_pool, default_pool, 0,
+					  mail_process_group_hash,
+					  mail_process_group_cmp);
+
 	child_process_set_destroy_callback(PROCESS_TYPE_IMAP,
 					   mail_process_destroyed);
 	child_process_set_destroy_callback(PROCESS_TYPE_POP3,
 					   mail_process_destroyed);
 }
+
+void mail_processes_deinit(void)
+{
+	struct hash_iterate_context *iter;
+	void *key, *value;
+
+	iter = hash_iterate_init(mail_process_groups);
+	while (hash_iterate(iter, &key, &value)) {
+		struct mail_process_group *group = value;
+		mail_process_group_free(group);
+	}
+	hash_iterate_deinit(iter);
+
+	hash_destroy(mail_process_groups);
+}
--- a/src/master/mail-process.h	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/mail-process.h	Sat Jun 30 19:14:08 2007 +0300
@@ -9,12 +9,15 @@
 void mail_process_exec(const char *protocol, const char *section)
 	__attr_noreturn__;
 
-bool create_mail_process(enum process_type process_type, struct settings *set,
-			 int socket, const struct ip_addr *local_ip,
-			 const struct ip_addr *remote_ip,
-			 const char *user, const char *const *args,
-			 bool dump_capability);
+
+enum master_login_status
+create_mail_process(enum process_type process_type, struct settings *set,
+		    int socket, const struct ip_addr *local_ip,
+		    const struct ip_addr *remote_ip,
+		    const char *user, const char *const *args,
+		    bool dump_capability);
 
 void mail_processes_init(void);
+void mail_processes_deinit(void);
 
 #endif
--- a/src/master/main.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/main.c	Sat Jun 30 19:14:08 2007 +0300
@@ -461,11 +461,15 @@
 	(void)unlink(t_strconcat(settings_root->defaults->base_dir,
 				 "/master.pid", NULL));
 
+	login_processes_destroy_all();
+	/* call process destroy handlers first */
+	child_processes_deinit();
+
+	mail_processes_deinit();
 	login_processes_deinit();
 	auth_processes_deinit();
 	dict_process_deinit();
 	ssl_deinit();
-	child_processes_deinit();
 
 	if (close(null_fd) < 0)
 		i_error("close(null_fd) failed: %m");
--- a/src/master/master-login-interface.h	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/master-login-interface.h	Sat Jun 30 19:14:08 2007 +0300
@@ -36,9 +36,16 @@
 	struct ip_addr local_ip, remote_ip;
 };
 
+enum master_login_status {
+	MASTER_LOGIN_STATUS_OK,
+	MASTER_LOGIN_STATUS_INTERNAL_ERROR,
+	/* user reached max. simultaneous connections */
+	MASTER_LOGIN_STATUS_MAX_CONNECTIONS
+};
+
 struct master_login_reply {
 	unsigned int tag;
-	bool success;
+	enum master_login_status status;
 };
 
 #endif
--- a/src/master/master-settings-defs.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/master-settings-defs.c	Sat Jun 30 19:14:08 2007 +0300
@@ -55,6 +55,7 @@
 	DEF_STR(valid_chroot_dirs),
 	DEF_STR(mail_chroot),
 	DEF_INT(max_mail_processes),
+	DEF_INT(mail_max_user_connections),
 	DEF_BOOL(verbose_proctitle),
 
 	DEF_INT(first_valid_uid),
--- a/src/master/master-settings.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/master-settings.c	Sat Jun 30 19:14:08 2007 +0300
@@ -10,6 +10,7 @@
 #include "unlink-directory.h"
 #include "syslog-util.h"
 #include "mail-process.h"
+#include "master-login-interface.h"
 #include "settings.h"
 
 #include <stdio.h>
@@ -205,6 +206,7 @@
 	MEMBER(valid_chroot_dirs) "",
 	MEMBER(mail_chroot) "",
 	MEMBER(max_mail_processes) 1024,
+	MEMBER(mail_max_user_connections) 10,
 	MEMBER(verbose_proctitle) FALSE,
 
 	MEMBER(first_valid_uid) 500,
@@ -546,6 +548,7 @@
 		"gid=65534",
 		NULL
 	};
+	enum master_login_status login_status;
 	struct ip_addr ip;
 	char buf[4096];
 	int fd[2], status;
@@ -577,8 +580,10 @@
 	}
 	fd_close_on_exec(fd[0], TRUE);
 	fd_close_on_exec(fd[1], TRUE);
-	if (!create_mail_process(PROCESS_TYPE_IMAP, set, fd[1],
-				 &ip, &ip, "dump-capability", args, TRUE)) {
+	login_status = create_mail_process(PROCESS_TYPE_IMAP, set, fd[1],
+					   &ip, &ip, "dump-capability",
+					   args, TRUE);
+	if (login_status != MASTER_LOGIN_STATUS_OK) {
 		(void)close(fd[0]);
 		(void)close(fd[1]);
 		return FALSE;
--- a/src/master/master-settings.h	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/master-settings.h	Sat Jun 30 19:14:08 2007 +0300
@@ -61,6 +61,7 @@
 	const char *valid_chroot_dirs;
 	const char *mail_chroot;
 	unsigned int max_mail_processes;
+	unsigned int mail_max_user_connections;
 	bool verbose_proctitle;
 
 	unsigned int first_valid_uid, last_valid_uid;
--- a/src/master/ssl-init.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/master/ssl-init.c	Sat Jun 30 19:14:08 2007 +0300
@@ -67,7 +67,7 @@
 
 static void
 ssl_parameter_process_destroyed(struct child_process *process __attr_unused__,
-				bool abnormal_exit)
+				pid_t pid __attr_unused__, bool abnormal_exit)
 {
 	if (!abnormal_exit) {
 		if (file_copy(SSL_PARAMETERS_PERM_PATH,
--- a/src/pop3-login/client-authenticate.c	Sat Jun 30 19:01:06 2007 +0300
+++ b/src/pop3-login/client-authenticate.c	Sat Jun 30 19:14:08 2007 +0300
@@ -194,7 +194,13 @@
 		}
 		break;
 	case SASL_SERVER_REPLY_MASTER_FAILED:
-		client_destroy_internal_failure(client);
+		if (data == NULL)
+			client_destroy_internal_failure(client);
+		else {
+			client_send_line(client,
+				t_strconcat("-ERR [IN-USE] ", data, NULL));
+			client_destroy(client, data);
+		}
 		break;
 	case SASL_SERVER_REPLY_CONTINUE:
 		data_len = strlen(data);