-
Stephan Bosch authoredStephan Bosch authored
managesieve-proxy.c 15.46 KiB
/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
*/
#include <string.h>
#include "login-common.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "str.h"
#include "str-sanitize.h"
#include "safe-memset.h"
#include "buffer.h"
#include "base64.h"
#include "dsasl-client.h"
#include "client.h"
#include "client-authenticate.h"
#include "managesieve-quote.h"
#include "managesieve-proxy.h"
#include "managesieve-parser.h"
typedef enum {
MANAGESIEVE_RESPONSE_NONE,
MANAGESIEVE_RESPONSE_OK,
MANAGESIEVE_RESPONSE_NO,
MANAGESIEVE_RESPONSE_BYE
} managesieve_response_t;
static const char *managesieve_proxy_state_names[MSIEVE_PROXY_STATE_COUNT] = {
"none", "tls-start", "tls-ready", "xclient", "auth"
};
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);
}
static void proxy_write_xclient
(struct managesieve_client *client, string_t *str)
{
str_printfa(str,
"XCLIENT ADDR=%s PORT=%u SESSION=%s TTL=%u\r\n",
net_ip2addr(&client->common.ip),
client->common.remote_port,
client_get_session_id(&client->common),
client->common.proxy_ttl - 1);
}
static void proxy_write_auth_data
(const unsigned char *data, unsigned int data_len,
string_t *str)
{
if (data_len == 0)
str_append(str, "\"\"");
else {
string_t *data_str = t_str_new(128);
base64_encode(data, data_len, data_str);
managesieve_quote_append_string(str, str_c(data_str), FALSE);
}
}
static int proxy_write_auth
(struct managesieve_client *client, string_t *str)
{
struct dsasl_client_settings sasl_set;
const unsigned char *output;
size_t len;
const char *mech_name, *error;
i_assert(client->common.proxy_ttl > 1);
if ( !client->proxy_sasl ) {
/* Prevent sending credentials to a server that has login disabled;
i.e., due to the lack of TLS */
client_log_err(&client->common, "proxy: "
"Server has disabled authentication (TLS required?)");
return -1;
}
if (client->common.proxy_mech == NULL)
client->common.proxy_mech = &dsasl_client_mech_plain;
i_assert(client->common.proxy_sasl_client == NULL);
i_zero(&sasl_set);
sasl_set.authid = client->common.proxy_master_user != NULL ?
client->common.proxy_master_user : client->common.proxy_user;
sasl_set.authzid = client->common.proxy_user;
sasl_set.password = client->common.proxy_password;
client->common.proxy_sasl_client =
dsasl_client_new(client->common.proxy_mech, &sasl_set);
mech_name = dsasl_client_mech_get_name(client->common.proxy_mech);
str_append(str, "AUTHENTICATE ");
managesieve_quote_append_string(str, mech_name, FALSE);
if (dsasl_client_output(client->common.proxy_sasl_client,
&output, &len, &error) < 0) {
client_log_err(&client->common, t_strdup_printf(
"proxy: SASL mechanism %s init failed: %s",
mech_name, error));
return -1;
}
if (len > 0) {
str_append_c(str, ' ');
proxy_write_auth_data(output, len, str);
}
str_append(str, "\r\n");
proxy_free_password(&client->common);
return 0;
}
static int proxy_input_auth_challenge
(struct managesieve_client *client, const char *line,
const char **challenge_r)
{
struct istream *input;
struct managesieve_parser *parser;
const struct managesieve_arg *args;
const char *challenge;
bool fatal = FALSE;
int ret;
i_assert(client->common.proxy_sasl_client != NULL);
*challenge_r = NULL;
/* Build an input stream for the managesieve parser
* FIXME: Ugly, see proxy_input_capability().
*/
line = t_strconcat(line, "\r\n", NULL);
input = i_stream_create_from_data(line, strlen(line));
parser = managesieve_parser_create(input, MAX_MANAGESIEVE_LINE);
managesieve_parser_reset(parser);
(void)i_stream_read(input);
ret = managesieve_parser_read_args(parser, 1, 0, &args);
if ( ret >= 0 ) {
if ( ret > 0 && managesieve_arg_get_string(&args[0], &challenge) ) {
*challenge_r = t_strdup(challenge);
} else {
client_log_err(&client->common, t_strdup_printf("proxy: "
"Server sent invalid SASL challenge line: %s",
str_sanitize(line,160)));
fatal = TRUE;
}
} else if ( ret == -2 ) {
/* Parser needs more data (not possible on mem stream) */
i_unreached();
} else {
const char *error_str = managesieve_parser_get_error(parser, &fatal);
error_str = (error_str != NULL ? error_str : "unknown (bug)" );
/* Do not accept faulty server */
client_log_err(&client->common, t_strdup_printf("proxy: "
"Protocol parse error(%d) int SASL challenge line: %s (line=`%s')",
ret, error_str, line));
fatal = TRUE;
}
/* Cleanup parser */
managesieve_parser_destroy(&parser);
i_stream_destroy(&input);
/* Time to exit if greeting was not accepted */
if ( fatal ) return -1;
return 0;
}
static int proxy_write_auth_response
(struct managesieve_client *client,
const char *challenge, string_t *str)
{
const unsigned char *data;
size_t data_len;
const char *error;
int ret;
if (base64_decode(challenge, strlen(challenge), NULL, str) < 0) {
client_log_err(&client->common,
"proxy: Server sent invalid base64 data in AUTHENTICATE response");
return -1;
}
ret = dsasl_client_input(client->common.proxy_sasl_client,
str_data(str), str_len(str), &error);
if (ret == 0) {
ret = dsasl_client_output(client->common.proxy_sasl_client,
&data, &data_len, &error);
}
if (ret < 0) {
client_log_err(&client->common, t_strdup_printf(
"proxy: Server sent invalid authentication data: %s",
error));
return -1;
}
i_assert(ret == 0);
str_truncate(str, 0);
proxy_write_auth_data(data, data_len, str);
str_append(str, "\r\n");
return 0;
}
static managesieve_response_t proxy_read_response
(const struct managesieve_arg *args)
{
const char *response;
if ( managesieve_arg_get_atom(&args[0], &response) ) {
if ( strcasecmp(response, "OK") == 0 ) {
/* Received OK response; greeting is finished */
return MANAGESIEVE_RESPONSE_OK;
} else if ( strcasecmp(response, "NO") == 0 ) {
/* Received OK response; greeting is finished */
return MANAGESIEVE_RESPONSE_NO;
} else if ( strcasecmp(response, "BYE") == 0 ) {
/* Received OK response; greeting is finished */
return MANAGESIEVE_RESPONSE_BYE;
}
}
return MANAGESIEVE_RESPONSE_NONE;
}
static int proxy_input_capability
(struct managesieve_client *client, const char *line,
managesieve_response_t *resp_r)
{
struct istream *input;
struct managesieve_parser *parser;
const struct managesieve_arg *args;
const char *capability;
int ret;
bool fatal = FALSE;
*resp_r = MANAGESIEVE_RESPONSE_NONE;
/* Build an input stream for the managesieve parser
* FIXME: It would be nice if the line-wise parsing could be
* substituded by something similar to the command line interpreter.
* However, the current login_proxy structure does not make streams
* known until inside proxy_input handler.
*/
line = t_strconcat(line, "\r\n", NULL);
input = i_stream_create_from_data(line, strlen(line));
parser = managesieve_parser_create(input, MAX_MANAGESIEVE_LINE);
managesieve_parser_reset(parser);
/* Parse input
* FIXME: Theoretically the OK response could include a
* response code which could be rejected by the parser.
*/
(void)i_stream_read(input);
ret = managesieve_parser_read_args(parser, 2, 0, &args);
if ( ret == 0 ) {
client_log_err(&client->common, t_strdup_printf("proxy: "
"Remote returned with invalid capability/greeting line: %s",
str_sanitize(line,160)));
fatal = TRUE;
} else if ( ret > 0 ) {
if ( args[0].type == MANAGESIEVE_ARG_ATOM ) {
*resp_r = proxy_read_response(args);
if ( *resp_r == MANAGESIEVE_RESPONSE_NONE ) {
client_log_err(&client->common, t_strdup_printf("proxy: "
"Remote sent invalid response: %s",
str_sanitize(line,160)));
fatal = TRUE;
}
} else if ( managesieve_arg_get_string(&args[0], &capability) ) {
if ( strcasecmp(capability, "SASL") == 0 ) {
const char *sasl_mechs;
/* Check whether the server supports the SASL mechanism
* we are going to use (currently only PLAIN supported).
*/
if ( ret == 2 && managesieve_arg_get_string(&args[1], &sasl_mechs) ) {
const char *const *mechs = t_strsplit(sasl_mechs, " ");
if ( *mechs != NULL ) {
/* At least one SASL mechanism is supported */
client->proxy_sasl = TRUE;
}
} else {
client_log_err(&client->common, "proxy: "
"Server returned erroneous SASL capability");
fatal = TRUE;
}
} else if ( strcasecmp(capability, "STARTTLS") == 0 ) {
client->proxy_starttls = TRUE;
} else if ( strcasecmp(capability, "XCLIENT") == 0 ) {
client->proxy_xclient = TRUE;
}
} else {
/* Do not accept faulty server */
client_log_err(&client->common, t_strdup_printf("proxy: "
"Remote returned with invalid capability/greeting line: %s",
str_sanitize(line,160)));
fatal = TRUE;
}
} else if ( ret == -2 ) {
/* Parser needs more data (not possible on mem stream) */
i_unreached();
} else {
const char *error_str = managesieve_parser_get_error(parser, &fatal);
error_str = (error_str != NULL ? error_str : "unknown (bug)" );
/* Do not accept faulty server */
client_log_err(&client->common, t_strdup_printf("proxy: "
"Protocol parse error(%d) in capability/greeting line: %s (line=`%s')",
ret, error_str, line));
fatal = TRUE;
}
/* Cleanup parser */
managesieve_parser_destroy(&parser);
i_stream_destroy(&input);
/* Time to exit if greeting was not accepted */
if ( fatal ) return -1;
/* Wait until greeting is received completely */
if ( *resp_r == MANAGESIEVE_RESPONSE_NONE ) return 1;
return 0;
}
int managesieve_proxy_parse_line(struct client *client, const char *line)
{
struct managesieve_client *msieve_client =
(struct managesieve_client *) client;
struct ostream *output;
enum login_proxy_ssl_flags ssl_flags;
managesieve_response_t response = MANAGESIEVE_RESPONSE_NONE;
string_t *command;
int ret = 0;
i_assert(!client->destroyed);
output = login_proxy_get_ostream(client->login_proxy);
switch ( msieve_client->proxy_state ) {
case MSIEVE_PROXY_STATE_NONE:
if ( (ret=proxy_input_capability
(msieve_client, line, &response)) < 0 ) {
client_proxy_failed(client, TRUE);
return -1;
}
if ( ret == 0 ) {
if ( response != MANAGESIEVE_RESPONSE_OK ) {
client_log_err(client, "proxy: "
"Remote sent unexpected NO/BYE instead of capability response");
client_proxy_failed(client, TRUE);
return -1;
}
command = t_str_new(128);
ssl_flags = login_proxy_get_ssl_flags(client->login_proxy);
if ((ssl_flags & PROXY_SSL_FLAG_STARTTLS) != 0) {
if ( !msieve_client->proxy_starttls ) {
client_log_err(client, "proxy: Remote doesn't support STARTTLS");
client_proxy_failed(client, TRUE);
return -1;
}
str_append(command, "STARTTLS\r\n");
msieve_client->proxy_state = MSIEVE_PROXY_STATE_TLS_START;
} else if (msieve_client->proxy_xclient) {
proxy_write_xclient(msieve_client, command);
msieve_client->proxy_state = MSIEVE_PROXY_STATE_XCLIENT;
} else {
if ( proxy_write_auth(msieve_client, command) < 0 ) {
client_proxy_failed(client, TRUE);
return -1;
}
msieve_client->proxy_state = MSIEVE_PROXY_STATE_AUTH;
}
o_stream_nsend(output, str_data(command), str_len(command));
}
return 0;
case MSIEVE_PROXY_STATE_TLS_START:
if ( strncasecmp(line, "OK", 2) == 0 &&
( strlen(line) == 2 || line[2] == ' ' ) ) {
/* STARTTLS successful, begin TLS negotiation. */
if ( login_proxy_starttls(client->login_proxy) < 0 ) {
client_proxy_failed(client, TRUE);
return -1;
}
msieve_client->proxy_sasl = FALSE;
msieve_client->proxy_xclient = FALSE;
msieve_client->proxy_state = MSIEVE_PROXY_STATE_TLS_READY;
return 1;
}
client_log_err(client, "proxy: Remote refused STARTTLS command");
client_proxy_failed(client, TRUE);
return -1;
case MSIEVE_PROXY_STATE_TLS_READY:
if ( (ret=proxy_input_capability(msieve_client, line, &response)) < 0 ) {
client_proxy_failed(client, TRUE);
return -1;
}
if ( ret == 0 ) {
if ( response != MANAGESIEVE_RESPONSE_OK ) {
/* STARTTLS failed */
client_log_err(client,
t_strdup_printf("proxy: Remote STARTTLS failed: %s",
str_sanitize(line, 160)));
client_proxy_failed(client, TRUE);
return -1;
}
command = t_str_new(128);
if ( msieve_client->proxy_xclient ) {
proxy_write_xclient(msieve_client, command);
msieve_client->proxy_state = MSIEVE_PROXY_STATE_XCLIENT;
} else {
if ( proxy_write_auth(msieve_client, command) < 0 ) {
client_proxy_failed(client, TRUE);
return -1;
}
msieve_client->proxy_state = MSIEVE_PROXY_STATE_AUTH;
}
o_stream_nsend(output, str_data(command), str_len(command));
}
return 0;
case MSIEVE_PROXY_STATE_XCLIENT:
if ( strncasecmp(line, "OK", 2) == 0 &&
( strlen(line) == 2 || line[2] == ' ' ) ) {
command = t_str_new(128);
if ( proxy_write_auth(msieve_client, command) < 0 ) {
client_proxy_failed(client, TRUE);
return -1;
}
o_stream_nsend(output, str_data(command), str_len(command));
msieve_client->proxy_state = MSIEVE_PROXY_STATE_AUTH;
return 0;
}
client_log_err(client, t_strdup_printf(
"proxy: Remote XCLIENT failed: %s",
str_sanitize(line, 160)));
client_proxy_failed(client, TRUE);
return -1;
case MSIEVE_PROXY_STATE_AUTH:
/* Challenge? */
if ( *line == '"' ) {
const char *challenge;
if ( proxy_input_auth_challenge
(msieve_client, line, &challenge) < 0 ) {
client_proxy_failed(client, TRUE);
return -1;
}
command = t_str_new(128);
if ( proxy_write_auth_response
(msieve_client, challenge, command) < 0 ) {
client_proxy_failed(client, TRUE);
return -1;
}
o_stream_nsend(output, str_data(command), str_len(command));
return 0;
}
/* Check login status */
if ( strncasecmp(line, "OK", 2) == 0 &&
(strlen(line) == 2 || line[2] == ' ') ) {
string_t *str = t_str_new(128);
/* Login successful */
/* FIXME: some SASL mechanisms cause a capability response to be sent */
/* Send this line to client. */
str_append(str, line );
str_append(str, "\r\n");
o_stream_nsend(client->output, str_data(str), str_len(str));
(void)client_skip_line(msieve_client);
client_proxy_finish_destroy_client(client);
return 1;
}
/* Authentication failed */
if ( client->set->auth_verbose ) {
const char *log_line = line;
if (strncasecmp(log_line, "NO ", 3) == 0)
log_line += 3;
client_proxy_log_failure(client, log_line);
}
/* FIXME: properly parse and handle response codes */
/* Login failed. Send our own failure reply so client can't
* figure out if user exists or not just by looking at the
* reply string.
*/
client_send_no(client, AUTH_FAILED_MSG);
client->proxy_auth_failed = TRUE;
client_proxy_failed(client, FALSE);
return -1;
default:
/* Not supposed to happen */
break;
}
i_unreached();
return -1;
}
void managesieve_proxy_reset(struct client *client)
{
struct managesieve_client *msieve_client =
(struct managesieve_client *) client;
msieve_client->proxy_starttls = FALSE;
msieve_client->proxy_sasl = FALSE;
msieve_client->proxy_xclient = FALSE;
msieve_client->proxy_state = MSIEVE_PROXY_STATE_NONE;
}
void managesieve_proxy_error(struct client *client, const char *text)
{
client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_NO, "TRYLATER", text);
}
const char *managesieve_proxy_get_state(struct client *client)
{
struct managesieve_client *msieve_client =
(struct managesieve_client *) client;
return managesieve_proxy_state_names[msieve_client->proxy_state];
}