changeset 9754:b9ad5b841f7e HEAD

*-login: Use a common client_send_line() API.
author Timo Sirainen <tss@iki.fi>
date Sun, 09 Aug 2009 17:55:43 -0400
parents fc025d93b274
children c2037505a66b
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/login-common/client-common.h src/pop3-login/client-authenticate.c src/pop3-login/client.c src/pop3-login/client.h src/pop3-login/pop3-proxy.c
diffstat 10 files changed, 342 insertions(+), 167 deletions(-) [+]
line wrap: on
line diff
--- a/src/imap-login/client-authenticate.c	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/imap-login/client-authenticate.c	Sun Aug 09 17:55:43 2009 -0400
@@ -189,6 +189,8 @@
 		   .. [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);
@@ -203,7 +205,8 @@
 			str_append(reply, "Logged in, but you should use "
 				   "this server instead.");
 		}
-		client_send_tagline(client, str_c(reply));
+		str_append(reply, "\r\n");
+		client_send_raw(client, str_c(reply));
 		if (!nologin) {
 			client_destroy_success(client, "Login with referral");
 			return TRUE;
@@ -211,18 +214,23 @@
 	} else if (nologin) {
 		/* Authentication went ok, but for some reason user isn't
 		   allowed to log in. Shouldn't probably happen. */
-		reply = t_str_new(128);
-		if (reason != NULL)
-			str_printfa(reply, "NO [ALERT] %s", reason);
-		else if (temp) {
-			str_append(reply, "NO ["IMAP_RESP_CODE_UNAVAILABLE"] "
-				   AUTH_TEMP_FAILED_MSG);
+		if (reason != NULL) {
+			client_send_line(&client->common,
+					 CLIENT_CMD_REPLY_AUTH_FAIL_REASON,
+					 reason);
+		} else if (temp) {
+			client_send_line(&client->common,
+					 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+					 AUTH_TEMP_FAILED_MSG);
 		} else if (authz_failure) {
-			str_append(reply, "NO "IMAP_AUTHZ_FAILED_MSG);
+			client_send_line(&client->common,
+					 CLIENT_CMD_REPLY_AUTHZ_FAILED,
+					 "Authorization failed");
 		} else {
-			str_append(reply, "NO "IMAP_AUTH_FAILED_MSG);
+			client_send_line(&client->common,
+					 CLIENT_CMD_REPLY_AUTH_FAILED,
+					 AUTH_FAILED_MSG);
 		}
-		client_send_tagline(client, str_c(reply));
 	} else {
 		/* normal login/failure */
 		return FALSE;
@@ -240,7 +248,6 @@
 {
 	struct imap_client *client = (struct imap_client *)_client;
 	struct const_iovec iov[3];
-	const char *msg;
 	size_t data_len;
 	bool nodelay;
 
@@ -267,13 +274,18 @@
 				break;
 		}
 
-		if (reply == SASL_SERVER_REPLY_AUTH_ABORTED)
-			msg = "BAD Authentication aborted by client.";
-		else if (data == NULL)
-			msg = "NO "IMAP_AUTH_FAILED_MSG;
-		else
-			msg = t_strconcat("NO [ALERT] ", data, NULL);
-		client_send_tagline(client, msg);
+		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);
@@ -282,8 +294,8 @@
 		if (data == NULL)
 			client_destroy_internal_failure(client);
 		else {
-			client_send_tagline(client,
-					    t_strconcat("NO ", data, NULL));
+			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);
@@ -365,8 +377,8 @@
 				      "SSL required for authentication");
 		}
 		client->common.auth_attempts++;
-		client_send_tagline(client,
-			"NO ["IMAP_RESP_CODE_PRIVACYREQUIRED"] "
+		client_send_line(&client->common,
+			CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
 			"Authentication not allowed until SSL/TLS is enabled.");
 		return 1;
 	}
@@ -401,12 +413,13 @@
 		}
 		client->common.auth_tried_disabled_plaintext = TRUE;
 		client->common.auth_attempts++;
-		client_send_line(client,
+		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.");
-		client_send_tagline(client, "NO ["IMAP_RESP_CODE_CLIENTBUG"] "
-				    AUTH_PLAINTEXT_DISABLED_MSG);
+			"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);
 		return 1;
 	}
 
--- a/src/imap-login/client-authenticate.h	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/imap-login/client-authenticate.h	Sun Aug 09 17:55:43 2009 -0400
@@ -3,11 +3,6 @@
 
 struct imap_arg;
 
-#define IMAP_AUTH_FAILED_MSG \
-	"["IMAP_RESP_CODE_AUTHFAILED"] "AUTH_FAILED_MSG
-#define IMAP_AUTHZ_FAILED_MSG \
-	"["IMAP_RESP_CODE_AUTHZFAILED"] Authorization failed"
-
 const char *client_authenticate_get_capabilities(struct imap_client *client);
 
 int cmd_login(struct imap_client *client, const struct imap_arg *args);
--- a/src/imap-login/client.c	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/imap-login/client.c	Sun Aug 09 17:55:43 2009 -0400
@@ -11,6 +11,7 @@
 #include "strescape.h"
 #include "imap-parser.h"
 #include "imap-id.h"
+#include "imap-resp-code.h"
 #include "master-service.h"
 #include "master-auth.h"
 #include "client.h"
@@ -45,9 +46,9 @@
 #endif
 
 #define AUTH_SERVER_WAITING_MSG \
-	"* OK Waiting for authentication process to respond.."
+	"Waiting for authentication process to respond.."
 #define AUTH_MASTER_WAITING_MSG \
-	"* OK Waiting for authentication master process to respond.."
+	"Waiting for authentication master process to respond.."
 
 const char *login_protocol = "imap";
 const char *login_process_name = "imap-login";
@@ -116,9 +117,10 @@
 	   CAPABILITY commands. */
 	if (!client->starttls)
 		client->client_ignores_capability_resp_code = TRUE;
-	client_send_line(client, t_strconcat(
-		"* CAPABILITY ", get_capability(client), NULL));
-	client_send_tagline(client, "OK Capability completed.");
+	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;
 }
 
@@ -133,7 +135,8 @@
 	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, "* BYE TLS initialization failed.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
+				 "TLS initialization failed.");
 		client_destroy(client,
 			       "Disconnected: TLS initialization failed.");
 		return;
@@ -176,12 +179,14 @@
 static int cmd_starttls(struct imap_client *client)
 {
 	if (client->common.tls) {
-		client_send_tagline(client, "BAD TLS is already active.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+				 "TLS is already active.");
 		return 1;
 	}
 
 	if (!ssl_initialized) {
-		client_send_tagline(client, "BAD TLS support isn't enabled.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+				 "TLS support isn't enabled.");
 		return 1;
 	}
 
@@ -190,7 +195,8 @@
 	if (client->io != NULL)
 		io_remove(&client->io);
 
-	client_send_tagline(client, "OK Begin TLS negotiation now.");
+	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
+			 "Begin TLS negotiation now.");
 
 	/* uncork the old fd */
 	o_stream_uncork(client->output);
@@ -249,31 +255,34 @@
 	}
 
 	env = getenv("IMAP_ID_SEND");
-	client_send_line(client, t_strdup_printf("* ID %s",
-						 imap_id_reply_generate(env)));
-	client_send_tagline(client, "OK ID completed.");
+	client_send_raw(client,
+		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;
 }
 
 static int cmd_noop(struct imap_client *client)
 {
-	client_send_tagline(client, "OK NOOP completed.");
+	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
+			 "NOOP completed.");
 	return 1;
 }
 
 static int cmd_logout(struct imap_client *client)
 {
-	client_send_line(client, "* BYE Logging out");
-	client_send_tagline(client, "OK Logout completed.");
+	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");
 	return 1;
 }
 
 static int cmd_enable(struct imap_client *client)
 {
-	client_send_line(client, "* ENABLED");
-	client_send_tagline(client,
-			    "OK ENABLE ignored in non-authenticated state.");
+	client_send_raw(client, "* ENABLED\r\n");
+	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
+			 "ENABLE ignored in non-authenticated state.");
 	return 1;
 }
 
@@ -345,14 +354,14 @@
 		/* error */
 		msg = imap_parser_get_error(client->parser, &fatal);
 		if (fatal) {
-			client_send_line(client, t_strconcat("* BYE ",
-							     msg, NULL));
+			client_send_line(&client->common,
+					 CLIENT_CMD_REPLY_BYE, msg);
 			client_destroy(client,
 				t_strconcat("Disconnected: ", msg, NULL));
 			return FALSE;
 		}
 
-		client_send_tagline(client, t_strconcat("BAD ", msg, NULL));
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD, msg);
 		client->cmd_finished = TRUE;
 		client->skip_line = TRUE;
 		return TRUE;
@@ -374,14 +383,14 @@
 		if (*client->cmd_tag == '\0')
 			client->cmd_tag = "*";
 		if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) {
-			client_send_line(client,
-				"* BYE Too many invalid IMAP commands.");
+			client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
+				"Too many invalid IMAP commands.");
 			client_destroy(client,
 				"Disconnected: Too many invalid commands");
 			return FALSE;
 		}  
-		client_send_tagline(client,
-			"BAD Error in IMAP command received by server.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+			"Error in IMAP command received by server.");
 	}
 
 	return ret != 0;
@@ -392,7 +401,8 @@
 	switch (i_stream_read(client->common.input)) {
 	case -2:
 		/* buffer full */
-		client_send_line(client, "* BYE Input buffer full, aborting");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
+				 "Input buffer full, aborting");
 		client_destroy(client, "Disconnected: Input buffer full");
 		return FALSE;
 	case -1:
@@ -419,7 +429,8 @@
 	if (!auth_client_is_connected(auth_client)) {
 		/* we're not yet connected to auth process -
 		   don't allow any commands */
-		client_send_line(client, AUTH_SERVER_WAITING_MSG);
+		client_send_line(&client->common, CLIENT_CMD_REPLY_STATUS,
+				 AUTH_SERVER_WAITING_MSG);
 		if (client->to_auth_waiting != NULL)
 			timeout_remove(&client->to_auth_waiting);
 
@@ -480,20 +491,23 @@
 	str_append(greet, "* OK ");
 	str_printfa(greet, "[CAPABILITY %s] ", get_capability(client));
 	str_append(greet, client->common.set->login_greeting);
+	str_append(greet, "\r\n");
 
-	client_send_line(client, str_c(greet));
+	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, "* BYE Disconnected for inactivity.");
+	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, client->common.master_tag == 0 ?
+	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);
 }
@@ -631,7 +645,8 @@
 
 void client_destroy_internal_failure(struct imap_client *client)
 {
-	client_send_line(client, "* BYE Internal login failure. "
+	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");
 }
@@ -667,29 +682,84 @@
 	return FALSE;
 }
 
-void client_send_line(struct imap_client *client, const char *line)
+static void
+client_send_raw_data(struct imap_client *client, const void *data, size_t size)
 {
-	struct const_iovec iov[2];
 	ssize_t ret;
 
-	iov[0].iov_base = line;
-	iov[0].iov_len = strlen(line);
-	iov[1].iov_base = "\r\n";
-	iov[1].iov_len = 2;
-
-	ret = o_stream_sendv(client->output, iov, 2);
-	if (ret < 0 || (size_t)ret != iov[0].iov_len + iov[1].iov_len) {
-		/* 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.. */
+	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_tagline(struct imap_client *client, const char *line)
+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,
+		      const char *text)
 {
-	client_send_line(client, t_strconcat(client->cmd_tag, " ", line, NULL));
+	struct imap_client *imap_client = (struct imap_client *)client;
+	const char *resp_code = NULL;
+	const char *prefix = "NO";
+	bool tagged = TRUE;
+
+	switch (reply) {
+	case CLIENT_CMD_REPLY_OK:
+		prefix = "OK";
+		break;
+	case CLIENT_CMD_REPLY_AUTH_FAILED:
+		resp_code = IMAP_RESP_CODE_AUTHFAILED;
+		break;
+	case CLIENT_CMD_REPLY_AUTHZ_FAILED:
+		resp_code = IMAP_RESP_CODE_AUTHZFAILED;
+		break;
+	case CLIENT_CMD_REPLY_AUTH_FAIL_TEMP:
+		resp_code = IMAP_RESP_CODE_UNAVAILABLE;
+		break;
+	case CLIENT_CMD_REPLY_AUTH_FAIL_REASON:
+		resp_code = "ALERT";
+		break;
+	case CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL:
+		resp_code = IMAP_RESP_CODE_PRIVACYREQUIRED;
+		break;
+	case CLIENT_CMD_REPLY_BAD:
+		prefix = "BAD";
+		break;
+	case CLIENT_CMD_REPLY_BYE:
+		prefix = "BYE";
+		tagged = FALSE;
+		break;
+	case CLIENT_CMD_REPLY_STATUS:
+		prefix = "OK";
+		tagged = FALSE;
+		break;
+	}
+
+	T_BEGIN {
+		string_t *line = t_str_new(256);
+
+		if (tagged)
+			str_append(line, imap_client->cmd_tag);
+		else
+			str_append_c(line, '*');
+		str_append_c(line, ' ');
+		str_append(line, prefix);
+		str_append_c(line, ' ');
+		if (resp_code != NULL)
+			str_printfa(line, "[%s] ", resp_code);
+		str_append(line, text);
+		str_append(line, "\r\n");
+
+		client_send_raw_data(imap_client, str_data(line),
+				     str_len(line));
+	} T_END;
 }
 
 void clients_notify_auth_connected(void)
--- a/src/imap-login/client.h	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/imap-login/client.h	Sun Aug 09 17:55:43 2009 -0400
@@ -45,13 +45,11 @@
 void client_destroy_success(struct imap_client *client, const char *reason);
 void client_destroy_internal_failure(struct imap_client *client);
 
-void client_send_line(struct imap_client *client, const char *line);
-void client_send_tagline(struct imap_client *client, const char *line);
-
 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);
 
--- a/src/imap-login/imap-proxy.c	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/imap-login/imap-proxy.c	Sun Aug 09 17:55:43 2009 -0400
@@ -17,9 +17,6 @@
 
 #include <stdlib.h>
 
-#define PROXY_FAILURE_MSG \
-	"NO ["IMAP_RESP_CODE_UNAVAILABLE"] "AUTH_TEMP_FAILED_MSG
-
 static void proxy_write_id(struct imap_client *client, string_t *str)
 {
 	str_printfa(str, "I ID ("
@@ -44,8 +41,11 @@
 
 static void proxy_failed(struct imap_client *client, bool send_tagline)
 {
-	if (send_tagline)
-		client_send_tagline(client, PROXY_FAILURE_MSG);
+	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);
@@ -271,9 +271,13 @@
 			   the remote is sending a different error message
 			   an attacker can't find out what users exist in
 			   the system. */
-			line = "NO "IMAP_AUTH_FAILED_MSG;
+			client_send_line(&client->common,
+					 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));
 		} else {
 			/* there was no [resp-code], so remote isn't Dovecot
 			   v1.2+. we could either forward the line as-is and
@@ -282,9 +286,10 @@
 			   failures. since other errors are pretty rare,
 			   it's safer to just hide them. they're still
 			   available in logs though. */
-			line = "NO "IMAP_AUTH_FAILED_MSG;
+			client_send_line(&client->common,
+					 CLIENT_CMD_REPLY_AUTH_FAILED,
+					 AUTH_FAILED_MSG);
 		}
-		client_send_tagline(client, line);
 
 		proxy_failed(client, FALSE);
 		return -1;
@@ -298,7 +303,7 @@
 		return 0;
 	} else if (strncmp(line, "* ", 2) == 0) {
 		/* untagged reply. just foward it. */
-		client_send_line(client, line);
+		client_send_raw(client, t_strconcat(line, "\r\n", NULL));
 		return 0;
 	} else {
 		/* tagged reply, shouldn't happen. */
@@ -358,7 +363,9 @@
 
 	if (password == NULL) {
 		client_syslog_err(&client->common, "proxy: password not given");
-		client_send_tagline(client, PROXY_FAILURE_MSG);
+		client_send_line(&client->common,
+				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+				 AUTH_TEMP_FAILED_MSG);
 		return -1;
 	}
 
@@ -371,14 +378,18 @@
 	}
 	if (login_proxy_is_ourself(&client->common, host, port, user)) {
 		client_syslog_err(&client->common, "Proxying loops to itself");
-		client_send_tagline(client, PROXY_FAILURE_MSG);
+		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_tagline(client, PROXY_FAILURE_MSG);
+		client_send_line(&client->common,
+				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+				 AUTH_TEMP_FAILED_MSG);
 		return -1;
 	}
 
--- a/src/login-common/client-common.h	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/login-common/client-common.h	Sun Aug 09 17:55:43 2009 -0400
@@ -12,6 +12,18 @@
 */
 #define LOGIN_MAX_INBUF_SIZE 4096
 
+enum client_cmd_reply {
+	CLIENT_CMD_REPLY_OK,
+	CLIENT_CMD_REPLY_AUTH_FAILED,
+	CLIENT_CMD_REPLY_AUTHZ_FAILED,
+	CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+	CLIENT_CMD_REPLY_AUTH_FAIL_REASON,
+	CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
+	CLIENT_CMD_REPLY_BAD,
+	CLIENT_CMD_REPLY_BYE,
+	CLIENT_CMD_REPLY_STATUS
+};
+
 struct client {
 	struct client *prev, *next;
 	pool_t pool;
@@ -57,6 +69,9 @@
 void client_unlink(struct client *client);
 unsigned int clients_get_count(void) ATTR_PURE;
 
+void client_send_line(struct client *client, enum client_cmd_reply reply,
+		      const char *text);
+
 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);
--- a/src/pop3-login/client-authenticate.c	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/pop3-login/client-authenticate.c	Sun Aug 09 17:55:43 2009 -0400
@@ -46,9 +46,9 @@
 		str_append_c(str, ' ');
 		str_append(str, mech[i].name);
 	}
-	str_append(str, "\r\n.");
+	str_append(str, "\r\n.\r\n");
 
-	client_send_line(client, str_c(str));
+	client_send_raw(client, str_c(str));
 	return TRUE;
 }
 
@@ -121,7 +121,6 @@
 	const char *master_user = NULL;
 	const char *key, *value, *p;
 	enum login_proxy_ssl_flags ssl_flags = 0;
-	string_t *reply;
 	unsigned int port = 110;
 	bool proxy = FALSE, temp = FALSE, nologin = !success;
 
@@ -181,16 +180,17 @@
 	if (!nologin)
 		return FALSE;
 
-	reply = t_str_new(128);
-	str_append(reply, "-ERR ");
-	if (reason != NULL)
-		str_append(reply, reason);
-	else if (temp)
-		str_append(reply, "[IN-USE] "AUTH_TEMP_FAILED_MSG);
-	else
-		str_append(reply, AUTH_FAILED_MSG);
-
-	client_send_line(client, str_c(reply));
+	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,
+				 AUTH_TEMP_FAILED_MSG);
+	} else {
+		client_send_line(&client->common, CLIENT_CMD_REPLY_AUTH_FAILED,
+				 AUTH_FAILED_MSG);
+	}
 
 	if (!client->destroyed)
 		client_auth_failed(client, *nodelay_r);
@@ -202,7 +202,6 @@
 {
 	struct pop3_client *client = (struct pop3_client *)_client;
 	struct const_iovec iov[3];
-	const char *msg;
 	size_t data_len;
 	bool nodelay;
 
@@ -226,13 +225,18 @@
 				break;
 		}
 
-		if (reply == SASL_SERVER_REPLY_AUTH_ABORTED)
-			msg = "-ERR Authentication aborted by client.";
-		else if (data == NULL)
-			msg = "-ERR "AUTH_FAILED_MSG;
-		else
-			msg = t_strconcat("-ERR ", data, NULL);
-		client_send_line(client, msg);
+		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);
@@ -241,8 +245,10 @@
 		if (data == NULL)
 			client_destroy_internal_failure(client);
 		else {
-			client_send_line(client,
-				t_strconcat("-ERR [IN-USE] ", data, NULL));
+			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;
@@ -281,8 +287,9 @@
 				      "SSL required for authentication");
 		}
 		client->common.auth_attempts++;
-		client_send_line(client, "-ERR Authentication not allowed "
-				 "until SSL/TLS is enabled.");
+		client_send_line(&client->common,
+			CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL,
+			"Authentication not allowed until SSL/TLS is enabled.");
 		return TRUE;
 	}
 
@@ -290,16 +297,18 @@
 		/* Old-style SASL discovery, used by MS Outlook */
 		unsigned int i, count;
 
-		client_send_line(client, "+OK");
+		client_send_raw(client, "+OK\r\n");
 		mech = auth_client_get_available_mechs(auth_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_line(client, mech[i].name);
+			     (mech[i].flags & MECH_SEC_PLAINTEXT) == 0)) {
+				client_send_raw(client, mech[i].name);
+				client_send_raw(client, "\r\n");
+			}
 		}
- 		client_send_line(client, ".");
+ 		client_send_raw(client, ".\r\n");
  		return TRUE;
 	}
 
@@ -335,7 +344,8 @@
 		client_syslog(&client->common, "Login failed: "
 			      "Plaintext authentication disabled");
 	}
-	client_send_line(client, "-ERR "AUTH_PLAINTEXT_DISABLED_MSG);
+	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;
@@ -349,7 +359,7 @@
 	i_free(client->last_user);
 	client->last_user = i_strdup(args);
 
-	client_send_line(client, "+OK");
+	client_send_raw(client, "+OK\r\n");
 	return TRUE;
 }
 
@@ -363,7 +373,8 @@
 		if (!check_plaintext_auth(client))
 			return TRUE;
 
-		client_send_line(client, "-ERR No username given.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+				 "No username given.");
 		return TRUE;
 	}
 
@@ -405,7 +416,8 @@
 			client_syslog(&client->common,
 				      "APOP failed: APOP not enabled");
 		}
-	        client_send_line(client, "-ERR APOP not enabled.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+				 "APOP not enabled.");
 		return TRUE;
 	}
 
@@ -416,7 +428,8 @@
 			client_syslog(&client->common,
 				      "APOP failed: Invalid parameters");
 		}
-	        client_send_line(client, "-ERR Invalid parameters.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+				 "Invalid parameters.");
 		return TRUE;
 	}
 
@@ -432,8 +445,8 @@
 			client_syslog(&client->common, "APOP failed: "
 				      "Invalid characters in MD5 response");
 		}
-		client_send_line(client,
-				 "-ERR Invalid characters in MD5 response.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+				 "Invalid characters in MD5 response.");
 		return TRUE;
 	}
 
--- a/src/pop3-login/client.c	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/pop3-login/client.c	Sun Aug 09 17:55:43 2009 -0400
@@ -9,6 +9,7 @@
 #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"
@@ -72,7 +73,8 @@
 	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, "-ERR TLS initialization failed.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
+				 "TLS initialization failed.");
 		client_destroy(client,
 			       "Disconnected: TLS initialization failed.");
 		return;
@@ -111,12 +113,14 @@
 static bool cmd_stls(struct pop3_client *client)
 {
 	if (client->common.tls) {
-		client_send_line(client, "-ERR TLS is already active.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+				 "TLS is already active.");
 		return TRUE;
 	}
 
 	if (!ssl_initialized) {
-		client_send_line(client, "-ERR TLS support isn't enabled.");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+				 "TLS support isn't enabled.");
 		return TRUE;
 	}
 
@@ -125,7 +129,8 @@
 	if (client->io != NULL)
 		io_remove(&client->io);
 
-	client_send_line(client, "+OK Begin TLS negotiation now.");
+	client_send_line(&client->common, CLIENT_CMD_REPLY_OK,
+			 "Begin TLS negotiation now.");
 
 	/* uncork the old fd */
 	o_stream_uncork(client->output);
@@ -143,7 +148,7 @@
 
 static bool cmd_quit(struct pop3_client *client)
 {
-	client_send_line(client, "+OK Logging out");
+	client_send_line(&client->common, CLIENT_CMD_REPLY_OK, "Logging out");
 	client_destroy(client, "Aborted login");
 	return TRUE;
 }
@@ -167,7 +172,8 @@
 	if (strcmp(cmd, "QUIT") == 0)
 		return cmd_quit(client);
 
-	client_send_line(client, "-ERR Unknown command.");
+	client_send_line(&client->common, CLIENT_CMD_REPLY_BAD,
+			 "Unknown command.");
 	return FALSE;
 }
 
@@ -176,7 +182,8 @@
 	switch (i_stream_read(client->common.input)) {
 	case -2:
 		/* buffer full */
-		client_send_line(client, "-ERR Input line too long, aborting");
+		client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
+				 "Input buffer full, aborting");
 		client_destroy(client, "Disconnected: Input buffer full");
 		return FALSE;
 	case -1:
@@ -217,7 +224,8 @@
 					   args != NULL ? args : ""))
 			client->bad_counter = 0;
 		else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) {
-			client_send_line(client, "-ERR Too many bad commands.");
+			client_send_line(&client->common, CLIENT_CMD_REPLY_BYE,
+				"Too many invalid IMAP commands.");
 			client_destroy(client,
 				       "Disconnected: Too many bad commands");
 		}
@@ -292,11 +300,14 @@
 	client->io = io_add(client->common.fd, IO_READ, client_input, client);
 
 	client->apop_challenge = get_apop_challenge(client);
-	client_send_line(client, t_strconcat("+OK ",
-					     client->common.set->login_greeting,
-					     client->apop_challenge != NULL ?
-					     " " : NULL,
-					     client->apop_challenge, NULL));
+	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)
@@ -423,7 +434,8 @@
 
 void client_destroy_internal_failure(struct pop3_client *client)
 {
-	client_send_line(client, "-ERR [IN-USE] Internal login failure. "
+	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");
 }
@@ -459,26 +471,65 @@
 	return FALSE;
 }
 
-void client_send_line(struct pop3_client *client, const char *line)
+static void
+client_send_raw_data(struct pop3_client *client, const void *data, size_t size)
 {
-	struct const_iovec iov[2];
 	ssize_t ret;
 
-	iov[0].iov_base = line;
-	iov[0].iov_len = strlen(line);
-	iov[1].iov_base = "\r\n";
-	iov[1].iov_len = 2;
-
-	ret = o_stream_sendv(client->output, iov, 2);
-	if (ret < 0 || (size_t)ret != iov[0].iov_len + iov[1].iov_len) {
-		/* 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.. */
+	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,
+		      const char *text)
+{
+	struct pop3_client *pop3_client = (struct pop3_client *)client;
+	const char *prefix = "-ERR";
+
+	switch (reply) {
+	case CLIENT_CMD_REPLY_OK:
+		prefix = "+OK";
+		break;
+	case CLIENT_CMD_REPLY_AUTH_FAIL_TEMP:
+		prefix = "-ERR [IN-USE]";
+		break;
+	case CLIENT_CMD_REPLY_AUTH_FAILED:
+	case CLIENT_CMD_REPLY_AUTHZ_FAILED:
+	case CLIENT_CMD_REPLY_AUTH_FAIL_REASON:
+	case CLIENT_CMD_REPLY_AUTH_FAIL_NOSSL:
+	case CLIENT_CMD_REPLY_BAD:
+	case CLIENT_CMD_REPLY_BYE:
+		break;
+	case CLIENT_CMD_REPLY_STATUS:
+		/* can't send status notifications */
+		return;
+	}
+
+	T_BEGIN {
+		string_t *line = t_str_new(256);
+
+		str_append(line, prefix);
+		str_append_c(line, ' ');
+		str_append(line, text);
+		str_append(line, "\r\n");
+
+		client_send_raw_data(pop3_client, str_data(line),
+				     str_len(line));
+	} T_END;
+}
+
+
 void clients_notify_auth_connected(void)
 {
 	struct client *client;
--- a/src/pop3-login/client.h	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/pop3-login/client.h	Sun Aug 09 17:55:43 2009 -0400
@@ -46,11 +46,11 @@
 void client_destroy_success(struct pop3_client *client, const char *reason);
 void client_destroy_internal_failure(struct pop3_client *client);
 
-void client_send_line(struct pop3_client *client, const char *line);
-
 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);
 
--- a/src/pop3-login/pop3-proxy.c	Sun Aug 09 16:20:31 2009 -0400
+++ b/src/pop3-login/pop3-proxy.c	Sun Aug 09 17:55:43 2009 -0400
@@ -11,8 +11,6 @@
 #include "client.h"
 #include "pop3-proxy.h"
 
-#define PROXY_FAILURE_MSG "-ERR [IN-USE] "AUTH_TEMP_FAILED_MSG
-
 static void proxy_free_password(struct pop3_client *client)
 {
 	if (client->proxy_password == NULL)
@@ -24,8 +22,11 @@
 
 static void proxy_failed(struct pop3_client *client, bool send_line)
 {
-	if (send_line)
-		client_send_line(client, PROXY_FAILURE_MSG);
+	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);
@@ -184,9 +185,11 @@
 	   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, "-ERR "AUTH_FAILED_MSG);
-	else
-		client_send_line(client, line);
+		client_send_line(&client->common, CLIENT_CMD_REPLY_AUTH_FAILED,
+				 AUTH_FAILED_MSG);
+	else {
+		client_send_raw(client, t_strconcat(line, "\r\n", NULL));
+	}
 
 	if (client->common.set->verbose_auth) {
 		str = t_str_new(128);
@@ -267,7 +270,9 @@
 
 	if (password == NULL) {
 		client_syslog_err(&client->common, "proxy: password not given");
-		client_send_line(client, PROXY_FAILURE_MSG);
+		client_send_line(&client->common,
+				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+				 AUTH_TEMP_FAILED_MSG);
 		return -1;
 	}
 
@@ -280,14 +285,18 @@
 	}
 	if (login_proxy_is_ourself(&client->common, host, port, user)) {
 		client_syslog_err(&client->common, "Proxying loops to itself");
-		client_send_line(client, PROXY_FAILURE_MSG);
+		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, PROXY_FAILURE_MSG);
+		client_send_line(&client->common,
+				 CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
+				 AUTH_TEMP_FAILED_MSG);
 		return -1;
 	}