Mercurial > dovecot > original-hg > dovecot-1.2
changeset 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 | 555c27e58cb1 |
children | 93e62d5d12e4 |
files | dovecot-example.conf src/imap-login/client.c src/login-common/common.h src/login-common/login-proxy.c src/login-common/login-proxy.h src/login-common/main.c src/login-common/master.c src/login-common/master.h src/login-common/ssl-proxy-openssl.c src/login-common/ssl-proxy.c src/login-common/ssl-proxy.h src/master/login-process.c src/master/login-process.h src/master/main.c src/master/master-login-interface.h src/master/master-settings.c src/master/master-settings.h src/pop3-login/client.c |
diffstat | 18 files changed, 601 insertions(+), 404 deletions(-) [+] |
line wrap: on
line diff
--- a/dovecot-example.conf Fri Aug 04 20:46:11 2006 +0300 +++ b/dovecot-example.conf Sun Aug 06 23:05:32 2006 +0300 @@ -142,24 +142,35 @@ # to create processes all the time. #login_process_per_connection = yes -# Number of login processes to create. If login_process_per_connection is -# yes, this is the number of extra processes waiting for users to log in. +# Number of login processes to keep for listening new connections. #login_processes_count = 3 -# Maximum number of extra login processes to create. The extra process count +# Maximum number of login processes to create. The listening process count # usually stays at login_processes_count, but when multiple users start logging # in at the same time more extra processes are created. To prevent fork-bombing # we check only once in a second if new processes should be created - if all -# of them are used at the time, we double their amount until limit set by this -# setting is reached. This setting is used only if -# login_process_per_connection is yes. +# of them are used at the time, we double their amount until the limit set by +# this setting is reached. #login_max_processes_count = 128 -# Maximum number of connections allowed in login state. When this limit is -# reached, the oldest connections are dropped. If login_process_per_connection -# is no, this is a per-process value, so the absolute maximum number of users -# logging in actually login_processes_count * max_logging_users. -#login_max_logging_users = 256 +# Maximum number of connections allowed per each login process. This setting +# is used only if login_process_per_connection=no. Once the limit is reached, +# the process notifies master so that it can create a new login process. +# When counting the number of connections and used file descriptors, you +# can use this table: +# Type Logged in SSL/TLS fds used connections used +# Client - - 1 1 +# Client x - 0 0 +# Client - x 3 2 +# Client x x 2 1 +# Proxy - - 2 2 +# Proxy x - 2 1 +# Proxy - x 4 3 +# Proxy x x 3 2 +# So in worst case scenario when you have n clients logging in, after STARTTLS +# they can use 3*n fds and cause the connection count to go 2 times higher +# than this limit. With proxying it can use 4*n fds and go 3 times higher. +#login_max_connections = 256 # Greeting message for clients. #login_greeting = Dovecot ready.
--- a/src/imap-login/client.c Fri Aug 04 20:46:11 2006 +0300 +++ b/src/imap-login/client.c Sun Aug 06 23:05:32 2006 +0300 @@ -121,7 +121,8 @@ &client->common.proxy); if (fd_ssl == -1) { client_send_line(client, "* BYE TLS initialization failed."); - client_destroy(client, "TLS initialization failed."); + client_destroy(client, + "Disconnected: TLS initialization failed."); return; } @@ -358,16 +359,18 @@ struct hash_iterate_context *iter; void *key, *value; struct imap_client *destroy_buf[CLIENT_DESTROY_OLDEST_COUNT]; - int i; + unsigned int i, destroy_count; /* find the oldest clients and put them to destroy-buffer */ memset(destroy_buf, 0, sizeof(destroy_buf)); + destroy_count = max_connections > CLIENT_DESTROY_OLDEST_COUNT*2 ? + CLIENT_DESTROY_OLDEST_COUNT : I_MIN(max_connections/2, 1); iter = hash_iterate_init(clients); while (hash_iterate(iter, &key, &value)) { struct imap_client *client = key; - for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) { + for (i = 0; i < destroy_count; i++) { if (destroy_buf[i] == NULL || destroy_buf[i]->created > client->created) { /* @UNSAFE */ @@ -382,7 +385,7 @@ hash_iterate_deinit(iter); /* then kill them */ - for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) { + for (i = 0; i < destroy_count; i++) { if (destroy_buf[i] == NULL) break; @@ -411,12 +414,22 @@ const struct ip_addr *ip) { struct imap_client *client; + unsigned int current_count; - if (max_logging_users > CLIENT_DESTROY_OLDEST_COUNT && - hash_size(clients) >= max_logging_users) { - /* reached max. users count, kill few of the - oldest connections */ - client_destroy_oldest(); + if (!process_per_connection) { + current_count = hash_size(clients) + + ssl_proxy_get_count() + login_proxy_get_count(); + if (current_count >= max_connections) { + /* already reached max. users count, kill few of the + oldest connections. this happens when we've maxed + out the login process count also. */ + client_destroy_oldest(); + } + if (current_count + 1 >= max_connections) { + /* after this client we've reached max users count, + so stop listening for more */ + main_listen_stop(); + } } /* always use nonblocking I/O */ @@ -444,7 +457,6 @@ client_send_greeting(client); client_set_title(client); - client->created = TRUE; return &client->common; } @@ -500,6 +512,8 @@ client->common.proxy = NULL; } client_unref(client); + + main_listen_start(); } void client_destroy_internal_failure(struct imap_client *client) @@ -617,7 +631,7 @@ while (hash_iterate(iter, &key, &value)) { struct imap_client *client = key; - client_destroy(client, NULL); + client_destroy(client, "Disconnected: Shutting down"); } hash_iterate_deinit(iter); }
--- a/src/login-common/common.h Fri Aug 04 20:46:11 2006 +0300 +++ b/src/login-common/common.h Sun Aug 06 23:05:32 2006 +0300 @@ -16,13 +16,15 @@ extern const char *greeting, *log_format; extern const char *const *log_format_elements; extern const char *capability_string; -extern unsigned int max_logging_users; +extern unsigned int max_connections; extern unsigned int login_process_uid; extern struct auth_client *auth_client; +extern bool closing_down; void main_ref(void); void main_unref(void); -void main_close_listen(void); +void main_listen_start(void); +void main_listen_stop(void); #endif
--- a/src/login-common/login-proxy.c Fri Aug 04 20:46:11 2006 +0300 +++ b/src/login-common/login-proxy.c Sun Aug 06 23:05:32 2006 +0300 @@ -220,6 +220,13 @@ i_free(proxy->host); i_free(proxy->user); i_free(proxy); + + main_listen_start(); +} + +unsigned int login_proxy_get_count(void) +{ + return login_proxies == NULL ? 0 : hash_size(login_proxies); } void login_proxy_detach(struct login_proxy *proxy, struct istream *client_input,
--- a/src/login-common/login-proxy.h Fri Aug 04 20:46:11 2006 +0300 +++ b/src/login-common/login-proxy.h Sun Aug 06 23:05:32 2006 +0300 @@ -20,6 +20,9 @@ void login_proxy_detach(struct login_proxy *proxy, struct istream *client_input, struct ostream *client_output); +/* Return number of active detached login proxies */ +unsigned int login_proxy_get_count(void); + void login_proxy_deinit(void); #endif
--- a/src/login-common/main.c Fri Aug 04 20:46:11 2006 +0300 +++ b/src/login-common/main.c Sun Aug 06 23:05:32 2006 +0300 @@ -22,15 +22,16 @@ bool verbose_proctitle, verbose_ssl, verbose_auth; const char *greeting, *log_format; const char *const *log_format_elements; -unsigned int max_logging_users; +unsigned int max_connections; unsigned int login_process_uid; struct auth_client *auth_client; +bool closing_down; static const char *process_name; static struct ioloop *ioloop; static struct io *io_listen, *io_ssl_listen; static int main_refcount; -static bool is_inetd, closing_down; +static bool is_inetd, listening; void main_ref(void) { @@ -49,27 +50,6 @@ } } -void main_close_listen(void) -{ - if (closing_down) - return; - - if (io_listen != NULL) { - io_remove(&io_listen); - if (close(LOGIN_LISTEN_FD) < 0) - i_fatal("close(listen) failed: %m"); - } - - if (io_ssl_listen != NULL) { - io_remove(&io_ssl_listen); - if (close(LOGIN_SSL_LISTEN_FD) < 0) - i_fatal("close(ssl_listen) failed: %m"); - } - - closing_down = TRUE; - master_notify_finished(); -} - static void sig_die(int signo, void *context __attr_unused__) { /* warn about being killed because of some signal, except SIGINT (^C) @@ -91,13 +71,15 @@ return; } - if (process_per_connection) - main_close_listen(); - if (net_getsockname(fd, &local_ip, NULL) < 0) memset(&local_ip, 0, sizeof(local_ip)); (void)client_create(fd, FALSE, &local_ip, &ip); + + if (process_per_connection) { + closing_down = TRUE; + main_listen_stop(); + } } static void login_accept_ssl(void *context __attr_unused__) @@ -114,8 +96,6 @@ return; } - if (process_per_connection) - main_close_listen(); if (net_getsockname(fd, &local_ip, NULL) < 0) memset(&local_ip, 0, sizeof(local_ip)); @@ -126,6 +106,82 @@ client = client_create(fd_ssl, TRUE, &local_ip, &ip); client->proxy = proxy; } + + if (process_per_connection) { + closing_down = TRUE; + main_listen_stop(); + } +} + +void main_listen_start(void) +{ + unsigned int current_count; + + if (listening) + return; + if (closing_down) { + /* typically happens only with + login_process_per_connection=yes after client logs in */ + master_notify_state_change(LOGIN_STATE_FULL_LOGINS); + return; + } + + current_count = ssl_proxy_get_count() + login_proxy_get_count(); + if (current_count >= max_connections) { + /* can't accept any more connections until existing proxies + get destroyed */ + return; + } + + if (net_getsockname(LOGIN_LISTEN_FD, NULL, NULL) == 0) { + io_listen = io_add(LOGIN_LISTEN_FD, IO_READ, + login_accept, NULL); + } + + if (net_getsockname(LOGIN_SSL_LISTEN_FD, NULL, NULL) == 0) { + if (!ssl_initialized) { + /* this shouldn't happen, master should have + disabled the ssl socket.. */ + i_fatal("BUG: SSL initialization parameters not given " + "while they should have been"); + } + + io_ssl_listen = io_add(LOGIN_SSL_LISTEN_FD, IO_READ, + login_accept_ssl, NULL); + } + listening = TRUE; + + /* the initial notification tells master that we're ok. if we die + before sending it, the master should shutdown itself. */ + master_notify_state_change(LOGIN_STATE_LISTENING); +} + +void main_listen_stop(void) +{ + if (!listening) + return; + + listening = FALSE; + if (io_listen != NULL) { + io_remove(&io_listen); + if (closing_down) { + if (close(LOGIN_LISTEN_FD) < 0) + i_fatal("close(listen) failed: %m"); + } + } + + if (io_ssl_listen != NULL) { + io_remove(&io_ssl_listen); + if (closing_down) { + if (close(LOGIN_SSL_LISTEN_FD) < 0) + i_fatal("close(ssl_listen) failed: %m"); + } + } + + listening = FALSE; + master_notify_state_change(clients_get_count() == 0 ? + LOGIN_STATE_FULL_LOGINS : + LOGIN_STATE_FULL_PRELOGINS); } static void auth_connect_notify(struct auth_client *client __attr_unused__, @@ -183,8 +239,8 @@ verbose_ssl = getenv("VERBOSE_SSL") != NULL; verbose_auth = getenv("VERBOSE_AUTH") != NULL; - value = getenv("MAX_LOGGING_USERS"); - max_logging_users = value == NULL ? 0 : strtoul(value, NULL, 10); + value = getenv("MAX_CONNECTIONS"); + max_connections = value == NULL ? 0 : strtoul(value, NULL, 10); greeting = getenv("GREETING"); if (greeting == NULL) @@ -222,27 +278,8 @@ io_listen = io_ssl_listen = NULL; if (!is_inetd) { - if (net_getsockname(LOGIN_LISTEN_FD, NULL, NULL) == 0) { - io_listen = io_add(LOGIN_LISTEN_FD, IO_READ, - login_accept, NULL); - } - - if (net_getsockname(LOGIN_SSL_LISTEN_FD, NULL, NULL) == 0) { - if (!ssl_initialized) { - /* this shouldn't happen, master should have - disabled the ssl socket.. */ - i_fatal("BUG: SSL initialization parameters " - "not given while they should have " - "been"); - } - - io_ssl_listen = io_add(LOGIN_SSL_LISTEN_FD, IO_READ, - login_accept_ssl, NULL); - } - - /* initialize master last - it sends the "we're ok" - notification */ - master_init(LOGIN_MASTER_SOCKET_FD, TRUE); + master_init(LOGIN_MASTER_SOCKET_FD); + main_listen_start(); } } @@ -332,7 +369,7 @@ return 1; } - master_init(master_fd, FALSE); + master_init(master_fd); closing_down = TRUE; if (fd != -1) {
--- a/src/login-common/master.c Fri Aug 04 20:46:11 2006 +0300 +++ b/src/login-common/master.c Sun Aug 06 23:05:32 2006 +0300 @@ -26,6 +26,13 @@ struct client *client; master_callback_t *master_callback; + if (reply->tag == 0 && !process_per_connection) { + /* this means we have to start listening again. + we've reached maximum number of login processes. */ + main_listen_start(); + return; + } + client = hash_lookup(master_requests, POINTER_CAST(reply->tag)); if (client == NULL) i_fatal("Master sent reply with unknown tag %u", reply->tag); @@ -73,7 +80,7 @@ client->master_callback = NULL; } -void master_notify_finished(void) +void master_notify_state_change(enum master_login_state state) { struct master_login_request req; @@ -82,6 +89,7 @@ memset(&req, 0, sizeof(req)); req.version = MASTER_LOGIN_PROTOCOL_VERSION; + req.tag = state; /* sending -1 as fd does the notification */ if (fd_send(master_fd, -1, &req, sizeof(req)) != sizeof(req)) @@ -98,7 +106,8 @@ i_fatal("close(master) failed: %m"); master_fd = -1; - main_close_listen(); + closing_down = TRUE; + main_listen_stop(); main_unref(); /* may call this function again through main_unref() */ @@ -223,7 +232,7 @@ master_pos = 0; } -void master_init(int fd, bool notify) +void master_init(int fd) { main_ref(); @@ -233,12 +242,6 @@ master_pos = 0; io_master = io_add(master_fd, IO_READ, master_input, NULL); - - if (notify) { - /* just a note to master that we're ok. if we die before, - master should shutdown itself. */ - master_notify_finished(); - } } void master_deinit(void)
--- a/src/login-common/master.h Fri Aug 04 20:46:11 2006 +0300 +++ b/src/login-common/master.h Sun Aug 06 23:05:32 2006 +0300 @@ -11,8 +11,8 @@ unsigned int auth_pid, unsigned int auth_id); void master_request_abort(struct client *client); -/* Notify master that we're not listening for new connections anymore. */ -void master_notify_finished(void); +/* Notify master of a change in our state */ +void master_notify_state_change(enum master_login_state state); /* Close connection to master process */ void master_close(void); @@ -20,7 +20,7 @@ /* inetd: Connect to existing master process, or create new one. */ int master_connect(const char *group_name); -void master_init(int fd, bool notify); +void master_init(int fd); void master_deinit(void); #endif
--- a/src/login-common/ssl-proxy-openssl.c Fri Aug 04 20:46:11 2006 +0300 +++ b/src/login-common/ssl-proxy-openssl.c Sun Aug 06 23:05:32 2006 +0300 @@ -26,11 +26,11 @@ /* Check every 30 minutes if parameters file has been updated */ #define SSL_PARAMFILE_CHECK_INTERVAL (60*30) -#define PLAIN_OUTPUT_OPTIMAL_SIZE 2048 - -enum ssl_want { - WANT_INPUT, - WANT_OUTPUT +enum ssl_io_action { + SSL_ADD_INPUT, + SSL_REMOVE_INPUT, + SSL_ADD_OUTPUT, + SSL_REMOVE_OUTPUT }; struct ssl_proxy { @@ -40,13 +40,10 @@ struct ip_addr ip; int fd_ssl, fd_plain; - struct io *io_ssl, *io_plain_input; + struct io *io_ssl_read, *io_ssl_write, *io_plain_read, *io_plain_write; - enum ssl_want want; - void (*step)(struct ssl_proxy *); - unsigned int ssl_want_size; - - struct ostream *plain_output; + unsigned char plainout_buf[1024]; + unsigned int plainout_size; unsigned char sslout_buf[1024]; unsigned int sslout_size; @@ -70,8 +67,10 @@ static struct hash_table *ssl_proxies; static struct ssl_parameters ssl_params; -static void ssl_input(struct ssl_proxy *proxy); -static void ssl_output(struct ssl_proxy *proxy); +static void plain_read(void *context); +static void plain_write(void *context); +static void ssl_read(struct ssl_proxy *proxy); +static void ssl_write(struct ssl_proxy *proxy); static void ssl_step(void *context); static void ssl_proxy_destroy(struct ssl_proxy *proxy); static void ssl_proxy_unref(struct ssl_proxy *proxy); @@ -188,6 +187,120 @@ } } +static void ssl_set_io(struct ssl_proxy *proxy, enum ssl_io_action action) +{ + switch (action) { + case SSL_ADD_INPUT: + if (proxy->io_ssl_read != NULL) + break; + proxy->io_ssl_read = io_add(proxy->fd_ssl, IO_READ, + ssl_step, proxy); + break; + case SSL_REMOVE_INPUT: + if (proxy->io_ssl_read != NULL) + io_remove(&proxy->io_ssl_read); + break; + case SSL_ADD_OUTPUT: + if (proxy->io_ssl_write != NULL) + break; + proxy->io_ssl_write = io_add(proxy->fd_ssl, IO_WRITE, + ssl_step, proxy); + break; + case SSL_REMOVE_OUTPUT: + if (proxy->io_ssl_write != NULL) + io_remove(&proxy->io_ssl_write); + break; + } +} + +static void plain_block_input(struct ssl_proxy *proxy, bool block) +{ + if (block) { + if (proxy->io_plain_read != NULL) + io_remove(&proxy->io_plain_read); + } else { + if (proxy->io_plain_read == NULL) { + proxy->io_plain_read = io_add(proxy->fd_plain, IO_READ, + plain_read, proxy); + } + } +} + +static void plain_read(void *context) +{ + struct ssl_proxy *proxy = context; + ssize_t ret; + bool corked = FALSE; + + if (proxy->sslout_size == sizeof(proxy->sslout_buf)) { + /* buffer full, block input until it's written */ + plain_block_input(proxy, TRUE); + return; + } + + proxy->refcount++; + + while (proxy->sslout_size < sizeof(proxy->sslout_buf) && + !proxy->destroyed) { + ret = net_receive(proxy->fd_plain, + proxy->sslout_buf + proxy->sslout_size, + sizeof(proxy->sslout_buf) - + proxy->sslout_size); + if (ret <= 0) { + if (ret < 0) + ssl_proxy_destroy(proxy); + break; + } else { + proxy->sslout_size += ret; + if (!corked) { + net_set_cork(proxy->fd_ssl, TRUE); + corked = TRUE; + } + ssl_write(proxy); + } + } + + if (corked) + net_set_cork(proxy->fd_ssl, FALSE); + + ssl_proxy_unref(proxy); +} + +static void plain_write(void *context) +{ + struct ssl_proxy *proxy = context; + ssize_t ret; + + proxy->refcount++; + + ret = net_transmit(proxy->fd_plain, proxy->plainout_buf, + proxy->plainout_size); + if (ret < 0) + ssl_proxy_destroy(proxy); + else { + proxy->plainout_size -= ret; + memmove(proxy->plainout_buf, proxy->plainout_buf + ret, + proxy->plainout_size); + + if (proxy->plainout_size > 0) { + if (proxy->io_plain_write == NULL) { + proxy->io_plain_write = + io_add(proxy->fd_plain, IO_WRITE, + plain_write, proxy); + } + } else { + if (proxy->io_plain_write != NULL) + io_remove(&proxy->io_plain_write); + } + + ssl_set_io(proxy, SSL_ADD_INPUT); + if (SSL_pending(proxy->ssl) > 0) + ssl_read(proxy); + } + + ssl_proxy_unref(proxy); +} + static const char *ssl_last_error(void) { unsigned long err; @@ -204,30 +317,8 @@ return buf; } -static void ssl_set_io(struct ssl_proxy *proxy, enum ssl_want want) -{ - if (proxy->io_ssl != NULL) { - if (want == proxy->want) - return; - io_remove(&proxy->io_ssl); - } - - proxy->want = want; - switch (want) { - case WANT_INPUT: - proxy->io_ssl = - io_add(proxy->fd_ssl, IO_READ, ssl_step, proxy); - break; - case WANT_OUTPUT: - proxy->io_ssl = - io_add(proxy->fd_ssl, IO_WRITE, ssl_step, proxy); - break; - } -} - -static void -ssl_handle_error(struct ssl_proxy *proxy, int ret, const char *func_name, - void (*func)(struct ssl_proxy *), unsigned int want_size) +static void ssl_handle_error(struct ssl_proxy *proxy, int ret, + const char *func_name) { const char *errstr; int err; @@ -236,14 +327,10 @@ switch (err) { case SSL_ERROR_WANT_READ: - proxy->step = func; - proxy->ssl_want_size = want_size; - ssl_set_io(proxy, WANT_INPUT); + ssl_set_io(proxy, SSL_ADD_INPUT); break; case SSL_ERROR_WANT_WRITE: - proxy->step = func; - proxy->ssl_want_size = want_size; - ssl_set_io(proxy, WANT_OUTPUT); + ssl_set_io(proxy, SSL_ADD_OUTPUT); break; case SSL_ERROR_SYSCALL: /* eat up the error queue */ @@ -282,150 +369,56 @@ } } -static void plain_input(void *context) +static void ssl_handshake(struct ssl_proxy *proxy) { - struct ssl_proxy *proxy = context; - ssize_t ret; + int ret; - if (proxy->sslout_size == sizeof(proxy->sslout_buf)) { - /* buffer full, block input until it's written */ - io_remove(&proxy->io_plain_input); - return; + ret = SSL_accept(proxy->ssl); + if (ret != 1) + ssl_handle_error(proxy, ret, "SSL_accept()"); + else { + proxy->handshaked = TRUE; + + ssl_set_io(proxy, SSL_ADD_INPUT); + plain_block_input(proxy, FALSE); } +} - ret = net_receive(proxy->fd_plain, - proxy->sslout_buf + proxy->sslout_size, - sizeof(proxy->sslout_buf) - proxy->sslout_size); - if (ret <= 0) { - if (ret < 0) - ssl_proxy_destroy(proxy); - } else { - proxy->sslout_size += ret; - if (SSL_want(proxy->ssl) == SSL_NOTHING) { - i_assert(proxy->ssl_want_size == 0); - ssl_output(proxy); +static void ssl_read(struct ssl_proxy *proxy) +{ + int ret; + + while (proxy->plainout_size < sizeof(proxy->plainout_buf) && + !proxy->destroyed) { + ret = SSL_read(proxy->ssl, + proxy->plainout_buf + proxy->plainout_size, + sizeof(proxy->plainout_buf) - + proxy->plainout_size); + if (ret <= 0) { + ssl_handle_error(proxy, ret, "SSL_read()"); + break; + } else { + proxy->plainout_size += ret; + plain_write(proxy); } } } -static int plain_output(void *context) +static void ssl_write(struct ssl_proxy *proxy) { - struct ssl_proxy *proxy = context; int ret; - if (proxy->ssl_want_size != 0) - return 0; - - if ((ret = o_stream_flush(proxy->plain_output)) < 0) { - ssl_proxy_destroy(proxy); - return 1; - } - - if (o_stream_get_buffer_used_size(proxy->plain_output) < - PLAIN_OUTPUT_OPTIMAL_SIZE && - proxy->want == WANT_INPUT && proxy->io_ssl == NULL) - ssl_set_io(proxy, WANT_INPUT); - - return ret; -} - -static void ssl_handshake(struct ssl_proxy *proxy) -{ - int ret, old_errno; - - net_set_cork(proxy->fd_ssl, TRUE); - ret = SSL_accept(proxy->ssl); - - old_errno = errno; - net_set_cork(proxy->fd_ssl, FALSE); - errno = old_errno; - - if (ret != 1) - ssl_handle_error(proxy, ret, "SSL_accept()", ssl_handshake, 0); + ret = SSL_write(proxy->ssl, proxy->sslout_buf, proxy->sslout_size); + if (ret <= 0) + ssl_handle_error(proxy, ret, "SSL_write()"); else { - proxy->handshaked = TRUE; - proxy->step = ssl_input; - ssl_set_io(proxy, WANT_INPUT); - - proxy->io_plain_input = io_add(proxy->fd_plain, IO_READ, - plain_input, proxy); - } -} - -static void ssl_input(struct ssl_proxy *proxy) -{ - unsigned char buf[PLAIN_OUTPUT_OPTIMAL_SIZE]; - size_t size, used; - ssize_t ret, ret2; - - used = o_stream_get_buffer_used_size(proxy->plain_output); - if (used >= PLAIN_OUTPUT_OPTIMAL_SIZE) { - io_remove(&proxy->io_ssl); - return; - } + proxy->sslout_size -= ret; + memmove(proxy->sslout_buf, proxy->sslout_buf + ret, + proxy->sslout_size); - size = sizeof(buf) - used; - if (proxy->ssl_want_size != 0) { - i_assert(proxy->ssl_want_size <= size); - size = proxy->ssl_want_size; - proxy->ssl_want_size = 0; - } - - do { - ret = SSL_read(proxy->ssl, buf, size); - if (ret <= 0) { - ssl_handle_error(proxy, ret, "SSL_read()", - ssl_input, size); - return; - } - o_stream_cork(proxy->plain_output); - ret2 = o_stream_send(proxy->plain_output, buf, ret); - i_assert(ret2 < 0 || ret2 == ret); - o_stream_uncork(proxy->plain_output); - - if (proxy->sslout_size > 0) - ssl_output(proxy); - } while (SSL_pending(proxy->ssl) > 0); -} - -static void ssl_output(struct ssl_proxy *proxy) -{ - unsigned int size; - int ret, old_errno; - - if (proxy->ssl_want_size == 0) - size = proxy->sslout_size; - else { - i_assert(proxy->ssl_want_size <= proxy->sslout_size); - size = proxy->ssl_want_size; - proxy->ssl_want_size = 0; - } - - net_set_cork(proxy->fd_ssl, TRUE); - ret = SSL_write(proxy->ssl, proxy->sslout_buf, size); - - old_errno = errno; - net_set_cork(proxy->fd_ssl, FALSE); - errno = old_errno; - - if (ret <= 0) { - ssl_handle_error(proxy, ret, "SSL_write()", ssl_output, size); - return; - } - - proxy->sslout_size -= ret; - memmove(proxy->sslout_buf, proxy->sslout_buf + ret, proxy->sslout_size); - - if (proxy->sslout_size > 0) { - ssl_set_io(proxy, WANT_OUTPUT); - proxy->step = ssl_output; - } else { - ssl_set_io(proxy, WANT_INPUT); - proxy->step = ssl_input; - } - if (proxy->io_plain_input == NULL) { - proxy->io_plain_input = io_add(proxy->fd_plain, IO_READ, - plain_input, proxy); + ssl_set_io(proxy, proxy->sslout_size > 0 ? + SSL_ADD_OUTPUT : SSL_REMOVE_OUTPUT); + plain_block_input(proxy, FALSE); } } @@ -433,7 +426,27 @@ { struct ssl_proxy *proxy = context; - proxy->step(proxy); + proxy->refcount++; + + if (!proxy->handshaked) + ssl_handshake(proxy); + + if (proxy->handshaked) { + if (proxy->plainout_size == sizeof(proxy->plainout_buf)) + ssl_set_io(proxy, SSL_REMOVE_INPUT); + else + ssl_read(proxy); + + if (proxy->sslout_size == 0) + ssl_set_io(proxy, SSL_REMOVE_OUTPUT); + else { + net_set_cork(proxy->fd_ssl, TRUE); + ssl_write(proxy); + net_set_cork(proxy->fd_ssl, FALSE); + } + } + + ssl_proxy_unref(proxy); } int ssl_proxy_new(int fd, struct ip_addr *ip, struct ssl_proxy **proxy_r) @@ -479,16 +492,10 @@ proxy->fd_ssl = fd; proxy->fd_plain = sfd[0]; proxy->ip = *ip; - proxy->plain_output = - o_stream_create_file(proxy->fd_plain, default_pool, - (size_t)-1, FALSE); - o_stream_set_flush_callback(proxy->plain_output, plain_output, proxy); - SSL_set_ex_data(ssl, extdata_index, proxy); hash_insert(ssl_proxies, proxy, proxy); - proxy->step = ssl_handshake; ssl_handshake(proxy); main_ref(); @@ -549,16 +556,21 @@ hash_remove(ssl_proxies, proxy); - if (proxy->io_ssl != NULL) - io_remove(&proxy->io_ssl); - if (proxy->io_plain_input != NULL) - io_remove(&proxy->io_plain_input); + if (proxy->io_ssl_read != NULL) + io_remove(&proxy->io_ssl_read); + if (proxy->io_ssl_write != NULL) + io_remove(&proxy->io_ssl_write); + if (proxy->io_plain_read != NULL) + io_remove(&proxy->io_plain_read); + if (proxy->io_plain_write != NULL) + io_remove(&proxy->io_plain_write); - o_stream_unref(&proxy->plain_output); (void)net_disconnect(proxy->fd_ssl); (void)net_disconnect(proxy->fd_plain); ssl_proxy_unref(proxy); + + main_listen_start(); } static RSA *ssl_gen_rsa_key(SSL *ssl __attr_unused__, @@ -641,6 +653,11 @@ return strlen(buf); } +unsigned int ssl_proxy_get_count(void) +{ + return hash_size(ssl_proxies); +} + void ssl_proxy_init(void) { const char *cafile, *certfile, *keyfile, *cipher_list;
--- a/src/login-common/ssl-proxy.c Fri Aug 04 20:46:11 2006 +0300 +++ b/src/login-common/ssl-proxy.c Sun Aug 06 23:05:32 2006 +0300 @@ -28,6 +28,11 @@ void ssl_proxy_free(struct ssl_proxy *proxy __attr_unused__) {} +unsigned int ssl_proxy_get_count(void) +{ + return 0; +} + void ssl_proxy_init(void) {} void ssl_proxy_deinit(void) {}
--- a/src/login-common/ssl-proxy.h Fri Aug 04 20:46:11 2006 +0300 +++ b/src/login-common/ssl-proxy.h Sun Aug 06 23:05:32 2006 +0300 @@ -14,6 +14,9 @@ const char *ssl_proxy_get_peer_name(struct ssl_proxy *proxy); void ssl_proxy_free(struct ssl_proxy *proxy); +/* Return number of active SSL proxies */ +unsigned int ssl_proxy_get_count(void); + void ssl_proxy_init(void); void ssl_proxy_deinit(void);
--- 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); }
--- a/src/master/login-process.h Fri Aug 04 20:46:11 2006 +0300 +++ b/src/master/login-process.h Sun Aug 06 23:05:32 2006 +0300 @@ -11,12 +11,15 @@ unsigned int listening_processes; unsigned int wanted_processes_count; - struct login_process *oldest_nonlisten_process; - struct login_process *newest_nonlisten_process; + /* if login_process_per_connection=yes this contains the list of + processes that are in LOGIN_STATE_FULL_PRELOGINS state */ + struct login_process *oldest_prelogin_process; + struct login_process *newest_prelogin_process; }; -void login_process_abormal_exit(pid_t pid); -void login_processes_destroy_all(void); +void login_process_destroyed(pid_t pid, bool abnormal_exit); + +void login_processes_destroy_all(bool unref); void login_processes_init(void); void login_processes_deinit(void);
--- a/src/master/main.c Fri Aug 04 20:46:11 2006 +0300 +++ b/src/master/main.c Sun Aug 06 23:05:32 2006 +0300 @@ -37,7 +37,6 @@ }; static const char *configfile = SYSCONFDIR "/" PACKAGE ".conf"; -static struct timeout *to; static const char *env_tz; struct ioloop *ioloop; @@ -134,7 +133,7 @@ i_warning("SIGHUP received - reloading configuration"); /* restart auth and login processes */ - login_processes_destroy_all(); + login_processes_destroy_all(FALSE); auth_processes_destroy_all(); dict_process_kill(); @@ -191,18 +190,45 @@ return NULL; } -static void timeout_handler(void *context __attr_unused__) +static void sigchld_handler(int signo __attr_unused__, + void *context __attr_unused__) { const char *process_type_name, *msg; pid_t pid; int status, process_type; + bool abnormal_exit; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { /* get the type and remove from hash */ process_type = PID_GET_PROCESS_TYPE(pid); PID_REMOVE_PROCESS_TYPE(pid); + abnormal_exit = TRUE; + + /* write errors to syslog */ + process_type_name = process_names[process_type]; + if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + if (status == 0) + abnormal_exit = FALSE; + else { + msg = get_exit_status_message(status); + msg = msg == NULL ? "" : + t_strconcat(" (", msg, ")", NULL); + i_error("child %s (%s) returned error %d%s", + dec2str(pid), process_type_name, + status, msg); + } + } else if (WIFSIGNALED(status)) { + i_error("child %s (%s) killed with signal %d", + dec2str(pid), process_type_name, + WTERMSIG(status)); + } + switch (process_type) { + case PROCESS_TYPE_LOGIN: + login_process_destroyed(pid, abnormal_exit); + break; case PROCESS_TYPE_IMAP: case PROCESS_TYPE_POP3: mail_process_destroyed(pid); @@ -214,29 +240,6 @@ dict_process_restart(); break; } - - /* write errors to syslog */ - process_type_name = process_names[process_type]; - if (WIFEXITED(status)) { - status = WEXITSTATUS(status); - if (status != 0) { - if (process_type == PROCESS_TYPE_LOGIN) - login_process_abormal_exit(pid); - - msg = get_exit_status_message(status); - msg = msg == NULL ? "" : - t_strconcat(" (", msg, ")", NULL); - i_error("child %s (%s) returned error %d%s", - dec2str(pid), process_type_name, - status, msg); - } - } else if (WIFSIGNALED(status)) { - if (process_type == PROCESS_TYPE_LOGIN) - login_process_abormal_exit(pid); - i_error("child %s (%s) killed with signal %d", - dec2str(pid), process_type_name, - WTERMSIG(status)); - } } if (pid == -1 && errno != EINTR && errno != ECHILD) @@ -558,7 +561,7 @@ lib_signals_set_handler(SIGUSR1, TRUE, sig_reopen_logs, NULL); pids = hash_create(default_pool, default_pool, 128, NULL, NULL); - to = timeout_add(100, timeout_handler, NULL); + lib_signals_set_handler(SIGCHLD, TRUE, sigchld_handler, NULL); ssl_init(); dict_process_init(); @@ -575,14 +578,14 @@ "/master.pid", NULL)); /* make sure we log if child processes died unexpectedly */ - timeout_handler(NULL); + sigchld_handler(SIGCHLD, NULL); login_processes_deinit(); auth_processes_deinit(); dict_process_deinit(); ssl_deinit(); - timeout_remove(&to); + lib_signals_unset_handler(SIGCHLD, sigchld_handler, NULL); if (close(null_fd) < 0) i_error("close(null_fd) failed: %m");
--- a/src/master/master-login-interface.h Fri Aug 04 20:46:11 2006 +0300 +++ b/src/master/master-login-interface.h Sun Aug 06 23:05:32 2006 +0300 @@ -9,10 +9,23 @@ /* Increase the version number every time master_login_request (or something else) is changed. */ -#define MASTER_LOGIN_PROTOCOL_VERSION 1 +#define MASTER_LOGIN_PROTOCOL_VERSION 2 + +enum master_login_state { + /* process is accepting new connections */ + LOGIN_STATE_LISTENING = 0, + /* process isn't accepting new connections, but it'd be able to kill + some connections which haven't logged in yet */ + LOGIN_STATE_FULL_PRELOGINS, + /* process is handling only logged in users */ + LOGIN_STATE_FULL_LOGINS, + + LOGIN_STATE_COUNT +}; struct master_login_request { uint32_t version; + /* if fd == -1, tag is used as master_login_state */ uint32_t tag; uint32_t auth_pid;
--- a/src/master/master-settings.c Fri Aug 04 20:46:11 2006 +0300 +++ b/src/master/master-settings.c Sun Aug 06 23:05:32 2006 +0300 @@ -93,7 +93,7 @@ DEF(SET_INT, login_process_size), DEF(SET_INT, login_processes_count), DEF(SET_INT, login_max_processes_count), - DEF(SET_INT, login_max_logging_users), + DEF(SET_INT, login_max_connections), /* mail */ DEF(SET_STR, valid_chroot_dirs), @@ -299,7 +299,7 @@ MEMBER(login_process_size) 32, MEMBER(login_processes_count) 3, MEMBER(login_max_processes_count) 128, - MEMBER(login_max_logging_users) 256, + MEMBER(login_max_connections) 256, /* mail */ MEMBER(valid_chroot_dirs) "", @@ -846,8 +846,8 @@ i_error("login_processes_count must be at least 1"); return FALSE; } - if (set->login_max_logging_users < 1) { - i_error("login_max_logging_users must be at least 1"); + if (set->login_max_connections < 1) { + i_error("login_max_connections must be at least 1"); return FALSE; }
--- a/src/master/master-settings.h Fri Aug 04 20:46:11 2006 +0300 +++ b/src/master/master-settings.h Sun Aug 06 23:05:32 2006 +0300 @@ -54,7 +54,7 @@ unsigned int login_process_size; unsigned int login_processes_count; unsigned int login_max_processes_count; - unsigned int login_max_logging_users; + unsigned int login_max_connections; /* mail */ const char *valid_chroot_dirs;
--- a/src/pop3-login/client.c Fri Aug 04 20:46:11 2006 +0300 +++ b/src/pop3-login/client.c Sun Aug 06 23:05:32 2006 +0300 @@ -77,7 +77,8 @@ &client->common.proxy); if (fd_ssl == -1) { client_send_line(client, "-ERR TLS initialization failed."); - client_destroy(client, "TLS initialization failed."); + client_destroy(client, + "Disconnected: TLS initialization failed."); return; } @@ -231,16 +232,18 @@ struct hash_iterate_context *iter; void *key, *value; struct pop3_client *destroy_buf[CLIENT_DESTROY_OLDEST_COUNT]; - int i; + unsigned int i, destroy_count; /* find the oldest clients and put them to destroy-buffer */ memset(destroy_buf, 0, sizeof(destroy_buf)); + destroy_count = max_connections > CLIENT_DESTROY_OLDEST_COUNT*2 ? + CLIENT_DESTROY_OLDEST_COUNT : I_MIN(max_connections/2, 1); iter = hash_iterate_init(clients); while (hash_iterate(iter, &key, &value)) { struct pop3_client *client = key; - for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) { + for (i = 0; i < destroy_count; i++) { if (destroy_buf[i] == NULL || destroy_buf[i]->created > client->created) { /* @UNSAFE */ @@ -255,7 +258,7 @@ hash_iterate_deinit(iter); /* then kill them */ - for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) { + for (i = 0; i < destroy_count; i++) { if (destroy_buf[i] == NULL) break; @@ -304,12 +307,22 @@ const struct ip_addr *ip) { struct pop3_client *client; + unsigned int current_count; - if (max_logging_users > CLIENT_DESTROY_OLDEST_COUNT && - hash_size(clients) >= max_logging_users) { - /* reached max. users count, kill few of the - oldest connections */ - client_destroy_oldest(); + if (!process_per_connection) { + current_count = hash_size(clients) + + ssl_proxy_get_count() + login_proxy_get_count(); + if (current_count >= max_connections) { + /* already reached max. users count, kill few of the + oldest connections. this happens when we've maxed + out the login process count also. */ + client_destroy_oldest(); + } + if (current_count + 1 >= max_connections) { + /* after this client we've reached max users count, + so stop listening for more */ + main_listen_stop(); + } } /* always use nonblocking I/O */ @@ -388,6 +401,8 @@ if (client->common.proxy != NULL) ssl_proxy_free(client->common.proxy); client_unref(client); + + main_listen_start(); } void client_destroy_internal_failure(struct pop3_client *client) @@ -496,7 +511,7 @@ while (hash_iterate(iter, &key, &value)) { struct pop3_client *client = key; - client_destroy(client, NULL); + client_destroy(client, "Disconnected: Shutting down"); } hash_iterate_deinit(iter); }