changeset 9756:e30495ae11de HEAD

*-login: Moved most of the common code to login-common.
author Timo Sirainen <tss@iki.fi>
date Sun, 09 Aug 2009 21:53:14 -0400
parents c2037505a66b
children 1c01e6bf1090
files src/imap-login/client-authenticate.c src/imap-login/client-authenticate.h src/imap-login/client.c src/imap-login/client.h src/imap-login/imap-proxy.c src/imap-login/imap-proxy.h src/login-common/Makefile.am src/login-common/client-common-auth.c src/login-common/client-common.c src/login-common/client-common.h 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/sasl-server.c src/login-common/ssl-proxy-openssl.c src/login-common/ssl-proxy.c src/login-common/ssl-proxy.h src/pop3-login/client-authenticate.c src/pop3-login/client-authenticate.h src/pop3-login/client.c src/pop3-login/client.h src/pop3-login/pop3-proxy.c src/pop3-login/pop3-proxy.h
diffstat 24 files changed, 1314 insertions(+), 1936 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap-login/client-authenticate.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/imap-login/client-authenticate.c	Sun Aug 09 21:53:14 2009 -0400
@@ -18,18 +18,14 @@
 
 #include <stdlib.h>
 
-#define AUTH_FAILURE_DELAY_INCREASE_MSECS 5000
-
-#define IMAP_SERVICE_NAME "imap"
-
-const char *client_authenticate_get_capabilities(struct imap_client *client)
+const char *client_authenticate_get_capabilities(struct client *client)
 {
 	const struct auth_mech_desc *mech;
 	unsigned int i, count;
 	string_t *str;
 
 	str = t_str_new(128);
-	mech = sasl_server_get_advertised_mechs(&client->common, &count);
+	mech = sasl_server_get_advertised_mechs(client, &count);
 	for (i = 0; i < count; i++) {
 		str_append_c(str, ' ');
 		str_append(str, "AUTH=");
@@ -39,146 +35,13 @@
 	return str_c(str);
 }
 
-static void client_auth_input(struct imap_client *client)
-{
-	char *line;
-
-	if (!client_read(client))
-		return;
-
-	if (client->skip_line) {
-		if (i_stream_next_line(client->common.input) == NULL)
-			return;
-
-		client->skip_line = FALSE;
-	}
-
-	/* @UNSAFE */
-	line = i_stream_next_line(client->common.input);
-	if (line == NULL)
-		return;
-
-	if (strcmp(line, "*") == 0)
-		sasl_server_auth_abort(&client->common);
-	else {
-		client_set_auth_waiting(client);
-		auth_client_request_continue(client->common.auth_request, line);
-		io_remove(&client->io);
-
-		/* clear sensitive data */
-		safe_memset(line, 0, strlen(line));
-	}
-}
-
-static void client_authfail_delay_timeout(struct imap_client *client)
+bool imap_client_auth_handle_reply(struct client *client,
+				   const struct client_auth_reply *reply)
 {
-	timeout_remove(&client->to_authfail_delay);
-
-	/* get back to normal client input. */
-	i_assert(client->io == NULL);
-	client->io = io_add(client->common.fd, IO_READ, client_input, client);
-	client_input(client);
-}
-
-void client_auth_failed(struct imap_client *client, bool nodelay)
-{
-	unsigned int delay_msecs;
-
-	i_free_and_null(client->common.master_data_prefix);
-
-	if (client->auth_initializing)
-		return;
-
-	if (client->io != NULL)
-		io_remove(&client->io);
-	if (nodelay) {
-		client->io = io_add(client->common.fd, IO_READ,
-				    client_input, client);
-		client_input(client);
-		return;
-	}
-
-	/* increase the timeout after each unsuccessful attempt, but don't
-	   increase it so high that the idle timeout would be triggered */
-	delay_msecs = client->common.auth_attempts *
-		AUTH_FAILURE_DELAY_INCREASE_MSECS;
-	if (delay_msecs > CLIENT_LOGIN_IDLE_TIMEOUT_MSECS)
-		delay_msecs = CLIENT_LOGIN_IDLE_TIMEOUT_MSECS - 1000;
+	struct imap_client *imap_client = (struct imap_client *)client;
+	string_t *str;
 
-	i_assert(client->to_authfail_delay == NULL);
-	client->to_authfail_delay =
-		timeout_add(delay_msecs, client_authfail_delay_timeout, client);
-}
-
-static bool client_handle_args(struct imap_client *client,
-			       const char *const *args, bool success,
-			       bool *nodelay_r)
-{
-	const char *reason = NULL, *host = NULL, *destuser = NULL, *pass = NULL;
-	const char *master_user = NULL;
-	const char *key, *value, *p;
-	enum login_proxy_ssl_flags ssl_flags = 0;
-	string_t *reply;
-	unsigned int port = 143;
-	bool proxy = FALSE, temp = FALSE, nologin = !success;
-	bool authz_failure = FALSE;
-
-	*nodelay_r = FALSE;
-	for (; *args != NULL; args++) {
-		p = strchr(*args, '=');
-		if (p == NULL) {
-			key = *args;
-			value = "";
-		} else {
-			key = t_strdup_until(*args, p);
-			value = p + 1;
-		}
-		if (strcmp(key, "nologin") == 0)
-			nologin = TRUE;
-		else if (strcmp(key, "nodelay") == 0)
-			*nodelay_r = TRUE;
-		else if (strcmp(key, "proxy") == 0)
-			proxy = TRUE;
-		else if (strcmp(key, "temp") == 0)
-			temp = TRUE;
-		else if (strcmp(key, "authz") == 0)
-			authz_failure = TRUE;
-		else if (strcmp(key, "reason") == 0)
-			reason = value;
-		else if (strcmp(key, "host") == 0)
-			host = value;
-		else if (strcmp(key, "port") == 0)
-			port = atoi(value);
-		else if (strcmp(key, "destuser") == 0)
-			destuser = value;
-		else if (strcmp(key, "pass") == 0)
-			pass = value;
-		else if (strcmp(key, "master") == 0)
-			master_user = value;
-		else if (strcmp(key, "user") == 0) {
-			/* already handled in login-common */
-		} else if (client->common.set->auth_debug)
-			i_info("Ignoring unknown passdb extra field: %s", key);
-	}
-
-	if (destuser == NULL)
-		destuser = client->common.virtual_user;
-
-	if (proxy) {
-		/* we want to proxy the connection to another server.
-		   don't do this unless authentication succeeded. with
-		   master user proxying we can get FAIL with proxy still set.
-
-		   proxy host=.. [port=..] [destuser=..] pass=.. */
-		if (!success)
-			return FALSE;
-		if (imap_proxy_new(client, host, port, destuser, master_user,
-				   pass, ssl_flags) < 0)
-			client_auth_failed(client, TRUE);
-		return TRUE;
-	}
-
-	if (host != NULL) {
+	if (reply->host != NULL) {
 		/* IMAP referral
 
 		   [nologin] referral host=.. [port=..] [destuser=..]
@@ -188,47 +51,46 @@
 		   OK [...] Logged in, but you should use this server instead.
 		   .. [REFERRAL ..] (Reason from auth server)
 		*/
-		reply = t_str_new(128);
-		str_append(reply, client->cmd_tag);
-		str_append_c(reply, ' ');
-		str_append(reply, nologin ? "NO " : "OK ");
-		str_printfa(reply, "[REFERRAL imap://%s;AUTH=%s@%s",
-			    destuser, client->common.auth_mech_name, host);
-		if (port != 143)
-			str_printfa(reply, ":%u", port);
-		str_append(reply, "/] ");
-		if (reason != NULL)
-			str_append(reply, reason);
-		else if (nologin)
-			str_append(reply, "Try this server instead.");
+		str = t_str_new(128);
+		str_append(str, imap_client->cmd_tag);
+		str_append_c(str, ' ');
+		str_append(str, reply->nologin ? "NO " : "OK ");
+		str_printfa(str, "[REFERRAL imap://%s;AUTH=%s@%s",
+			    reply->destuser, client->auth_mech_name,
+			    reply->host);
+		if (reply->port != 143)
+			str_printfa(str, ":%u", reply->port);
+		str_append(str, "/] ");
+		if (reply->reason != NULL)
+			str_append(str, reply->reason);
+		else if (reply->nologin)
+			str_append(str, "Try this server instead.");
 		else {
-			str_append(reply, "Logged in, but you should use "
+			str_append(str, "Logged in, but you should use "
 				   "this server instead.");
 		}
-		str_append(reply, "\r\n");
-		client_send_raw(client, str_c(reply));
-		if (!nologin) {
+		str_append(str, "\r\n");
+		client_send_raw(client, str_c(str));
+		if (!reply->nologin) {
 			client_destroy_success(client, "Login with referral");
 			return TRUE;
 		}
-	} else if (nologin) {
+	} else if (reply->nologin) {
 		/* Authentication went ok, but for some reason user isn't
 		   allowed to log in. Shouldn't probably happen. */
-		if (reason != NULL) {
-			client_send_line(&client->common,
+		if (reply->reason != NULL) {
+			client_send_line(client,
 					 CLIENT_CMD_REPLY_AUTH_FAIL_REASON,
-					 reason);
-		} else if (temp) {
-			client_send_line(&client->common,
+					 reply->reason);
+		} else if (reply->temp) {
+			client_send_line(client,
 					 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
 					 AUTH_TEMP_FAILED_MSG);
-		} else if (authz_failure) {
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTHZ_FAILED,
+		} else if (reply->authz_failure) {
+			client_send_line(client, CLIENT_CMD_REPLY_AUTHZ_FAILED,
 					 "Authorization failed");
 		} else {
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTH_FAILED,
+			client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
 					 AUTH_FAILED_MSG);
 		}
 	} else {
@@ -236,127 +98,34 @@
 		return FALSE;
 	}
 
-	i_assert(nologin);
+	i_assert(reply->nologin);
 
 	if (!client->destroyed)
-		client_auth_failed(client, *nodelay_r);
+		client_auth_failed(client, reply->nodelay);
 	return TRUE;
 }
 
-static void sasl_callback(struct client *_client, enum sasl_server_reply reply,
-			  const char *data, const char *const *args)
-{
-	struct imap_client *client = (struct imap_client *)_client;
-	struct const_iovec iov[3];
-	size_t data_len;
-	bool nodelay;
-
-	i_assert(!client->destroyed ||
-		 reply == SASL_SERVER_REPLY_AUTH_ABORTED ||
-		 reply == SASL_SERVER_REPLY_MASTER_FAILED);
-
-	switch (reply) {
-	case SASL_SERVER_REPLY_SUCCESS:
-		if (client->to_auth_waiting != NULL)
-			timeout_remove(&client->to_auth_waiting);
-		if (args != NULL) {
-			if (client_handle_args(client, args, TRUE, &nodelay))
-				break;
-		}
-		client_destroy_success(client, "Login");
-		break;
-	case SASL_SERVER_REPLY_AUTH_FAILED:
-	case SASL_SERVER_REPLY_AUTH_ABORTED:
-		if (client->to_auth_waiting != NULL)
-			timeout_remove(&client->to_auth_waiting);
-		if (args != NULL) {
-			if (client_handle_args(client, args, FALSE, &nodelay))
-				break;
-		}
-
-		if (reply == SASL_SERVER_REPLY_AUTH_ABORTED) {
-			client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-					 "Authentication aborted by client.");
-		} else if (data == NULL) {
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTH_FAILED,
-					 AUTH_FAILED_MSG);
-		} else {
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTH_FAIL_REASON,
-					 data);
-		}
-
-		if (!client->destroyed)
-			client_auth_failed(client, nodelay);
-		break;
-	case SASL_SERVER_REPLY_MASTER_FAILED:
-		if (data == NULL)
-			client_destroy_internal_failure(client);
-		else {
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP, data);
-			/* authentication itself succeeded, we just hit some
-			   internal failure. */
-			client_destroy_success(client, data);
-		}
-		break;
-	case SASL_SERVER_REPLY_CONTINUE:
-		data_len = strlen(data);
-		iov[0].iov_base = "+ ";
-		iov[0].iov_len = 2;
-		iov[1].iov_base = data;
-		iov[1].iov_len = data_len;
-		iov[2].iov_base = "\r\n";
-		iov[2].iov_len = 2;
-
-		/* don't check return value here. it gets tricky if we try
-		   to call client_destroy() in here. */
-		(void)o_stream_sendv(client->output, iov, 3);
-
-		if (client->to_auth_waiting != NULL)
-			timeout_remove(&client->to_auth_waiting);
-
-		i_assert(client->io == NULL);
-		client->io = io_add(client->common.fd, IO_READ,
-				    client_auth_input, client);
-		client_auth_input(client);
-		return;
-	}
-
-	client_unref(client);
-}
-
-static int client_auth_begin(struct imap_client *client, const char *mech_name,
-			     const char *init_resp)
+static int
+imap_client_auth_begin(struct imap_client *imap_client, const char *mech_name,
+		       const char *init_resp)
 {
 	char *prefix;
 
 	prefix = i_strdup_printf("%d%s",
-				 client->client_ignores_capability_resp_code,
-				 client->cmd_tag);
-
-	i_free(client->common.master_data_prefix);
-	client->common.master_data_prefix = (void *)prefix;
-	client->common.master_data_prefix_len = strlen(prefix)+1;
+			imap_client->client_ignores_capability_resp_code,
+			imap_client->cmd_tag);
 
-	client_ref(client);
-	client->auth_initializing = TRUE;
-	sasl_server_auth_begin(&client->common, IMAP_SERVICE_NAME, mech_name,
-			       init_resp, sasl_callback);
-	client->auth_initializing = FALSE;
-	if (!client->common.authenticating)
-		return 1;
+	i_free(imap_client->common.master_data_prefix);
+	imap_client->common.master_data_prefix = (void *)prefix;
+	imap_client->common.master_data_prefix_len = strlen(prefix)+1;
 
-	/* don't handle input until we get the initial auth reply */
-	if (client->io != NULL)
-		io_remove(&client->io);
-	client_set_auth_waiting(client);
-	return 0;
+	return client_auth_begin(&imap_client->common, mech_name, init_resp);
 }
 
-int cmd_authenticate(struct imap_client *client, const struct imap_arg *args)
+int cmd_authenticate(struct imap_client *imap_client,
+		     const struct imap_arg *args)
 {
+	struct client *client = &imap_client->common;
 	const char *mech_name, *init_resp = NULL;
 
 	/* we want only one argument: authentication mechanism name */
@@ -370,15 +139,13 @@
 		init_resp = IMAP_ARG_STR(&args[1]);
 	}
 
-	if (!client->common.secured &&
-	    strcmp(client->common.set->ssl, "required") == 0) {
-		if (client->common.set->verbose_auth) {
-			client_syslog(&client->common, "Login failed: "
-				      "SSL required for authentication");
+	if (!client->secured && strcmp(client->set->ssl, "required") == 0) {
+		if (client->set->verbose_auth) {
+			client_log(client, "Login failed: "
+				   "SSL required for authentication");
 		}
-		client->common.auth_attempts++;
-		client_send_line(&client->common,
-			CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
+		client->auth_attempts++;
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
 			"Authentication not allowed until SSL/TLS is enabled.");
 		return 1;
 	}
@@ -386,11 +153,12 @@
 	mech_name = IMAP_ARG_STR(&args[0]);
 	if (*mech_name == '\0')
 		return -1;
-	return client_auth_begin(client, mech_name, init_resp);
+	return imap_client_auth_begin(imap_client, mech_name, init_resp);
 }
 
-int cmd_login(struct imap_client *client, const struct imap_arg *args)
+int cmd_login(struct imap_client *imap_client, const struct imap_arg *args)
 {
+	struct client *client = &imap_client->common;
 	const char *user, *pass;
 	string_t *plain_login, *base64;
 
@@ -405,23 +173,8 @@
 	user = IMAP_ARG_STR(&args[0]);
 	pass = IMAP_ARG_STR(&args[1]);
 
-	if (!client->common.secured &&
-	    client->common.set->disable_plaintext_auth) {
-		if (client->common.set->verbose_auth) {
-			client_syslog(&client->common, "Login failed: "
-				      "Plaintext authentication disabled");
-		}
-		client->common.auth_tried_disabled_plaintext = TRUE;
-		client->common.auth_attempts++;
-		client_send_raw(client,
-			"* BAD [ALERT] Plaintext authentication not allowed "
-			"without SSL/TLS, but your client did it anyway. "
-			"If anyone was listening, the password was exposed.\r\n");
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
-				 AUTH_PLAINTEXT_DISABLED_MSG);
+	if (!client_check_plaintext_auth(client, TRUE))
 		return 1;
-	}
 
 	/* authorization ID \0 authentication ID \0 pass */
 	plain_login = buffer_create_dynamic(pool_datastack_create(), 64);
@@ -433,5 +186,5 @@
 	base64 = buffer_create_dynamic(pool_datastack_create(),
         			MAX_BASE64_ENCODED_SIZE(plain_login->used));
 	base64_encode(plain_login->data, plain_login->used, base64);
-	return client_auth_begin(client, "PLAIN", str_c(base64));
+	return imap_client_auth_begin(imap_client, "PLAIN", str_c(base64));
 }
--- a/src/imap-login/client-authenticate.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/imap-login/client-authenticate.h	Sun Aug 09 21:53:14 2009 -0400
@@ -3,7 +3,10 @@
 
 struct imap_arg;
 
-const char *client_authenticate_get_capabilities(struct imap_client *client);
+const char *client_authenticate_get_capabilities(struct client *client);
+
+bool imap_client_auth_handle_reply(struct client *client,
+				   const struct client_auth_reply *reply);
 
 int cmd_login(struct imap_client *client, const struct imap_arg *args);
 int cmd_authenticate(struct imap_client *client, const struct imap_arg *args);
--- a/src/imap-login/client.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/imap-login/client.c	Sun Aug 09 21:53:14 2009 -0400
@@ -22,61 +22,15 @@
 
 #include <stdlib.h>
 
-/* max. size of output buffer. if it gets full, the client is disconnected.
-   SASL authentication gives the largest output. */
-#define MAX_OUTBUF_SIZE 4096
-
 /* maximum length for IMAP command line. */
 #define MAX_IMAP_LINE 8192
 
 /* Disconnect client when it sends too many bad commands */
 #define CLIENT_MAX_BAD_COMMANDS 10
 
-/* When max. number of simultaneous connections is reached, few of the
-   oldest connections are disconnected. Since we have to go through all of the
-   clients, it's faster if we disconnect multiple clients. */
-#define CLIENT_DESTROY_OLDEST_COUNT 16
-
-/* If we've been waiting auth server to respond for over this many milliseconds,
-   send a "waiting" message. */
-#define AUTH_WAITING_TIMEOUT_MSECS (30*1000)
-
-#if CLIENT_LOGIN_IDLE_TIMEOUT_MSECS < AUTH_REQUEST_TIMEOUT*1000
-#  error client idle timeout must be larger than authentication timeout
-#endif
-
-#define AUTH_SERVER_WAITING_MSG \
-	"Waiting for authentication process to respond.."
-#define AUTH_MASTER_WAITING_MSG \
-	"Waiting for authentication master process to respond.."
-
 const char *login_protocol = "imap";
 const char *login_process_name = "imap-login";
-
-static void client_set_title(struct imap_client *client)
-{
-	const char *addr;
-
-	if (!client->common.set->verbose_proctitle ||
-	    !client->common.set->login_process_per_connection)
-		return;
-
-	addr = net_ip2addr(&client->common.ip);
-	if (addr == NULL)
-		addr = "??";
-
-	process_title_set(t_strdup_printf(client->common.tls ?
-					  "[%s TLS]" : "[%s]", addr));
-}
-
-static void client_open_streams(struct imap_client *client, int fd)
-{
-	client->common.input =
-		i_stream_create_fd(fd, LOGIN_MAX_INBUF_SIZE, FALSE);
-	client->output = o_stream_create_fd(fd, MAX_OUTBUF_SIZE, FALSE);
-	client->parser = imap_parser_create(client->common.input,
-					    client->output, MAX_IMAP_LINE);
-}
+unsigned int login_default_port = 143;
 
 /* Skip incoming data until newline is found,
    returns TRUE if newline was found. */
@@ -97,118 +51,36 @@
 	return FALSE;
 }
 
-static const char *get_capability(struct imap_client *client)
+static const char *get_capability(struct client *client)
 {
 	const char *auths;
 
 	auths = client_authenticate_get_capabilities(client);
 	return t_strconcat(CAPABILITY_BANNER_STRING,
-			   (ssl_initialized && !client->common.tls) ?
-			   " STARTTLS" : "",
-			   client->common.set->disable_plaintext_auth &&
-			   !client->common.secured ?
-			   " LOGINDISABLED" : "", auths, NULL);
+			   (ssl_initialized && !client->tls) ? " STARTTLS" : "",
+			   client->set->disable_plaintext_auth &&
+			   !client->secured ? " LOGINDISABLED" : "",
+			   auths, NULL);
 }
 
-static int cmd_capability(struct imap_client *client)
+static int cmd_capability(struct imap_client *imap_client)
 {
+	struct client *client = &imap_client->common;
+
 	/* Client is required to send CAPABILITY after STARTTLS, so the
 	   capability resp-code workaround checks only pre-STARTTLS
 	   CAPABILITY commands. */
 	if (!client->starttls)
-		client->client_ignores_capability_resp_code = TRUE;
+		imap_client->client_ignores_capability_resp_code = TRUE;
 	client_send_raw(client, t_strconcat(
 		"* CAPABILITY ", get_capability(client), "\r\n", NULL));
-	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
-			 "Capability completed.");
-	return 1;
-}
-
-static void client_start_tls(struct imap_client *client)
-{
-	int fd_ssl;
-
-	client_ref(client);
-	if (!client_unref(client) || client->destroyed)
-		return;
-
-	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip,
-			       client->common.set, &client->common.proxy);
-	if (fd_ssl == -1) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
-				 "TLS initialization failed.");
-		client_destroy(client,
-			       "Disconnected: TLS initialization failed.");
-		return;
-	}
-
-	client->starttls = TRUE;
-	client->common.proxying = TRUE;
-	client->common.tls = TRUE;
-	client->common.secured = TRUE;
-	client_set_title(client);
-
-	client->common.fd = fd_ssl;
-	i_stream_unref(&client->common.input);
-	o_stream_unref(&client->output);
-	imap_parser_destroy(&client->parser);
-
-	/* CRLF is lost from buffer when streams are reopened. */
-	client->skip_line = FALSE;
-
-	client_open_streams(client, fd_ssl);
-	client->io = io_add(client->common.fd, IO_READ, client_input, client);
-}
-
-static int client_output_starttls(struct imap_client *client)
-{
-	int ret;
-
-	if ((ret = o_stream_flush(client->output)) < 0) {
-		client_destroy(client, "Disconnected");
-		return 1;
-	}
-
-	if (ret > 0) {
-		o_stream_unset_flush_callback(client->output);
-		client_start_tls(client);
-	}
+	client_send_line(client, CLIENT_CMD_REPLY_OK, "Capability completed.");
 	return 1;
 }
 
 static int cmd_starttls(struct imap_client *client)
 {
-	if (client->common.tls) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-				 "TLS is already active.");
-		return 1;
-	}
-
-	if (!ssl_initialized) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-				 "TLS support isn't enabled.");
-		return 1;
-	}
-
-	/* remove input handler, SSL proxy gives us a new fd. we also have to
-	   remove it in case we have to wait for buffer to be flushed */
-	if (client->io != NULL)
-		io_remove(&client->io);
-
-	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
-			 "Begin TLS negotiation now.");
-
-	/* uncork the old fd */
-	o_stream_uncork(client->output);
-
-	if (o_stream_flush(client->output) <= 0) {
-		/* the buffer has to be flushed */
-		o_stream_set_flush_pending(client->output, TRUE);
-		o_stream_set_flush_callback(client->output,
-					    client_output_starttls, client);
-	} else {
-		client_start_tls(client);
-	}
+	client_cmd_starttls(&client->common);
 	return 1;
 }
 
@@ -249,13 +121,13 @@
 		env = getenv("IMAP_ID_LOG");
 		value = imap_id_args_get_log_reply(args, env);
 		if (value != NULL) {
-			client_syslog(&client->common,
-				      t_strdup_printf("ID sent: %s", value));
+			client_log(&client->common,
+				   t_strdup_printf("ID sent: %s", value));
 		}
 	}
 
 	env = getenv("IMAP_ID_SEND");
-	client_send_raw(client,
+	client_send_raw(&client->common,
 		t_strdup_printf("* ID %s\r\n", imap_id_reply_generate(env)));
 	client_send_line(&client->common, CLIENT_CMD_REPLY_OK, "ID completed.");
 	return 1;
@@ -270,17 +142,16 @@
 
 static int cmd_logout(struct imap_client *client)
 {
-	client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
-			 "Logging out");
+	client_send_line(&client->common, CLIENT_CMD_REPLY_BYE, "Logging out");
 	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
 			 "Logout completed.");
-	client_destroy(client, "Aborted login");
+	client_destroy(&client->common, "Aborted login");
 	return 1;
 }
 
 static int cmd_enable(struct imap_client *client)
 {
-	client_send_raw(client, "* ENABLED\r\n");
+	client_send_raw(&client->common, "* ENABLED\r\n");
 	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
 			 "ENABLE ignored in non-authenticated state.");
 	return 1;
@@ -356,7 +227,7 @@
 		if (fatal) {
 			client_send_line(&client->common,
 					 CLIENT_CMD_REPLY_BYE, msg);
-			client_destroy(client,
+			client_destroy(&client->common,
 				t_strconcat("Disconnected: ", msg, NULL));
 			return FALSE;
 		}
@@ -382,10 +253,10 @@
 	if (ret < 0) {
 		if (*client->cmd_tag == '\0')
 			client->cmd_tag = "*";
-		if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
+		if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
 			client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
 				"Too many invalid IMAP commands.");
-			client_destroy(client,
+			client_destroy(&client->common,
 				"Disconnected: Too many invalid commands");
 			return FALSE;
 		}  
@@ -396,31 +267,10 @@
 	return ret != 0;
 }
 
-bool client_read(struct imap_client *client)
+static void imap_client_input(struct client *client)
 {
-	switch (i_stream_read(client->common.input)) {
-	case -2:
-		/* buffer full */
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
-				 "Input buffer full, aborting");
-		client_destroy(client, "Disconnected: Input buffer full");
-		return FALSE;
-	case -1:
-		/* disconnected */
-		client_destroy(client, "Disconnected");
-		return FALSE;
-	case 0:
-		/* nothing new read */
-		return TRUE;
-	default:
-		/* something was read */
-		timeout_reset(client->to_idle_disconnect);
-		return TRUE;
-	}
-}
+	struct imap_client *imap_client = (struct imap_client *)client;
 
-void client_input(struct imap_client *client)
-{
 	if (!client_read(client))
 		return;
 
@@ -429,280 +279,76 @@
 	if (!auth_client_is_connected(auth_client)) {
 		/* we're not yet connected to auth process -
 		   don't allow any commands */
-		client_send_line(&client->common, CLIENT_CMD_REPLY_STATUS,
+		client_send_line(client, CLIENT_CMD_REPLY_STATUS,
 				 AUTH_SERVER_WAITING_MSG);
 		if (client->to_auth_waiting != NULL)
 			timeout_remove(&client->to_auth_waiting);
 
 		client->input_blocked = TRUE;
 	} else {
-		o_stream_cork(client->output);
-		while (client_handle_input(client)) ;
-		o_stream_uncork(client->output);
+		o_stream_cork(imap_client->common.output);
+		while (client_handle_input(imap_client)) ;
+		o_stream_uncork(imap_client->common.output);
 	}
 
 	client_unref(client);
 }
 
-void client_destroy_oldest(void)
+static struct client *imap_client_alloc(pool_t pool)
 {
-	unsigned int max_connections =
-		global_login_settings->login_max_connections;
-	struct client *client;
-	struct imap_client *destroy_buf[CLIENT_DESTROY_OLDEST_COUNT];
-	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);
-	for (client = clients; client != NULL; client = client->next) {
-		struct imap_client *imap_client = (struct imap_client *)client;
+	struct imap_client *imap_client;
 
-		for (i = 0; i < destroy_count; i++) {
-			if (destroy_buf[i] == NULL ||
-			    destroy_buf[i]->created > imap_client->created) {
-				/* @UNSAFE */
-				memmove(destroy_buf+i+1, destroy_buf+i,
-					sizeof(destroy_buf) -
-					(i+1) * sizeof(struct imap_client *));
-				destroy_buf[i] = imap_client;
-				break;
-			}
-		}
-	}
-
-	/* then kill them */
-	for (i = 0; i < destroy_count; i++) {
-		if (destroy_buf[i] == NULL)
-			break;
-
-		client_destroy(destroy_buf[i],
-			       "Disconnected: Connection queue full");
-	}
+	imap_client = p_new(pool, struct imap_client, 1);
+	return &imap_client->common;
 }
 
-static void client_send_greeting(struct imap_client *client)
+static void imap_client_create(struct client *client)
+{
+	struct imap_client *imap_client = (struct imap_client *)client;
+
+	imap_client->parser =
+		imap_parser_create(imap_client->common.input,
+				   imap_client->common.output, MAX_IMAP_LINE);
+	client->io = io_add(client->fd, IO_READ, client_input, client);
+}
+
+static void imap_client_destroy(struct client *client)
+{
+	struct imap_client *imap_client = (struct imap_client *)client;
+
+	i_free_and_null(imap_client->proxy_backend_capability);
+	imap_parser_destroy(&imap_client->parser);
+}
+
+static void imap_client_send_greeting(struct client *client)
 {
 	string_t *greet;
 
 	greet = t_str_new(128);
 	str_append(greet, "* OK ");
 	str_printfa(greet, "[CAPABILITY %s] ", get_capability(client));
-	str_append(greet, client->common.set->login_greeting);
+	str_append(greet, client->set->login_greeting);
 	str_append(greet, "\r\n");
 
 	client_send_raw(client, str_c(greet));
 	client->greeting_sent = TRUE;
 }
 
-static void client_idle_disconnect_timeout(struct imap_client *client)
-{
-	client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-			 "Disconnected for inactivity.");
-	client_destroy(client, "Disconnected: Inactivity");
-}
-
-static void client_auth_waiting_timeout(struct imap_client *client)
-{
-	client_send_line(&client->common, CLIENT_CMD_REPLY_STATUS,
-			 client->common.master_tag == 0 ?
-			 AUTH_SERVER_WAITING_MSG : AUTH_MASTER_WAITING_MSG);
-	timeout_remove(&client->to_auth_waiting);
-}
-
-void client_set_auth_waiting(struct imap_client *client)
-{
-	i_assert(client->to_auth_waiting == NULL);
-	client->to_auth_waiting =
-		timeout_add(AUTH_WAITING_TIMEOUT_MSECS,
-			    client_auth_waiting_timeout, client);
-}
-
-struct client *client_create(int fd, bool ssl, pool_t pool,
-			     const struct login_settings *set,
-			     const struct ip_addr *local_ip,
-			     const struct ip_addr *remote_ip)
+static void imap_client_starttls(struct client *client)
 {
-	struct imap_client *client;
-
-	i_assert(fd != -1);
-
-	if (clients_get_count() >= set->login_max_connections) {
-		/* reached max. users count, kill few of the
-		   oldest connections */
-		client_destroy_oldest();
-	}
-
-	/* always use nonblocking I/O */
-	net_set_nonblock(fd, TRUE);
-
-	client = p_new(pool, struct imap_client, 1);
-	client->created = ioloop_time;
-	client->refcount = 1;
-
-	client->common.pool = pool;
-	client->common.set = set;
-	client->common.local_ip = *local_ip;
-	client->common.ip = *remote_ip;
-	client->common.fd = fd;
-	client->common.tls = ssl;
-	client->common.trusted = client_is_trusted(&client->common);
-	client->common.secured = ssl || client->common.trusted ||
-		net_ip_compare(remote_ip, local_ip);
-
-	client_open_streams(client, fd);
-	client->io = io_add(fd, IO_READ, client_input, client);
-
-	client_link(&client->common);
-
-	if (auth_client_is_connected(auth_client))
-		client_send_greeting(client);
-	else
-		client_set_auth_waiting(client);
-	client_set_title(client);
-
-	client->to_idle_disconnect =
-		timeout_add(CLIENT_LOGIN_IDLE_TIMEOUT_MSECS,
-			    client_idle_disconnect_timeout, client);
-	return &client->common;
-}
-
-void client_destroy(struct imap_client *client, const char *reason)
-{
-	if (client->destroyed)
-		return;
-	client->destroyed = TRUE;
-
-	if (!client->login_success && reason != NULL) {
-		reason = t_strconcat(reason, " ",
-			client_get_extra_disconnect_reason(&client->common),
-			NULL);
-	}
-	if (reason != NULL)
-		client_syslog(&client->common, reason);
-
-	client_unlink(&client->common);
+	struct imap_client *imap_client = (struct imap_client *)client;
 
-	if (client->common.input != NULL)
-		i_stream_close(client->common.input);
-	if (client->output != NULL)
-		o_stream_close(client->output);
-
-	if (client->common.master_tag != 0) {
-		i_assert(client->common.auth_request == NULL);
-		i_assert(client->common.authenticating);
-		master_auth_request_abort(master_service,
-					  client->common.master_tag);
-	} else if (client->common.auth_request != NULL) {
-		i_assert(client->common.authenticating);
-		sasl_server_auth_abort(&client->common);
-	} else {
-		i_assert(!client->common.authenticating);
-	}
-
-	if (client->io != NULL)
-		io_remove(&client->io);
-	if (client->to_idle_disconnect != NULL)
-		timeout_remove(&client->to_idle_disconnect);
-	if (client->to_auth_waiting != NULL)
-		timeout_remove(&client->to_auth_waiting);
-	if (client->to_authfail_delay != NULL)
-		timeout_remove(&client->to_authfail_delay);
-
-	if (client->common.fd != -1) {
-		net_disconnect(client->common.fd);
-		client->common.fd = -1;
-	}
-
-	if (client->proxy_password != NULL) {
-		safe_memset(client->proxy_password, 0,
-			    strlen(client->proxy_password));
-		i_free(client->proxy_password);
-		client->proxy_password = NULL;
-	}
-
-	i_free_and_null(client->proxy_user);
-	i_free_and_null(client->proxy_master_user);
-	i_free_and_null(client->proxy_backend_capability);
-
-	if (client->proxy != NULL)
-		login_proxy_free(&client->proxy);
+	imap_parser_destroy(&imap_client->parser);
+	imap_client->parser =
+		imap_parser_create(imap_client->common.input,
+				   imap_client->common.output, MAX_IMAP_LINE);
 
-	if (client->common.proxy != NULL) {
-		ssl_proxy_free(client->common.proxy);
-		client->common.proxy = NULL;
-	}
-	client_unref(client);
-}
-
-void client_destroy_success(struct imap_client *client, const char *reason)
-{
-	client->login_success = TRUE;
-	client_destroy(client, reason);
-}
-
-void client_destroy_internal_failure(struct imap_client *client)
-{
-	client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
-			 "Internal login failure. "
-			 "Refer to server log for more information.");
-	client_destroy(client, "Internal login failure");
-}
-
-void client_ref(struct imap_client *client)
-{
-	client->refcount++;
-}
-
-bool client_unref(struct imap_client *client)
-{
-	i_assert(client->refcount > 0);
-	if (--client->refcount > 0)
-		return TRUE;
-
-	i_assert(client->destroyed);
-
-	imap_parser_destroy(&client->parser);
-
-	if (client->common.input != NULL)
-		i_stream_unref(&client->common.input);
-	if (client->output != NULL)
-		o_stream_unref(&client->output);
-
-	if (!client->common.proxying) {
-		i_assert(client->common.proxy == NULL);
-		master_service_client_connection_destroyed(master_service);
-	}
-
-	i_free(client->common.virtual_user);
-	i_free(client->common.auth_mech_name);
-	pool_unref(&client->common.pool);
-	return FALSE;
+	/* CRLF is lost from buffer when streams are reopened. */
+	imap_client->skip_line = FALSE;
 }
 
 static void
-client_send_raw_data(struct imap_client *client, const void *data, size_t size)
-{
-	ssize_t ret;
-
-	ret = o_stream_send(client->output, data, size);
-	if (ret < 0 || (size_t)ret != size) {
-		/* either disconnection or buffer full. in either case we want
-		   this connection destroyed. however destroying it here might
-		   break things if client is still tried to be accessed without
-		   being referenced.. */
-		i_stream_close(client->common.input);
-	}
-}
-
-void client_send_raw(struct imap_client *client, const char *data)
-{
-	client_send_raw_data(client, data, strlen(data));
-}
-
-void client_send_line(struct client *client, enum client_cmd_reply reply,
+imap_client_send_line(struct client *client, enum client_cmd_reply reply,
 		      const char *text)
 {
 	struct imap_client *imap_client = (struct imap_client *)client;
@@ -740,6 +386,11 @@
 		prefix = "OK";
 		tagged = FALSE;
 		break;
+	case CLIENT_CMD_REPLY_STATUS_BAD:
+		prefix = "BAD";
+		tagged = FALSE;
+		resp_code = "ALERT";
+		break;
 	}
 
 	T_BEGIN {
@@ -757,41 +408,11 @@
 		str_append(line, text);
 		str_append(line, "\r\n");
 
-		client_send_raw_data(imap_client, str_data(line),
+		client_send_raw_data(client, str_data(line),
 				     str_len(line));
 	} T_END;
 }
 
-void clients_notify_auth_connected(void)
-{
-	struct client *client;
-
-	for (client = clients; client != NULL; client = client->next) {
-		struct imap_client *imap_client = (struct imap_client *)client;
-
-		if (imap_client->to_auth_waiting != NULL)
-			timeout_remove(&imap_client->to_auth_waiting);
-		if (!imap_client->greeting_sent)
-			client_send_greeting(imap_client);
-		if (imap_client->input_blocked) {
-			imap_client->input_blocked = FALSE;
-			client_input(imap_client);
-		}
-	}
-}
-
-void clients_destroy_all(void)
-{
-	struct client *client, *next;
-
-	for (client = clients; client != NULL; client = next) {
-		struct imap_client *imap_client = (struct imap_client *)client;
-
-		next = client->next;
-		client_destroy(imap_client, "Disconnected: Shutting down");
-	}
-}
-
 void clients_init(void)
 {
 	/* Nothing to initialize for IMAP */
@@ -801,3 +422,16 @@
 {
 	clients_destroy_all();
 }
+
+struct client_vfuncs client_vfuncs = {
+	imap_client_alloc,
+	imap_client_create,
+	imap_client_destroy,
+	imap_client_send_greeting,
+	imap_client_starttls,
+	imap_client_input,
+	imap_client_send_line,
+	imap_client_auth_handle_reply,
+	imap_proxy_reset,
+	imap_proxy_parse_line
+};
--- a/src/imap-login/client.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/imap-login/client.h	Sun Aug 09 21:53:14 2009 -0400
@@ -4,56 +4,22 @@
 #include "network.h"
 #include "client-common.h"
 
-/* Disconnect client after idling this many milliseconds */
-#define CLIENT_LOGIN_IDLE_TIMEOUT_MSECS (3*60*1000)
-
 struct imap_client {
 	struct client common;
 
-	time_t created;
-	int refcount;
-
-	struct io *io;
-	struct ostream *output;
 	struct imap_parser *parser;
-	struct timeout *to_idle_disconnect, *to_auth_waiting;
-	struct timeout *to_authfail_delay;
-
-	struct login_proxy *proxy;
-	char *proxy_user, *proxy_master_user, *proxy_password;
 	char *proxy_backend_capability;
 
-	unsigned int bad_counter;
-
 	const char *cmd_tag, *cmd_name;
 
-	unsigned int starttls:1;
-	unsigned int login_success:1;
 	unsigned int cmd_finished:1;
 	unsigned int proxy_sasl_ir:1;
 	unsigned int proxy_seen_banner:1;
 	unsigned int skip_line:1;
-	unsigned int input_blocked:1;
-	unsigned int destroyed:1;
-	unsigned int greeting_sent:1;
 	unsigned int id_logged:1;
-	unsigned int auth_initializing:1;
 	unsigned int client_ignores_capability_resp_code:1;
 };
 
-void client_destroy(struct imap_client *client, const char *reason);
-void client_destroy_success(struct imap_client *client, const char *reason);
-void client_destroy_internal_failure(struct imap_client *client);
-
-bool client_read(struct imap_client *client);
 bool client_skip_line(struct imap_client *client);
-void client_input(struct imap_client *client);
-
-void client_send_raw(struct imap_client *client, const char *data);
-void client_ref(struct imap_client *client);
-bool client_unref(struct imap_client *client);
-
-void client_set_auth_waiting(struct imap_client *client);
-void client_auth_failed(struct imap_client *client, bool nodelay);
 
 #endif
--- a/src/imap-login/imap-proxy.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/imap-login/imap-proxy.c	Sun Aug 09 21:53:14 2009 -0400
@@ -30,7 +30,7 @@
 		    client->common.local_port);
 }
 
-static void proxy_free_password(struct imap_client *client)
+static void proxy_free_password(struct client *client)
 {
 	if (client->proxy_password == NULL)
 		return;
@@ -39,24 +39,7 @@
 	i_free_and_null(client->proxy_password);
 }
 
-static void proxy_failed(struct imap_client *client, bool send_tagline)
-{
-	if (send_tagline) {
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 AUTH_TEMP_FAILED_MSG);
-	}
-
-	login_proxy_free(&client->proxy);
-	proxy_free_password(client);
-	i_free_and_null(client->proxy_user);
-	i_free_and_null(client->proxy_master_user);
-
-	/* call this last - it may destroy the client */
-	client_auth_failed(client, TRUE);
-}
-
-static void get_plain_auth(struct imap_client *client, string_t *dest)
+static void get_plain_auth(struct client *client, string_t *dest)
 {
 	string_t *str;
 
@@ -91,19 +74,20 @@
 	if (client->client_ignores_capability_resp_code)
 		str_append(str, "C CAPABILITY\r\n");
 
-	if (client->proxy_master_user == NULL) {
+	if (client->common.proxy_master_user == NULL) {
 		/* logging in normally - use LOGIN command */
 		str_append(str, "L LOGIN ");
-		imap_quote_append_string(str, client->proxy_user, FALSE);
+		imap_quote_append_string(str, client->common.proxy_user, FALSE);
 		str_append_c(str, ' ');
-		imap_quote_append_string(str, client->proxy_password, FALSE);
+		imap_quote_append_string(str, client->common.proxy_password,
+					 FALSE);
 
-		proxy_free_password(client);
+		proxy_free_password(&client->common);
 	} else if (client->proxy_sasl_ir) {
 		/* master user login with SASL initial response support */
 		str_append(str, "L AUTHENTICATE PLAIN ");
-		get_plain_auth(client, str);
-		proxy_free_password(client);
+		get_plain_auth(&client->common, str);
+		proxy_free_password(&client->common);
 	} else {
 		/* master user login without SASL initial response */
 		str_append(str, "L AUTHENTICATE PLAIN");
@@ -119,7 +103,7 @@
 	string_t *str;
 
 	if (strncmp(line, "* OK ", 5) != 0) {
-		client_syslog_err(&client->common, t_strdup_printf(
+		client_log_err(&client->common, t_strdup_printf(
 			"proxy: Remote returned invalid banner: %s",
 			str_sanitize(line, 160)));
 		return -1;
@@ -134,11 +118,11 @@
 			client->proxy_sasl_ir = TRUE;
 	}
 
-	ssl_flags = login_proxy_get_ssl_flags(client->proxy);
+	ssl_flags = login_proxy_get_ssl_flags(client->common.login_proxy);
 	if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) != 0) {
 		if (capabilities != NULL &&
 		    !str_array_icase_find(capabilities, "STARTTLS")) {
-			client_syslog_err(&client->common,
+			client_log_err(&client->common,
 				"proxy: Remote doesn't support STARTTLS");
 			return -1;
 		}
@@ -151,20 +135,21 @@
 	return 0;
 }
 
-static int proxy_input_line(struct imap_client *client, const char *line)
+int imap_proxy_parse_line(struct client *client, const char *line)
 {
+	struct imap_client *imap_client = (struct imap_client *)client;
 	struct ostream *output;
 	const char *capability;
 	string_t *str;
 
 	i_assert(!client->destroyed);
 
-	output = login_proxy_get_ostream(client->proxy);
-	if (!client->proxy_seen_banner) {
+	output = login_proxy_get_ostream(client->login_proxy);
+	if (!imap_client->proxy_seen_banner) {
 		/* this is a banner */
-		client->proxy_seen_banner = TRUE;
-		if (proxy_input_banner(client, output, line) < 0) {
-			proxy_failed(client, TRUE);
+		imap_client->proxy_seen_banner = TRUE;
+		if (proxy_input_banner(imap_client, output, line) < 0) {
+			client_proxy_failed(client, TRUE);
 			return -1;
 		}
 		return 0;
@@ -180,88 +165,48 @@
 	} else if (strncmp(line, "S ", 2) == 0) {
 		if (strncmp(line, "S OK ", 5) != 0) {
 			/* STARTTLS failed */
-			client_syslog_err(&client->common, t_strdup_printf(
+			client_log_err(client, t_strdup_printf(
 				"proxy: Remote STARTTLS failed: %s",
 				str_sanitize(line + 5, 160)));
-			proxy_failed(client, TRUE);
+			client_proxy_failed(client, TRUE);
 			return -1;
 		}
 		/* STARTTLS successful, begin TLS negotiation. */
-		if (login_proxy_starttls(client->proxy) < 0) {
-			proxy_failed(client, TRUE);
+		if (login_proxy_starttls(client->login_proxy) < 0) {
+			client_proxy_failed(client, TRUE);
 			return -1;
 		}
 		/* i/ostreams changed. */
-		output = login_proxy_get_ostream(client->proxy);
+		output = login_proxy_get_ostream(client->login_proxy);
 		str = t_str_new(128);
-		proxy_write_login(client, str);
+		proxy_write_login(imap_client, str);
 		(void)o_stream_send(output, str_data(str), str_len(str));
 		return 1;
 	} else if (strncmp(line, "L OK ", 5) == 0) {
 		/* Login successful. Send this line to client. */
-		capability = client->proxy_backend_capability;
+		capability = imap_client->proxy_backend_capability;
 		if (strncmp(line + 5, "[CAPABILITY ", 12) == 0)
 			capability = t_strcut(line + 5 + 12, ']');
 
 		str = t_str_new(128);
-		client_send_capability_if_needed(client, str, capability);
-		str_append(str, client->cmd_tag);
+		client_send_capability_if_needed(imap_client, str, capability);
+		str_append(str, imap_client->cmd_tag);
 		str_append(str, line + 1);
 		str_append(str, "\r\n");
 		(void)o_stream_send(client->output,
 				    str_data(str), str_len(str));
 
-		str_truncate(str, 0);
-		str_printfa(str, "proxy(%s): started proxying to %s:%u",
-			    client->common.virtual_user,
-			    login_proxy_get_host(client->proxy),
-			    login_proxy_get_port(client->proxy));
-		if (strcmp(client->common.virtual_user,
-			   client->proxy_user) != 0) {
-			/* remote username is different, log it */
-			str_append_c(str, '/');
-			str_append(str, client->proxy_user);
-		}
-		if (client->proxy_master_user != NULL) {
-			str_printfa(str, " (master %s)",
-				    client->proxy_master_user);
-		}
-
-		(void)client_skip_line(client);
-		login_proxy_detach(client->proxy, client->common.input,
-				   client->output);
-
-		client->proxy = NULL;
-		client->common.input = NULL;
-		client->output = NULL;
-		client->common.fd = -1;
-		client->common.proxying = TRUE;
-		client_destroy_success(client, str_c(str));
+		(void)client_skip_line(imap_client);
+		client_proxy_finish_destroy_client(client);
 		return 1;
 	} else if (strncmp(line, "L ", 2) == 0) {
 		line += 2;
-		if (client->common.set->verbose_auth) {
-			str = t_str_new(128);
-			str_printfa(str, "proxy(%s): Login failed to %s:%u",
-				    client->common.virtual_user,
-				    login_proxy_get_host(client->proxy),
-				    login_proxy_get_port(client->proxy));
-			if (strcmp(client->common.virtual_user,
-				   client->proxy_user) != 0) {
-				/* remote username is different, log it */
-				str_append_c(str, '/');
-				str_append(str, client->proxy_user);
-			}
-			if (client->proxy_master_user != NULL) {
-				str_printfa(str, " (master %s)",
-					    client->proxy_master_user);
-			}
-			str_append(str, ": ");
-			if (strncasecmp(line, "NO ", 3) == 0)
-				str_append(str, line + 3);
-			else
-				str_append(str, line);
-			i_info("%s", str_c(str));
+		if (client->set->verbose_auth) {
+			const char *log_line = line;
+
+			if (strncasecmp(log_line, "NO ", 3) == 0)
+				log_line += 3;
+			client_proxy_log_failure(client, log_line);
 		}
 #define STR_NO_IMAP_RESP_CODE_AUTHFAILED "NO ["IMAP_RESP_CODE_AUTHFAILED"]"
 		if (strncmp(line, STR_NO_IMAP_RESP_CODE_AUTHFAILED,
@@ -271,13 +216,12 @@
 			   the remote is sending a different error message
 			   an attacker can't find out what users exist in
 			   the system. */
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTH_FAILED,
+			client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
 					 AUTH_FAILED_MSG);
 		} else if (strncmp(line, "NO [", 4) == 0) {
 			/* remote sent some other resp-code. forward it. */
 			client_send_raw(client, t_strconcat(
-				client->cmd_tag, " ", line, "\r\n", NULL));
+				imap_client->cmd_tag, " ", line, "\r\n", NULL));
 		} else {
 			/* there was no [resp-code], so remote isn't Dovecot
 			   v1.2+. we could either forward the line as-is and
@@ -286,16 +230,15 @@
 			   failures. since other errors are pretty rare,
 			   it's safer to just hide them. they're still
 			   available in logs though. */
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTH_FAILED,
+			client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
 					 AUTH_FAILED_MSG);
 		}
 
-		proxy_failed(client, FALSE);
+		client_proxy_failed(client, FALSE);
 		return -1;
 	} else if (strncasecmp(line, "* CAPABILITY ", 13) == 0) {
-		i_free(client->proxy_backend_capability);
-		client->proxy_backend_capability = i_strdup(line + 13);
+		i_free(imap_client->proxy_backend_capability);
+		imap_client->proxy_backend_capability = i_strdup(line + 13);
 		return 0;
 	} else if (strncasecmp(line, "I ", 2) == 0 ||
 		   strncasecmp(line, "* ID ", 5) == 0) {
@@ -311,96 +254,10 @@
 	}
 }
 
-static void proxy_input(struct imap_client *client)
-{
-	struct istream *input;
-	const char *line;
-
-	if (client->proxy == NULL) {
-		/* we're just freeing the proxy */
-		return;
-	}
-
-	input = login_proxy_get_istream(client->proxy);
-	if (input == NULL) {
-		if (client->destroyed) {
-			/* we came here from client_destroy() */
-			return;
-		}
-
-		/* failed for some reason, probably server disconnected */
-		proxy_failed(client, TRUE);
-		return;
-	}
-
-	i_assert(!client->destroyed);
-
-	switch (i_stream_read(input)) {
-	case -2:
-		client_syslog_err(&client->common,
-				  "proxy: Remote input buffer full");
-		proxy_failed(client, TRUE);
-		return;
-	case -1:
-		client_syslog_err(&client->common,
-				  "proxy: Remote disconnected");
-		proxy_failed(client, TRUE);
-		return;
-	}
-
-	while ((line = i_stream_next_line(input)) != NULL) {
-		if (proxy_input_line(client, line) != 0)
-			break;
-	}
-}
-
-int imap_proxy_new(struct imap_client *client, const char *host,
-		   unsigned int port, const char *user, const char *master_user,
-		   const char *password, enum login_proxy_ssl_flags ssl_flags)
+void imap_proxy_reset(struct client *client)
 {
-	i_assert(user != NULL);
-	i_assert(!client->destroyed);
-
-	if (password == NULL) {
-		client_syslog_err(&client->common, "proxy: password not given");
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 AUTH_TEMP_FAILED_MSG);
-		return -1;
-	}
-
-	i_assert(client->refcount > 1);
+	struct imap_client *imap_client = (struct imap_client *)client;
 
-	if (client->destroyed) {
-		/* connection_queue_add() decided that we were the oldest
-		   connection and killed us. */
-		return -1;
-	}
-	if (login_proxy_is_ourself(&client->common, host, port, user)) {
-		client_syslog_err(&client->common, "Proxying loops to itself");
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 AUTH_TEMP_FAILED_MSG);
-		return -1;
-	}
-
-	client->proxy = login_proxy_new(&client->common, host, port, ssl_flags,
-					proxy_input, client);
-	if (client->proxy == NULL) {
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 AUTH_TEMP_FAILED_MSG);
-		return -1;
-	}
-
-	client->proxy_sasl_ir = FALSE;
-	client->proxy_seen_banner = FALSE;
-	client->proxy_user = i_strdup(user);
-	client->proxy_master_user = i_strdup(master_user);
-	client->proxy_password = i_strdup(password);
-
-	/* disable input until authentication is finished */
-	if (client->io != NULL)
-		io_remove(&client->io);
-	return 0;
+	imap_client->proxy_sasl_ir = FALSE;
+	imap_client->proxy_seen_banner = FALSE;
 }
--- a/src/imap-login/imap-proxy.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/imap-login/imap-proxy.h	Sun Aug 09 21:53:14 2009 -0400
@@ -1,10 +1,7 @@
 #ifndef IMAP_PROXY_H
 #define IMAP_PROXY_H
 
-#include "login-proxy.h"
-
-int imap_proxy_new(struct imap_client *client, const char *hosts,
-		   unsigned int port, const char *user, const char *master_user,
-		   const char *password, enum login_proxy_ssl_flags ssl_flags);
+void imap_proxy_reset(struct client *client);
+int imap_proxy_parse_line(struct client *client, const char *line);
 
 #endif
--- a/src/login-common/Makefile.am	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/Makefile.am	Sun Aug 09 21:53:14 2009 -0400
@@ -9,6 +9,7 @@
 
 liblogin_la_SOURCES = \
 	client-common.c \
+	client-common-auth.c \
 	login-proxy.c \
 	login-settings.c \
 	main.c \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/login-common/client-common-auth.c	Sun Aug 09 21:53:14 2009 -0400
@@ -0,0 +1,480 @@
+/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
+
+#include "common.h"
+#include "istream.h"
+#include "ostream.h"
+#include "str.h"
+#include "safe-memset.h"
+#include "login-proxy.h"
+#include "auth-client.h"
+#include "client-common.h"
+
+#include <stdlib.h>
+
+/* If we've been waiting auth server to respond for over this many milliseconds,
+   send a "waiting" message. */
+#define AUTH_WAITING_TIMEOUT_MSECS (30*1000)
+#define AUTH_FAILURE_DELAY_INCREASE_MSECS 5000
+
+#if CLIENT_LOGIN_IDLE_TIMEOUT_MSECS < AUTH_REQUEST_TIMEOUT*1000
+#  error client idle timeout must be larger than authentication timeout
+#endif
+
+static void client_authfail_delay_timeout(struct client *client)
+{
+	timeout_remove(&client->to_authfail_delay);
+
+	/* get back to normal client input. */
+	i_assert(client->io == NULL);
+	client->io = io_add(client->fd, IO_READ, client_input, client);
+	client_input(client);
+}
+
+void client_auth_failed(struct client *client, bool nodelay)
+{
+	unsigned int delay_msecs;
+
+	i_free_and_null(client->master_data_prefix);
+
+	if (client->auth_initializing)
+		return;
+
+	if (client->io != NULL)
+		io_remove(&client->io);
+	if (nodelay) {
+		client->io = io_add(client->fd, IO_READ, client_input, client);
+		client_input(client);
+		return;
+	}
+
+	/* increase the timeout after each unsuccessful attempt, but don't
+	   increase it so high that the idle timeout would be triggered */
+	delay_msecs = client->auth_attempts * AUTH_FAILURE_DELAY_INCREASE_MSECS;
+	if (delay_msecs > CLIENT_LOGIN_IDLE_TIMEOUT_MSECS)
+		delay_msecs = CLIENT_LOGIN_IDLE_TIMEOUT_MSECS - 1000;
+
+	i_assert(client->to_authfail_delay == NULL);
+	client->to_authfail_delay =
+		timeout_add(delay_msecs, client_authfail_delay_timeout, client);
+}
+
+static void client_auth_waiting_timeout(struct client *client)
+{
+	client_send_line(client, CLIENT_CMD_REPLY_STATUS,
+			 client->master_tag == 0 ?
+			 AUTH_SERVER_WAITING_MSG : AUTH_MASTER_WAITING_MSG);
+	timeout_remove(&client->to_auth_waiting);
+}
+
+void client_set_auth_waiting(struct client *client)
+{
+	i_assert(client->to_auth_waiting == NULL);
+	client->to_auth_waiting =
+		timeout_add(AUTH_WAITING_TIMEOUT_MSECS,
+			    client_auth_waiting_timeout, client);
+}
+
+static void client_auth_parse_args(struct client *client,
+				   const char *const *args,
+				   struct client_auth_reply *reply_r)
+{
+	const char *key, *value, *p;
+
+	memset(reply_r, 0, sizeof(*reply_r));
+	reply_r->port = login_default_port;
+
+	for (; *args != NULL; args++) {
+		p = strchr(*args, '=');
+		if (p == NULL) {
+			key = *args;
+			value = "";
+		} else {
+			key = t_strdup_until(*args, p);
+			value = p + 1;
+		}
+		if (strcmp(key, "nologin") == 0)
+			reply_r->nologin = TRUE;
+		else if (strcmp(key, "nodelay") == 0)
+			reply_r->nodelay = TRUE;
+		else if (strcmp(key, "proxy") == 0)
+			reply_r->proxy = TRUE;
+		else if (strcmp(key, "temp") == 0)
+			reply_r->temp = TRUE;
+		else if (strcmp(key, "authz") == 0)
+			reply_r->authz_failure = TRUE;
+		else if (strcmp(key, "reason") == 0)
+			reply_r->reason = value;
+		else if (strcmp(key, "host") == 0)
+			reply_r->host = value;
+		else if (strcmp(key, "port") == 0)
+			reply_r->port = atoi(value);
+		else if (strcmp(key, "destuser") == 0)
+			reply_r->destuser = value;
+		else if (strcmp(key, "pass") == 0)
+			reply_r->password = value;
+		else if (strcmp(key, "master") == 0)
+			reply_r->master_user = value;
+		else if (strcmp(key, "ssl") == 0) {
+			if (strcmp(value, "yes") == 0)
+				reply_r->ssl_flags |= PROXY_SSL_FLAG_YES;
+			else if (strcmp(value, "any-cert") == 0) {
+				reply_r->ssl_flags |= PROXY_SSL_FLAG_YES |
+					PROXY_SSL_FLAG_ANY_CERT;
+			}
+		} else if (strcmp(key, "starttls") == 0) {
+			reply_r->ssl_flags |= PROXY_SSL_FLAG_STARTTLS;
+		} else if (strcmp(key, "user") == 0) {
+			/* already handled in login-common */
+		} else if (client->set->auth_debug)
+			i_info("Ignoring unknown passdb extra field: %s", key);
+	}
+
+	if (reply_r->destuser == NULL)
+		reply_r->destuser = client->virtual_user;
+}
+
+static void proxy_free_password(struct client *client)
+{
+	if (client->proxy_password == NULL)
+		return;
+
+	safe_memset(client->proxy_password, 0, strlen(client->proxy_password));
+	i_free_and_null(client->proxy_password);
+}
+
+void client_proxy_finish_destroy_client(struct client *client)
+{
+	string_t *str = t_str_new(128);
+
+	str_printfa(str, "proxy(%s): started proxying to %s:%u",
+		    client->virtual_user,
+		    login_proxy_get_host(client->login_proxy),
+		    login_proxy_get_port(client->login_proxy));
+	if (strcmp(client->virtual_user, client->proxy_user) != 0) {
+		/* remote username is different, log it */
+		str_append_c(str, '/');
+		str_append(str, client->proxy_user);
+	}
+	if (client->proxy_master_user != NULL)
+		str_printfa(str, " (master %s)", client->proxy_master_user);
+
+	login_proxy_detach(client->login_proxy, client->input, client->output);
+
+	client->login_proxy = NULL;
+	client->input = NULL;
+	client->output = NULL;
+	client->fd = -1;
+	client->proxying = TRUE;
+	client_destroy_success(client, str_c(str));
+}
+
+void client_proxy_log_failure(struct client *client, const char *line)
+{
+	string_t *str = t_str_new(128);
+
+	str_printfa(str, "proxy(%s): Login failed to %s:%u",
+		    client->virtual_user,
+		    login_proxy_get_host(client->login_proxy),
+		    login_proxy_get_port(client->login_proxy));
+	if (strcmp(client->virtual_user, client->proxy_user) != 0) {
+		/* remote username is different, log it */
+		str_append_c(str, '/');
+		str_append(str, client->proxy_user);
+	}
+	if (client->proxy_master_user != NULL)
+		str_printfa(str, " (master %s)", client->proxy_master_user);
+	str_append(str, ": ");
+	str_append(str, line);
+	i_info("%s", str_c(str));
+}
+
+void client_proxy_failed(struct client *client, bool send_line)
+{
+	if (send_line) {
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+				 AUTH_TEMP_FAILED_MSG);
+	}
+
+	login_proxy_free(&client->login_proxy);
+	proxy_free_password(client);
+	i_free_and_null(client->proxy_user);
+	i_free_and_null(client->proxy_master_user);
+
+	/* call this last - it may destroy the client */
+	client_auth_failed(client, TRUE);
+}
+
+static void proxy_input(struct client *client)
+{
+	struct istream *input;
+	const char *line;
+
+	if (client->login_proxy == NULL) {
+		/* we're just freeing the proxy */
+		return;
+	}
+
+	input = login_proxy_get_istream(client->login_proxy);
+	if (input == NULL) {
+		if (client->destroyed) {
+			/* we came here from client_destroy() */
+			return;
+		}
+
+		/* failed for some reason, probably server disconnected */
+		client_proxy_failed(client, TRUE);
+		return;
+	}
+
+	i_assert(!client->destroyed);
+
+	switch (i_stream_read(input)) {
+	case -2:
+		client_log_err(client, "proxy: Remote input buffer full");
+		client_proxy_failed(client, TRUE);
+		return;
+	case -1:
+		client_log_err(client, "proxy: Remote disconnected");
+		client_proxy_failed(client, TRUE);
+		return;
+	}
+
+	while ((line = i_stream_next_line(input)) != NULL) {
+		if (client->v.proxy_parse_line(client, line) != 0)
+			break;
+	}
+}
+
+static int proxy_start(struct client *client,
+		       const struct client_auth_reply *reply)
+{
+	i_assert(reply->destuser != NULL);
+	i_assert(!client->destroyed);
+
+	client->v.proxy_reset(client);
+
+	if (reply->password == NULL) {
+		client_log_err(client, "proxy: password not given");
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+				 AUTH_TEMP_FAILED_MSG);
+		return -1;
+	}
+
+	i_assert(client->refcount > 1);
+
+	if (client->destroyed) {
+		/* connection_queue_add() decided that we were the oldest
+		   connection and killed us. */
+		return -1;
+	}
+	if (login_proxy_is_ourself(client, reply->host, reply->port,
+				   reply->destuser)) {
+		client_log_err(client, "Proxying loops to itself");
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+				 AUTH_TEMP_FAILED_MSG);
+		return -1;
+	}
+
+	client->login_proxy =
+		login_proxy_new(client, reply->host, reply->port,
+				reply->ssl_flags, proxy_input, client);
+	if (client->login_proxy == NULL) {
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+				 AUTH_TEMP_FAILED_MSG);
+		return -1;
+	}
+
+	client->proxy_user = i_strdup(reply->destuser);
+	client->proxy_master_user = i_strdup(reply->master_user);
+	client->proxy_password = i_strdup(reply->password);
+
+	/* disable input until authentication is finished */
+	if (client->io != NULL)
+		io_remove(&client->io);
+	return 0;
+}
+
+static bool
+client_auth_handle_reply(struct client *client,
+			 const struct client_auth_reply *reply, bool success)
+{
+	if (reply->proxy) {
+		/* we want to proxy the connection to another server.
+		   don't do this unless authentication succeeded. with
+		   master user proxying we can get FAIL with proxy still set.
+
+		   proxy host=.. [port=..] [destuser=..] pass=.. */
+		if (!success)
+			return FALSE;
+		if (proxy_start(client, reply) < 0)
+			client_auth_failed(client, TRUE);
+		return TRUE;
+	}
+	return client->v.auth_handle_reply(client, reply);
+}
+
+static void client_auth_input(struct client *client)
+{
+	char *line;
+
+	if (!client_read(client))
+		return;
+
+	/* @UNSAFE */
+	line = i_stream_next_line(client->input);
+	if (line == NULL)
+		return;
+
+	if (strcmp(line, "*") == 0)
+		sasl_server_auth_abort(client);
+	else {
+		client_set_auth_waiting(client);
+		auth_client_request_continue(client->auth_request, line);
+		io_remove(&client->io);
+
+		/* clear sensitive data */
+		safe_memset(line, 0, strlen(line));
+	}
+}
+
+static void
+sasl_callback(struct client *client, enum sasl_server_reply sasl_reply,
+	      const char *data, const char *const *args)
+{
+	struct const_iovec iov[3];
+	struct client_auth_reply reply;
+	size_t data_len;
+
+	i_assert(!client->destroyed ||
+		 sasl_reply == SASL_SERVER_REPLY_AUTH_ABORTED ||
+		 sasl_reply == SASL_SERVER_REPLY_MASTER_FAILED);
+
+	switch (sasl_reply) {
+	case SASL_SERVER_REPLY_SUCCESS:
+		if (client->to_auth_waiting != NULL)
+			timeout_remove(&client->to_auth_waiting);
+		if (args != NULL) {
+			client_auth_parse_args(client, args, &reply);
+			if (client_auth_handle_reply(client, &reply, TRUE))
+				break;
+		}
+		client_destroy_success(client, "Login");
+		break;
+	case SASL_SERVER_REPLY_AUTH_FAILED:
+	case SASL_SERVER_REPLY_AUTH_ABORTED:
+		if (client->to_auth_waiting != NULL)
+			timeout_remove(&client->to_auth_waiting);
+		if (args != NULL) {
+			client_auth_parse_args(client, args, &reply);
+			reply.nologin = TRUE;
+			if (client_auth_handle_reply(client, &reply, FALSE))
+				break;
+		}
+
+		if (sasl_reply == SASL_SERVER_REPLY_AUTH_ABORTED) {
+			client_send_line(client, CLIENT_CMD_REPLY_BAD,
+					 "Authentication aborted by client.");
+		} else if (data == NULL) {
+			client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
+					 AUTH_FAILED_MSG);
+		} else {
+			client_send_line(client,
+					 CLIENT_CMD_REPLY_AUTH_FAIL_REASON,
+					 data);
+		}
+
+		if (!client->destroyed)
+			client_auth_failed(client, reply.nodelay);
+		break;
+	case SASL_SERVER_REPLY_MASTER_FAILED:
+		if (data == NULL)
+			client_destroy_internal_failure(client);
+		else {
+			client_send_line(client,
+					 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP, data);
+			/* authentication itself succeeded, we just hit some
+			   internal failure. */
+			client_destroy_success(client, data);
+		}
+		break;
+	case SASL_SERVER_REPLY_CONTINUE:
+		data_len = strlen(data);
+		iov[0].iov_base = "+ ";
+		iov[0].iov_len = 2;
+		iov[1].iov_base = data;
+		iov[1].iov_len = data_len;
+		iov[2].iov_base = "\r\n";
+		iov[2].iov_len = 2;
+
+		/* don't check return value here. it gets tricky if we try
+		   to call client_destroy() in here. */
+		(void)o_stream_sendv(client->output, iov, 3);
+
+		if (client->to_auth_waiting != NULL)
+			timeout_remove(&client->to_auth_waiting);
+
+		i_assert(client->io == NULL);
+		client->io = io_add(client->fd, IO_READ,
+				    client_auth_input, client);
+		client_auth_input(client);
+		return;
+	}
+
+	client_unref(client);
+}
+
+int client_auth_begin(struct client *client, const char *mech_name,
+		      const char *init_resp)
+{
+	client_ref(client);
+	client->auth_initializing = TRUE;
+	sasl_server_auth_begin(client, login_protocol, mech_name,
+			       init_resp, sasl_callback);
+	client->auth_initializing = FALSE;
+	if (!client->authenticating)
+		return 1;
+
+	/* don't handle input until we get the initial auth reply */
+	if (client->io != NULL)
+		io_remove(&client->io);
+	client_set_auth_waiting(client);
+	return 0;
+}
+
+bool client_check_plaintext_auth(struct client *client, bool pass_sent)
+{
+	if (client->secured || !client->set->disable_plaintext_auth)
+		return TRUE;
+
+	if (client->set->verbose_auth) {
+		client_log(client, "Login failed: "
+			   "Plaintext authentication disabled");
+	}
+	if (pass_sent) {
+		client_send_line(client, CLIENT_CMD_REPLY_STATUS_BAD,
+			 "Plaintext authentication not allowed "
+			 "without SSL/TLS, but your client did it anyway. "
+			 "If anyone was listening, the password was exposed.");
+	}
+	client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
+			 AUTH_PLAINTEXT_DISABLED_MSG);
+	client->auth_tried_disabled_plaintext = TRUE;
+	client->auth_attempts++;
+	return FALSE;
+}
+
+void clients_notify_auth_connected(void)
+{
+	struct client *client;
+
+	for (client = clients; client != NULL; client = client->next) {
+		if (client->to_auth_waiting != NULL)
+			timeout_remove(&client->to_auth_waiting);
+		if (!client->greeting_sent)
+			client->v.send_greeting(client);
+		if (client->input_blocked) {
+			client->input_blocked = FALSE;
+			client_input(client);
+		}
+	}
+}
--- a/src/login-common/client-common.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/client-common.c	Sun Aug 09 21:53:14 2009 -0400
@@ -3,29 +3,333 @@
 #include "common.h"
 #include "hostpid.h"
 #include "llist.h"
+#include "istream.h"
+#include "ostream.h"
+#include "process-title.h"
 #include "str.h"
 #include "str-sanitize.h"
+#include "safe-memset.h"
 #include "var-expand.h"
+#include "master-service.h"
+#include "master-auth.h"
+#include "auth-client.h"
+#include "login-proxy.h"
 #include "ssl-proxy.h"
 #include "client-common.h"
 
 #include <stdlib.h>
 
+/* When max. number of simultaneous connections is reached, few of the
+   oldest connections are disconnected. Since we have to go through all of the
+   clients, it's faster if we disconnect multiple clients. */
+#define CLIENT_DESTROY_OLDEST_COUNT 16
+
 struct client *clients = NULL;
 static unsigned int clients_count = 0;
 
-void client_link(struct client *client)
+static void client_idle_disconnect_timeout(struct client *client)
+{
+	client_send_line(client, CLIENT_CMD_REPLY_BAD,
+			 "Disconnected for inactivity.");
+	client_destroy(client, "Disconnected: Inactivity");
+}
+
+static void client_open_streams(struct client *client)
+{
+	client->input =
+		i_stream_create_fd(client->fd, LOGIN_MAX_INBUF_SIZE, FALSE);
+	client->output =
+		o_stream_create_fd(client->fd, LOGIN_MAX_OUTBUF_SIZE, FALSE);
+}
+
+struct client *client_create(int fd, bool ssl, pool_t pool,
+			     const struct login_settings *set,
+			     const struct ip_addr *local_ip,
+			     const struct ip_addr *remote_ip)
 {
+	struct client *client;
+
+	i_assert(fd != -1);
+
+	if (clients_get_count() >= set->login_max_connections) {
+		/* reached max. users count, kill few of the
+		   oldest connections */
+		client_destroy_oldest();
+	}
+
+	/* always use nonblocking I/O */
+	net_set_nonblock(fd, TRUE);
+
+	client = client_vfuncs.alloc(pool);
+	client->v = client_vfuncs;
+	client->created = ioloop_time;
+	client->refcount = 1;
+
+	client->pool = pool;
+	client->set = set;
+	client->local_ip = *local_ip;
+	client->ip = *remote_ip;
+	client->fd = fd;
+	client->tls = ssl;
+	client->trusted = client_is_trusted(client);
+	client->secured = ssl || client->trusted ||
+		net_ip_compare(remote_ip, local_ip);
+
 	DLLIST_PREPEND(&clients, client);
 	clients_count++;
+
+	client_set_title(client);
+
+	client->to_idle_disconnect =
+		timeout_add(CLIENT_LOGIN_IDLE_TIMEOUT_MSECS,
+			    client_idle_disconnect_timeout, client);
+	client_open_streams(client);
+
+	client->v.create(client);
+
+	if (auth_client_is_connected(auth_client))
+		client->v.send_greeting(client);
+	else
+		client_set_auth_waiting(client);
+	return client;
+}
+
+void client_destroy(struct client *client, const char *reason)
+{
+	if (client->destroyed)
+		return;
+	client->destroyed = TRUE;
+
+	if (!client->login_success && reason != NULL) {
+		reason = t_strconcat(reason, " ",
+			client_get_extra_disconnect_reason(client), NULL);
+	}
+	if (reason != NULL)
+		client_log(client, reason);
+
+	i_assert(clients_count > 0);
+	clients_count--;
+	DLLIST_REMOVE(&clients, client);
+
+	if (client->input != NULL)
+		i_stream_close(client->input);
+	if (client->output != NULL)
+		o_stream_close(client->output);
+
+	if (client->master_tag != 0) {
+		i_assert(client->auth_request == NULL);
+		i_assert(client->authenticating);
+		master_auth_request_abort(master_service, client->master_tag);
+	} else if (client->auth_request != NULL) {
+		i_assert(client->authenticating);
+		sasl_server_auth_abort(client);
+	} else {
+		i_assert(!client->authenticating);
+	}
+
+	if (client->io != NULL)
+		io_remove(&client->io);
+	if (client->to_idle_disconnect != NULL)
+		timeout_remove(&client->to_idle_disconnect);
+	if (client->to_auth_waiting != NULL)
+		timeout_remove(&client->to_auth_waiting);
+	if (client->to_authfail_delay != NULL)
+		timeout_remove(&client->to_authfail_delay);
+
+	if (client->fd != -1) {
+		net_disconnect(client->fd);
+		client->fd = -1;
+	}
+
+	if (client->proxy_password != NULL) {
+		safe_memset(client->proxy_password, 0,
+			    strlen(client->proxy_password));
+		i_free_and_null(client->proxy_password);
+	}
+
+	i_free_and_null(client->proxy_user);
+	i_free_and_null(client->proxy_master_user);
+
+	if (client->login_proxy != NULL)
+		login_proxy_free(&client->login_proxy);
+	if (client->ssl_proxy != NULL)
+		ssl_proxy_free(&client->ssl_proxy);
+	client_unref(client);
+}
+
+void client_destroy_success(struct client *client, const char *reason)
+{
+	client->login_success = TRUE;
+	client_destroy(client, reason);
+}
+
+void client_destroy_internal_failure(struct client *client)
+{
+	client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+			 "Internal login failure. "
+			 "Refer to server log for more information.");
+	client_destroy(client, "Internal login failure");
+}
+
+void client_ref(struct client *client)
+{
+	client->refcount++;
 }
 
-void client_unlink(struct client *client)
+bool client_unref(struct client *client)
+{
+	i_assert(client->refcount > 0);
+	if (--client->refcount > 0)
+		return TRUE;
+
+	i_assert(client->destroyed);
+
+	if (client->input != NULL)
+		i_stream_unref(&client->input);
+	if (client->output != NULL)
+		o_stream_unref(&client->output);
+
+	if (!client->proxying) {
+		i_assert(client->ssl_proxy == NULL);
+		master_service_client_connection_destroyed(master_service);
+	}
+
+	i_free(client->virtual_user);
+	i_free(client->auth_mech_name);
+	client->v.destroy(client);
+	pool_unref(&client->pool);
+	return FALSE;
+}
+
+void client_destroy_oldest(void)
+{
+	unsigned int max_connections =
+		global_login_settings->login_max_connections;
+	struct client *client;
+	struct client *destroy_buf[CLIENT_DESTROY_OLDEST_COUNT];
+	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);
+	for (client = clients; client != NULL; client = client->next) {
+		for (i = 0; i < destroy_count; i++) {
+			if (destroy_buf[i] == NULL ||
+			    destroy_buf[i]->created > client->created) {
+				/* @UNSAFE */
+				memmove(destroy_buf+i+1, destroy_buf+i,
+					sizeof(destroy_buf) -
+					(i+1) * sizeof(destroy_buf[0]));
+				destroy_buf[i] = client;
+				break;
+			}
+		}
+	}
+
+	/* then kill them */
+	for (i = 0; i < destroy_count; i++) {
+		if (destroy_buf[i] == NULL)
+			break;
+
+		client_destroy(destroy_buf[i],
+			       "Disconnected: Connection queue full");
+	}
+}
+
+void clients_destroy_all(void)
+{
+	struct client *client, *next;
+
+	for (client = clients; client != NULL; client = next) {
+		next = client->next;
+		client_destroy(client, "Disconnected: Shutting down");
+	}
+}
+
+static void client_start_tls(struct client *client)
 {
-	i_assert(clients_count > 0);
+	int fd_ssl;
+
+	client_ref(client);
+	if (!client_unref(client) || client->destroyed)
+		return;
+
+	fd_ssl = ssl_proxy_new(client->fd, &client->ip,
+			       client->set, &client->ssl_proxy);
+	if (fd_ssl == -1) {
+		client_send_line(client, CLIENT_CMD_REPLY_BYE,
+				 "TLS initialization failed.");
+		client_destroy(client,
+			       "Disconnected: TLS initialization failed.");
+		return;
+	}
+
+	client->starttls = TRUE;
+	client->proxying = TRUE;
+	client->tls = TRUE;
+	client->secured = TRUE;
+	client_set_title(client);
+
+	client->fd = fd_ssl;
+	client->io = io_add(client->fd, IO_READ, client_input, client);
+	i_stream_unref(&client->input);
+	o_stream_unref(&client->output);
+	client_open_streams(client);
+
+	client->v.starttls(client);
+}
+
+static int client_output_starttls(struct client *client)
+{
+	int ret;
+
+	if ((ret = o_stream_flush(client->output)) < 0) {
+		client_destroy(client, "Disconnected");
+		return 1;
+	}
 
-	clients_count--;
-	DLLIST_REMOVE(&clients, client);
+	if (ret > 0) {
+		o_stream_unset_flush_callback(client->output);
+		client_start_tls(client);
+	}
+	return 1;
+}
+
+void client_cmd_starttls(struct client *client)
+{
+	if (client->tls) {
+		client_send_line(client, CLIENT_CMD_REPLY_BAD,
+				 "TLS is already active.");
+		return;
+	}
+
+	if (!ssl_initialized) {
+		client_send_line(client, CLIENT_CMD_REPLY_BAD,
+				 "TLS support isn't enabled.");
+		return;
+	}
+
+	/* remove input handler, SSL proxy gives us a new fd. we also have to
+	   remove it in case we have to wait for buffer to be flushed */
+	if (client->io != NULL)
+		io_remove(&client->io);
+
+	client_send_line(client, CLIENT_CMD_REPLY_OK,
+			 "Begin TLS negotiation now.");
+
+	/* uncork the old fd */
+	o_stream_uncork(client->output);
+
+	if (o_stream_flush(client->output) <= 0) {
+		/* the buffer has to be flushed */
+		o_stream_set_flush_pending(client->output, TRUE);
+		o_stream_set_flush_callback(client->output,
+					    client_output_starttls, client);
+	} else {
+		client_start_tls(client);
+	}
 }
 
 unsigned int clients_get_count(void)
@@ -33,6 +337,22 @@
 	return clients_count;
 }
 
+void client_set_title(struct client *client)
+{
+	const char *addr;
+
+	if (!client->set->verbose_proctitle ||
+	    !client->set->login_process_per_connection)
+		return;
+
+	addr = net_ip2addr(&client->ip);
+	if (addr == NULL)
+		addr = "??";
+
+	process_title_set(t_strdup_printf(client->tls ?
+					  "[%s TLS]" : "[%s]", addr));
+}
+
 static const struct var_expand_table *
 get_var_expand_table(struct client *client)
 {
@@ -81,13 +401,16 @@
 		tab[11].value = client->secured ? "secured" : NULL;
 		tab[12].value = "";
 	} else {
-		const char *ssl_state = ssl_proxy_is_handshaked(client->proxy) ?
+		const char *ssl_state =
+			ssl_proxy_is_handshaked(client->ssl_proxy) ?
 			"TLS" : "TLS handshaking";
-		const char *ssl_error = ssl_proxy_get_last_error(client->proxy);
+		const char *ssl_error =
+			ssl_proxy_get_last_error(client->ssl_proxy);
 
 		tab[11].value = ssl_error == NULL ? ssl_state :
 			t_strdup_printf("%s: %s", ssl_state, ssl_error);
-		tab[12].value = ssl_proxy_get_security_string(client->proxy);
+		tab[12].value =
+			ssl_proxy_get_security_string(client->ssl_proxy);
 	}
 	tab[13].value = dec2str(client->mail_pid);
 	return tab;
@@ -151,14 +474,14 @@
 	return str_c(str);
 }
 
-void client_syslog(struct client *client, const char *msg)
+void client_log(struct client *client, const char *msg)
 {
 	T_BEGIN {
 		i_info("%s", client_get_log_str(client, msg));
 	} T_END;
 }
 
-void client_syslog_err(struct client *client, const char *msg)
+void client_log_err(struct client *client, const char *msg)
 {
 	T_BEGIN {
 		i_error("%s", client_get_log_str(client, msg));
@@ -190,10 +513,10 @@
 
 const char *client_get_extra_disconnect_reason(struct client *client)
 {
-	if (client->set->ssl_require_client_cert && client->proxy != NULL) {
-		if (ssl_proxy_has_broken_client_cert(client->proxy))
+	if (client->set->ssl_require_client_cert && client->ssl_proxy != NULL) {
+		if (ssl_proxy_has_broken_client_cert(client->ssl_proxy))
 			return "(client sent an invalid cert)";
-		if (!ssl_proxy_has_valid_client_cert(client->proxy))
+		if (!ssl_proxy_has_valid_client_cert(client->ssl_proxy))
 			return "(client didn't send a cert)";
 	}
 
@@ -209,3 +532,56 @@
 	return t_strdup_printf("(auth failed, %u attempts)",
 			       client->auth_attempts);
 }
+
+void client_send_line(struct client *client, enum client_cmd_reply reply,
+		      const char *text)
+{
+	client->v.send_line(client, reply, text);
+}
+
+void client_send_raw_data(struct client *client, const void *data, size_t size)
+{
+	ssize_t ret;
+
+	ret = o_stream_send(client->output, data, size);
+	if (ret < 0 || (size_t)ret != size) {
+		/* either disconnection or buffer full. in either case we want
+		   this connection destroyed. however destroying it here might
+		   break things if client is still tried to be accessed without
+		   being referenced.. */
+		i_stream_close(client->input);
+	}
+}
+
+void client_send_raw(struct client *client, const char *data)
+{
+	client_send_raw_data(client, data, strlen(data));
+}
+
+bool client_read(struct client *client)
+{
+	switch (i_stream_read(client->input)) {
+	case -2:
+		/* buffer full */
+		client_send_line(client, CLIENT_CMD_REPLY_BYE,
+				 "Input buffer full, aborting");
+		client_destroy(client, "Disconnected: Input buffer full");
+		return FALSE;
+	case -1:
+		/* disconnected */
+		client_destroy(client, "Disconnected");
+		return FALSE;
+	case 0:
+		/* nothing new read */
+		return TRUE;
+	default:
+		/* something was read */
+		timeout_reset(client->to_idle_disconnect);
+		return TRUE;
+	}
+}
+
+void client_input(struct client *client)
+{
+	client->v.input(client);
+}
--- a/src/login-common/client-common.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/client-common.h	Sun Aug 09 21:53:14 2009 -0400
@@ -2,6 +2,7 @@
 #define CLIENT_COMMON_H
 
 #include "network.h"
+#include "login-proxy.h"
 #include "sasl-server.h"
 
 /* max. size of input buffer. this means:
@@ -11,6 +12,17 @@
    POP3: Max. length of a command line (spec says 512 would be enough)
 */
 #define LOGIN_MAX_INBUF_SIZE 4096
+/* max. size of output buffer. if it gets full, the client is disconnected.
+   SASL authentication gives the largest output. */
+#define LOGIN_MAX_OUTBUF_SIZE 4096
+
+/* Disconnect client after idling this many milliseconds */
+#define CLIENT_LOGIN_IDLE_TIMEOUT_MSECS (3*60*1000)
+
+#define AUTH_SERVER_WAITING_MSG \
+	"Waiting for authentication process to respond.."
+#define AUTH_MASTER_WAITING_MSG \
+	"Waiting for authentication master process to respond.."
 
 enum client_cmd_reply {
 	CLIENT_CMD_REPLY_OK,
@@ -21,61 +33,133 @@
 	CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
 	CLIENT_CMD_REPLY_BAD,
 	CLIENT_CMD_REPLY_BYE,
-	CLIENT_CMD_REPLY_STATUS
+	CLIENT_CMD_REPLY_STATUS,
+	CLIENT_CMD_REPLY_STATUS_BAD
+};
+
+struct client_auth_reply {
+	const char *master_user, *reason;
+	/* for proxying */
+	const char *host, *destuser, *password;
+	unsigned int port;
+	enum login_proxy_ssl_flags ssl_flags;
+
+	unsigned int proxy:1;
+	unsigned int temp:1;
+	unsigned int nologin:1;
+	unsigned int nodelay:1;
+	unsigned int authz_failure:1;
+};
+
+struct client_vfuncs {
+	struct client *(*alloc)(pool_t pool);
+	void (*create)(struct client *client);
+	void (*destroy)(struct client *client);
+	void (*send_greeting)(struct client *client);
+	void (*starttls)(struct client *client);
+	void (*input)(struct client *client);
+	void (*send_line)(struct client *client, enum client_cmd_reply reply,
+			  const char *text);
+	bool (*auth_handle_reply)(struct client *client,
+				  const struct client_auth_reply *reply);
+	void (*proxy_reset)(struct client *client);
+	int (*proxy_parse_line)(struct client *client, const char *line);
 };
 
 struct client {
 	struct client *prev, *next;
 	pool_t pool;
+	struct client_vfuncs v;
+
+	time_t created;
+	int refcount;
 
 	struct ip_addr local_ip;
 	struct ip_addr ip;
 	unsigned int local_port, remote_port;
-	struct ssl_proxy *proxy;
+	struct ssl_proxy *ssl_proxy;
 	const struct login_settings *set;
 
 	int fd;
 	struct istream *input;
+	struct ostream *output;
+	struct io *io;
+	struct timeout *to_authfail_delay, *to_auth_waiting;
+	struct timeout *to_idle_disconnect;
+
 	unsigned char *master_data_prefix;
 	unsigned int master_data_prefix_len;
 
+	struct login_proxy *login_proxy;
+	char *proxy_user, *proxy_master_user, *proxy_password;
+
 	char *auth_mech_name;
 	struct auth_request *auth_request;
 
 	unsigned int master_tag;
 	sasl_server_callback_t *sasl_callback;
 
+	unsigned int bad_counter;
 	unsigned int auth_attempts;
 	pid_t mail_pid;
 
 	char *virtual_user;
+	unsigned int destroyed:1;
+	unsigned int input_blocked:1;
+	unsigned int login_success:1;
+	unsigned int greeting_sent:1;
+	unsigned int starttls:1;
 	unsigned int tls:1;
 	unsigned int secured:1;
 	unsigned int trusted:1;
 	unsigned int proxying:1;
 	unsigned int authenticating:1;
 	unsigned int auth_tried_disabled_plaintext:1;
+	unsigned int auth_initializing:1;
 	/* ... */
 };
 
 extern struct client *clients;
+extern struct client_vfuncs client_vfuncs;
 
 struct client *client_create(int fd, bool ssl, pool_t pool,
 			     const struct login_settings *set,
 			     const struct ip_addr *local_ip,
 			     const struct ip_addr *remote_ip);
+void client_destroy(struct client *client, const char *reason);
+void client_destroy_success(struct client *client, const char *reason);
+void client_destroy_internal_failure(struct client *client);
 
-void client_link(struct client *client);
-void client_unlink(struct client *client);
+void client_ref(struct client *client);
+bool client_unref(struct client *client);
+
+void client_cmd_starttls(struct client *client);
+
 unsigned int clients_get_count(void) ATTR_PURE;
 
+void client_set_title(struct client *client);
+void client_log(struct client *client, const char *msg);
+void client_log_err(struct client *client, const char *msg);
+const char *client_get_extra_disconnect_reason(struct client *client);
+bool client_is_trusted(struct client *client);
+void client_auth_failed(struct client *client, bool nodelay);
+
+bool client_read(struct client *client);
+void client_input(struct client *client);
+
 void client_send_line(struct client *client, enum client_cmd_reply reply,
 		      const char *text);
+void client_send_raw_data(struct client *client, const void *data, size_t size);
+void client_send_raw(struct client *client, const char *data);
 
-void client_syslog(struct client *client, const char *msg);
-void client_syslog_err(struct client *client, const char *msg);
-const char *client_get_extra_disconnect_reason(struct client *client);
-bool client_is_trusted(struct client *client);
+void client_set_auth_waiting(struct client *client);
+int client_auth_begin(struct client *client, const char *mech_name,
+		      const char *init_resp);
+bool client_check_plaintext_auth(struct client *client, bool pass_sent);
+
+void client_proxy_finish_destroy_client(struct client *client);
+void client_proxy_log_failure(struct client *client, const char *line);
+void client_proxy_failed(struct client *client, bool send_line);
 
 void clients_notify_auth_connected(void);
 void client_destroy_oldest(void);
--- a/src/login-common/common.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/common.h	Sun Aug 09 21:53:14 2009 -0400
@@ -13,6 +13,7 @@
 	"Plaintext authentication disallowed on non-secure (SSL/TLS) connections."
 
 extern const char *login_protocol, *login_process_name;
+extern unsigned int login_default_port;
 
 extern struct auth_client *auth_client;
 extern bool closing_down;
--- a/src/login-common/login-proxy.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/login-proxy.c	Sun Aug 09 21:53:14 2009 -0400
@@ -237,7 +237,7 @@
 	}
 
 	if (proxy->ssl_proxy != NULL)
-		ssl_proxy_free(proxy->ssl_proxy);
+		ssl_proxy_free(&proxy->ssl_proxy);
 	net_disconnect(proxy->server_fd);
 
 	i_free(proxy->host);
@@ -335,11 +335,11 @@
 		return 0;
 
 	if (!ssl_proxy_has_broken_client_cert(proxy->ssl_proxy)) {
-		client_syslog_err(proxy->prelogin_client, t_strdup_printf(
+		client_log_err(proxy->prelogin_client, t_strdup_printf(
 			"proxy: SSL certificate not received from %s:%u",
 			proxy->host, proxy->port));
 	} else {
-		client_syslog_err(proxy->prelogin_client, t_strdup_printf(
+		client_log_err(proxy->prelogin_client, t_strdup_printf(
 			"proxy: Received invalid SSL certificate from %s:%u",
 			proxy->host, proxy->port));
 	}
@@ -362,7 +362,7 @@
 				  login_proxy_ssl_handshaked, proxy,
 				  &proxy->ssl_proxy);
 	if (fd < 0) {
-		client_syslog_err(proxy->prelogin_client, t_strdup_printf(
+		client_log_err(proxy->prelogin_client, t_strdup_printf(
 			"proxy: SSL handshake failed to %s:%u",
 			proxy->host, proxy->port));
 		return -1;
--- a/src/login-common/login-proxy.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/login-proxy.h	Sun Aug 09 21:53:14 2009 -0400
@@ -1,6 +1,7 @@
 #ifndef LOGIN_PROXY_H
 #define LOGIN_PROXY_H
 
+struct client;
 struct login_proxy;
 
 enum login_proxy_ssl_flags {
--- a/src/login-common/main.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/main.c	Sun Aug 09 21:53:14 2009 -0400
@@ -59,7 +59,7 @@
 		client = client_create(fd_ssl, TRUE, pool, set,
 				       &local_ip, &conn->remote_ip);
 		client->proxying = TRUE;
-		client->proxy = proxy;
+		client->ssl_proxy = proxy;
 	}
 
 	client->remote_port = conn->remote_port;
--- a/src/login-common/sasl-server.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/sasl-server.c	Sun Aug 09 21:53:14 2009 -0400
@@ -54,8 +54,8 @@
 {
         enum auth_request_flags auth_flags = 0;
 
-	if (client->proxy != NULL &&
-	    ssl_proxy_has_valid_client_cert(client->proxy))
+	if (client->ssl_proxy != NULL &&
+	    ssl_proxy_has_valid_client_cert(client->ssl_proxy))
 		auth_flags |= AUTH_REQUEST_FLAG_VALID_CLIENT_CERT;
 	if (client->secured)
 		auth_flags |= AUTH_REQUEST_FLAG_SECURED;
@@ -256,8 +256,8 @@
 	memset(&info, 0, sizeof(info));
 	info.mech = mech->name;
 	info.service = service;
-	info.cert_username = client->proxy == NULL ? NULL :
-		ssl_proxy_get_peer_name(client->proxy);
+	info.cert_username = client->ssl_proxy == NULL ? NULL :
+		ssl_proxy_get_peer_name(client->ssl_proxy);
 	info.flags = client_get_auth_flags(client);
 	info.local_ip = client->local_ip;
 	info.remote_ip = client->ip;
@@ -282,9 +282,8 @@
 	if (client->set->verbose_auth && reason != NULL) {
 		const char *auth_name =
 			str_sanitize(client->auth_mech_name, MAX_MECH_NAME);
-		client_syslog(client,
-			t_strdup_printf("Authenticate %s failed: %s",
-					auth_name, reason));
+		client_log(client, t_strdup_printf(
+			"Authenticate %s failed: %s", auth_name, reason));
 	}
 
 	client->authenticating = FALSE;
--- a/src/login-common/ssl-proxy-openssl.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/ssl-proxy-openssl.c	Sun Aug 09 21:53:14 2009 -0400
@@ -669,8 +669,11 @@
 			       bits, alg_bits);
 }
 
-void ssl_proxy_free(struct ssl_proxy *proxy)
+void ssl_proxy_free(struct ssl_proxy **_proxy)
 {
+	struct ssl_proxy *proxy = *_proxy;
+
+	*_proxy = NULL;
 	ssl_proxy_unref(proxy);
 }
 
--- a/src/login-common/ssl-proxy.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/ssl-proxy.c	Sun Aug 09 21:53:14 2009 -0400
@@ -57,7 +57,7 @@
 	return "";
 }
 
-void ssl_proxy_free(struct ssl_proxy *proxy ATTR_UNUSED) {}
+void ssl_proxy_free(struct ssl_proxy **proxy ATTR_UNUSED) {}
 
 unsigned int ssl_proxy_get_count(void)
 {
--- a/src/login-common/ssl-proxy.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/login-common/ssl-proxy.h	Sun Aug 09 21:53:14 2009 -0400
@@ -26,7 +26,7 @@
 bool ssl_proxy_is_handshaked(const struct ssl_proxy *proxy) ATTR_PURE;
 const char *ssl_proxy_get_last_error(const struct ssl_proxy *proxy) ATTR_PURE;
 const char *ssl_proxy_get_security_string(struct ssl_proxy *proxy);
-void ssl_proxy_free(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) ATTR_PURE;
--- a/src/pop3-login/client-authenticate.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/pop3-login/client-authenticate.c	Sun Aug 09 21:53:14 2009 -0400
@@ -19,9 +19,6 @@
 
 #include <stdlib.h>
 
-#define POP3_SERVICE_NAME "pop3"
-#define AUTH_FAILURE_DELAY_INCREASE_MSECS 5000
-
 const char *capability_string = POP3_CAPABILITY_REPLY;
 
 bool cmd_capa(struct pop3_client *client, const char *args ATTR_UNUSED)
@@ -48,247 +45,45 @@
 	}
 	str_append(str, "\r\n.\r\n");
 
-	client_send_raw(client, str_c(str));
+	client_send_raw(&client->common, str_c(str));
 	return TRUE;
 }
 
-static void client_auth_input(struct pop3_client *client)
-{
-	char *line;
-
-	if (!client_read(client))
-		return;
-
-	/* @UNSAFE */
-	line = i_stream_next_line(client->common.input);
-	if (line == NULL)
-		return;
-
-	if (strcmp(line, "*") == 0)
-		sasl_server_auth_abort(&client->common);
-	else {
-		auth_client_request_continue(client->common.auth_request, line);
-		io_remove(&client->io);
-
-		/* clear sensitive data */
-		safe_memset(line, 0, strlen(line));
-	}
-}
-
-static void client_authfail_delay_timeout(struct pop3_client *client)
-{
-	timeout_remove(&client->to_authfail_delay);
-
-	/* get back to normal client input. */
-	i_assert(client->io == NULL);
-	client->io = io_add(client->common.fd, IO_READ, client_input, client);
-	client_input(client);
-}
-
-void client_auth_failed(struct pop3_client *client, bool nodelay)
+bool pop3_client_auth_handle_reply(struct client *client,
+				   const struct client_auth_reply *reply)
 {
-	unsigned int delay_msecs;
-
-	if (client->auth_initializing)
-		return;
-
-	if (client->io != NULL)
-		io_remove(&client->io);
-	if (nodelay) {
-		client->io = io_add(client->common.fd, IO_READ,
-				    client_input, client);
-		client_input(client);
-		return;
-	}
-
-	/* increase the timeout after each unsuccessful attempt, but don't
-	   increase it so high that the idle timeout would be triggered */
-	delay_msecs = client->common.auth_attempts *
-		AUTH_FAILURE_DELAY_INCREASE_MSECS;
-	if (delay_msecs > CLIENT_LOGIN_IDLE_TIMEOUT_MSECS)
-		delay_msecs = CLIENT_LOGIN_IDLE_TIMEOUT_MSECS - 1000;
-
-	i_assert(client->to_authfail_delay == NULL);
-	client->to_authfail_delay =
-		timeout_add(delay_msecs, client_authfail_delay_timeout, client);
-}
-
-static bool client_handle_args(struct pop3_client *client,
-			       const char *const *args, bool success,
-			       bool *nodelay_r)
-{
-	const char *reason = NULL, *host = NULL, *destuser = NULL, *pass = NULL;
-	const char *master_user = NULL;
-	const char *key, *value, *p;
-	enum login_proxy_ssl_flags ssl_flags = 0;
-	unsigned int port = 110;
-	bool proxy = FALSE, temp = FALSE, nologin = !success;
-
-	*nodelay_r = FALSE;
-	for (; *args != NULL; args++) {
-		p = strchr(*args, '=');
-		if (p == NULL) {
-			key = *args;
-			value = "";
-		} else {
-			key = t_strdup_until(*args, p);
-			value = p + 1;
-		}
-		if (strcmp(key, "nologin") == 0)
-			nologin = TRUE;
-		else if (strcmp(key, "nodelay") == 0)
-			*nodelay_r = TRUE;
-		else if (strcmp(key, "proxy") == 0)
-			proxy = TRUE;
-		else if (strcmp(key, "temp") == 0)
-			temp = TRUE;
-		else if (strcmp(key, "reason") == 0)
-			reason = value;
-		else if (strcmp(key, "host") == 0)
-			host = value;
-		else if (strcmp(key, "port") == 0)
-			port = atoi(value);
-		else if (strcmp(key, "destuser") == 0)
-			destuser = value;
-		else if (strcmp(key, "pass") == 0)
-			pass = value;
-		else if (strcmp(key, "master") == 0)
-			master_user = value;
-		else if (strcmp(key, "user") == 0) {
-			/* already handled in login-common */
-		} else if (client->common.set->auth_debug)
-			i_info("Ignoring unknown passdb extra field: %s", key);
-	}
-
-	if (destuser == NULL)
-		destuser = client->common.virtual_user;
-
-	if (proxy) {
-		/* we want to proxy the connection to another server.
-		   don't do this unless authentication succeeded. with
-		   master user proxying we can get FAIL with proxy still set.
-
-		   proxy host=.. [port=..] [destuser=..] pass=.. */
-		if (!success)
-			return FALSE;
-		if (pop3_proxy_new(client, host, port, destuser, master_user,
-				   pass, ssl_flags) < 0)
-			client_auth_failed(client, TRUE);
-		return TRUE;
-	}
-
-	if (!nologin)
+	if (!reply->nologin)
 		return FALSE;
 
-	if (reason != NULL) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_AUTH_FAILED,
-				 reason);
-	} else if (temp) {
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+	if (reply->reason != NULL) {
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
+				 reply->reason);
+	} else if (reply->temp) {
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
 				 AUTH_TEMP_FAILED_MSG);
 	} else {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_AUTH_FAILED,
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
 				 AUTH_FAILED_MSG);
 	}
 
 	if (!client->destroyed)
-		client_auth_failed(client, *nodelay_r);
+		client_auth_failed(client, reply->nodelay);
 	return TRUE;
 }
 
-static void sasl_callback(struct client *_client, enum sasl_server_reply reply,
-			  const char *data, const char *const *args)
+bool cmd_auth(struct pop3_client *pop3_client, const char *args)
 {
-	struct pop3_client *client = (struct pop3_client *)_client;
-	struct const_iovec iov[3];
-	size_t data_len;
-	bool nodelay;
-
-	i_assert(!client->destroyed ||
-		 reply == SASL_SERVER_REPLY_AUTH_ABORTED ||
-		 reply == SASL_SERVER_REPLY_MASTER_FAILED);
-
-	switch (reply) {
-	case SASL_SERVER_REPLY_SUCCESS:
-		if (args != NULL) {
-			if (client_handle_args(client, args, TRUE, &nodelay))
-				break;
-		}
-
-		client_destroy_success(client, "Login");
-		break;
-	case SASL_SERVER_REPLY_AUTH_FAILED:
-	case SASL_SERVER_REPLY_AUTH_ABORTED:
-		if (args != NULL) {
-			if (client_handle_args(client, args, FALSE, &nodelay))
-				break;
-		}
-
-		if (reply == SASL_SERVER_REPLY_AUTH_ABORTED) {
-			client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-					 "Authentication aborted by client.");
-		} else if (data == NULL) {
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTH_FAILED,
-					 AUTH_FAILED_MSG);
-		} else {
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTH_FAIL_REASON,
-					 data);
-		}
-
-		if (!client->destroyed)
-			client_auth_failed(client, nodelay);
-		break;
-	case SASL_SERVER_REPLY_MASTER_FAILED:
-		if (data == NULL)
-			client_destroy_internal_failure(client);
-		else {
-			client_send_line(&client->common,
-					 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP, data);
-			/* authentication itself succeeded, we just hit some
-			   internal failure. */
-			client_destroy_success(client, data);
-		}
-		break;
-	case SASL_SERVER_REPLY_CONTINUE:
-		data_len = strlen(data);
-		iov[0].iov_base = "+ ";
-		iov[0].iov_len = 2;
-		iov[1].iov_base = data;
-		iov[1].iov_len = data_len;
-		iov[2].iov_base = "\r\n";
-		iov[2].iov_len = 2;
-
-		/* don't check return value here. it gets tricky if we try
-		   to call client_destroy() in here. */
-		(void)o_stream_sendv(client->output, iov, 3);
-
-		i_assert(client->io == NULL);
-		client->io = io_add(client->common.fd, IO_READ,
-				    client_auth_input, client);
-		client_auth_input(client);
-		return;
-	}
-
-	client_unref(client);
-}
-
-bool cmd_auth(struct pop3_client *client, const char *args)
-{
+	struct client *client = &pop3_client->common;
 	const struct auth_mech_desc *mech;
 	const char *mech_name, *p;
 
-	if (!client->common.secured &&
-	    strcmp(client->common.set->ssl, "required") == 0) {
-		if (client->common.set->verbose_auth) {
-			client_syslog(&client->common, "Login failed: "
-				      "SSL required for authentication");
+	if (!client->secured && strcmp(client->set->ssl, "required") == 0) {
+		if (client->set->verbose_auth) {
+			client_log(client, "Login failed: "
+				   "SSL required for authentication");
 		}
-		client->common.auth_attempts++;
-		client_send_line(&client->common,
-			CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
+		client->auth_attempts++;
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
 			"Authentication not allowed until SSL/TLS is enabled.");
 		return TRUE;
 	}
@@ -298,15 +93,10 @@
 		unsigned int i, count;
 
 		client_send_raw(client, "+OK\r\n");
-		mech = auth_client_get_available_mechs(auth_client, &count);
+		mech = sasl_server_get_advertised_mechs(client, &count);
 		for (i = 0; i < count; i++) {
-			if ((mech[i].flags & MECH_SEC_PRIVATE) == 0 &&
-			    (client->common.secured ||
-			     client->common.set->disable_plaintext_auth ||
-			     (mech[i].flags & MECH_SEC_PLAINTEXT) == 0)) {
-				client_send_raw(client, mech[i].name);
-				client_send_raw(client, "\r\n");
-			}
+			client_send_raw(client, mech[i].name);
+			client_send_raw(client, "\r\n");
 		}
  		client_send_raw(client, ".\r\n");
  		return TRUE;
@@ -322,58 +112,34 @@
 		args = p+1;
 	}
 
-	client_ref(client);
-	sasl_server_auth_begin(&client->common, POP3_SERVICE_NAME, mech_name,
-			       args, sasl_callback);
-	if (!client->common.authenticating)
-		return TRUE;
-
-	/* don't handle input until we get the initial auth reply */
-	if (client->io != NULL)
-		io_remove(&client->io);
+	(void)client_auth_begin(client, mech_name, args);
 	return TRUE;
 }
 
-static bool check_plaintext_auth(struct pop3_client *client)
+bool cmd_user(struct pop3_client *pop3_client, const char *args)
 {
-	if (client->common.secured ||
-	    !client->common.set->disable_plaintext_auth)
+	if (!client_check_plaintext_auth(&pop3_client->common, FALSE))
 		return TRUE;
 
-	if (client->common.set->verbose_auth) {
-		client_syslog(&client->common, "Login failed: "
-			      "Plaintext authentication disabled");
-	}
-	client_send_line(&client->common, CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
-			 AUTH_PLAINTEXT_DISABLED_MSG);
-	client->common.auth_tried_disabled_plaintext = TRUE;
-	client->common.auth_attempts++;
-	return FALSE;
-}
+	i_free(pop3_client->last_user);
+	pop3_client->last_user = i_strdup(args);
 
-bool cmd_user(struct pop3_client *client, const char *args)
-{
-	if (!check_plaintext_auth(client))
-		return TRUE;
-
-	i_free(client->last_user);
-	client->last_user = i_strdup(args);
-
-	client_send_raw(client, "+OK\r\n");
+	client_send_raw(&pop3_client->common, "+OK\r\n");
 	return TRUE;
 }
 
-bool cmd_pass(struct pop3_client *client, const char *args)
+bool cmd_pass(struct pop3_client *pop3_client, const char *args)
 {
+	struct client *client = &pop3_client->common;
 	string_t *plain_login, *base64;
 
-	if (client->last_user == NULL) {
+	if (pop3_client->last_user == NULL) {
 		/* client may ignore the USER reply and only display the error
 		   message from PASS */
-		if (!check_plaintext_auth(client))
+		if (!client_check_plaintext_auth(client, TRUE))
 			return TRUE;
 
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+		client_send_line(client, CLIENT_CMD_REPLY_BAD,
 				 "No username given.");
 		return TRUE;
 	}
@@ -381,42 +147,30 @@
 	/* authorization ID \0 authentication ID \0 pass */
 	plain_login = t_str_new(128);
 	str_append_c(plain_login, '\0');
-	str_append(plain_login, client->last_user);
+	str_append(plain_login, pop3_client->last_user);
 	str_append_c(plain_login, '\0');
 	str_append(plain_login, args);
 
-	i_free(client->last_user);
-	client->last_user = NULL;
+	i_free_and_null(pop3_client->last_user);
 
 	base64 = buffer_create_dynamic(pool_datastack_create(),
         			MAX_BASE64_ENCODED_SIZE(plain_login->used));
 	base64_encode(plain_login->data, plain_login->used, base64);
 
-	client_ref(client);
-	client->auth_initializing = TRUE;
-	sasl_server_auth_begin(&client->common, POP3_SERVICE_NAME, "PLAIN",
-			       str_c(base64), sasl_callback);
-	client->auth_initializing = FALSE;
-	if (!client->common.authenticating)
-		return TRUE;
-
-	/* don't read any input from client until login is finished */
-	if (client->io != NULL)
-		io_remove(&client->io);
+	(void)client_auth_begin(client, "PLAIN", str_c(base64));
 	return TRUE;
 }
 
-bool cmd_apop(struct pop3_client *client, const char *args)
+bool cmd_apop(struct pop3_client *pop3_client, const char *args)
 {
+	struct client *client = &pop3_client->common;
 	buffer_t *apop_data, *base64;
 	const char *p;
 
-	if (client->apop_challenge == NULL) {
-		if (client->common.set->verbose_auth) {
-			client_syslog(&client->common,
-				      "APOP failed: APOP not enabled");
-		}
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+	if (pop3_client->apop_challenge == NULL) {
+		if (client->set->verbose_auth)
+			client_log(client, "APOP failed: APOP not enabled");
+		client_send_line(client, CLIENT_CMD_REPLY_BAD,
 				 "APOP not enabled.");
 		return TRUE;
 	}
@@ -424,28 +178,26 @@
 	/* <username> <md5 sum in hex> */
 	p = strchr(args, ' ');
 	if (p == NULL || strlen(p+1) != 32) {
-		if (client->common.set->verbose_auth) {
-			client_syslog(&client->common,
-				      "APOP failed: Invalid parameters");
-		}
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+		if (client->set->verbose_auth)
+			client_log(client, "APOP failed: Invalid parameters");
+		client_send_line(client, CLIENT_CMD_REPLY_BAD,
 				 "Invalid parameters.");
 		return TRUE;
 	}
 
 	/* APOP challenge \0 username \0 APOP response */
 	apop_data = buffer_create_dynamic(pool_datastack_create(), 128);
-	buffer_append(apop_data, client->apop_challenge,
-		      strlen(client->apop_challenge)+1);
+	buffer_append(apop_data, pop3_client->apop_challenge,
+		      strlen(pop3_client->apop_challenge)+1);
 	buffer_append(apop_data, args, (size_t)(p-args));
 	buffer_append_c(apop_data, '\0');
 
 	if (hex_to_binary(p+1, apop_data) < 0) {
-		if (client->common.set->verbose_auth) {
-			client_syslog(&client->common, "APOP failed: "
-				      "Invalid characters in MD5 response");
+		if (client->set->verbose_auth) {
+			client_log(client, "APOP failed: "
+				   "Invalid characters in MD5 response");
 		}
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+		client_send_line(client, CLIENT_CMD_REPLY_BAD,
 				 "Invalid characters in MD5 response.");
 		return TRUE;
 	}
@@ -454,14 +206,6 @@
         			MAX_BASE64_ENCODED_SIZE(apop_data->used));
 	base64_encode(apop_data->data, apop_data->used, base64);
 
-	client_ref(client);
-	sasl_server_auth_begin(&client->common, POP3_SERVICE_NAME, "APOP",
-			       str_c(base64), sasl_callback);
-	if (!client->common.authenticating)
-		return TRUE;
-
-	/* don't read any input from client until login is finished */
-	if (client->io != NULL)
-		io_remove(&client->io);
+	(void)client_auth_begin(client, "APOP", str_c(base64));
 	return TRUE;
 }
--- a/src/pop3-login/client-authenticate.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/pop3-login/client-authenticate.h	Sun Aug 09 21:53:14 2009 -0400
@@ -1,6 +1,9 @@
 #ifndef CLIENT_AUTHENTICATE_H
 #define CLIENT_AUTHENTICATE_H
 
+bool pop3_client_auth_handle_reply(struct client *client,
+				   const struct client_auth_reply *reply);
+
 bool cmd_capa(struct pop3_client *client, const char *args);
 bool cmd_user(struct pop3_client *client, const char *args);
 bool cmd_pass(struct pop3_client *client, const char *args);
--- a/src/pop3-login/client.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/pop3-login/client.c	Sun Aug 09 21:53:14 2009 -0400
@@ -7,12 +7,9 @@
 #include "istream.h"
 #include "ostream.h"
 #include "randgen.h"
-#include "process-title.h"
 #include "safe-memset.h"
 #include "str.h"
 #include "strescape.h"
-#include "master-service.h"
-#include "master-auth.h"
 #include "client.h"
 #include "client-authenticate.h"
 #include "auth-client.h"
@@ -20,136 +17,23 @@
 #include "pop3-proxy.h"
 #include "hostpid.h"
 
-/* max. size of output buffer. if it gets full, the client is disconnected.
-   SASL authentication gives the largest output. */
-#define MAX_OUTBUF_SIZE 4096
-
 /* Disconnect client when it sends too many bad commands */
 #define CLIENT_MAX_BAD_COMMANDS 10
 
-/* When max. number of simultaneous connections is reached, few of the
-   oldest connections are disconnected. Since we have to go through all of the
-   clients, it's faster if we disconnect multiple clients. */
-#define CLIENT_DESTROY_OLDEST_COUNT 16
-
-#if CLIENT_LOGIN_IDLE_TIMEOUT_MSECS < AUTH_REQUEST_TIMEOUT*1000
-#  error client idle timeout must be larger than authentication timeout
-#endif
-
 const char *login_protocol = "pop3";
 const char *login_process_name = "pop3-login";
-
-static void client_set_title(struct pop3_client *client)
-{
-	const char *addr;
-
-	if (!client->common.set->verbose_proctitle ||
-	    !client->common.set->login_process_per_connection)
-		return;
-
-	addr = net_ip2addr(&client->common.ip);
-	if (addr == NULL)
-		addr = "??";
-
-	process_title_set(t_strdup_printf(client->common.tls ?
-					  "[%s TLS]" : "[%s]", addr));
-}
-
-static void client_open_streams(struct pop3_client *client, int fd)
-{
-	client->common.input =
-		i_stream_create_fd(fd, LOGIN_MAX_INBUF_SIZE, FALSE);
-	client->output = o_stream_create_fd(fd, MAX_OUTBUF_SIZE, FALSE);
-}
-
-static void client_start_tls(struct pop3_client *client)
-{
-	int fd_ssl;
-
-	client_ref(client);
-	if (!client_unref(client) || client->destroyed)
-		return;
-
-	fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip,
-			       client->common.set, &client->common.proxy);
-	if (fd_ssl == -1) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
-				 "TLS initialization failed.");
-		client_destroy(client,
-			       "Disconnected: TLS initialization failed.");
-		return;
-	}
-
-	client->common.proxying = TRUE;
-	client->common.tls = TRUE;
-	client->common.secured = TRUE;
-	client_set_title(client);
-
-	client->common.fd = fd_ssl;
-
-	i_stream_unref(&client->common.input);
-	o_stream_unref(&client->output);
-
-	client_open_streams(client, fd_ssl);
-	client->io = io_add(client->common.fd, IO_READ, client_input, client);
-}
-
-static int client_output_starttls(struct pop3_client *client)
-{
-	int ret;
-
-	if ((ret = o_stream_flush(client->output)) < 0) {
-		client_destroy(client, "Disconnected");
-		return 1;
-	}
-
-	if (ret > 0) {
-		o_stream_unset_flush_callback(client->output);
-		client_start_tls(client);
-	}
-	return 1;
-}
+unsigned int login_default_port = 110;
 
 static bool cmd_stls(struct pop3_client *client)
 {
-	if (client->common.tls) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-				 "TLS is already active.");
-		return TRUE;
-	}
-
-	if (!ssl_initialized) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
-				 "TLS support isn't enabled.");
-		return TRUE;
-	}
-
-	/* remove input handler, SSL proxy gives us a new fd. we also have to
-	   remove it in case we have to wait for buffer to be flushed */
-	if (client->io != NULL)
-		io_remove(&client->io);
-
-	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
-			 "Begin TLS negotiation now.");
-
-	/* uncork the old fd */
-	o_stream_uncork(client->output);
-
-	if (o_stream_flush(client->output) <= 0) {
-		/* the buffer has to be flushed */
-		o_stream_set_flush_pending(client->output, TRUE);
-		o_stream_set_flush_callback(client->output,
-					    client_output_starttls, client);
-	} else {
-		client_start_tls(client);
-	}
+	client_cmd_starttls(&client->common);
 	return TRUE;
 }
 
 static bool cmd_quit(struct pop3_client *client)
 {
 	client_send_line(&client->common, CLIENT_CMD_REPLY_OK, "Logging out");
-	client_destroy(client, "Aborted login");
+	client_destroy(&client->common, "Aborted login");
 	return TRUE;
 }
 
@@ -177,34 +61,12 @@
 	return FALSE;
 }
 
-bool client_read(struct pop3_client *client)
+static void pop3_client_input(struct client *client)
 {
-	switch (i_stream_read(client->common.input)) {
-	case -2:
-		/* buffer full */
-		client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
-				 "Input buffer full, aborting");
-		client_destroy(client, "Disconnected: Input buffer full");
-		return FALSE;
-	case -1:
-		/* disconnected */
-		client_destroy(client, "Disconnected");
-		return FALSE;
-	case 0:
-		/* nothing new read */
-		return TRUE;
-	default:
-		/* something was read */
-		timeout_reset(client->to_idle_disconnect);
-		return TRUE;
-	}
-}
-
-void client_input(struct pop3_client *client)
-{
+	struct pop3_client *pop3_client = (struct pop3_client *)client;
 	char *line, *args;
 
-	i_assert(!client->common.authenticating);
+	i_assert(!client->authenticating);
 
 	if (!client_read(client))
 		return;
@@ -214,18 +76,18 @@
 	o_stream_cork(client->output);
 	/* if a command starts an authentication, stop processing further
 	   commands until the authentication is finished. */
-	while (!client->output->closed && !client->common.authenticating &&
-	       (line = i_stream_next_line(client->common.input)) != NULL) {
+	while (!client->output->closed && !client->authenticating &&
+	       (line = i_stream_next_line(client->input)) != NULL) {
 		args = strchr(line, ' ');
 		if (args != NULL)
 			*args++ = '\0';
 
-		if (client_command_execute(client, line,
+		if (client_command_execute(pop3_client, line,
 					   args != NULL ? args : ""))
 			client->bad_counter = 0;
 		else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) {
-			client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
-				"Too many invalid IMAP commands.");
+			client_send_line(client, CLIENT_CMD_REPLY_BYE,
+				"Too many invalid bad commands.");
 			client_destroy(client,
 				       "Disconnected: Too many bad commands");
 		}
@@ -235,43 +97,24 @@
 		o_stream_uncork(client->output);
 }
 
-void client_destroy_oldest(void)
+static struct client *pop3_client_alloc(pool_t pool)
 {
-	unsigned int max_connections =
-		global_login_settings->login_max_connections;
-	struct client *client;
-	struct pop3_client *destroy_buf[CLIENT_DESTROY_OLDEST_COUNT];
-	unsigned int i, destroy_count;
+	struct pop3_client *pop3_client;
 
-	/* 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);
-	for (client = clients; client != NULL; client = client->next) {
-		struct pop3_client *pop3_client = (struct pop3_client *)client;
+	pop3_client = p_new(pool, struct pop3_client, 1);
+	return &pop3_client->common;
+}
 
-		for (i = 0; i < destroy_count; i++) {
-			if (destroy_buf[i] == NULL ||
-			    destroy_buf[i]->created > pop3_client->created) {
-				/* @UNSAFE */
-				memmove(destroy_buf+i+1, destroy_buf+i,
-					sizeof(destroy_buf) -
-					(i+1) * sizeof(struct pop3_client *));
-				destroy_buf[i] = pop3_client;
-				break;
-			}
-		}
-	}
+static void pop3_client_create(struct client *client ATTR_UNUSED)
+{
+}
 
-	/* then kill them */
-	for (i = 0; i < destroy_count; i++) {
-		if (destroy_buf[i] == NULL)
-			break;
+static void pop3_client_destroy(struct client *client)
+{
+	struct pop3_client *pop3_client = (struct pop3_client *)client;
 
-		client_destroy(destroy_buf[i],
-			       "Disconnected: Connection queue full");
-	}
+	i_free(pop3_client->last_user);
+	i_free(pop3_client->apop_challenge);
 }
 
 static char *get_apop_challenge(struct pop3_client *client)
@@ -295,206 +138,32 @@
 			       (const char *)buf->data, my_hostname);
 }
 
-static void client_auth_ready(struct pop3_client *client)
-{
-	client->io = io_add(client->common.fd, IO_READ, client_input, client);
-
-	client->apop_challenge = get_apop_challenge(client);
-	if (client->apop_challenge == NULL) {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
-				 client->common.set->login_greeting);
-	} else {
-		client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
-			t_strconcat(client->common.set->login_greeting, " ",
-				    client->apop_challenge, NULL));
-	}
-}
-
-static void client_idle_disconnect_timeout(struct pop3_client *client)
+static void pop3_client_send_greeting(struct client *client)
 {
-	client_destroy(client, "Disconnected: Inactivity");
-}
+	struct pop3_client *pop3_client = (struct pop3_client *)client;
 
-struct client *client_create(int fd, bool ssl, pool_t pool,
-			     const struct login_settings *set,
-			     const struct ip_addr *local_ip,
-			     const struct ip_addr *remote_ip)
-{
-	struct pop3_client *client;
-
-	i_assert(fd != -1);
-
-	if (clients_get_count() >= set->login_max_connections) {
-		/* reached max. users count, kill few of the
-		   oldest connections */
-		client_destroy_oldest();
-	}
+	client->io = io_add(client->fd, IO_READ, client_input, client);
 
-	/* always use nonblocking I/O */
-	net_set_nonblock(fd, TRUE);
-
-	client = p_new(pool, struct pop3_client, 1);
-	client->created = ioloop_time;
-	client->refcount = 1;
-
-	client->common.pool = pool;
-	client->common.set = set;
-	client->common.local_ip = *local_ip;
-	client->common.ip = *remote_ip;
-	client->common.fd = fd;
-	client->common.tls = ssl;
-	client->common.trusted = client_is_trusted(&client->common);
-	client->common.secured = ssl || client->common.trusted ||
-		net_ip_compare(remote_ip, local_ip);
-
-	client_open_streams(client, fd);
-	client_link(&client->common);
-
-	client->auth_connected = auth_client_is_connected(auth_client);
-	if (client->auth_connected)
-		client_auth_ready(client);
-	client_set_title(client);
-
-	client->to_idle_disconnect =
-		timeout_add(CLIENT_LOGIN_IDLE_TIMEOUT_MSECS,
-			    client_idle_disconnect_timeout, client);
-	return &client->common;
-}
-
-void client_destroy_success(struct pop3_client *client, const char *reason)
-{
-	client->login_success = TRUE;
-	client_destroy(client, reason);
+	pop3_client->apop_challenge = get_apop_challenge(pop3_client);
+	if (pop3_client->apop_challenge == NULL) {
+		client_send_line(client, CLIENT_CMD_REPLY_OK,
+				 client->set->login_greeting);
+	} else {
+		client_send_line(client, CLIENT_CMD_REPLY_OK,
+			t_strconcat(client->set->login_greeting, " ",
+				    pop3_client->apop_challenge, NULL));
+	}
+	client->greeting_sent = TRUE;
 }
 
-void client_destroy(struct pop3_client *client, const char *reason)
+static void pop3_client_starttls(struct client *client ATTR_UNUSED)
 {
-	if (client->destroyed)
-		return;
-	client->destroyed = TRUE;
-
-	if (!client->login_success && reason != NULL) {
-		reason = t_strconcat(reason, " ",
-			client_get_extra_disconnect_reason(&client->common),
-			NULL);
-	}
-	if (reason != NULL)
-		client_syslog(&client->common, reason);
-
-	client_unlink(&client->common);
-
-	if (client->common.input != NULL)
-		i_stream_close(client->common.input);
-	if (client->output != NULL)
-		o_stream_close(client->output);
-
-	if (client->common.master_tag != 0) {
-		i_assert(client->common.auth_request == NULL);
-		i_assert(client->common.authenticating);
-		master_auth_request_abort(master_service,
-					  client->common.master_tag);
-	} else if (client->common.auth_request != NULL) {
-		i_assert(client->common.authenticating);
-		sasl_server_auth_abort(&client->common);
-	} else {
-		i_assert(!client->common.authenticating);
-	}
-
-	if (client->io != NULL)
-		io_remove(&client->io);
-	if (client->to_idle_disconnect != NULL)
-		timeout_remove(&client->to_idle_disconnect);
-	if (client->to_authfail_delay != NULL)
-		timeout_remove(&client->to_authfail_delay);
-
-	if (client->common.fd != -1) {
-		net_disconnect(client->common.fd);
-		client->common.fd = -1;
-	}
-
-	if (client->proxy_password != NULL) {
-		safe_memset(client->proxy_password, 0,
-			    strlen(client->proxy_password));
-		i_free(client->proxy_password);
-		client->proxy_password = NULL;
-	}
-
-	i_free(client->proxy_user);
-	client->proxy_user = NULL;
-
-	if (client->proxy != NULL)
-		login_proxy_free(&client->proxy);
-
-	if (client->common.proxy != NULL) {
-		ssl_proxy_free(client->common.proxy);
-		client->common.proxy = NULL;
-	}
-	client_unref(client);
-}
-
-void client_destroy_internal_failure(struct pop3_client *client)
-{
-	client_send_line(&client->common, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-			 "Internal login failure. "
-			 "Refer to server log for more information.");
-	client_destroy(client, "Internal login failure");
-}
-
-void client_ref(struct pop3_client *client)
-{
-	client->refcount++;
-}
-
-bool client_unref(struct pop3_client *client)
-{
-	i_assert(client->refcount > 0);
-	if (--client->refcount > 0)
-		return TRUE;
-
-	i_assert(client->destroyed);
-
-	if (client->common.input != NULL)
-		i_stream_unref(&client->common.input);
-	if (client->output != NULL)
-		o_stream_unref(&client->output);
-
-	if (!client->common.proxying) {
-		i_assert(client->common.proxy == NULL);
-		master_service_client_connection_destroyed(master_service);
-	}
-
-	i_free(client->last_user);
-	i_free(client->apop_challenge);
-	i_free(client->common.virtual_user);
-	i_free(client->common.auth_mech_name);
-	pool_unref(&client->common.pool);
-	return FALSE;
 }
 
 static void
-client_send_raw_data(struct pop3_client *client, const void *data, size_t size)
-{
-	ssize_t ret;
-
-	ret = o_stream_send(client->output, data, size);
-	if (ret < 0 || (size_t)ret != size) {
-		/* either disconnection or buffer full. in either case we want
-		   this connection destroyed. however destroying it here might
-		   break things if client is still tried to be accessed without
-		   being referenced.. */
-		i_stream_close(client->common.input);
-	}
-}
-
-void client_send_raw(struct pop3_client *client, const char *data)
-{
-	client_send_raw_data(client, data, strlen(data));
-}
-
-void client_send_line(struct client *client, enum client_cmd_reply reply,
+pop3_client_send_line(struct client *client, enum client_cmd_reply reply,
 		      const char *text)
 {
-	struct pop3_client *pop3_client = (struct pop3_client *)client;
 	const char *prefix = "-ERR";
 
 	switch (reply) {
@@ -512,6 +181,7 @@
 	case CLIENT_CMD_REPLY_BYE:
 		break;
 	case CLIENT_CMD_REPLY_STATUS:
+	case CLIENT_CMD_REPLY_STATUS_BAD:
 		/* can't send status notifications */
 		return;
 	}
@@ -524,44 +194,31 @@
 		str_append(line, text);
 		str_append(line, "\r\n");
 
-		client_send_raw_data(pop3_client, str_data(line),
+		client_send_raw_data(client, str_data(line),
 				     str_len(line));
 	} T_END;
 }
 
 
-void clients_notify_auth_connected(void)
-{
-	struct client *client;
-
-	for (client = clients; client != NULL; client = client->next) {
-		struct pop3_client *pop3_client = (struct pop3_client *)client;
-
-		if (!pop3_client->auth_connected) {
-			pop3_client->auth_connected = TRUE;
-			client_auth_ready(pop3_client);
-		}
-	}
-}
-
-void clients_destroy_all(void)
-{
-	struct client *client, *next;
-
-	for (client = clients; client != NULL; client = next) {
-		struct pop3_client *pop3_client = (struct pop3_client *)client;
-
-		next = client->next;
-		client_destroy(pop3_client, "Disconnected: Shutting down");
-	}
-}
-
 void clients_init(void)
 {
-    /* Nothing to initialize for POP3 */
+	/* Nothing to initialize for POP3 */
 }
 
 void clients_deinit(void)
 {
 	clients_destroy_all();
 }
+
+struct client_vfuncs client_vfuncs = {
+	pop3_client_alloc,
+	pop3_client_create,
+	pop3_client_destroy,
+	pop3_client_send_greeting,
+	pop3_client_starttls,
+	pop3_client_input,
+	pop3_client_send_line,
+	pop3_client_auth_handle_reply,
+	pop3_proxy_reset,
+	pop3_proxy_parse_line
+};
--- a/src/pop3-login/client.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/pop3-login/client.h	Sun Aug 09 21:53:14 2009 -0400
@@ -5,9 +5,6 @@
 #include "client-common.h"
 #include "auth-client.h"
 
-/* Disconnect client after idling this many milliseconds */
-#define CLIENT_LOGIN_IDLE_TIMEOUT_MSECS (3*60*1000)
-
 enum pop3_proxy_state {
 	POP3_PROXY_BANNER = 0,
 	POP3_PROXY_STARTTLS,
@@ -18,42 +15,11 @@
 struct pop3_client {
 	struct client common;
 
-	time_t created;
-	int refcount;
-
-	struct io *io;
-	struct ostream *output;
-	struct timeout *to_idle_disconnect, *to_authfail_delay;
-
-	struct login_proxy *proxy;
-	char *proxy_user, *proxy_master_user, *proxy_password;
 	enum pop3_proxy_state proxy_state;
 
-	unsigned int bad_counter;
-
 	char *last_user;
-
 	char *apop_challenge;
 	struct auth_connect_id auth_id;
-
-	unsigned int login_success:1;
-	unsigned int auth_connected:1;
-	unsigned int auth_initializing:1;
-	unsigned int destroyed:1;
 };
 
-void client_destroy(struct pop3_client *client, const char *reason);
-void client_destroy_success(struct pop3_client *client, const char *reason);
-void client_destroy_internal_failure(struct pop3_client *client);
-
-bool client_read(struct pop3_client *client);
-void client_input(struct pop3_client *client);
-
-void client_send_raw(struct pop3_client *client, const char *data);
-
-void client_ref(struct pop3_client *client);
-bool client_unref(struct pop3_client *client);
-
-void client_auth_failed(struct pop3_client *client, bool nodelay);
-
 #endif
--- a/src/pop3-login/pop3-proxy.c	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/pop3-login/pop3-proxy.c	Sun Aug 09 21:53:14 2009 -0400
@@ -11,7 +11,7 @@
 #include "client.h"
 #include "pop3-proxy.h"
 
-static void proxy_free_password(struct pop3_client *client)
+static void proxy_free_password(struct client *client)
 {
 	if (client->proxy_password == NULL)
 		return;
@@ -20,24 +20,7 @@
 	i_free_and_null(client->proxy_password);
 }
 
-static void proxy_failed(struct pop3_client *client, bool send_line)
-{
-	if (send_line) {
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 AUTH_TEMP_FAILED_MSG);
-	}
-
-	login_proxy_free(&client->proxy);
-	proxy_free_password(client);
-	i_free_and_null(client->proxy_user);
-	i_free_and_null(client->proxy_master_user);
-
-	/* call this last - it may destroy the client */
-	client_auth_failed(client, TRUE);
-}
-
-static void get_plain_auth(struct pop3_client *client, string_t *dest)
+static void get_plain_auth(struct client *client, string_t *dest)
 {
 	string_t *str;
 
@@ -55,10 +38,10 @@
 	string_t *str;
 
 	str = t_str_new(128);
-	if (client->proxy_master_user == NULL) {
+	if (client->common.proxy_master_user == NULL) {
 		/* send USER command */
 		str_append(str, "USER ");
-		str_append(str, client->proxy_user);
+		str_append(str, client->common.proxy_user);
 		str_append(str, "\r\n");
 	} else {
 		/* master user login - use AUTH PLAIN. */
@@ -68,49 +51,50 @@
 	client->proxy_state = POP3_PROXY_LOGIN1;
 }
 
-static int proxy_input_line(struct pop3_client *client, const char *line)
+int pop3_proxy_parse_line(struct client *client, const char *line)
 {
+	struct pop3_client *pop3_client = (struct pop3_client *)client;
 	struct ostream *output;
 	enum login_proxy_ssl_flags ssl_flags;
 	string_t *str;
 
 	i_assert(!client->destroyed);
 
-	output = login_proxy_get_ostream(client->proxy);
-	switch (client->proxy_state) {
+	output = login_proxy_get_ostream(client->login_proxy);
+	switch (pop3_client->proxy_state) {
 	case POP3_PROXY_BANNER:
 		/* this is a banner */
 		if (strncmp(line, "+OK", 3) != 0) {
-			client_syslog_err(&client->common, t_strdup_printf(
+			client_log_err(client, t_strdup_printf(
 				"proxy: Remote returned invalid banner: %s",
 				str_sanitize(line, 160)));
-			proxy_failed(client, TRUE);
+			client_proxy_failed(client, TRUE);
 			return -1;
 		}
 
-		ssl_flags = login_proxy_get_ssl_flags(client->proxy);
+		ssl_flags = login_proxy_get_ssl_flags(client->login_proxy);
 		if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) == 0) {
-			proxy_send_login(client, output);
+			proxy_send_login(pop3_client, output);
 		} else {
 			(void)o_stream_send_str(output, "STLS\r\n");
-			client->proxy_state = POP3_PROXY_STARTTLS;
+			pop3_client->proxy_state = POP3_PROXY_STARTTLS;
 		}
 		return 0;
 	case POP3_PROXY_STARTTLS:
 		if (strncmp(line, "+OK", 3) != 0) {
-			client_syslog_err(&client->common, t_strdup_printf(
+			client_log_err(client, t_strdup_printf(
 				"proxy: Remote STLS failed: %s",
 				str_sanitize(line, 160)));
-			proxy_failed(client, TRUE);
+			client_proxy_failed(client, TRUE);
 			return -1;
 		}
-		if (login_proxy_starttls(client->proxy) < 0) {
-			proxy_failed(client, TRUE);
+		if (login_proxy_starttls(client->login_proxy) < 0) {
+			client_proxy_failed(client, TRUE);
 			return -1;
 		}
 		/* i/ostreams changed. */
-		output = login_proxy_get_ostream(client->proxy);
-		proxy_send_login(client, output);
+		output = login_proxy_get_ostream(client->login_proxy);
+		proxy_send_login(pop3_client, output);
 		return 1;
 	case POP3_PROXY_LOGIN1:
 		str = t_str_new(128);
@@ -131,7 +115,7 @@
 		}
 		(void)o_stream_send(output, str_data(str), str_len(str));
 		proxy_free_password(client);
-		client->proxy_state = POP3_PROXY_LOGIN2;
+		pop3_client->proxy_state = POP3_PROXY_LOGIN2;
 		return 0;
 	case POP3_PROXY_LOGIN2:
 		if (strncmp(line, "+OK", 3) != 0)
@@ -141,31 +125,7 @@
 		line = t_strconcat(line, "\r\n", NULL);
 		(void)o_stream_send_str(client->output, line);
 
-		str = t_str_new(128);
-		str_printfa(str, "proxy(%s): started proxying to %s:%u",
-			    client->common.virtual_user,
-			    login_proxy_get_host(client->proxy),
-			    login_proxy_get_port(client->proxy));
-		if (strcmp(client->common.virtual_user,
-			   client->proxy_user) != 0) {
-			/* remote username is different, log it */
-			str_append_c(str, '/');
-			str_append(str, client->proxy_user);
-		}
-		if (client->proxy_master_user != NULL) {
-			str_printfa(str, " (master %s)",
-				    client->proxy_master_user);
-		}
-
-		login_proxy_detach(client->proxy, client->common.input,
-				   client->output);
-
-		client->proxy = NULL;
-		client->common.input = NULL;
-		client->output = NULL;
-		client->common.fd = -1;
-		client->common.proxying = TRUE;
-		client_destroy_success(client, str_c(str));
+		client_proxy_finish_destroy_client(client);
 		return 1;
 	}
 
@@ -184,129 +144,25 @@
 	   So for now we'll just forward the error message. This
 	   shouldn't be a real problem since of course everyone will
 	   be using only Dovecot as their backend :) */
-	if (strncmp(line, "-ERR ", 5) != 0)
-		client_send_line(&client->common, CLIENT_CMD_REPLY_AUTH_FAILED,
+	if (strncmp(line, "-ERR ", 5) != 0) {
+		client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAILED,
 				 AUTH_FAILED_MSG);
-	else {
+	} else {
 		client_send_raw(client, t_strconcat(line, "\r\n", NULL));
 	}
 
-	if (client->common.set->verbose_auth) {
-		str = t_str_new(128);
-		str_printfa(str, "proxy(%s): Login failed to %s:%u",
-			    client->common.virtual_user,
-			    login_proxy_get_host(client->proxy),
-			    login_proxy_get_port(client->proxy));
-		if (strcmp(client->common.virtual_user,
-			   client->proxy_user) != 0) {
-			/* remote username is different, log it */
-			str_append_c(str, '/');
-			str_append(str, client->proxy_user);
-		}
-		if (client->proxy_master_user != NULL) {
-			str_printfa(str, " (master %s)",
-				    client->proxy_master_user);
-		}
-		str_append(str, ": ");
+	if (client->set->verbose_auth) {
 		if (strncmp(line, "-ERR ", 5) == 0)
-			str_append(str, line + 5);
-		else
-			str_append(str, line);
-		i_info("%s", str_c(str));
+			line += 5;
+		client_proxy_log_failure(client, line);
 	}
-	proxy_failed(client, FALSE);
+	client_proxy_failed(client, FALSE);
 	return -1;
 }
 
-static void proxy_input(struct pop3_client *client)
+void pop3_proxy_reset(struct client *client)
 {
-	struct istream *input;
-	const char *line;
-
-	if (client->proxy == NULL) {
-		/* we're just freeing the proxy */
-		return;
-	}
-
-	input = login_proxy_get_istream(client->proxy);
-	if (input == NULL) {
-		if (client->destroyed) {
-			/* we came here from client_destroy() */
-			return;
-		}
-
-		/* failed for some reason, probably server disconnected */
-		proxy_failed(client, TRUE);
-		return;
-	}
-
-	i_assert(!client->destroyed);
-
-	switch (i_stream_read(input)) {
-	case -2:
-		client_syslog_err(&client->common,
-				  "proxy: Remote input buffer full");
-		proxy_failed(client, TRUE);
-		return;
-	case -1:
-		client_syslog_err(&client->common,
-				  "proxy: Remote disconnected");
-		proxy_failed(client, TRUE);
-		return;
-	}
-
-	while ((line = i_stream_next_line(input)) != NULL) {
-		if (proxy_input_line(client, line) != 0)
-			break;
-	}
-}
+	struct pop3_client *pop3_client = (struct pop3_client *)client;
 
-int pop3_proxy_new(struct pop3_client *client, const char *host,
-		   unsigned int port, const char *user, const char *master_user,
-		   const char *password, enum login_proxy_ssl_flags ssl_flags)
-{
-	i_assert(user != NULL);
-	i_assert(!client->destroyed);
-
-	if (password == NULL) {
-		client_syslog_err(&client->common, "proxy: password not given");
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 AUTH_TEMP_FAILED_MSG);
-		return -1;
-	}
-
-	i_assert(client->refcount > 1);
-
-	if (client->destroyed) {
-		/* connection_queue_add() decided that we were the oldest
-		   connection and killed us. */
-		return -1;
-	}
-	if (login_proxy_is_ourself(&client->common, host, port, user)) {
-		client_syslog_err(&client->common, "Proxying loops to itself");
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 AUTH_TEMP_FAILED_MSG);
-		return -1;
-	}
-
-	client->proxy = login_proxy_new(&client->common, host, port, ssl_flags,
-					proxy_input, client);
-	if (client->proxy == NULL) {
-		client_send_line(&client->common,
-				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
-				 AUTH_TEMP_FAILED_MSG);
-		return -1;
-	}
-
-	client->proxy_state = POP3_PROXY_BANNER;
-	client->proxy_user = i_strdup(user);
-	client->proxy_master_user = i_strdup(master_user);
-	client->proxy_password = i_strdup(password);
-
-	/* disable input until authentication is finished */
-	if (client->io != NULL)
-		io_remove(&client->io);
-	return 0;
+	pop3_client->proxy_state = POP3_PROXY_BANNER;
 }
--- a/src/pop3-login/pop3-proxy.h	Sun Aug 09 21:48:45 2009 -0400
+++ b/src/pop3-login/pop3-proxy.h	Sun Aug 09 21:53:14 2009 -0400
@@ -1,10 +1,7 @@
 #ifndef POP3_PROXY_H
 #define POP3_PROXY_H
 
-#include "login-proxy.h"
-
-int pop3_proxy_new(struct pop3_client *client, const char *host,
-		   unsigned int port, const char *user, const char *master_user,
-		   const char *password, enum login_proxy_ssl_flags ssl_flags);
+void pop3_proxy_reset(struct client *client);
+int pop3_proxy_parse_line(struct client *client, const char *line);
 
 #endif