/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "login-common.h" #include "str.h" #include "imap-parser.h" #include "imap-quote.h" #include "imap-login-settings.h" #include "imap-login-client.h" struct imap_id_param_handler { const char *key; bool key_is_prefix; void (*callback)(struct imap_client *client, const char *key, const char *value); }; static void cmd_id_x_originating_ip(struct imap_client *client, const char *key ATTR_UNUSED, const char *value) { (void)net_addr2ip(value, &client->common.ip); } static void cmd_id_x_originating_port(struct imap_client *client, const char *key ATTR_UNUSED, const char *value) { (void)net_str2port(value, &client->common.remote_port); } static void cmd_id_x_connected_ip(struct imap_client *client, const char *key ATTR_UNUSED, const char *value) { (void)net_addr2ip(value, &client->common.local_ip); } static void cmd_id_x_connected_port(struct imap_client *client, const char *key ATTR_UNUSED, const char *value) { (void)net_str2port(value, &client->common.local_port); } static void cmd_id_x_proxy_ttl(struct imap_client *client, const char *key ATTR_UNUSED, const char *value) { if (str_to_uint(value, &client->common.proxy_ttl) < 0) { /* nothing */ } } static void cmd_id_x_session_id(struct imap_client *client, const char *key ATTR_UNUSED, const char *value) { if (strlen(value) <= LOGIN_MAX_SESSION_ID_LEN) { client->common.session_id = p_strdup(client->common.pool, value); } } static void cmd_id_x_forward_(struct imap_client *client, const char *key, const char *value) { i_assert(strncasecmp(key, "x-forward-", 10) == 0); client_add_forward_field(&client->common, key+10, value); } static const struct imap_id_param_handler imap_login_id_params[] = { { "x-originating-ip", FALSE, cmd_id_x_originating_ip }, { "x-originating-port", FALSE, cmd_id_x_originating_port }, { "x-connected-ip", FALSE, cmd_id_x_connected_ip }, { "x-connected-port", FALSE, cmd_id_x_connected_port }, { "x-proxy-ttl", FALSE, cmd_id_x_proxy_ttl }, { "x-session-id", FALSE, cmd_id_x_session_id }, { "x-session-ext-id", FALSE, cmd_id_x_session_id }, { "x-forward-", TRUE, cmd_id_x_forward_ }, { NULL, FALSE, NULL } }; static const struct imap_id_param_handler * imap_id_param_handler_find(const char *key) { for (unsigned int i = 0; imap_login_id_params[i].key != NULL; i++) { unsigned int prefix_len = strlen(imap_login_id_params[i].key); if (strncasecmp(imap_login_id_params[i].key, key, prefix_len) == 0 && (key[prefix_len] == '\0' || imap_login_id_params[i].key_is_prefix)) return &imap_login_id_params[i]; } return NULL; } static bool client_try_update_info(struct imap_client *client, const char *key, const char *value) { const struct imap_id_param_handler *handler; handler = imap_id_param_handler_find(key); if (handler == NULL) return FALSE; /* do not try to process NIL values as client-info, but store them for non-reserved keys */ if (client->common.trusted && !client->id_logged && value != NULL) handler->callback(client, key, value); return TRUE; } static void cmd_id_handle_keyvalue(struct imap_client *client, const char *key, const char *value) { bool client_id_str; /* length of key + length of value (NIL for NULL) and two set of quotes and space */ size_t kvlen = strlen(key) + 2 + 1 + (value == NULL ? 3 : strlen(value)) + 2; client_id_str = !client_try_update_info(client, key, value); if (client->set->imap_id_retain && client_id_str && (client->common.client_id == NULL || str_len(client->common.client_id) + kvlen < LOGIN_MAX_CLIENT_ID_LEN)) { if (client->common.client_id == NULL) { client->common.client_id = str_new(client->common.preproxy_pool, 64); } else { str_append_c(client->common.client_id, ' '); } imap_append_quoted(client->common.client_id, key); str_append_c(client->common.client_id, ' '); if (value == NULL) str_append(client->common.client_id, "NIL"); else imap_append_quoted(client->common.client_id, 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) { e_info(client->common.event, "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; 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; } 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->common.client_id != NULL) str_truncate(client->common.client_id, 0); 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, IMAP_LOGIN_MAX_LINE_LENGTH); if (client->set->imap_literal_minus) imap_parser_enable_literal_minus(id->parser); 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; } }