diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:51:24 +0000 |
commit | f7548d6d28c313cf80e6f3ef89aed16a19815df1 (patch) | |
tree | a3f6f2a3f247293bee59ecd28e8cd8ceb6ca064a /src/pop3-login/client.c | |
parent | Initial commit. (diff) | |
download | dovecot-upstream.tar.xz dovecot-upstream.zip |
Adding upstream version 1:2.3.19.1+dfsg1.upstream/1%2.3.19.1+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pop3-login/client.c')
-rw-r--r-- | src/pop3-login/client.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/src/pop3-login/client.c b/src/pop3-login/client.c new file mode 100644 index 0000000..d57d284 --- /dev/null +++ b/src/pop3-login/client.c @@ -0,0 +1,395 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "login-common.h" +#include "base64.h" +#include "buffer.h" +#include "ioloop.h" +#include "istream.h" +#include "ostream.h" +#include "randgen.h" +#include "hostpid.h" +#include "safe-memset.h" +#include "str.h" +#include "strescape.h" +#include "master-service.h" +#include "client.h" +#include "client-authenticate.h" +#include "auth-client.h" +#include "pop3-proxy.h" +#include "pop3-login-settings.h" + +#include <ctype.h> + +/* Disconnect client when it sends too many bad commands */ +#define CLIENT_MAX_BAD_COMMANDS 3 +#define CLIENT_MAX_CMD_LEN 8 + +static bool cmd_stls(struct pop3_client *client) +{ + client_cmd_starttls(&client->common); + return TRUE; +} + +static bool cmd_quit(struct pop3_client *client) +{ + client_send_reply(&client->common, POP3_CMD_REPLY_OK, "Logging out"); + client_destroy(&client->common, CLIENT_UNAUTHENTICATED_LOGOUT_MSG); + return TRUE; +} + +static bool cmd_xclient(struct pop3_client *client, const char *args) +{ + const char *const *tmp; + in_port_t remote_port; + bool args_ok = TRUE; + + if (!client->common.trusted) { + client_send_reply(&client->common, POP3_CMD_REPLY_OK, + "You are not from trusted IP - ignoring"); + return TRUE; + } + for (tmp = t_strsplit(args, " "); *tmp != NULL; tmp++) { + if (strncasecmp(*tmp, "ADDR=", 5) == 0) { + if (net_addr2ip(*tmp + 5, &client->common.ip) < 0) + args_ok = FALSE; + } else if (strncasecmp(*tmp, "PORT=", 5) == 0) { + if (net_str2port(*tmp + 5, &remote_port) < 0) + args_ok = FALSE; + else + client->common.remote_port = remote_port; + } else if (strncasecmp(*tmp, "SESSION=", 8) == 0) { + const char *value = *tmp + 8; + + if (strlen(value) <= LOGIN_MAX_SESSION_ID_LEN) { + client->common.session_id = + p_strdup(client->common.pool, value); + } + } else if (strncasecmp(*tmp, "TTL=", 4) == 0) { + if (str_to_uint(*tmp + 4, &client->common.proxy_ttl) < 0) + args_ok = FALSE; + } else if (strncasecmp(*tmp, "FORWARD=", 8) == 0) { + size_t value_len = strlen((*tmp)+8); + client->common.forward_fields = + str_new(client->common.preproxy_pool, + MAX_BASE64_DECODED_SIZE(value_len)); + if (base64_decode((*tmp)+8, value_len, NULL, + client->common.forward_fields) < 0) + args_ok = FALSE; + } + } + if (!args_ok) { + client_send_reply(&client->common, POP3_CMD_REPLY_ERROR, + "Invalid parameters"); + return TRUE; + } + + /* args ok, set them and reset the state */ + client_send_reply(&client->common, POP3_CMD_REPLY_OK, "Updated"); + return TRUE; +} + +static bool client_command_execute(struct pop3_client *client, const char *cmd, + const char *args) +{ + if (strcmp(cmd, "CAPA") == 0) + return cmd_capa(client, args); + if (strcmp(cmd, "USER") == 0) + return cmd_user(client, args); + if (strcmp(cmd, "PASS") == 0) + return cmd_pass(client, args); + if (strcmp(cmd, "APOP") == 0) + return cmd_apop(client, args); + if (strcmp(cmd, "STLS") == 0) + return cmd_stls(client); + if (strcmp(cmd, "QUIT") == 0) + return cmd_quit(client); + if (strcmp(cmd, "XCLIENT") == 0) + return cmd_xclient(client, args); + if (strcmp(cmd, "XOIP") == 0) { + /* Compatibility with Zimbra's patched nginx */ + return cmd_xclient(client, t_strconcat("ADDR=", args, NULL)); + } + + client_send_reply(&client->common, POP3_CMD_REPLY_ERROR, + "Unknown command."); + return FALSE; +} + +static void pop3_client_input(struct client *client) +{ + i_assert(!client->authenticating); + + if (!client_read(client)) + return; + + client_ref(client); + + o_stream_cork(client->output); + /* if a command starts an authentication, stop processing further + commands until the authentication is finished. */ + while (!client->output->closed && !client->authenticating && + auth_client_is_connected(auth_client)) { + if (!client->v.input_next_cmd(client)) + break; + } + + if (auth_client != NULL && !auth_client_is_connected(auth_client)) + client->input_blocked = TRUE; + + o_stream_uncork(client->output); + client_unref(&client); +} + +static bool client_read_cmd_name(struct client *client, const char **cmd_r) +{ + const unsigned char *data; + size_t size, i; + string_t *cmd = t_str_new(CLIENT_MAX_CMD_LEN); + if (i_stream_read_more(client->input, &data, &size) <= 0) + return FALSE; + for(i = 0; i < size; i++) { + if (data[i] == '\r') continue; + if (data[i] == ' ' || + data[i] == '\n' || + data[i] == '\0' || + i >= CLIENT_MAX_CMD_LEN) { + *cmd_r = str_c(cmd); + /* only skip ws */ + i_stream_skip(client->input, i + (data[i] == ' ' ? 1 : 0)); + return TRUE; + } + str_append_c(cmd, i_toupper(data[i])); + } + return FALSE; +} + +static bool pop3_client_input_next_cmd(struct client *client) +{ + struct pop3_client *pop3_client = (struct pop3_client *)client; + const char *cmd, *args; + + if (pop3_client->current_cmd == NULL) { + if (!client_read_cmd_name(client, &cmd)) + return FALSE; + pop3_client->current_cmd = i_strdup(cmd); + } + + if (strcmp(pop3_client->current_cmd, "AUTH") == 0) { + if (cmd_auth(pop3_client) <= 0) { + /* Need more input / destroyed. We also get here when + SASL authentication is actually started. */ + return FALSE; + } + /* AUTH command finished already (SASL probe or ERR reply) */ + i_free(pop3_client->current_cmd); + return TRUE; + } + + if ((args = i_stream_next_line(client->input)) == NULL) + return FALSE; + + if (client_command_execute(pop3_client, pop3_client->current_cmd, args)) + client->bad_counter = 0; + else if (++client->bad_counter >= CLIENT_MAX_BAD_COMMANDS) { + client_send_reply(client, POP3_CMD_REPLY_ERROR, + "Too many invalid bad commands."); + client_destroy(client, + "Disconnected: Too many bad commands"); + return FALSE; + } + i_free(pop3_client->current_cmd); + return TRUE; +} + +static struct client *pop3_client_alloc(pool_t pool) +{ + struct pop3_client *pop3_client; + + pop3_client = p_new(pool, struct pop3_client, 1); + return &pop3_client->common; +} + +static void pop3_client_create(struct client *client ATTR_UNUSED, + void **other_sets ATTR_UNUSED) +{ +} + +static void pop3_client_destroy(struct client *client) +{ + struct pop3_client *pop3_client = (struct pop3_client *)client; + + i_free_and_null(pop3_client->current_cmd); + i_free_and_null(pop3_client->last_user); + i_free_and_null(pop3_client->apop_challenge); +} + +static char *get_apop_challenge(struct pop3_client *client) +{ + unsigned char buffer[16]; + unsigned char buffer_base64[MAX_BASE64_ENCODED_SIZE(sizeof(buffer)) + 1]; + buffer_t buf; + + if (sasl_server_find_available_mech(&client->common, "APOP") == NULL) { + /* disabled, no need to present the challenge */ + return NULL; + } + + auth_client_get_connect_id(auth_client, &client->apop_server_pid, + &client->apop_connect_uid); + + random_fill(buffer, sizeof(buffer)); + buffer_create_from_data(&buf, buffer_base64, sizeof(buffer_base64)); + base64_encode(buffer, sizeof(buffer), &buf); + buffer_append_c(&buf, '\0'); + + return i_strdup_printf("<%x.%x.%lx.%s@%s>", + client->apop_server_pid, + client->apop_connect_uid, + (unsigned long)ioloop_time, + (const char *)buf.data, my_hostname); +} + +static void pop3_client_notify_auth_ready(struct client *client) +{ + struct pop3_client *pop3_client = (struct pop3_client *)client; + string_t *str; + + client->io = io_add_istream(client->input, client_input, client); + + str = t_str_new(128); + if (client->trusted) { + /* Dovecot extension to avoid extra roundtrip for CAPA */ + str_append(str, "[XCLIENT] "); + } + str_append(str, client->set->login_greeting); + + pop3_client->apop_challenge = get_apop_challenge(pop3_client); + if (pop3_client->apop_challenge != NULL) + str_printfa(str, " %s", pop3_client->apop_challenge); + client_send_reply(client, POP3_CMD_REPLY_OK, str_c(str)); + + client->banner_sent = TRUE; +} + +static void +pop3_client_notify_starttls(struct client *client, + bool success, const char *text) +{ + if (success) + client_send_reply(client, POP3_CMD_REPLY_OK, text); + else + client_send_reply(client, POP3_CMD_REPLY_ERROR, text); +} + +static void pop3_client_starttls(struct client *client ATTR_UNUSED) +{ +} + +void client_send_reply(struct client *client, enum pop3_cmd_reply reply, + const char *text) +{ + const char *prefix = "-ERR"; + + switch (reply) { + case POP3_CMD_REPLY_OK: + prefix = "+OK"; + break; + case POP3_CMD_REPLY_TEMPFAIL: + prefix = "-ERR [SYS/TEMP]"; + break; + case POP3_CMD_REPLY_AUTH_ERROR: + if (text[0] == '[') + prefix = "-ERR"; + else + prefix = "-ERR [AUTH]"; + break; + case POP3_CMD_REPLY_ERROR: + break; + } + + T_BEGIN { + string_t *line = t_str_new(256); + + str_append(line, prefix); + str_append_c(line, ' '); + str_append(line, text); + str_append(line, "\r\n"); + + client_send_raw_data(client, str_data(line), str_len(line)); + } T_END; +} + +static void +pop3_client_notify_disconnect(struct client *client, + enum client_disconnect_reason reason, + const char *text) +{ + if (reason == CLIENT_DISCONNECT_INTERNAL_ERROR) + client_send_reply(client, POP3_CMD_REPLY_TEMPFAIL, text); + else + client_send_reply(client, POP3_CMD_REPLY_ERROR, text); +} + +static void pop3_login_die(void) +{ + /* do nothing. pop3 connections typically die pretty quick anyway. */ +} + +static void pop3_login_preinit(void) +{ + login_set_roots = pop3_login_setting_roots; +} + +static void pop3_login_init(void) +{ + /* override the default login_die() */ + master_service_set_die_callback(master_service, pop3_login_die); +} + +static void pop3_login_deinit(void) +{ + clients_destroy_all(); +} + +static struct client_vfuncs pop3_client_vfuncs = { + .alloc = pop3_client_alloc, + .create = pop3_client_create, + .destroy = pop3_client_destroy, + .notify_auth_ready = pop3_client_notify_auth_ready, + .notify_disconnect = pop3_client_notify_disconnect, + .notify_starttls = pop3_client_notify_starttls, + .starttls = pop3_client_starttls, + .input = pop3_client_input, + .auth_result = pop3_client_auth_result, + .proxy_reset = pop3_proxy_reset, + .proxy_parse_line = pop3_proxy_parse_line, + .proxy_failed = pop3_proxy_failed, + .proxy_get_state = pop3_proxy_get_state, + .send_raw_data = client_common_send_raw_data, + .input_next_cmd = pop3_client_input_next_cmd, + .free = client_common_default_free, +}; + +static struct login_binary pop3_login_binary = { + .protocol = "pop3", + .process_name = "pop3-login", + .default_port = 110, + .default_ssl_port = 995, + + .event_category = { + .name = "pop3", + }, + + .client_vfuncs = &pop3_client_vfuncs, + .preinit = pop3_login_preinit, + .init = pop3_login_init, + .deinit = pop3_login_deinit, + + .sasl_support_final_reply = FALSE, + .anonymous_login_acceptable = TRUE, +}; + +int main(int argc, char *argv[]) +{ + return login_binary_run(&pop3_login_binary, argc, argv); +} |