diff options
Diffstat (limited to 'src/imap-login/imap-login-client.c')
-rw-r--r-- | src/imap-login/imap-login-client.c | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/src/imap-login/imap-login-client.c b/src/imap-login/imap-login-client.c new file mode 100644 index 0000000..29ade52 --- /dev/null +++ b/src/imap-login/imap-login-client.c @@ -0,0 +1,571 @@ +/* Copyright (c) 2002-2018 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 "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 "imap-proxy.h" +#include "imap-quote.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 + +/* 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; +} + +bool client_handle_parser_error(struct imap_client *client, + struct imap_parser *parser) +{ + const char *msg; + enum imap_parser_error parse_error; + + msg = imap_parser_get_error(parser, &parse_error); + switch (parse_error) { + case IMAP_PARSE_ERROR_NONE: + i_unreached(); + case IMAP_PARSE_ERROR_LITERAL_TOO_BIG: + client_send_reply(&client->common, + IMAP_CMD_REPLY_BYE, msg); + client_destroy(&client->common, msg); + return FALSE; + default: + break; + } + + 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 (sasl_server_find_available_mech(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); + bool explicit_capability = FALSE; + + if (*imap_client->set->imap_capability == '\0') + str_append(cap_str, CAPABILITY_BANNER_STRING); + else if (*imap_client->set->imap_capability != '+') { + explicit_capability = TRUE; + 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 (!explicit_capability) { + if (imap_client->set->imap_literal_minus) + str_append(cap_str, " LITERAL-"); + else + str_append(cap_str, " LITERAL+"); + } + + 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 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, CLIENT_UNAUTHENTICATED_LOGOUT_MSG); + 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 client_invalid_command(struct imap_client *client) +{ + if (client->cmd_tag == NULL || *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, "Too many invalid commands"); + return FALSE; + } + client_send_reply(&client->common, IMAP_CMD_REPLY_BAD, + "Error in IMAP command received by server."); + 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 char *tag, *name; + 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) { + ret = imap_parser_read_tag(client->parser, &tag); + if (ret == 0) + return FALSE; /* need more data */ + if (ret < 0 || strlen(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->skip_line = TRUE; + client->cmd_finished = TRUE; + if (!client_invalid_command(client)) + return FALSE; + return client_handle_input(client); + } + client->cmd_tag = tag; + } + + if (client->cmd_name == NULL) { + ret = imap_parser_read_command_name(client->parser, &name); + if (ret == 0) + return FALSE; /* need more data */ + if (ret < 0) { + client->skip_line = TRUE; + client->cmd_finished = TRUE; + if (!client_invalid_command(client)) + return FALSE; + return client_handle_input(client); + } + client->cmd_name = name; + } + return client->common.v.input_next_cmd(&client->common); +} + +static bool imap_client_input_next_cmd(struct client *_client) +{ + struct imap_client *client = (struct imap_client *)_client; + const struct imap_arg *args; + bool parsed; + int ret; + + 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_invalid_command(client)) + return FALSE; + } + + 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); + 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, + IMAP_LOGIN_MAX_LINE_LENGTH); + if (imap_client->set->imap_literal_minus) + imap_parser_enable_literal_minus(imap_client->parser); + client->io = io_add_istream(client->input, 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)); + + client->banner_sent = TRUE; +} + +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, + IMAP_LOGIN_MAX_LINE_LENGTH); + + /* 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 = { + .alloc = imap_client_alloc, + .create = imap_client_create, + .destroy = imap_client_destroy, + .notify_auth_ready = imap_client_notify_auth_ready, + .notify_disconnect = imap_client_notify_disconnect, + .notify_status = imap_client_notify_status, + .notify_starttls = imap_client_notify_starttls, + .starttls = imap_client_starttls, + .input = imap_client_input, + .auth_result = imap_client_auth_result, + .proxy_reset = imap_proxy_reset, + .proxy_parse_line = imap_proxy_parse_line, + .proxy_failed = imap_proxy_failed, + .proxy_get_state = imap_proxy_get_state, + .send_raw_data = client_common_send_raw_data, + .input_next_cmd = imap_client_input_next_cmd, + .free = client_common_default_free, +}; + +static struct login_binary imap_login_binary = { + .protocol = "imap", + .process_name = "imap-login", + .default_port = 143, + .default_ssl_port = 993, + + .event_category = { + .name = "imap", + }, + + .client_vfuncs = &imap_client_vfuncs, + .preinit = imap_login_preinit, + .init = imap_login_init, + .deinit = imap_login_deinit, + + .sasl_support_final_reply = FALSE, + .anonymous_login_acceptable = TRUE, +}; + +int main(int argc, char *argv[]) +{ + return login_binary_run(&imap_login_binary, argc, argv); +} |