view src/pop3-login/pop3-proxy.c @ 7216:56f8ac7a3a77 HEAD

If proxy fails to log in to the backend server, forward the error message instead of always replying with a generic "Authentication failed", which could hide temporary failure and "too many connections" errors. However if the backend isn't Dovecot, this could allow an attacker to find out what users exist on the system.
author Timo Sirainen <tss@iki.fi>
date Wed, 06 Feb 2008 19:02:25 +0200
parents 769181a20483
children 65fbb6226141
line wrap: on
line source

/* Copyright (c) 2004-2008 Dovecot authors, see the included COPYING file */

#include "common.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "base64.h"
#include "safe-memset.h"
#include "str.h"
#include "str-sanitize.h"
#include "client.h"
#include "pop3-proxy.h"

static void proxy_input(struct istream *input, struct ostream *output,
			struct pop3_client *client)
{
	string_t *str;
	const char *line, *msg;

	if (input == NULL) {
		if (client->io != NULL) {
			/* remote authentication failed, we're just
			   freeing the proxy */
			return;
		}

		if (client->destroyed) {
			/* we came here from client_destroy() */
			return;
		}

		/* failed for some reason, probably server disconnected */
		client_send_line(client,
				 "-ERR [IN-USE] Temporary login failure.");
		client_destroy(client, NULL);
		return;
	}

	i_assert(!client->destroyed);

	switch (i_stream_read(input)) {
	case -2:
		/* buffer full */
		client_syslog(&client->common,
			      "proxy: Remote input buffer full");
		client_destroy_internal_failure(client);
		return;
	case -1:
		/* disconnected */
		client_destroy(client, "Proxy: Remote disconnected");
		return;
	}

	line = i_stream_next_line(input);
	if (line == NULL)
		return;

	switch (client->proxy_state) {
	case 0:
		/* this is a banner */
		if (strncmp(line, "+OK", 3) != 0) {
			client_syslog(&client->common, t_strdup_printf(
				"proxy: Remote returned invalid banner: %s",
				str_sanitize(line, 160)));
			client_destroy_internal_failure(client);
			return;
		}

		/* send USER command */
		str = t_str_new(128);
		str_append(str, "USER ");
		str_append(str, client->proxy_user);
		str_append(str, "\r\n");
		(void)o_stream_send(output, str_data(str), str_len(str));

		client->proxy_state++;
		return;
	case 1:
		if (strncmp(line, "+OK", 3) != 0)
			break;

		/* USER successful, send PASS */
		str = t_str_new(128);
		str_append(str, "PASS ");
		str_append(str, client->proxy_password);
		str_append(str, "\r\n");
		(void)o_stream_send(output, str_data(str),
				    str_len(str));

		safe_memset(client->proxy_password, 0,
			    strlen(client->proxy_password));
		i_free(client->proxy_password);
		client->proxy_password = NULL;

		client->proxy_state++;
		return;
	case 2:
		if (strncmp(line, "+OK", 3) != 0)
			break;

		/* Login successful. Send this line to client. */
		(void)o_stream_send_str(client->output, line);
		(void)o_stream_send(client->output, "\r\n", 2);

		msg = t_strdup_printf("proxy(%s): started proxying to %s:%u",
				      client->common.virtual_user,
				      login_proxy_get_host(client->proxy),
				      login_proxy_get_port(client->proxy));

		login_proxy_detach(client->proxy, client->input,
				   client->output);

		client->proxy = NULL;
		client->input = NULL;
		client->output = NULL;
		client->common.fd = -1;
		client_destroy(client, msg);
		return;
	}

	/* Login failed. Pass through the error message to client
	   (see imap-proxy code for potential problems with this) */
	if (strncmp(line, "-ERR ", 5) != 0)
		client_send_line(client, "-ERR "AUTH_FAILED_MSG);
	else
		client_send_line(client, line);

	/* allow client input again */
	i_assert(client->io == NULL);
	client->io = io_add(client->common.fd, IO_READ,
			    client_input, client);

	login_proxy_free(client->proxy);
	client->proxy = NULL;

	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;
}

int pop3_proxy_new(struct pop3_client *client, const char *host,
		   unsigned int port, const char *user, const char *password)
{
	i_assert(user != NULL);
	i_assert(!client->destroyed);

	if (password == NULL) {
		client_syslog(&client->common, "proxy: password not given");
		return -1;
	}

	i_assert(client->refcount > 1);
	connection_queue_add(1);

	if (client->destroyed) {
		/* connection_queue_add() decided that we were the oldest
		   connection and killed us. */
		return -1;
	}

	client->proxy = login_proxy_new(&client->common, host, port,
					proxy_input, client);
	if (client->proxy == NULL)
		return -1;

	client->proxy_state = 0;
	client->proxy_user = i_strdup(user);
	client->proxy_password = i_strdup(password);

	/* disable input until authentication is finished */
	if (client->io != NULL)
		io_remove(&client->io);
	return 0;
}