Mercurial > dovecot > core-2.2
changeset 19569:5bad98da9310
imap-login: Renamed client.[ch] to imap-login-client.[ch]
This allows installing the imap-login-client.h without conflicting.
author | Timo Sirainen <timo.sirainen@dovecot.fi> |
---|---|
date | Fri, 15 Jan 2016 16:49:05 +0200 |
parents | 828dd58e03a4 |
children | da28bf026c2e |
files | src/imap-login/Makefile.am src/imap-login/client-authenticate.c src/imap-login/client.c src/imap-login/client.h src/imap-login/imap-login-client.c src/imap-login/imap-login-client.h src/imap-login/imap-proxy.c |
diffstat | 7 files changed, 784 insertions(+), 784 deletions(-) [+] |
line wrap: on
line diff
--- a/src/imap-login/Makefile.am Fri Jan 15 16:46:34 2016 +0200 +++ b/src/imap-login/Makefile.am Fri Jan 15 16:49:05 2016 +0200 @@ -20,14 +20,14 @@ $(LIBDOVECOT_DEPS) imap_login_SOURCES = \ - client.c \ + imap-login-client.c \ client-authenticate.c \ imap-login-commands.c \ imap-login-settings.c \ imap-proxy.c noinst_HEADERS = \ - client.h \ + imap-login-client.h \ client-authenticate.h \ imap-login-commands.h \ imap-login-settings.h \
--- a/src/imap-login/client-authenticate.c Fri Jan 15 16:46:34 2016 +0200 +++ b/src/imap-login/client-authenticate.c Fri Jan 15 16:49:05 2016 +0200 @@ -14,7 +14,7 @@ #include "imap-parser.h" #include "imap-url.h" #include "auth-client.h" -#include "client.h" +#include "imap-login-client.h" #include "client-authenticate.h" #include "imap-proxy.h"
--- a/src/imap-login/client.c Fri Jan 15 16:46:34 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,717 +0,0 @@ -/* Copyright (c) 2002-2016 Dovecot 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 "imap-parser.h" -#include "imap-id.h" -#include "imap-resp-code.h" -#include "master-service.h" -#include "master-service-ssl-settings.h" -#include "master-auth.h" -#include "client.h" -#include "client-authenticate.h" -#include "auth-client.h" -#include "ssl-proxy.h" -#include "imap-proxy.h" -#include "imap-login-commands.h" -#include "imap-login-settings.h" - -#if LOGIN_MAX_INBUF_SIZE < 1024+2 -# error LOGIN_MAX_INBUF_SIZE too short to fit all ID command parameters -#endif - -/* maximum length for IMAP command line. */ -#define MAX_IMAP_LINE 8192 - -/* Disconnect client when it sends too many bad commands */ -#define CLIENT_MAX_BAD_COMMANDS 3 - -/* Skip incoming data until newline is found, - returns TRUE if newline was found. */ -bool client_skip_line(struct imap_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 bool client_handle_parser_error(struct imap_client *client, - struct imap_parser *parser) -{ - const char *msg; - bool fatal; - - msg = imap_parser_get_error(parser, &fatal); - if (fatal) { - client_send_reply(&client->common, - IMAP_CMD_REPLY_BYE, msg); - client_destroy(&client->common, - t_strconcat("Disconnected: ", msg, NULL)); - return FALSE; - } - - client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, msg); - client->cmd_finished = TRUE; - client->skip_line = TRUE; - return TRUE; -} - -static bool is_login_cmd_disabled(struct client *client) -{ - if (client->secured) { - if (auth_client_find_mech(auth_client, "PLAIN") == NULL) { - /* no PLAIN authentication, can't use LOGIN command */ - return TRUE; - } - return FALSE; - } - if (client->set->disable_plaintext_auth) - return TRUE; - if (strcmp(client->ssl_set->ssl, "required") == 0) - return TRUE; - return FALSE; -} - -static const char *get_capability(struct client *client) -{ - struct imap_client *imap_client = (struct imap_client *)client; - string_t *cap_str = t_str_new(256); - - if (*imap_client->set->imap_capability == '\0') - str_append(cap_str, CAPABILITY_BANNER_STRING); - else if (*imap_client->set->imap_capability != '+') - str_append(cap_str, imap_client->set->imap_capability); - else { - str_append(cap_str, CAPABILITY_BANNER_STRING); - str_append_c(cap_str, ' '); - str_append(cap_str, imap_client->set->imap_capability + 1); - } - - if (client_is_tls_enabled(client) && !client->tls) - str_append(cap_str, " STARTTLS"); - if (is_login_cmd_disabled(client)) - str_append(cap_str, " LOGINDISABLED"); - - client_authenticate_get_capabilities(client, cap_str); - return str_c(cap_str); -} - -static int cmd_capability(struct imap_client *imap_client, - const struct imap_arg *args ATTR_UNUSED) -{ - struct client *client = &imap_client->common; - - /* Client is required to send CAPABILITY after STARTTLS, so the - capability resp-code workaround checks only pre-STARTTLS - CAPABILITY commands. */ - if (!client->starttls) - imap_client->client_ignores_capability_resp_code = TRUE; - client_send_raw(client, t_strconcat( - "* CAPABILITY ", get_capability(client), "\r\n", NULL)); - client_send_reply(client, IMAP_CMD_REPLY_OK, - "Pre-login capabilities listed, post-login capabilities have more."); - return 1; -} - -static int cmd_starttls(struct imap_client *client, - const struct imap_arg *args ATTR_UNUSED) -{ - client_cmd_starttls(&client->common); - return 1; -} - -static void -imap_client_notify_starttls(struct client *client, - bool success, const char *text) -{ - if (success) - client_send_reply(client, IMAP_CMD_REPLY_OK, text); - else - client_send_reply(client, IMAP_CMD_REPLY_BAD, text); -} - -static void -client_update_info(struct imap_client *client, - const char *key, const char *value) -{ - if (strcasecmp(key, "x-originating-ip") == 0) { - (void)net_addr2ip(value, &client->common.ip); - } else if (strcasecmp(key, "x-originating-port") == 0) { - (void)net_str2port(value, &client->common.remote_port); - } else if (strcasecmp(key, "x-connected-ip") == 0) { - (void)net_addr2ip(value, &client->common.local_ip); - } else if (strcasecmp(key, "x-connected-port") == 0) { - (void)net_str2port(value, &client->common.local_port); - } else if (strcasecmp(key, "x-proxy-ttl") == 0) { - if (str_to_uint(value, &client->common.proxy_ttl) < 0) { - /* nothing */ - } - } else if (strcasecmp(key, "x-session-id") == 0 || - strcasecmp(key, "x-session-ext-id") == 0) { - if (strlen(value) <= LOGIN_MAX_SESSION_ID_LEN) { - client->common.session_id = - p_strdup(client->common.pool, value); - } - } -} - -static void cmd_id_handle_keyvalue(struct imap_client *client, - const char *key, const char *value) -{ - if (client->common.trusted && !client->id_logged) - client_update_info(client, key, value); - - if (client->cmd_id->log_reply != NULL && - (client->cmd_id->log_keys == NULL || - str_array_icase_find((void *)client->cmd_id->log_keys, key))) - imap_id_log_reply_append(client->cmd_id->log_reply, key, value); -} - -static int cmd_id_handle_args(struct imap_client *client, - const struct imap_arg *arg) -{ - struct imap_client_cmd_id *id = client->cmd_id; - const char *key, *value; - - switch (id->state) { - case IMAP_CLIENT_ID_STATE_LIST: - if (arg->type == IMAP_ARG_NIL) - return 1; - if (arg->type != IMAP_ARG_LIST) - return -1; - if (client->set->imap_id_log[0] == '\0') { - /* no ID logging */ - } else if (client->id_logged) { - /* already logged the ID reply */ - } else { - id->log_reply = str_new(default_pool, 64); - if (strcmp(client->set->imap_id_log, "*") == 0) { - /* log all keys */ - } else { - /* log only specified keys */ - id->log_keys = p_strsplit_spaces(default_pool, - client->set->imap_id_log, " "); - } - } - id->state = IMAP_CLIENT_ID_STATE_KEY; - break; - case IMAP_CLIENT_ID_STATE_KEY: - if (!imap_arg_get_string(arg, &key)) - return -1; - if (i_strocpy(id->key, key, sizeof(id->key)) < 0) - return -1; - id->state = IMAP_CLIENT_ID_STATE_VALUE; - break; - case IMAP_CLIENT_ID_STATE_VALUE: - if (!imap_arg_get_nstring(arg, &value)) - return -1; - cmd_id_handle_keyvalue(client, id->key, value); - id->state = IMAP_CLIENT_ID_STATE_KEY; - break; - } - return 0; -} - -static void cmd_id_finish(struct imap_client *client) -{ - /* finished handling the parameters */ - if (!client->id_logged) { - client->id_logged = TRUE; - - if (client->cmd_id->log_reply != NULL) { - client_log(&client->common, t_strdup_printf( - "ID sent: %s", str_c(client->cmd_id->log_reply))); - } - } - - client_send_raw(&client->common, - t_strdup_printf("* ID %s\r\n", - imap_id_reply_generate(client->set->imap_id_send))); - client_send_reply(&client->common, IMAP_CMD_REPLY_OK, "ID completed."); -} - -static void cmd_id_free(struct imap_client *client) -{ - struct imap_client_cmd_id *id = client->cmd_id; - - if (id->log_reply != NULL) - str_free(&id->log_reply); - if (id->log_keys != NULL) - p_strsplit_free(default_pool, id->log_keys); - imap_parser_unref(&id->parser); - - i_free_and_null(client->cmd_id); - client->skip_line = TRUE; -} - -static int cmd_id(struct imap_client *client) -{ - struct imap_client_cmd_id *id; - enum imap_parser_flags parser_flags; - const struct imap_arg *args; - int ret; - - if (client->cmd_id == NULL) { - client->cmd_id = id = i_new(struct imap_client_cmd_id, 1); - id->parser = imap_parser_create(client->common.input, - client->common.output, - MAX_IMAP_LINE); - parser_flags = IMAP_PARSE_FLAG_STOP_AT_LIST; - } else { - id = client->cmd_id; - parser_flags = IMAP_PARSE_FLAG_INSIDE_LIST; - } - - while ((ret = imap_parser_read_args(id->parser, 1, parser_flags, &args)) > 0) { - i_assert(ret == 1); - - if ((ret = cmd_id_handle_args(client, args)) < 0) { - client_send_reply(&client->common, - IMAP_CMD_REPLY_BAD, - "Invalid ID parameters"); - cmd_id_free(client); - return -1; - } - if (ret > 0) { - /* NIL parameter */ - ret = 0; - break; - } - imap_parser_reset(id->parser); - parser_flags = IMAP_PARSE_FLAG_INSIDE_LIST; - } - if (ret == 0) { - /* finished the line */ - cmd_id_finish(client); - cmd_id_free(client); - return 1; - } else if (ret == -1) { - if (!client_handle_parser_error(client, id->parser)) - return 0; - cmd_id_free(client); - return -1; - } else { - i_assert(ret == -2); - return 0; - } -} - -static int cmd_noop(struct imap_client *client, - const struct imap_arg *args ATTR_UNUSED) -{ - client_send_reply(&client->common, IMAP_CMD_REPLY_OK, - "NOOP completed."); - return 1; -} - -static int cmd_logout(struct imap_client *client, - const struct imap_arg *args ATTR_UNUSED) -{ - client_send_reply(&client->common, IMAP_CMD_REPLY_BYE, "Logging out"); - client_send_reply(&client->common, IMAP_CMD_REPLY_OK, - "Logout completed."); - client_destroy(&client->common, "Aborted login"); - return 1; -} - -static int cmd_enable(struct imap_client *client, - const struct imap_arg *args ATTR_UNUSED) -{ - client_send_raw(&client->common, "* ENABLED\r\n"); - client_send_reply(&client->common, IMAP_CMD_REPLY_OK, - "ENABLE ignored in non-authenticated state."); - return 1; -} - -static int client_command_execute(struct imap_client *client, const char *cmd, - const struct imap_arg *args) -{ - struct imap_login_command *login_cmd; - - login_cmd = imap_login_command_lookup(cmd); - if (login_cmd == NULL) - return -2; - return login_cmd->func(client, args); -} - -static bool imap_is_valid_tag(const char *tag) -{ - for (; *tag != '\0'; tag++) { - switch (*tag) { - case '+': - /* atom-specials: */ - case '(': - case ')': - case '{': - case '/': - case ' ': - /* list-wildcards: */ - case '%': - case '*': - /* quoted-specials: */ - case '"': - case '\\': - return FALSE; - default: - if (*tag < ' ') /* CTL */ - return FALSE; - break; - } - } - return TRUE; -} - -static int client_parse_command(struct imap_client *client, - const struct imap_arg **args_r) -{ - switch (imap_parser_read_args(client->parser, 0, 0, args_r)) { - case -1: - /* error */ - if (!client_handle_parser_error(client, client->parser)) { - /* client destroyed */ - return 0; - } - return -1; - case -2: - /* not enough data */ - return 0; - default: - /* we read the entire line - skip over the CRLF */ - if (!client_skip_line(client)) - i_unreached(); - return 1; - } -} - -static bool client_handle_input(struct imap_client *client) -{ - const struct imap_arg *args; - bool parsed; - int ret; - - i_assert(!client->common.authenticating); - - if (client->cmd_finished) { - /* clear the previous command from memory. don't do this - immediately after handling command since we need the - cmd_tag to stay some time after authentication commands. */ - client->cmd_tag = NULL; - client->cmd_name = NULL; - imap_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_tag == NULL) { - client->cmd_tag = imap_parser_read_word(client->parser); - if (client->cmd_tag == NULL) - return FALSE; /* need more data */ - if (!imap_is_valid_tag(client->cmd_tag) || - strlen(client->cmd_tag) > IMAP_TAG_MAX_LEN) { - /* the tag is invalid, don't allow it and don't - send it back. this attempts to prevent any - potentially dangerous replies in case someone tries - to access us using HTTP protocol. */ - client->cmd_tag = ""; - } - } - - if (client->cmd_name == NULL) { - client->cmd_name = imap_parser_read_word(client->parser); - if (client->cmd_name == NULL) - return FALSE; /* need more data */ - } - - if (strcasecmp(client->cmd_name, "AUTHENTICATE") == 0) { - /* SASL-IR may need more space than input buffer's size, - so we'll handle it as a special case. */ - ret = cmd_authenticate(client, &parsed); - if (ret == 0 && !parsed) - return FALSE; - } else if (strcasecmp(client->cmd_name, "ID") == 0) { - /* ID extensions allows max. 30 parameters, - each max. 1024 bytes long. that brings us over the input - buffer's size, so handle the parameters one at a time */ - ret = cmd_id(client); - if (ret == 0) - return FALSE; - if (ret < 0) - ret = 1; /* don't send the error reply again */ - } else { - ret = client_parse_command(client, &args); - if (ret < 0) - return TRUE; - if (ret == 0) - return FALSE; - ret = *client->cmd_tag == '\0' ? -1 : - client_command_execute(client, client->cmd_name, args); - } - - client->cmd_finished = TRUE; - if (ret == -2 && strcasecmp(client->cmd_tag, "LOGIN") == 0) { - client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, - "First parameter in line is IMAP's command tag, " - "not the command name. Add that before the command, " - "like: a login user pass"); - } else if (ret < 0) { - if (*client->cmd_tag == '\0') - client->cmd_tag = "*"; - if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) { - client_send_reply(&client->common, IMAP_CMD_REPLY_BYE, - "Too many invalid IMAP commands."); - client_destroy(&client->common, - "Disconnected: Too many invalid commands"); - return FALSE; - } - client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, - "Error in IMAP command received by server."); - } - - return ret != 0 && !client->common.destroyed; -} - -static void imap_client_input(struct client *client) -{ - struct imap_client *imap_client = (struct imap_client *)client; - - if (!client_read(client)) - return; - - client_ref(client); - o_stream_cork(imap_client->common.output); - for (;;) { - if (!auth_client_is_connected(auth_client)) { - /* we're not currently connected to auth process - - don't allow any commands */ - client_notify_status(client, FALSE, - AUTH_SERVER_WAITING_MSG); - if (client->to_auth_waiting != NULL) - timeout_remove(&client->to_auth_waiting); - - client->input_blocked = TRUE; - break; - } else { - if (!client_handle_input(imap_client)) - break; - } - } - o_stream_uncork(imap_client->common.output); - client_unref(&client); -} - -static struct client *imap_client_alloc(pool_t pool) -{ - struct imap_client *imap_client; - - imap_client = p_new(pool, struct imap_client, 1); - return &imap_client->common; -} - -static void imap_client_create(struct client *client, void **other_sets) -{ - struct imap_client *imap_client = (struct imap_client *)client; - - imap_client->set = other_sets[0]; - imap_client->parser = - imap_parser_create(imap_client->common.input, - imap_client->common.output, MAX_IMAP_LINE); - client->io = io_add(client->fd, IO_READ, client_input, client); -} - -static void imap_client_destroy(struct client *client) -{ - struct imap_client *imap_client = (struct imap_client *)client; - - i_free_and_null(imap_client->proxy_backend_capability); - imap_parser_unref(&imap_client->parser); -} - -static void imap_client_notify_auth_ready(struct client *client) -{ - string_t *greet; - - greet = t_str_new(128); - str_append(greet, "* OK "); - str_printfa(greet, "[CAPABILITY %s] ", get_capability(client)); - str_append(greet, client->set->login_greeting); - str_append(greet, "\r\n"); - - client_send_raw(client, str_c(greet)); -} - -static void imap_client_starttls(struct client *client) -{ - struct imap_client *imap_client = (struct imap_client *)client; - - imap_parser_unref(&imap_client->parser); - imap_client->parser = - imap_parser_create(imap_client->common.input, - imap_client->common.output, MAX_IMAP_LINE); - - /* CRLF is lost from buffer when streams are reopened. */ - imap_client->skip_line = FALSE; -} - -static void ATTR_NULL(3) -client_send_reply_raw(struct client *client, - const char *prefix, const char *resp_code, - const char *text, bool tagged) -{ - struct imap_client *imap_client = (struct imap_client *)client; - - 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(client, str_data(line), str_len(line)); - } T_END; -} - -void client_send_reply_code(struct client *client, enum imap_cmd_reply reply, - const char *resp_code, const char *text) -{ - const char *prefix = "NO"; - bool tagged = TRUE; - - switch (reply) { - case IMAP_CMD_REPLY_OK: - prefix = "OK"; - break; - case IMAP_CMD_REPLY_NO: - break; - case IMAP_CMD_REPLY_BAD: - prefix = "BAD"; - break; - case IMAP_CMD_REPLY_BYE: - prefix = "BYE"; - tagged = FALSE; - break; - } - client_send_reply_raw(client, prefix, resp_code, text, tagged); -} - -void client_send_reply(struct client *client, enum imap_cmd_reply reply, - const char *text) -{ - client_send_reply_code(client, reply, NULL, text); -} - -static void -imap_client_notify_status(struct client *client, bool bad, const char *text) -{ - if (bad) - client_send_reply_raw(client, "BAD", "ALERT", text, FALSE); - else - client_send_reply_raw(client, "OK", NULL, text, FALSE); -} - -static void -imap_client_notify_disconnect(struct client *client, - enum client_disconnect_reason reason, - const char *text) -{ - if (reason == CLIENT_DISCONNECT_INTERNAL_ERROR) { - client_send_reply_code(client, IMAP_CMD_REPLY_BYE, - IMAP_RESP_CODE_UNAVAILABLE, text); - } else { - client_send_reply_code(client, IMAP_CMD_REPLY_BYE, NULL, text); - } -} - -static void imap_login_preinit(void) -{ - login_set_roots = imap_login_setting_roots; -} - -static const struct imap_login_command imap_login_commands[] = { - { "LOGIN", cmd_login }, - { "CAPABILITY", cmd_capability }, - { "STARTTLS", cmd_starttls }, - { "NOOP", cmd_noop }, - { "LOGOUT", cmd_logout }, - { "ENABLE", cmd_enable } -}; - -static void imap_login_init(void) -{ - imap_login_commands_init(); - imap_login_commands_register(imap_login_commands, - N_ELEMENTS(imap_login_commands)); -} - -static void imap_login_deinit(void) -{ - clients_destroy_all(); - imap_login_commands_deinit(); -} - -static struct client_vfuncs imap_client_vfuncs = { - imap_client_alloc, - imap_client_create, - imap_client_destroy, - imap_client_notify_auth_ready, - imap_client_notify_disconnect, - imap_client_notify_status, - imap_client_notify_starttls, - imap_client_starttls, - imap_client_input, - NULL, - NULL, - imap_client_auth_result, - imap_proxy_reset, - imap_proxy_parse_line, - imap_proxy_error -}; - -static const struct login_binary imap_login_binary = { - .protocol = "imap", - .process_name = "imap-login", - .default_port = 143, - .default_ssl_port = 993, - - .client_vfuncs = &imap_client_vfuncs, - .preinit = imap_login_preinit, - .init = imap_login_init, - .deinit = imap_login_deinit, - - .sasl_support_final_reply = FALSE -}; - -int main(int argc, char *argv[]) -{ - return login_binary_run(&imap_login_binary, argc, argv); -}
--- a/src/imap-login/client.h Fri Jan 15 16:46:34 2016 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -#ifndef CLIENT_H -#define CLIENT_H - -#include "net.h" -#include "imap-id.h" -#include "client-common.h" - -/* Master prefix is: <1|0><imap tag><NUL> */ -#define IMAP_TAG_MAX_LEN (LOGIN_MAX_MASTER_PREFIX_LEN-2) - -enum imap_client_id_state { - IMAP_CLIENT_ID_STATE_LIST = 0, - IMAP_CLIENT_ID_STATE_KEY, - IMAP_CLIENT_ID_STATE_VALUE -}; - -struct imap_client_cmd_id { - struct imap_parser *parser; - - enum imap_client_id_state state; - char key[IMAP_ID_KEY_MAX_LEN+1]; - - char **log_keys; - string_t *log_reply; -}; - -struct imap_client { - struct client common; - - const struct imap_login_settings *set; - struct imap_parser *parser; - char *proxy_backend_capability; - - const char *cmd_tag, *cmd_name; - struct imap_client_cmd_id *cmd_id; - - unsigned int cmd_finished:1; - unsigned int proxy_sasl_ir:1; - unsigned int proxy_seen_banner:1; - unsigned int skip_line:1; - unsigned int id_logged:1; - unsigned int proxy_capability_request_sent:1; - unsigned int client_ignores_capability_resp_code:1; - unsigned int auth_mech_name_parsed:1; -}; - -bool client_skip_line(struct imap_client *client); - -enum imap_cmd_reply { - IMAP_CMD_REPLY_OK, - IMAP_CMD_REPLY_NO, - IMAP_CMD_REPLY_BAD, - IMAP_CMD_REPLY_BYE -}; - -void client_send_reply(struct client *client, - enum imap_cmd_reply reply, const char *text); - -void client_send_reply_code(struct client *client, - enum imap_cmd_reply reply, const char *resp_code, - const char *text) ATTR_NULL(3); - -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap-login/imap-login-client.c Fri Jan 15 16:49:05 2016 +0200 @@ -0,0 +1,717 @@ +/* Copyright (c) 2002-2016 Dovecot 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 "imap-parser.h" +#include "imap-id.h" +#include "imap-resp-code.h" +#include "master-service.h" +#include "master-service-ssl-settings.h" +#include "master-auth.h" +#include "imap-login-client.h" +#include "client-authenticate.h" +#include "auth-client.h" +#include "ssl-proxy.h" +#include "imap-proxy.h" +#include "imap-login-commands.h" +#include "imap-login-settings.h" + +#if LOGIN_MAX_INBUF_SIZE < 1024+2 +# error LOGIN_MAX_INBUF_SIZE too short to fit all ID command parameters +#endif + +/* maximum length for IMAP command line. */ +#define MAX_IMAP_LINE 8192 + +/* Disconnect client when it sends too many bad commands */ +#define CLIENT_MAX_BAD_COMMANDS 3 + +/* Skip incoming data until newline is found, + returns TRUE if newline was found. */ +bool client_skip_line(struct imap_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 bool client_handle_parser_error(struct imap_client *client, + struct imap_parser *parser) +{ + const char *msg; + bool fatal; + + msg = imap_parser_get_error(parser, &fatal); + if (fatal) { + client_send_reply(&client->common, + IMAP_CMD_REPLY_BYE, msg); + client_destroy(&client->common, + t_strconcat("Disconnected: ", msg, NULL)); + return FALSE; + } + + client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, msg); + client->cmd_finished = TRUE; + client->skip_line = TRUE; + return TRUE; +} + +static bool is_login_cmd_disabled(struct client *client) +{ + if (client->secured) { + if (auth_client_find_mech(auth_client, "PLAIN") == NULL) { + /* no PLAIN authentication, can't use LOGIN command */ + return TRUE; + } + return FALSE; + } + if (client->set->disable_plaintext_auth) + return TRUE; + if (strcmp(client->ssl_set->ssl, "required") == 0) + return TRUE; + return FALSE; +} + +static const char *get_capability(struct client *client) +{ + struct imap_client *imap_client = (struct imap_client *)client; + string_t *cap_str = t_str_new(256); + + if (*imap_client->set->imap_capability == '\0') + str_append(cap_str, CAPABILITY_BANNER_STRING); + else if (*imap_client->set->imap_capability != '+') + str_append(cap_str, imap_client->set->imap_capability); + else { + str_append(cap_str, CAPABILITY_BANNER_STRING); + str_append_c(cap_str, ' '); + str_append(cap_str, imap_client->set->imap_capability + 1); + } + + if (client_is_tls_enabled(client) && !client->tls) + str_append(cap_str, " STARTTLS"); + if (is_login_cmd_disabled(client)) + str_append(cap_str, " LOGINDISABLED"); + + client_authenticate_get_capabilities(client, cap_str); + return str_c(cap_str); +} + +static int cmd_capability(struct imap_client *imap_client, + const struct imap_arg *args ATTR_UNUSED) +{ + struct client *client = &imap_client->common; + + /* Client is required to send CAPABILITY after STARTTLS, so the + capability resp-code workaround checks only pre-STARTTLS + CAPABILITY commands. */ + if (!client->starttls) + imap_client->client_ignores_capability_resp_code = TRUE; + client_send_raw(client, t_strconcat( + "* CAPABILITY ", get_capability(client), "\r\n", NULL)); + client_send_reply(client, IMAP_CMD_REPLY_OK, + "Pre-login capabilities listed, post-login capabilities have more."); + return 1; +} + +static int cmd_starttls(struct imap_client *client, + const struct imap_arg *args ATTR_UNUSED) +{ + client_cmd_starttls(&client->common); + return 1; +} + +static void +imap_client_notify_starttls(struct client *client, + bool success, const char *text) +{ + if (success) + client_send_reply(client, IMAP_CMD_REPLY_OK, text); + else + client_send_reply(client, IMAP_CMD_REPLY_BAD, text); +} + +static void +client_update_info(struct imap_client *client, + const char *key, const char *value) +{ + if (strcasecmp(key, "x-originating-ip") == 0) { + (void)net_addr2ip(value, &client->common.ip); + } else if (strcasecmp(key, "x-originating-port") == 0) { + (void)net_str2port(value, &client->common.remote_port); + } else if (strcasecmp(key, "x-connected-ip") == 0) { + (void)net_addr2ip(value, &client->common.local_ip); + } else if (strcasecmp(key, "x-connected-port") == 0) { + (void)net_str2port(value, &client->common.local_port); + } else if (strcasecmp(key, "x-proxy-ttl") == 0) { + if (str_to_uint(value, &client->common.proxy_ttl) < 0) { + /* nothing */ + } + } else if (strcasecmp(key, "x-session-id") == 0 || + strcasecmp(key, "x-session-ext-id") == 0) { + if (strlen(value) <= LOGIN_MAX_SESSION_ID_LEN) { + client->common.session_id = + p_strdup(client->common.pool, value); + } + } +} + +static void cmd_id_handle_keyvalue(struct imap_client *client, + const char *key, const char *value) +{ + if (client->common.trusted && !client->id_logged) + client_update_info(client, key, value); + + if (client->cmd_id->log_reply != NULL && + (client->cmd_id->log_keys == NULL || + str_array_icase_find((void *)client->cmd_id->log_keys, key))) + imap_id_log_reply_append(client->cmd_id->log_reply, key, value); +} + +static int cmd_id_handle_args(struct imap_client *client, + const struct imap_arg *arg) +{ + struct imap_client_cmd_id *id = client->cmd_id; + const char *key, *value; + + switch (id->state) { + case IMAP_CLIENT_ID_STATE_LIST: + if (arg->type == IMAP_ARG_NIL) + return 1; + if (arg->type != IMAP_ARG_LIST) + return -1; + if (client->set->imap_id_log[0] == '\0') { + /* no ID logging */ + } else if (client->id_logged) { + /* already logged the ID reply */ + } else { + id->log_reply = str_new(default_pool, 64); + if (strcmp(client->set->imap_id_log, "*") == 0) { + /* log all keys */ + } else { + /* log only specified keys */ + id->log_keys = p_strsplit_spaces(default_pool, + client->set->imap_id_log, " "); + } + } + id->state = IMAP_CLIENT_ID_STATE_KEY; + break; + case IMAP_CLIENT_ID_STATE_KEY: + if (!imap_arg_get_string(arg, &key)) + return -1; + if (i_strocpy(id->key, key, sizeof(id->key)) < 0) + return -1; + id->state = IMAP_CLIENT_ID_STATE_VALUE; + break; + case IMAP_CLIENT_ID_STATE_VALUE: + if (!imap_arg_get_nstring(arg, &value)) + return -1; + cmd_id_handle_keyvalue(client, id->key, value); + id->state = IMAP_CLIENT_ID_STATE_KEY; + break; + } + return 0; +} + +static void cmd_id_finish(struct imap_client *client) +{ + /* finished handling the parameters */ + if (!client->id_logged) { + client->id_logged = TRUE; + + if (client->cmd_id->log_reply != NULL) { + client_log(&client->common, t_strdup_printf( + "ID sent: %s", str_c(client->cmd_id->log_reply))); + } + } + + client_send_raw(&client->common, + t_strdup_printf("* ID %s\r\n", + imap_id_reply_generate(client->set->imap_id_send))); + client_send_reply(&client->common, IMAP_CMD_REPLY_OK, "ID completed."); +} + +static void cmd_id_free(struct imap_client *client) +{ + struct imap_client_cmd_id *id = client->cmd_id; + + if (id->log_reply != NULL) + str_free(&id->log_reply); + if (id->log_keys != NULL) + p_strsplit_free(default_pool, id->log_keys); + imap_parser_unref(&id->parser); + + i_free_and_null(client->cmd_id); + client->skip_line = TRUE; +} + +static int cmd_id(struct imap_client *client) +{ + struct imap_client_cmd_id *id; + enum imap_parser_flags parser_flags; + const struct imap_arg *args; + int ret; + + if (client->cmd_id == NULL) { + client->cmd_id = id = i_new(struct imap_client_cmd_id, 1); + id->parser = imap_parser_create(client->common.input, + client->common.output, + MAX_IMAP_LINE); + parser_flags = IMAP_PARSE_FLAG_STOP_AT_LIST; + } else { + id = client->cmd_id; + parser_flags = IMAP_PARSE_FLAG_INSIDE_LIST; + } + + while ((ret = imap_parser_read_args(id->parser, 1, parser_flags, &args)) > 0) { + i_assert(ret == 1); + + if ((ret = cmd_id_handle_args(client, args)) < 0) { + client_send_reply(&client->common, + IMAP_CMD_REPLY_BAD, + "Invalid ID parameters"); + cmd_id_free(client); + return -1; + } + if (ret > 0) { + /* NIL parameter */ + ret = 0; + break; + } + imap_parser_reset(id->parser); + parser_flags = IMAP_PARSE_FLAG_INSIDE_LIST; + } + if (ret == 0) { + /* finished the line */ + cmd_id_finish(client); + cmd_id_free(client); + return 1; + } else if (ret == -1) { + if (!client_handle_parser_error(client, id->parser)) + return 0; + cmd_id_free(client); + return -1; + } else { + i_assert(ret == -2); + return 0; + } +} + +static int cmd_noop(struct imap_client *client, + const struct imap_arg *args ATTR_UNUSED) +{ + client_send_reply(&client->common, IMAP_CMD_REPLY_OK, + "NOOP completed."); + return 1; +} + +static int cmd_logout(struct imap_client *client, + const struct imap_arg *args ATTR_UNUSED) +{ + client_send_reply(&client->common, IMAP_CMD_REPLY_BYE, "Logging out"); + client_send_reply(&client->common, IMAP_CMD_REPLY_OK, + "Logout completed."); + client_destroy(&client->common, "Aborted login"); + return 1; +} + +static int cmd_enable(struct imap_client *client, + const struct imap_arg *args ATTR_UNUSED) +{ + client_send_raw(&client->common, "* ENABLED\r\n"); + client_send_reply(&client->common, IMAP_CMD_REPLY_OK, + "ENABLE ignored in non-authenticated state."); + return 1; +} + +static int client_command_execute(struct imap_client *client, const char *cmd, + const struct imap_arg *args) +{ + struct imap_login_command *login_cmd; + + login_cmd = imap_login_command_lookup(cmd); + if (login_cmd == NULL) + return -2; + return login_cmd->func(client, args); +} + +static bool imap_is_valid_tag(const char *tag) +{ + for (; *tag != '\0'; tag++) { + switch (*tag) { + case '+': + /* atom-specials: */ + case '(': + case ')': + case '{': + case '/': + case ' ': + /* list-wildcards: */ + case '%': + case '*': + /* quoted-specials: */ + case '"': + case '\\': + return FALSE; + default: + if (*tag < ' ') /* CTL */ + return FALSE; + break; + } + } + return TRUE; +} + +static int client_parse_command(struct imap_client *client, + const struct imap_arg **args_r) +{ + switch (imap_parser_read_args(client->parser, 0, 0, args_r)) { + case -1: + /* error */ + if (!client_handle_parser_error(client, client->parser)) { + /* client destroyed */ + return 0; + } + return -1; + case -2: + /* not enough data */ + return 0; + default: + /* we read the entire line - skip over the CRLF */ + if (!client_skip_line(client)) + i_unreached(); + return 1; + } +} + +static bool client_handle_input(struct imap_client *client) +{ + const struct imap_arg *args; + bool parsed; + int ret; + + i_assert(!client->common.authenticating); + + if (client->cmd_finished) { + /* clear the previous command from memory. don't do this + immediately after handling command since we need the + cmd_tag to stay some time after authentication commands. */ + client->cmd_tag = NULL; + client->cmd_name = NULL; + imap_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_tag == NULL) { + client->cmd_tag = imap_parser_read_word(client->parser); + if (client->cmd_tag == NULL) + return FALSE; /* need more data */ + if (!imap_is_valid_tag(client->cmd_tag) || + strlen(client->cmd_tag) > IMAP_TAG_MAX_LEN) { + /* the tag is invalid, don't allow it and don't + send it back. this attempts to prevent any + potentially dangerous replies in case someone tries + to access us using HTTP protocol. */ + client->cmd_tag = ""; + } + } + + if (client->cmd_name == NULL) { + client->cmd_name = imap_parser_read_word(client->parser); + if (client->cmd_name == NULL) + return FALSE; /* need more data */ + } + + if (strcasecmp(client->cmd_name, "AUTHENTICATE") == 0) { + /* SASL-IR may need more space than input buffer's size, + so we'll handle it as a special case. */ + ret = cmd_authenticate(client, &parsed); + if (ret == 0 && !parsed) + return FALSE; + } else if (strcasecmp(client->cmd_name, "ID") == 0) { + /* ID extensions allows max. 30 parameters, + each max. 1024 bytes long. that brings us over the input + buffer's size, so handle the parameters one at a time */ + ret = cmd_id(client); + if (ret == 0) + return FALSE; + if (ret < 0) + ret = 1; /* don't send the error reply again */ + } else { + ret = client_parse_command(client, &args); + if (ret < 0) + return TRUE; + if (ret == 0) + return FALSE; + ret = *client->cmd_tag == '\0' ? -1 : + client_command_execute(client, client->cmd_name, args); + } + + client->cmd_finished = TRUE; + if (ret == -2 && strcasecmp(client->cmd_tag, "LOGIN") == 0) { + client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, + "First parameter in line is IMAP's command tag, " + "not the command name. Add that before the command, " + "like: a login user pass"); + } else if (ret < 0) { + if (*client->cmd_tag == '\0') + client->cmd_tag = "*"; + if (++client->common.bad_counter >= CLIENT_MAX_BAD_COMMANDS) { + client_send_reply(&client->common, IMAP_CMD_REPLY_BYE, + "Too many invalid IMAP commands."); + client_destroy(&client->common, + "Disconnected: Too many invalid commands"); + return FALSE; + } + client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, + "Error in IMAP command received by server."); + } + + return ret != 0 && !client->common.destroyed; +} + +static void imap_client_input(struct client *client) +{ + struct imap_client *imap_client = (struct imap_client *)client; + + if (!client_read(client)) + return; + + client_ref(client); + o_stream_cork(imap_client->common.output); + for (;;) { + if (!auth_client_is_connected(auth_client)) { + /* we're not currently connected to auth process - + don't allow any commands */ + client_notify_status(client, FALSE, + AUTH_SERVER_WAITING_MSG); + if (client->to_auth_waiting != NULL) + timeout_remove(&client->to_auth_waiting); + + client->input_blocked = TRUE; + break; + } else { + if (!client_handle_input(imap_client)) + break; + } + } + o_stream_uncork(imap_client->common.output); + client_unref(&client); +} + +static struct client *imap_client_alloc(pool_t pool) +{ + struct imap_client *imap_client; + + imap_client = p_new(pool, struct imap_client, 1); + return &imap_client->common; +} + +static void imap_client_create(struct client *client, void **other_sets) +{ + struct imap_client *imap_client = (struct imap_client *)client; + + imap_client->set = other_sets[0]; + imap_client->parser = + imap_parser_create(imap_client->common.input, + imap_client->common.output, MAX_IMAP_LINE); + client->io = io_add(client->fd, IO_READ, client_input, client); +} + +static void imap_client_destroy(struct client *client) +{ + struct imap_client *imap_client = (struct imap_client *)client; + + i_free_and_null(imap_client->proxy_backend_capability); + imap_parser_unref(&imap_client->parser); +} + +static void imap_client_notify_auth_ready(struct client *client) +{ + string_t *greet; + + greet = t_str_new(128); + str_append(greet, "* OK "); + str_printfa(greet, "[CAPABILITY %s] ", get_capability(client)); + str_append(greet, client->set->login_greeting); + str_append(greet, "\r\n"); + + client_send_raw(client, str_c(greet)); +} + +static void imap_client_starttls(struct client *client) +{ + struct imap_client *imap_client = (struct imap_client *)client; + + imap_parser_unref(&imap_client->parser); + imap_client->parser = + imap_parser_create(imap_client->common.input, + imap_client->common.output, MAX_IMAP_LINE); + + /* CRLF is lost from buffer when streams are reopened. */ + imap_client->skip_line = FALSE; +} + +static void ATTR_NULL(3) +client_send_reply_raw(struct client *client, + const char *prefix, const char *resp_code, + const char *text, bool tagged) +{ + struct imap_client *imap_client = (struct imap_client *)client; + + 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(client, str_data(line), str_len(line)); + } T_END; +} + +void client_send_reply_code(struct client *client, enum imap_cmd_reply reply, + const char *resp_code, const char *text) +{ + const char *prefix = "NO"; + bool tagged = TRUE; + + switch (reply) { + case IMAP_CMD_REPLY_OK: + prefix = "OK"; + break; + case IMAP_CMD_REPLY_NO: + break; + case IMAP_CMD_REPLY_BAD: + prefix = "BAD"; + break; + case IMAP_CMD_REPLY_BYE: + prefix = "BYE"; + tagged = FALSE; + break; + } + client_send_reply_raw(client, prefix, resp_code, text, tagged); +} + +void client_send_reply(struct client *client, enum imap_cmd_reply reply, + const char *text) +{ + client_send_reply_code(client, reply, NULL, text); +} + +static void +imap_client_notify_status(struct client *client, bool bad, const char *text) +{ + if (bad) + client_send_reply_raw(client, "BAD", "ALERT", text, FALSE); + else + client_send_reply_raw(client, "OK", NULL, text, FALSE); +} + +static void +imap_client_notify_disconnect(struct client *client, + enum client_disconnect_reason reason, + const char *text) +{ + if (reason == CLIENT_DISCONNECT_INTERNAL_ERROR) { + client_send_reply_code(client, IMAP_CMD_REPLY_BYE, + IMAP_RESP_CODE_UNAVAILABLE, text); + } else { + client_send_reply_code(client, IMAP_CMD_REPLY_BYE, NULL, text); + } +} + +static void imap_login_preinit(void) +{ + login_set_roots = imap_login_setting_roots; +} + +static const struct imap_login_command imap_login_commands[] = { + { "LOGIN", cmd_login }, + { "CAPABILITY", cmd_capability }, + { "STARTTLS", cmd_starttls }, + { "NOOP", cmd_noop }, + { "LOGOUT", cmd_logout }, + { "ENABLE", cmd_enable } +}; + +static void imap_login_init(void) +{ + imap_login_commands_init(); + imap_login_commands_register(imap_login_commands, + N_ELEMENTS(imap_login_commands)); +} + +static void imap_login_deinit(void) +{ + clients_destroy_all(); + imap_login_commands_deinit(); +} + +static struct client_vfuncs imap_client_vfuncs = { + imap_client_alloc, + imap_client_create, + imap_client_destroy, + imap_client_notify_auth_ready, + imap_client_notify_disconnect, + imap_client_notify_status, + imap_client_notify_starttls, + imap_client_starttls, + imap_client_input, + NULL, + NULL, + imap_client_auth_result, + imap_proxy_reset, + imap_proxy_parse_line, + imap_proxy_error +}; + +static const struct login_binary imap_login_binary = { + .protocol = "imap", + .process_name = "imap-login", + .default_port = 143, + .default_ssl_port = 993, + + .client_vfuncs = &imap_client_vfuncs, + .preinit = imap_login_preinit, + .init = imap_login_init, + .deinit = imap_login_deinit, + + .sasl_support_final_reply = FALSE +}; + +int main(int argc, char *argv[]) +{ + return login_binary_run(&imap_login_binary, argc, argv); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/imap-login/imap-login-client.h Fri Jan 15 16:49:05 2016 +0200 @@ -0,0 +1,63 @@ +#ifndef IMAP_LOGIN_CLIENT_H +#define IMAP_LOGIN_CLIENT_H + +#include "net.h" +#include "imap-id.h" +#include "client-common.h" + +/* Master prefix is: <1|0><imap tag><NUL> */ +#define IMAP_TAG_MAX_LEN (LOGIN_MAX_MASTER_PREFIX_LEN-2) + +enum imap_client_id_state { + IMAP_CLIENT_ID_STATE_LIST = 0, + IMAP_CLIENT_ID_STATE_KEY, + IMAP_CLIENT_ID_STATE_VALUE +}; + +struct imap_client_cmd_id { + struct imap_parser *parser; + + enum imap_client_id_state state; + char key[IMAP_ID_KEY_MAX_LEN+1]; + + char **log_keys; + string_t *log_reply; +}; + +struct imap_client { + struct client common; + + const struct imap_login_settings *set; + struct imap_parser *parser; + char *proxy_backend_capability; + + const char *cmd_tag, *cmd_name; + struct imap_client_cmd_id *cmd_id; + + unsigned int cmd_finished:1; + unsigned int proxy_sasl_ir:1; + unsigned int proxy_seen_banner:1; + unsigned int skip_line:1; + unsigned int id_logged:1; + unsigned int proxy_capability_request_sent:1; + unsigned int client_ignores_capability_resp_code:1; + unsigned int auth_mech_name_parsed:1; +}; + +bool client_skip_line(struct imap_client *client); + +enum imap_cmd_reply { + IMAP_CMD_REPLY_OK, + IMAP_CMD_REPLY_NO, + IMAP_CMD_REPLY_BAD, + IMAP_CMD_REPLY_BYE +}; + +void client_send_reply(struct client *client, + enum imap_cmd_reply reply, const char *text); + +void client_send_reply_code(struct client *client, + enum imap_cmd_reply reply, const char *resp_code, + const char *text) ATTR_NULL(3); + +#endif
--- a/src/imap-login/imap-proxy.c Fri Jan 15 16:46:34 2016 +0200 +++ b/src/imap-login/imap-proxy.c Fri Jan 15 16:49:05 2016 +0200 @@ -10,7 +10,7 @@ #include "str-sanitize.h" #include "safe-memset.h" #include "dsasl-client.h" -#include "client.h" +#include "imap-login-client.h" #include "client-authenticate.h" #include "imap-resp-code.h" #include "imap-quote.h"