diff src/master/login-process.c @ 4538:9d9e72374164 HEAD

Fixes to login process handling, especially with login_process_per_connection=no. Removed login_max_logging_users setting since it was somewhat weird in how it worked. Added login_max_connections to replace it with login_process_per_connection=no, and with =yes its functionality is now within login_max_processes_count.
author Timo Sirainen <tss@iki.fi>
date Sun, 06 Aug 2006 23:05:32 +0300
parents af61031c746f
children 9dc62b7594de
line wrap: on
line diff
--- a/src/master/login-process.c	Fri Aug 04 20:46:11 2006 +0300
+++ b/src/master/login-process.c	Sun Aug 06 23:05:32 2006 +0300
@@ -21,16 +21,18 @@
 
 struct login_process {
 	struct login_group *group;
-	struct login_process *prev_nonlisten, *next_nonlisten;
+	struct login_process *prev_prelogin, *next_prelogin;
 	int refcount;
 
 	pid_t pid;
 	int fd;
 	struct io *io;
 	struct ostream *output;
+	enum master_login_state state;
+
 	unsigned int initialized:1;
-	unsigned int listening:1;
 	unsigned int destroyed:1;
+	unsigned int inetd_child:1;
 };
 
 struct login_auth_request {
@@ -114,28 +116,77 @@
 	i_free(request);
 }
 
-static void login_process_mark_nonlistening(struct login_process *p)
+static void process_remove_from_prelogin_lists(struct login_process *p)
+{
+	if (p->state != LOGIN_STATE_FULL_PRELOGINS)
+		return;
+
+	if (p->prev_prelogin == NULL)
+		p->group->oldest_prelogin_process = p->next_prelogin;
+	else
+		p->prev_prelogin->next_prelogin = p->next_prelogin;
+
+	if (p->next_prelogin == NULL)
+		p->group->newest_prelogin_process = p->prev_prelogin;
+	else
+		p->next_prelogin->prev_prelogin = p->prev_prelogin;
+
+	p->prev_prelogin = p->next_prelogin = NULL;
+}
+
+static void process_mark_nonlistening(struct login_process *p,
+				      enum master_login_state new_state)
 {
-	if (!p->listening) {
-		i_error("login: received another \"not listening\" "
-			"notification (if you can't login at all, "
-			"see src/lib/fdpass.c)");
+	if (p->group == NULL)
+		return;
+
+	if (p->state == LOGIN_STATE_LISTENING)
+		p->group->listening_processes--;
+
+	if (new_state == LOGIN_STATE_FULL_PRELOGINS) {
+		/* add to prelogin list */
+		i_assert(p->state != new_state);
+
+		p->prev_prelogin = p->group->newest_prelogin_process;
+		if (p->group->newest_prelogin_process == NULL)
+			p->group->oldest_prelogin_process = p;
+		else
+			p->group->newest_prelogin_process->next_prelogin = p;
+		p->group->newest_prelogin_process = p;
+	} else {
+		process_remove_from_prelogin_lists(p);
+	}
+}
+
+static void process_mark_listening(struct login_process *p)
+{
+	if (p->group == NULL)
+		return;
+
+	if (p->state != LOGIN_STATE_LISTENING)
+		p->group->listening_processes++;
+
+	process_remove_from_prelogin_lists(p);
+}
+
+static void
+login_process_set_state(struct login_process *p, enum master_login_state state)
+{
+	if (state == p->state || state > LOGIN_STATE_COUNT ||
+	    (state < p->state && p->group->set->login_process_per_connection)) {
+		i_error("login: tried to change state %d -> %d "
+			"(if you can't login at all, see src/lib/fdpass.c)",
+			p->state, state);
 		return;
 	}
 
-	p->listening = FALSE;
-
-	if (p->group != NULL) {
-		p->group->listening_processes--;
-		p->prev_nonlisten = p->group->newest_nonlisten_process;
+	if (state == LOGIN_STATE_LISTENING) {
+		process_mark_listening(p);
+	} else {
+		process_mark_nonlistening(p, state);
+	}
 
-		if (p->group->newest_nonlisten_process != NULL)
-			p->group->newest_nonlisten_process->next_nonlisten = p;
-		p->group->newest_nonlisten_process = p;
-
-		if (p->group->oldest_nonlisten_process == NULL)
-			p->group->oldest_nonlisten_process = p;
-	}
+	p->state = state;
 }
 
 static void login_process_groups_create(void)
@@ -271,12 +322,14 @@
 
 	if (client_fd == -1) {
 		/* just a notification that the login process */
+		enum master_login_state state = req.tag;
+
 		if (!p->initialized) {
 			/* initialization notify */
 			p->initialized = TRUE;;
 		} else {
-			/* not listening for new connections anymore */
-			login_process_mark_nonlistening(p);
+			/* change "listening for new connections" status */
+			login_process_set_state(p, state);
 		}
 		return;
 	}
@@ -316,7 +369,6 @@
 	p->refcount = 1;
 	p->pid = pid;
 	p->fd = fd;
-	p->listening = TRUE;
 	p->io = io_add(fd, IO_READ, login_process_input, p);
 	p->output = o_stream_create_file(fd, default_pool,
 					 sizeof(struct master_login_reply)*10,
@@ -325,6 +377,8 @@
 	PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_LOGIN);
 	hash_insert(processes, POINTER_CAST(pid), p);
 
+	p->state = LOGIN_STATE_LISTENING;
+
 	if (p->group != NULL) {
 		p->group->processes++;
 		p->group->listening_processes++;
@@ -332,22 +386,13 @@
 	return p;
 }
 
-static void login_process_remove_from_lists(struct login_process *p)
+static void login_process_exited(struct login_process *p)
 {
-	if (p->group == NULL)
-		return;
+	if (p->group != NULL)
+		p->group->processes--;
 
-	if (p == p->group->oldest_nonlisten_process)
-		p->group->oldest_nonlisten_process = p->next_nonlisten;
-	else
-		p->prev_nonlisten->next_nonlisten = p->next_nonlisten;
-
-	if (p == p->group->newest_nonlisten_process)
-		p->group->newest_nonlisten_process = p->prev_nonlisten;
-	else
-		p->next_nonlisten->prev_nonlisten = p->prev_nonlisten;
-
-	p->next_nonlisten = p->prev_nonlisten = NULL;
+	hash_remove(processes, POINTER_CAST(p->pid));
+	login_process_unref(p);
 }
 
 static void login_process_destroy(struct login_process *p)
@@ -361,24 +406,15 @@
 		io_loop_stop(ioloop);
 	}
 
-	if (p->listening && p->group != NULL)
-		p->group->listening_processes--;
-
 	o_stream_close(p->output);
 	io_remove(&p->io);
 	if (close(p->fd) < 0)
 		i_error("close(login) failed: %m");
 
-	if (!p->listening)
-		login_process_remove_from_lists(p);
+	process_mark_nonlistening(p, LOGIN_STATE_FULL_LOGINS);
 
-	if (p->group != NULL)
-		p->group->processes--;
-
-	if (p->pid != 0)
-		hash_remove(processes, POINTER_CAST(p->pid));
-
-	login_process_unref(p);
+	if (p->inetd_child)
+		login_process_exited(p);
 }
 
 static void login_process_unref(struct login_process *p)
@@ -445,8 +481,8 @@
 		env_put("PROCESS_PER_CONNECTION=1");
 		env_put("MAX_LOGGING_USERS=1");
 	} else {
-		env_put(t_strdup_printf("MAX_LOGGING_USERS=%u",
-					set->login_max_logging_users));
+		env_put(t_strdup_printf("MAX_CONNECTIONS=%u",
+					set->login_max_connections));
 	}
 
 	env_put(t_strconcat("PROCESS_UID=", dec2str(pid), NULL));
@@ -473,13 +509,6 @@
 	pid_t pid;
 	int fd[2], log_fd;
 
-	if (group->set->login_process_per_connection &&
-	    group->processes - group->listening_processes >=
-	    group->set->login_max_logging_users) {
-		if (group->oldest_nonlisten_process != NULL)
-			login_process_destroy(group->oldest_nonlisten_process);
-	}
-
 	if (group->set->login_uid == 0)
 		i_fatal("Login process must not run as root");
 
@@ -569,25 +598,36 @@
 	return -1;
 }
 
-void login_process_abormal_exit(pid_t pid)
+void login_process_destroyed(pid_t pid, bool abnormal_exit)
 {
 	struct login_process *p;
 
-	/* don't start raising the process count if they're dying all
-	   the time */
 	p = hash_lookup(processes, POINTER_CAST(pid));
-	if (p != NULL && p->group != NULL)
-		p->group->wanted_processes_count = 0;
+	if (p == NULL)
+		i_panic("Lost login process PID %s", dec2str(pid));
+	i_assert(!p->inetd_child);
+
+	if (abnormal_exit) {
+		/* don't start raising the process count if they're dying all
+		   the time */
+		if (p->group != NULL)
+			p->group->wanted_processes_count = 0;
+	}
+
+	login_process_destroy(p);
+	login_process_exited(p);
 }
 
-void login_processes_destroy_all(void)
+void login_processes_destroy_all(bool unref)
 {
 	struct hash_iterate_context *iter;
 	void *key, *value;
 
 	iter = hash_iterate_init(processes);
-	while (hash_iterate(iter, &key, &value))
+	while (hash_iterate(iter, &key, &value)) {
 		login_process_destroy(value);
+		if (unref) login_process_unref(value);
+	}
 	hash_iterate_deinit(iter);
 
 	while (login_groups != NULL) {
@@ -598,17 +638,34 @@
 	}
 }
 
+static void login_processes_notify_group(struct login_group *group)
+{
+	struct hash_iterate_context *iter;
+	struct master_login_reply reply;
+	void *key, *value;
+
+	memset(&reply, 0, sizeof(reply));
+
+	iter = hash_iterate_init(processes);
+	while (hash_iterate(iter, &key, &value)) {
+		struct login_process *p = value;
+
+		if (p->group == group)
+			(void)o_stream_send(p->output, &reply, sizeof(reply));
+	}
+	hash_iterate_deinit(iter);
+}
+
 static int login_group_start_missings(struct login_group *group)
 {
-	if (!group->set->login_process_per_connection) {
-		/* create max. one process every second, that way if it keeps
-		   dying all the time we don't eat all cpu with fork()ing. */
-		if (group->listening_processes <
-		    group->set->login_processes_count) {
-			if (create_login_process(group) < 0)
-				return -1;
-		}
-		return 0;
+	if (group->set->login_process_per_connection &&
+	    group->processes >= group->set->login_max_processes_count &&
+	    group->listening_processes == 0) {
+		/* destroy the oldest listening process. non-listening
+		   processes are logged in users who we don't want to kick out
+		   because someone's started flooding */
+		if (group->oldest_prelogin_process != NULL)
+			login_process_destroy(group->oldest_prelogin_process);
 	}
 
 	/* we want to respond fast when multiple clients are connecting
@@ -627,15 +684,18 @@
 		 group->set->login_processes_count)
 		group->wanted_processes_count--;
 
-	if (group->wanted_processes_count >
-	    group->set->login_max_processes_count) {
-		group->wanted_processes_count =
-			group->set->login_max_processes_count;
+	while (group->listening_processes < group->wanted_processes_count &&
+	       group->processes < group->set->login_max_processes_count) {
+		if (create_login_process(group) < 0)
+			return -1;
 	}
 
-	while (group->listening_processes < group->wanted_processes_count) {
-		if (create_login_process(group) < 0)
-			return -1;
+	if (group->listening_processes == 0 &&
+	    !group->set->login_process_per_connection) {
+		/* we've reached our limit. notify the processes to start
+		   listening again which makes them kill some of their
+		   oldest clients when accepting the next connection */
+		login_processes_notify_group(group);
 	}
 	return 0;
 }
@@ -744,6 +804,7 @@
 
 		p = login_process_new(NULL, ++login_pid_counter, fd);
 		p->initialized = TRUE;
+		p->inetd_child = TRUE;
 	}
 }
 
@@ -771,6 +832,6 @@
 	if (io_listen != NULL)
 		io_remove(&io_listen);
 
-        login_processes_destroy_all();
+        login_processes_destroy_all(TRUE);
 	hash_destroy(processes);
 }