changeset 7927:2351a81ce699 HEAD

If commands are pipelined after the login command, pass them to the IMAP/POP3 process so it can process the command instead of discarding it.
author Timo Sirainen <tss@iki.fi>
date Sat, 21 Jun 2008 12:23:08 +0300
parents 187183c360dd
children 9e226056a208
files src/imap-login/client-authenticate.c src/imap-login/client.c src/imap-login/client.h src/imap-login/imap-proxy.c src/imap/client.c src/imap/client.h src/imap/main.c src/login-common/client-common.h src/login-common/master.c src/master/login-process.c src/master/mail-process.c src/master/mail-process.h src/master/master-login-interface.h src/master/master-settings.c src/pop3-login/client-authenticate.c src/pop3-login/client.c src/pop3-login/client.h src/pop3-login/pop3-proxy.c src/pop3/client.c src/pop3/client.h src/pop3/main.c
diffstat 21 files changed, 204 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap-login/client-authenticate.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/imap-login/client-authenticate.c	Sat Jun 21 12:23:08 2008 +0300
@@ -52,14 +52,14 @@
 		return;
 
 	if (client->skip_line) {
-		if (i_stream_next_line(client->input) == NULL)
+		if (i_stream_next_line(client->common.input) == NULL)
 			return;
 
 		client->skip_line = FALSE;
 	}
 
 	/* @UNSAFE */
-	line = i_stream_next_line(client->input);
+	line = i_stream_next_line(client->common.input);
 	if (line == NULL)
 		return;
 
--- a/src/imap-login/client.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/imap-login/client.c	Sat Jun 21 12:23:08 2008 +0300
@@ -19,10 +19,6 @@
 
 #include <stdlib.h>
 
-/* max. size of one parameter in line, or max reply length in SASL
-   authentication */
-#define MAX_INBUF_SIZE 4096
-
 /* max. size of output buffer. if it gets full, the client is disconnected.
    SASL authentication gives the largest output. */
 #define MAX_OUTBUF_SIZE 4096
@@ -72,10 +68,11 @@
 
 static void client_open_streams(struct imap_client *client, int fd)
 {
-	client->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE);
+	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->input, client->output,
-					    MAX_IMAP_LINE);
+	client->parser = imap_parser_create(client->common.input,
+					    client->output, MAX_IMAP_LINE);
 }
 
 /* Skip incoming data until newline is found,
@@ -85,11 +82,11 @@
 	const unsigned char *data;
 	size_t i, data_size;
 
-	data = i_stream_get_data(client->input, &data_size);
+	data = i_stream_get_data(client->common.input, &data_size);
 
 	for (i = 0; i < data_size; i++) {
 		if (data[i] == '\n') {
-			i_stream_skip(client->input, i+1);
+			i_stream_skip(client->common.input, i+1);
 			return TRUE;
 		}
 	}
@@ -141,7 +138,7 @@
 	client_set_title(client);
 
 	client->common.fd = fd_ssl;
-	i_stream_unref(&client->input);
+	i_stream_unref(&client->common.input);
 	o_stream_unref(&client->output);
 	imap_parser_destroy(&client->parser);
 
@@ -360,7 +357,9 @@
 		/* not enough data */
 		return FALSE;
 	}
-	client->skip_line = TRUE;
+	/* we read the entire line - skip over the CRLF */
+	if (!client_skip_line(client))
+		i_unreached();
 
 	if (*client->cmd_tag == '\0')
 		ret = -1;
@@ -387,7 +386,7 @@
 
 bool client_read(struct imap_client *client)
 {
-	switch (i_stream_read(client->input)) {
+	switch (i_stream_read(client->common.input)) {
 	case -2:
 		/* buffer full */
 		client_send_line(client, "* BYE Input buffer full, aborting");
@@ -557,8 +556,8 @@
 
 	client_unlink(&client->common);
 
-	if (client->input != NULL)
-		i_stream_close(client->input);
+	if (client->common.input != NULL)
+		i_stream_close(client->common.input);
 	if (client->output != NULL)
 		o_stream_close(client->output);
 
@@ -637,8 +636,8 @@
 
 	imap_parser_destroy(&client->parser);
 
-	if (client->input != NULL)
-		i_stream_unref(&client->input);
+	if (client->common.input != NULL)
+		i_stream_unref(&client->common.input);
 	if (client->output != NULL)
 		o_stream_unref(&client->output);
 
@@ -665,7 +664,7 @@
 		   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);
+		i_stream_close(client->common.input);
 	}
 }
 
--- a/src/imap-login/client.h	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/imap-login/client.h	Sat Jun 21 12:23:08 2008 +0300
@@ -12,7 +12,6 @@
 	int refcount;
 
 	struct io *io;
-	struct istream *input;
 	struct ostream *output;
 	struct imap_parser *parser;
 	struct timeout *to_idle_disconnect, *to_auth_waiting;
--- a/src/imap-login/imap-proxy.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/imap-login/imap-proxy.c	Sat Jun 21 12:23:08 2008 +0300
@@ -97,11 +97,11 @@
 				      login_proxy_get_port(client->proxy));
 
 		(void)client_skip_line(client);
-		login_proxy_detach(client->proxy, client->input,
+		login_proxy_detach(client->proxy, client->common.input,
 				   client->output);
 
 		client->proxy = NULL;
-		client->input = NULL;
+		client->common.input = NULL;
 		client->output = NULL;
 		client->common.fd = -1;
 		client_destroy_success(client, msg);
--- a/src/imap/client.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/imap/client.c	Sat Jun 21 12:23:08 2008 +0300
@@ -18,8 +18,6 @@
 
 static struct client *my_client; /* we don't need more than one currently */
 
-static bool client_handle_input(struct client *client);
-
 static void client_idle_timeout(struct client *client)
 {
 	if (client->output_lock == NULL)
@@ -684,7 +682,7 @@
 	return client_command_input(client->input_lock);
 }
 
-static bool client_handle_input(struct client *client)
+bool client_handle_input(struct client *client)
 {
 	bool ret, remove_io, handled_commands = FALSE;
 
--- a/src/imap/client.h	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/imap/client.h	Sat Jun 21 12:23:08 2008 +0300
@@ -171,6 +171,7 @@
 void client_continue_pending_input(struct client **_client);
 
 void client_input(struct client *client);
+bool client_handle_input(struct client *client);
 int client_output(struct client *client);
 
 #endif
--- a/src/imap/main.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/imap/main.c	Sat Jun 21 12:23:08 2008 +0300
@@ -5,6 +5,8 @@
 #include "network.h"
 #include "ostream.h"
 #include "str.h"
+#include "base64.h"
+#include "istream.h"
 #include "lib-signals.h"
 #include "restrict-access.h"
 #include "fd-close-on-exec.h"
@@ -163,6 +165,7 @@
 static void main_init(void)
 {
 	struct client *client;
+	struct ostream *output;
 	struct mail_namespace *ns;
 	const char *user, *str;
 
@@ -238,7 +241,9 @@
 		i_fatal("Namespace initialization failed");
 	client = client_create(0, 1, ns);
 
-        o_stream_cork(client->output);
+	output = client->output;
+	o_stream_ref(output);
+	o_stream_cork(output);
 	if (IS_STANDALONE()) {
 		client_send_line(client, t_strconcat(
 			"* PREAUTH [CAPABILITY ",
@@ -249,7 +254,18 @@
 		client_send_line(client, t_strconcat(getenv("IMAPLOGINTAG"),
 						     " OK Logged in.", NULL));
 	}
-        o_stream_uncork(client->output);
+	str = getenv("CLIENT_INPUT");
+	if (str != NULL) T_BEGIN {
+		buffer_t *buf = t_base64_decode_str(str);
+		if (buf->used > 0) {
+			if (!i_stream_add_data(client->input, buf->data,
+					       buf->used))
+				i_panic("Couldn't add client input to stream");
+			(void)client_handle_input(client);
+		}
+	} T_END;
+        o_stream_uncork(output);
+	o_stream_unref(&output);
 }
 
 static void main_deinit(void)
@@ -292,8 +308,12 @@
         process_title_init(argv, envp);
 	ioloop = io_loop_create();
 
+	/* fake that we're running, so we know if client was destroyed
+	   while initializing */
+	io_loop_set_running(ioloop);
 	main_init();
-        io_loop_run(ioloop);
+	if (io_loop_is_running(ioloop))
+		io_loop_run(ioloop);
 	main_deinit();
 
 	io_loop_destroy(&ioloop);
--- a/src/login-common/client-common.h	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/login-common/client-common.h	Sat Jun 21 12:23:08 2008 +0300
@@ -5,6 +5,14 @@
 #include "master.h"
 #include "sasl-server.h"
 
+/* max. size of input buffer. this means:
+
+   SASL: Max SASL request length from client
+   IMAP: Max. length of a single parameter
+   POP3: Max. length of a command line (spec says 512 would be enough)
+*/
+#define LOGIN_MAX_INBUF_SIZE 4096
+
 struct client {
 	struct client *prev, *next;
 
@@ -14,6 +22,7 @@
 	struct ssl_proxy *proxy;
 
 	int fd;
+	struct istream *input;
 
 	char *auth_mech_name;
 	struct auth_request *auth_request;
--- a/src/login-common/master.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/login-common/master.c	Sat Jun 21 12:23:08 2008 +0300
@@ -2,6 +2,7 @@
 
 #include "common.h"
 #include "hash.h"
+#include "buffer.h"
 #include "ioloop.h"
 #include "network.h"
 #include "fdpass.h"
@@ -59,32 +60,50 @@
 void master_request_login(struct client *client, master_callback_t *callback,
 			  unsigned int auth_pid, unsigned int auth_id)
 {
-	struct master_login_request req;
+	buffer_t *buf;
+	struct master_login_request *req;
 	struct stat st;
+	const unsigned char *data;
+	size_t size;
+	ssize_t ret;
 
 	i_assert(auth_pid != 0);
 
-	memset(&req, 0, sizeof(req));
-	req.version = MASTER_LOGIN_PROTOCOL_VERSION;
-	req.tag = ++master_tag_counter;
-	if (req.tag == 0)
-		req.tag = ++master_tag_counter;
-	req.auth_pid = auth_pid;
-	req.auth_id = auth_id;
-	req.local_ip = client->local_ip;
-	req.remote_ip = client->ip;
+	data = i_stream_get_data(client->input, &size);
+	buf = buffer_create_dynamic(pool_datastack_create(),
+				    sizeof(*req) + size);
+	buffer_write(buf, sizeof(*req), data, size);
+	req = buffer_get_space_unsafe(buf, 0, sizeof(*req));
+	req->version = MASTER_LOGIN_PROTOCOL_VERSION;
+	req->tag = ++master_tag_counter;
+	if (req->tag == 0)
+		req->tag = ++master_tag_counter;
+	req->auth_pid = auth_pid;
+	req->auth_id = auth_id;
+	req->local_ip = client->local_ip;
+	req->remote_ip = client->ip;
+	req->data_size = size;
+#if LOGIN_MAX_INBUF_SIZE != MASTER_LOGIN_MAX_DATA_SIZE
+#  error buffer max sizes unsynced
+#endif
+	i_assert(req->data_size <= LOGIN_MAX_INBUF_SIZE);
 
 	if (fstat(client->fd, &st) < 0)
 		i_fatal("fstat(client) failed: %m");
-	req.ino = st.st_ino;
+	req->ino = st.st_ino;
 
-	if (fd_send(master_fd, client->fd, &req, sizeof(req)) != sizeof(req))
+	ret = fd_send(master_fd, client->fd, buf->data, buf->used);
+	if (ret < 0)
 		i_fatal("fd_send(%d) failed: %m", client->fd);
+	if ((size_t)ret != buf->used) {
+		i_fatal("fd_send() sent only %d of %d bytes",
+			(int)ret, (int)buf->used);
+	}
 
-	client->master_tag = req.tag;
+	client->master_tag = req->tag;
 	client->master_callback = callback;
 
-	hash_insert(master_requests, POINTER_CAST(req.tag), client);
+	hash_insert(master_requests, POINTER_CAST(req->tag), client);
 }
 
 void master_request_abort(struct client *client)
--- a/src/master/login-process.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/master/login-process.c	Sat Jun 21 12:23:08 2008 +0300
@@ -48,7 +48,9 @@
 	unsigned int login_tag;
 	int fd;
 
+	unsigned int data_size;
 	struct ip_addr local_ip, remote_ip;
+	unsigned char data[];
 };
 
 static unsigned int auth_id_counter, login_pid_counter;
@@ -105,6 +107,7 @@
 					    group->set,
 					    request->fd, &request->local_ip,
 					    &request->remote_ip, user, args,
+					    request->data_size, request->data,
 					    FALSE);
 	} T_END;
 
@@ -304,6 +307,7 @@
 
 static int
 login_read_request(struct login_process *p, struct master_login_request *req,
+		   unsigned char data[MASTER_LOGIN_MAX_DATA_SIZE],
 		   int *client_fd_r)
 {
 	struct stat st;
@@ -343,6 +347,26 @@
 		}
 		return 1;
 	}
+	if (req->data_size != 0) {
+		if (req->data_size > MASTER_LOGIN_MAX_DATA_SIZE) {
+			i_error("login: Too large data_size sent");
+			return -1;
+		}
+		/* @UNSAFE */
+		ret = read(p->fd, data, req->data_size);
+		if (ret != req->data_size) {
+			if (ret == 0) {
+				/* disconnected */
+			} else if (ret > 0) {
+				/* request wasn't fully read */
+				i_error("login: Data read partially %d/%u",
+					(int)ret, req->data_size);
+			} else {
+				i_error("login: read(data) failed: %m");
+			}
+			return -1;
+		}
+	}
 
 	if (*client_fd_r == -1) {
 		i_error("login: Login request missing a file descriptor");
@@ -353,7 +377,6 @@
 		i_error("login: fstat(mail client) failed: %m");
 		return -1;
 	}
-
 	if (st.st_ino != req->ino) {
 		i_error("login: Login request inode mismatch: %s != %s",
 			dec2str(st.st_ino), dec2str(req->ino));
@@ -367,6 +390,7 @@
 	struct auth_process *auth_process;
 	struct login_auth_request *authreq;
 	struct master_login_request req;
+	unsigned char data[MASTER_LOGIN_MAX_DATA_SIZE];
 	int client_fd;
 	ssize_t ret;
 
@@ -377,7 +401,7 @@
 		return;
 	}
 
-	ret = login_read_request(p, &req, &client_fd);
+	ret = login_read_request(p, &req, data, &client_fd);
 	if (ret == 0)
 		return;
 	if (ret < 0) {
@@ -412,7 +436,7 @@
 	fd_close_on_exec(client_fd, TRUE);
 
 	/* ask the cookie from the auth process */
-	authreq = i_new(struct login_auth_request, 1);
+	authreq = i_malloc(sizeof(*authreq) + req.data_size);
 	p->refcount++;
 	authreq->process = p;
 	authreq->tag = ++auth_id_counter;
@@ -420,6 +444,8 @@
 	authreq->fd = client_fd;
 	authreq->local_ip = req.local_ip;
 	authreq->remote_ip = req.remote_ip;
+	authreq->data_size = req.data_size;
+	memcpy(authreq->data, data, req.data_size);
 
 	auth_process = auth_process_find(req.auth_pid);
 	if (auth_process == NULL) {
--- a/src/master/mail-process.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/master/mail-process.c	Sat Jun 21 12:23:08 2008 +0300
@@ -5,6 +5,7 @@
 #include "hash.h"
 #include "fd-close-on-exec.h"
 #include "env-util.h"
+#include "base64.h"
 #include "str.h"
 #include "network.h"
 #include "mountpoint.h"
@@ -524,6 +525,7 @@
 		    int socket_fd, const struct ip_addr *local_ip,
 		    const struct ip_addr *remote_ip,
 		    const char *user, const char *const *args,
+		    unsigned int input_size, const unsigned char *input,
 		    bool dump_capability)
 {
 	const struct var_expand_table *var_expand_table;
@@ -832,6 +834,13 @@
 	addr = net_ip2addr(remote_ip);
 	env_put(t_strconcat("IP=", addr, NULL));
 
+	if (input_size > 0) {
+		str_truncate(str, 0);
+		str_append(str, "CLIENT_INPUT=");
+		base64_encode(input, input_size, str);
+		env_put(str_c(str));
+	}
+
 	if (!set->verbose_proctitle)
 		title[0] = '\0';
 	else {
--- a/src/master/mail-process.h	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/master/mail-process.h	Sat Jun 21 12:23:08 2008 +0300
@@ -13,6 +13,7 @@
 		    int socket_fd, const struct ip_addr *local_ip,
 		    const struct ip_addr *remote_ip,
 		    const char *user, const char *const *args,
+		    unsigned int input_size, const unsigned char *input,
 		    bool dump_capability);
 
 void mail_processes_init(void);
--- a/src/master/master-login-interface.h	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/master/master-login-interface.h	Sat Jun 21 12:23:08 2008 +0300
@@ -9,6 +9,9 @@
    (or something else) is changed. */
 #define MASTER_LOGIN_PROTOCOL_VERSION 3
 
+/* This should be kept in sync with LOGIN_MAX_INBUF_SIZE */
+#define MASTER_LOGIN_MAX_DATA_SIZE 4096
+
 enum master_login_state {
 	/* process is accepting new connections */
 	LOGIN_STATE_LISTENING = 0,
@@ -28,6 +31,8 @@
 
 	uint32_t auth_pid;
 	uint32_t auth_id;
+	/* request follows this many bytes of client input */
+	uint32_t data_size;
 
 	ino_t ino;
 
--- a/src/master/master-settings.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/master/master-settings.c	Sat Jun 21 12:23:08 2008 +0300
@@ -647,7 +647,7 @@
 	fd_close_on_exec(fd[1], TRUE);
 	login_status = create_mail_process(PROCESS_TYPE_IMAP, set, fd[1],
 					   &ip, &ip, "dump-capability",
-					   args, TRUE);
+					   args, 0, NULL, TRUE);
 	if (login_status != MASTER_LOGIN_STATUS_OK) {
 		(void)close(fd[0]);
 		(void)close(fd[1]);
--- a/src/pop3-login/client-authenticate.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/pop3-login/client-authenticate.c	Sat Jun 21 12:23:08 2008 +0300
@@ -66,7 +66,7 @@
 		return;
 
 	/* @UNSAFE */
-	line = i_stream_next_line(client->input);
+	line = i_stream_next_line(client->common.input);
 	if (line == NULL)
 		return;
 
--- a/src/pop3-login/client.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/pop3-login/client.c	Sat Jun 21 12:23:08 2008 +0300
@@ -17,10 +17,6 @@
 #include "pop3-proxy.h"
 #include "hostpid.h"
 
-/* max. length of input command line (spec says 512), or max reply length in
-   SASL authentication */
-#define MAX_INBUF_SIZE 4096
-
 /* max. size of output buffer. if it gets full, the client is disconnected.
    SASL authentication gives the largest output. */
 #define MAX_OUTBUF_SIZE 4096
@@ -59,7 +55,8 @@
 
 static void client_open_streams(struct pop3_client *client, int fd)
 {
-	client->input = i_stream_create_fd(fd, MAX_INBUF_SIZE, FALSE);
+	client->common.input =
+		i_stream_create_fd(fd, LOGIN_MAX_INBUF_SIZE, FALSE);
 	client->output = o_stream_create_fd(fd, MAX_OUTBUF_SIZE, FALSE);
 }
 
@@ -87,7 +84,7 @@
 
 	client->common.fd = fd_ssl;
 
-	i_stream_unref(&client->input);
+	i_stream_unref(&client->common.input);
 	o_stream_unref(&client->output);
 
 	client_open_streams(client, fd_ssl);
@@ -180,7 +177,7 @@
 
 bool client_read(struct pop3_client *client)
 {
-	switch (i_stream_read(client->input)) {
+	switch (i_stream_read(client->common.input)) {
 	case -2:
 		/* buffer full */
 		client_send_line(client, "-ERR Input line too long, aborting");
@@ -208,8 +205,8 @@
 	client_ref(client);
 
 	o_stream_cork(client->output);
-	while (!client->output->closed &&
-	       (line = i_stream_next_line(client->input)) != NULL) {
+	while (!client->output->closed && !client->common.authenticating &&
+	       (line = i_stream_next_line(client->common.input)) != NULL) {
 		args = strchr(line, ' ');
 		if (args != NULL)
 			*args++ = '\0';
@@ -363,8 +360,8 @@
 
 	client_unlink(&client->common);
 
-	if (client->input != NULL)
-		i_stream_close(client->input);
+	if (client->common.input != NULL)
+		i_stream_close(client->common.input);
 	if (client->output != NULL)
 		o_stream_close(client->output);
 
@@ -433,8 +430,8 @@
 
 	i_assert(client->destroyed);
 
-	if (client->input != NULL)
-		i_stream_unref(&client->input);
+	if (client->common.input != NULL)
+		i_stream_unref(&client->common.input);
 	if (client->output != NULL)
 		o_stream_unref(&client->output);
 
@@ -463,7 +460,7 @@
 		   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);
+		i_stream_close(client->common.input);
 	}
 }
 
--- a/src/pop3-login/client.h	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/pop3-login/client.h	Sat Jun 21 12:23:08 2008 +0300
@@ -13,7 +13,6 @@
 	int refcount;
 
 	struct io *io;
-	struct istream *input;
 	struct ostream *output;
 	struct timeout *to_idle_disconnect;
 
@@ -29,7 +28,6 @@
 	struct auth_connect_id auth_id;
 
 	unsigned int login_success:1;
-	unsigned int authenticating:1;
 	unsigned int auth_connected:1;
 	unsigned int destroyed:1;
 };
--- a/src/pop3-login/pop3-proxy.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/pop3-login/pop3-proxy.c	Sat Jun 21 12:23:08 2008 +0300
@@ -107,11 +107,11 @@
 				      login_proxy_get_host(client->proxy),
 				      login_proxy_get_port(client->proxy));
 
-		login_proxy_detach(client->proxy, client->input,
+		login_proxy_detach(client->proxy, client->common.input,
 				   client->output);
 
 		client->proxy = NULL;
-		client->input = NULL;
+		client->common.input = NULL;
 		client->output = NULL;
 		client->common.fd = -1;
 		client_destroy_success(client, msg);
--- a/src/pop3/client.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/pop3/client.c	Sat Jun 21 12:23:08 2008 +0300
@@ -375,35 +375,11 @@
 						     &error));
 }
 
-static void client_input(struct client *client)
+bool client_handle_input(struct client *client)
 {
 	char *line, *args;
 	int ret;
 
-	if (client->cmd != NULL) {
-		/* we're still processing a command. wait until it's
-		   finished. */
-		io_remove(&client->io);
-		client->waiting_input = TRUE;
-		return;
-	}
-
-	client->waiting_input = FALSE;
-	client->last_input = ioloop_time;
-	timeout_reset(client->to_idle);
-
-	switch (i_stream_read(client->input)) {
-	case -1:
-		/* disconnected */
-		client_destroy(client, NULL);
-		return;
-	case -2:
-		/* line too long, kill it */
-		client_send_line(client, "-ERR Input line too long.");
-		client_destroy(client, "Input line too long");
-		return;
-	}
-
 	o_stream_cork(client->output);
 	while (!client->output->closed &&
 	       (line = i_stream_next_line(client->input)) != NULL) {
@@ -430,8 +406,40 @@
 	}
 	o_stream_uncork(client->output);
 
-	if (client->output->closed)
+	if (client->output->closed) {
 		client_destroy(client, NULL);
+		return FALSE;
+	}
+	return TRUE;
+}
+
+static void client_input(struct client *client)
+{
+	if (client->cmd != NULL) {
+		/* we're still processing a command. wait until it's
+		   finished. */
+		io_remove(&client->io);
+		client->waiting_input = TRUE;
+		return;
+	}
+
+	client->waiting_input = FALSE;
+	client->last_input = ioloop_time;
+	timeout_reset(client->to_idle);
+
+	switch (i_stream_read(client->input)) {
+	case -1:
+		/* disconnected */
+		client_destroy(client, NULL);
+		return;
+	case -2:
+		/* line too long, kill it */
+		client_send_line(client, "-ERR Input line too long.");
+		client_destroy(client, "Input line too long");
+		return;
+	}
+
+	(void)client_handle_input(client);
 }
 
 static int client_output(struct client *client)
--- a/src/pop3/client.h	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/pop3/client.h	Sat Jun 21 12:23:08 2008 +0300
@@ -61,6 +61,8 @@
 	ATTR_FORMAT(2, 3);
 void client_send_storage_error(struct client *client);
 
+bool client_handle_input(struct client *client);
+
 void clients_init(void);
 void clients_deinit(void);
 
--- a/src/pop3/main.c	Sat Jun 21 12:22:01 2008 +0300
+++ b/src/pop3/main.c	Sat Jun 21 12:23:08 2008 +0300
@@ -7,6 +7,9 @@
 #include "lib-signals.h"
 #include "restrict-access.h"
 #include "fd-close-on-exec.h"
+#include "base64.h"
+#include "buffer.h"
+#include "istream.h"
 #include "process-title.h"
 #include "randgen.h"
 #include "module-dir.h"
@@ -180,9 +183,12 @@
 	restrict_access_by_env(!IS_STANDALONE());
 }
 
-static int main_init(void)
+static bool main_init(void)
 {
 	struct mail_namespace *ns;
+	struct client *client;
+	const char *str;
+	bool ret = TRUE;
 
 	lib_signals_init();
         lib_signals_set_handler(SIGINT, TRUE, sig_die, NULL);
@@ -232,7 +238,21 @@
 	namespace_pool = pool_alloconly_create("namespaces", 1024);
 	if (mail_namespaces_init(namespace_pool, getenv("USER"), &ns) < 0)
 		i_fatal("Namespace initialization failed");
-	return client_create(0, 1, ns) != NULL;
+	client = client_create(0, 1, ns);
+	if (client == NULL)
+		return FALSE;
+
+	str = getenv("CLIENT_INPUT");
+	if (str != NULL) T_BEGIN {
+		buffer_t *buf = t_base64_decode_str(str);
+		if (buf->used > 0) {
+			if (!i_stream_add_data(client->input, buf->data,
+					       buf->used))
+				i_panic("Couldn't add client input to stream");
+			ret = client_handle_input(client);
+		}
+	} T_END;
+	return ret;
 }
 
 static void main_deinit(void)