diff options
Diffstat (limited to 'pigeonhole/src/managesieve-login/client.c')
-rw-r--r-- | pigeonhole/src/managesieve-login/client.c | 574 |
1 files changed, 574 insertions, 0 deletions
diff --git a/pigeonhole/src/managesieve-login/client.c b/pigeonhole/src/managesieve-login/client.c new file mode 100644 index 0000000..fe8acff --- /dev/null +++ b/pigeonhole/src/managesieve-login/client.c @@ -0,0 +1,574 @@ +/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file + */ + +#include "login-common.h" +#include "buffer.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "safe-memset.h" +#include "str.h" +#include "strescape.h" +#include "base64.h" +#include "master-service.h" +#include "master-auth.h" +#include "auth-client.h" + +#include "managesieve-parser.h" +#include "managesieve-quote.h" + +#include "client.h" +#include "client-authenticate.h" + +#include "managesieve-login-settings.h" +#include "managesieve-proxy.h" + +/* Disconnect client when it sends too many bad commands */ +#define CLIENT_MAX_BAD_COMMANDS 3 + +struct managesieve_command { + const char *name; + int (*func)(struct managesieve_client *client, + const struct managesieve_arg *args); + int preparsed_args; +}; + +/* Skip incoming data until newline is found, + returns TRUE if newline was found. */ +bool client_skip_line(struct managesieve_client *client) +{ + const unsigned char *data; + size_t i, 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->common.input, i+1); + return TRUE; + } + } + + return FALSE; +} + +static void client_send_capabilities(struct client *client) +{ + struct managesieve_client *msieve_client = + (struct managesieve_client *)client; + const char *saslcap; + + T_BEGIN { + saslcap = client_authenticate_get_capabilities(client); + + /* Default capabilities */ + client_send_raw(client, t_strconcat( + "\"IMPLEMENTATION\" \"", + msieve_client->set->managesieve_implementation_string, + "\"\r\n", NULL)); + client_send_raw(client, t_strconcat( + "\"SIEVE\" \"", + msieve_client->set->managesieve_sieve_capability, + "\"\r\n", NULL)); + if (msieve_client->set->managesieve_notify_capability != NULL) { + client_send_raw(client, t_strconcat( + "\"NOTIFY\" \"", + msieve_client->set->managesieve_notify_capability, + "\"\r\n", NULL)); + } + client_send_raw(client, t_strconcat("\"SASL\" \"", saslcap, + "\"\r\n", NULL)); + + /* STARTTLS */ + if (login_ssl_initialized && !client->tls) + client_send_raw(client, "\"STARTTLS\"\r\n"); + + /* Protocol version */ + client_send_raw(client, "\"VERSION\" \"1.0\"\r\n"); + + /* XCLIENT */ + if (client->trusted) + client_send_raw(client, "\"XCLIENT\"\r\n"); + } T_END; +} + +static int +cmd_capability(struct managesieve_client *client, + const struct managesieve_arg *args ATTR_UNUSED) +{ + o_stream_cork(client->common.output); + + client_send_capabilities(&client->common); + client_send_ok(&client->common, "Capability completed."); + + o_stream_uncork(client->common.output); + + return 1; +} + +static int +cmd_starttls(struct managesieve_client *client, + const struct managesieve_arg *args ATTR_UNUSED) +{ + client_cmd_starttls(&client->common); + return 1; +} + +static void +managesieve_client_notify_starttls(struct client *client, bool success, + const char *text) +{ + if (success) + client_send_ok(client, text); + else + client_send_no(client, text); +} + +static int +cmd_noop(struct managesieve_client *client, const struct managesieve_arg *args) +{ + const char *text; + string_t *resp_code; + + if (MANAGESIEVE_ARG_IS_EOL(&args[0])) { + client_send_ok(&client->common, "NOOP Completed"); + return 1; + } + if (!MANAGESIEVE_ARG_IS_EOL(&args[1])) + return -1; + if (!managesieve_arg_get_string(&args[0], &text)) { + client_send_no(&client->common, "Invalid echo tag."); + return 1; + } + + resp_code = t_str_new(256); + str_append(resp_code, "TAG "); + managesieve_quote_append_string(resp_code, text, FALSE); + + client_send_okresp(&client->common, str_c(resp_code), "Done"); + return 1; +} + +static int +cmd_logout(struct managesieve_client *client, + const struct managesieve_arg *args ATTR_UNUSED) +{ + client_send_ok(&client->common, "Logout completed."); + client_destroy(&client->common, CLIENT_UNAUTHENTICATED_LOGOUT_MSG); + return 1; +} + +static int +cmd_xclient_parse_forward(struct managesieve_client *client, const char *value) +{ + size_t value_len = strlen(value); + + if (client->common.forward_fields != NULL) + str_truncate(client->common.forward_fields, 0); + else { + client->common.forward_fields = str_new( + client->common.preproxy_pool, + MAX_BASE64_DECODED_SIZE(value_len)); + } + + if (base64_decode(value, value_len, NULL, + client->common.forward_fields) < 0) + return -1; + + return 0; +} + +static int +cmd_xclient(struct managesieve_client *client, + const struct managesieve_arg *args) +{ + const char *arg; + bool args_ok = TRUE; + + if (!client->common.trusted) { + client_send_no(&client->common, "You are not from trusted IP"); + return 1; + } + while (!MANAGESIEVE_ARG_IS_EOL(&args[0]) && + managesieve_arg_get_atom(&args[0], &arg)) { + if (strncasecmp(arg, "ADDR=", 5) == 0) { + if (net_addr2ip(arg + 5, &client->common.ip) < 0) + args_ok = FALSE; + } else if (strncasecmp(arg, "FORWARD=", 8) == 0) { + if (cmd_xclient_parse_forward(client, arg + 8) < 0) + args_ok = FALSE; + } else if (strncasecmp(arg, "PORT=", 5) == 0) { + if (net_str2port(arg + 5, + &client->common.remote_port) < 0) + args_ok = FALSE; + } else if (strncasecmp(arg, "SESSION=", 8) == 0) { + const char *value = arg + 8; + + if (strlen(value) <= LOGIN_MAX_SESSION_ID_LEN) { + client->common.session_id = + p_strdup(client->common.pool, value); + } + } else if (strncasecmp(arg, "TTL=", 4) == 0) { + if (str_to_uint(arg + 4, &client->common.proxy_ttl) < 0) + args_ok = FALSE; + } + args++; + } + if (!args_ok || !MANAGESIEVE_ARG_IS_EOL(&args[0])) + return -1; + + client_send_ok(&client->common, "Updated"); + return 1; +} + +static struct managesieve_command commands[] = { + { "AUTHENTICATE", cmd_authenticate, 1 }, + { "CAPABILITY", cmd_capability, -1 }, + { "STARTTLS", cmd_starttls, -1 }, + { "NOOP", cmd_noop, 0 }, + { "LOGOUT", cmd_logout, -1 }, + { "XCLIENT", cmd_xclient, 0 }, + { NULL, NULL, 0 } +}; + +static bool client_handle_input(struct managesieve_client *client) +{ + i_assert(!client->common.authenticating); + + if (client->cmd_finished) { + /* Clear the previous command from memory */ + client->cmd_name = NULL; + client->cmd_parsed_args = FALSE; + client->cmd = NULL; + managesieve_parser_reset(client->parser); + + /* Remove \r\n */ + if (client->skip_line) { + if (!client_skip_line(client)) + return FALSE; + client->skip_line = FALSE; + } + + client->cmd_finished = FALSE; + } + + if (client->cmd == NULL) { + struct managesieve_command *cmd; + const char *cmd_name; + + client->cmd_name = managesieve_parser_read_word(client->parser); + if (client->cmd_name == NULL) + return FALSE; /* Need more data */ + + cmd_name = t_str_ucase(client->cmd_name); + cmd = commands; + while (cmd->name != NULL) { + if (strcmp(cmd->name, cmd_name) == 0) + break; + cmd++; + } + + if (cmd->name != NULL) + client->cmd = cmd; + else + client->skip_line = TRUE; + } + return client->common.v.input_next_cmd(&client->common); +} + +static bool managesieve_client_input_next_cmd(struct client *_client) +{ + struct managesieve_client *client = + (struct managesieve_client *)_client; + const struct managesieve_arg *args = NULL; + const char *msg; + int ret = 1; + bool fatal; + + if (client->cmd == NULL) { + /* Unknown command */ + ret = -1; + } else if (!client->cmd_parsed_args) { + unsigned int arg_count = + (client->cmd->preparsed_args > 0 ? + client->cmd->preparsed_args : 0); + + switch (managesieve_parser_read_args(client->parser, arg_count, + 0, &args)) { + case -1: + /* Error */ + msg = managesieve_parser_get_error(client->parser, + &fatal); + if (fatal) { + client_send_bye(&client->common, msg); + client_destroy(&client->common, msg); + return FALSE; + } + client_send_no(&client->common, msg); + client->cmd_finished = TRUE; + client->skip_line = TRUE; + return TRUE; + case -2: + /* Not enough data */ + return FALSE; + } + i_assert(args != NULL); + + if (arg_count == 0) { + /* We read the entire line - skip over the CRLF */ + if (!client_skip_line(client)) + i_unreached(); + } else { + /* Get rid of it later */ + client->skip_line = TRUE; + } + + client->cmd_parsed_args = TRUE; + + if (client->cmd->preparsed_args == -1) { + /* Check absence of arguments */ + if (args[0].type != MANAGESIEVE_ARG_EOL) + ret = -1; + } + } + if (ret > 0) { + i_assert(client->cmd != NULL); + ret = client->cmd->func(client, args); + } + + if (ret != 0) + client->cmd_finished = TRUE; + if (ret < 0) { + if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) { + client_send_bye(&client->common, + "Too many invalid MANAGESIEVE commands."); + client_destroy(&client->common, + "Too many invalid commands."); + return FALSE; + } + client_send_no(&client->common, + "Error in MANAGESIEVE command received by server."); + } + + return ret != 0 && !client->common.destroyed; +} + +static void managesieve_client_input(struct client *client) +{ + struct managesieve_client *managesieve_client = + (struct managesieve_client *)client; + + if (!client_read(client)) + return; + + client_ref(client); + o_stream_cork(managesieve_client->common.output); + for (;;) { + if (!auth_client_is_connected(auth_client)) { + /* We're not currently connected to auth process - + don't allow any commands */ + /* FIXME: Can't do untagged responses with managesieve. + Any other ways? + client_send_ok(client, AUTH_SERVER_WAITING_MSG); + */ + timeout_remove(&client->to_auth_waiting); + + client->input_blocked = TRUE; + break; + } else { + if (!client_handle_input(managesieve_client)) + break; + } + } + o_stream_uncork(managesieve_client->common.output); + client_unref(&client); +} + +static struct client *managesieve_client_alloc(pool_t pool) +{ + struct managesieve_client *msieve_client; + + msieve_client = p_new(pool, struct managesieve_client, 1); + return &msieve_client->common; +} + +static void managesieve_client_create(struct client *client, void **other_sets) +{ + struct managesieve_client *msieve_client = + (struct managesieve_client *)client; + + msieve_client->set = other_sets[0]; + msieve_client->parser = managesieve_parser_create( + msieve_client->common.input, MAX_MANAGESIEVE_LINE); + client->io = io_add(client->fd, IO_READ, client_input, client); +} + +static void managesieve_client_destroy(struct client *client) +{ + struct managesieve_client *managesieve_client = + (struct managesieve_client *)client; + + managesieve_parser_destroy(&managesieve_client->parser); +} + +static void managesieve_client_notify_auth_ready(struct client *client) +{ + /* Cork the stream to send the capability data as a single tcp frame + Some naive clients break if we don't. + */ + o_stream_cork(client->output); + + /* Send initial capabilities */ + client_send_capabilities(client); + client_send_ok(client, client->set->login_greeting); + + o_stream_uncork(client->output); + + client->banner_sent = TRUE; +} + +static void managesieve_client_starttls(struct client *client) +{ + struct managesieve_client *msieve_client = + (struct managesieve_client *)client; + + managesieve_parser_destroy(&msieve_client->parser); + msieve_client->parser = managesieve_parser_create( + msieve_client->common.input, MAX_MANAGESIEVE_LINE); + + /* CRLF is lost from buffer when streams are reopened. */ + msieve_client->skip_line = FALSE; + + /* Cork the stream to send the capability data as a single tcp frame + Some naive clients break if we don't. + */ + o_stream_cork(client->output); + + client_send_capabilities(client); + client_send_ok(client, "TLS negotiation successful."); + + o_stream_uncork(client->output); +} + +static void +client_send_reply_raw(struct client *client, const char *prefix, + const char *resp_code, const char *text) +{ + T_BEGIN { + string_t *line = t_str_new(256); + + str_append(line, prefix); + + if (resp_code != NULL) { + str_append(line, " ("); + str_append(line, resp_code); + str_append_c(line, ')'); + } + + if (text != NULL) { + str_append_c(line, ' '); + managesieve_quote_append_string(line, text, TRUE); + } + + str_append(line, "\r\n"); + + client_send_raw_data(client, str_data(line), str_len(line)); + } T_END; +} + +void client_send_reply_code(struct client *client, + enum managesieve_cmd_reply reply, + const char *resp_code, const char *text) +{ + const char *prefix = "NO"; + + switch (reply) { + case MANAGESIEVE_CMD_REPLY_OK: + prefix = "OK"; + break; + case MANAGESIEVE_CMD_REPLY_NO: + break; + case MANAGESIEVE_CMD_REPLY_BYE: + prefix = "BYE"; + break; + } + + client_send_reply_raw(client, prefix, resp_code, text); +} + +void client_send_reply(struct client *client, enum managesieve_cmd_reply reply, + const char *text) +{ + client_send_reply_code(client, reply, NULL, text); +} + +static void +managesieve_client_notify_disconnect(struct client *client, + enum client_disconnect_reason reason, + const char *text) +{ + if (reason == CLIENT_DISCONNECT_SYSTEM_SHUTDOWN) { + client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_BYE, + "TRYLATER", text); + } else { + client_send_reply_code(client, MANAGESIEVE_CMD_REPLY_BYE, + NULL, text); + } +} + +static void managesieve_login_preinit(void) +{ + login_set_roots = managesieve_login_settings_set_roots; +} + +static void managesieve_login_init(void) +{ +} + +static void managesieve_login_deinit(void) +{ + clients_destroy_all(); +} + +static struct client_vfuncs managesieve_client_vfuncs = { + .alloc = managesieve_client_alloc, + .create = managesieve_client_create, + .destroy = managesieve_client_destroy, + .notify_auth_ready = managesieve_client_notify_auth_ready, + .notify_disconnect = managesieve_client_notify_disconnect, + .notify_starttls = managesieve_client_notify_starttls, + .starttls = managesieve_client_starttls, + .input = managesieve_client_input, + .auth_send_challenge = managesieve_client_auth_send_challenge, + .auth_parse_response = managesieve_client_auth_parse_response, + .auth_result = managesieve_client_auth_result, + .proxy_reset = managesieve_proxy_reset, + .proxy_parse_line = managesieve_proxy_parse_line, + .proxy_failed = managesieve_proxy_failed, + .proxy_get_state = managesieve_proxy_get_state, + .send_raw_data = client_common_send_raw_data, + .input_next_cmd = managesieve_client_input_next_cmd, + .free = client_common_default_free, +}; + +static struct login_binary managesieve_login_binary = { + .protocol = "sieve", + .process_name = "managesieve-login", + .default_port = 4190, + + .event_category = { + .name = "managesieve", + }, + + .client_vfuncs = &managesieve_client_vfuncs, + .preinit = managesieve_login_preinit, + .init = managesieve_login_init, + .deinit = managesieve_login_deinit, + + .anonymous_login_acceptable = FALSE, +}; + +int main(int argc, char *argv[]) +{ + return login_binary_run(&managesieve_login_binary, argc, argv); +} |