client.c revision feb665db52583259a1f42037c6e8a22852aa8889
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch/* Copyright (C) 2002 Timo Sirainen */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "common.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "base64.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "buffer.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "hash.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "ioloop.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "istream.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "ostream.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "randgen.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "process-title.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "safe-memset.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "strescape.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "client.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "client-authenticate.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "auth-client.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "ssl-proxy.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "pop3-proxy.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#include "hostpid.h"
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch/* max. length of input command line (spec says 512), or max reply length in
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch SASL authentication */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#define MAX_INBUF_SIZE 4096
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch/* max. size of output buffer. if it gets full, the client is disconnected.
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch SASL authentication gives the largest output. */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#define MAX_OUTBUF_SIZE 4096
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch/* Disconnect client after idling this many seconds */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#define CLIENT_LOGIN_IDLE_TIMEOUT 60
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch/* Disconnect client when it sends too many bad commands */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#define CLIENT_MAX_BAD_COMMANDS 10
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch/* When max. number of simultaneous connections is reached, few of the
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch oldest connections are disconnected. Since we have to go through the whole
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client hash, it's faster if we disconnect multiple clients. */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#define CLIENT_DESTROY_OLDEST_COUNT 16
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#if CLIENT_LOGIN_IDLE_TIMEOUT >= AUTH_REQUEST_TIMEOUT
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch# error client idle timeout must be smaller than authentication timeout
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch#endif
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschconst char *login_protocol = "POP3";
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschstatic struct hash_table *clients;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschstatic struct timeout *to_idle;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschstatic void client_set_title(struct pop3_client *client)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch{
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch const char *addr;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (!verbose_proctitle || !process_per_connection)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch addr = net_ip2addr(&client->common.ip);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (addr == NULL)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch addr = "??";
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch process_title_set(t_strdup_printf(client->common.tls ?
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch "[%s TLS]" : "[%s]", addr));
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch}
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschstatic void client_open_streams(struct pop3_client *client, int fd)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch{
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client->input = i_stream_create_file(fd, default_pool,
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch MAX_INBUF_SIZE, FALSE);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client->output = o_stream_create_file(fd, default_pool,
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch MAX_OUTBUF_SIZE, FALSE);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch}
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschstatic void client_start_tls(struct pop3_client *client)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch{
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch int fd_ssl;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_ref(client);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch connection_queue_add(1);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (!client_unref(client))
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch fd_ssl = ssl_proxy_new(client->common.fd, &client->common.ip,
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch &client->common.proxy);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (fd_ssl == -1) {
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_send_line(client, "-ERR TLS initialization failed.");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_destroy(client,
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch "Disconnected: TLS initialization failed.");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch }
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client->common.tls = TRUE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client->common.secured = TRUE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_set_title(client);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client->common.fd = fd_ssl;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch i_stream_unref(&client->input);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch o_stream_unref(&client->output);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_open_streams(client, fd_ssl);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client->io = io_add(client->common.fd, IO_READ, client_input, client);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch}
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschstatic int client_output_starttls(void *context)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch{
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch struct pop3_client *client = context;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch int ret;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if ((ret = o_stream_flush(client->output)) < 0) {
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_destroy(client, "Disconnected");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return 1;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch }
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (ret > 0) {
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch o_stream_set_flush_callback(client->output, NULL, NULL);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_start_tls(client);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch }
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return 1;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch}
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschstatic bool cmd_stls(struct pop3_client *client)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch{
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (client->common.tls) {
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_send_line(client, "-ERR TLS is already active.");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return TRUE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch }
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (!ssl_initialized) {
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_send_line(client, "-ERR TLS support isn't enabled.");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return TRUE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch }
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch /* remove input handler, SSL proxy gives us a new fd. we also have to
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch remove it in case we have to wait for buffer to be flushed */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (client->io != NULL)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch io_remove(&client->io);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_send_line(client, "+OK Begin TLS negotiation now.");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch /* uncork the old fd */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch o_stream_uncork(client->output);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (o_stream_flush(client->output) <= 0) {
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch /* the buffer has to be flushed */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch o_stream_set_flush_pending(client->output, TRUE);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch o_stream_set_flush_callback(client->output,
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_output_starttls, client);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch } else {
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_start_tls(client);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch }
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return TRUE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch}
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschstatic bool cmd_quit(struct pop3_client *client)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch{
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_send_line(client, "+OK Logging out");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_destroy(client, "Aborted login");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return TRUE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch}
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschstatic bool client_command_execute(struct pop3_client *client, const char *cmd,
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch const char *args)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch{
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch cmd = t_str_ucase(cmd);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (strcmp(cmd, "CAPA") == 0)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return cmd_capa(client, args);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (strcmp(cmd, "USER") == 0)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return cmd_user(client, args);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (strcmp(cmd, "PASS") == 0)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return cmd_pass(client, args);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (strcmp(cmd, "AUTH") == 0)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return cmd_auth(client, args);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (strcmp(cmd, "APOP") == 0)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return cmd_apop(client, args);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (strcmp(cmd, "STLS") == 0)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return cmd_stls(client);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (strcmp(cmd, "QUIT") == 0)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return cmd_quit(client);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_send_line(client, "-ERR Unknown command.");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return FALSE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch}
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschbool client_read(struct pop3_client *client)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch{
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch switch (i_stream_read(client->input)) {
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch case -2:
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch /* buffer full */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_send_line(client, "-ERR Input line too long, aborting");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_destroy(client, "Disconnected: Input buffer full");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return FALSE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch case -1:
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch /* disconnected */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_destroy(client, "Disconnected");
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return FALSE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch default:
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch /* something was read */
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return TRUE;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch }
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch}
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Boschvoid client_input(void *context)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch{
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch struct pop3_client *client = context;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch char *line, *args;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client->last_input = ioloop_time;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (!client_read(client))
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch return;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client_ref(client);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch o_stream_cork(client->output);
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch while (!client->output->closed &&
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch (line = i_stream_next_line(client->input)) != NULL) {
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch args = strchr(line, ' ');
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (args == NULL)
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch args = "";
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch else
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch *args++ = '\0';
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch if (client_command_execute(client, line, args))
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch client->bad_counter = 0;
1bd20e2a575473f3d05499f05f1d72da59b34fd6Stephan Bosch else if (++client->bad_counter > CLIENT_MAX_BAD_COMMANDS) {
client_send_line(client, "-ERR Too many bad commands.");
client_destroy(client,
"Disconnected: Too many bad commands");
}
}
if (client_unref(client))
o_stream_uncork(client->output);
}
void client_destroy_oldest(void)
{
struct hash_iterate_context *iter;
void *key, *value;
struct pop3_client *destroy_buf[CLIENT_DESTROY_OLDEST_COUNT];
unsigned int i, destroy_count;
/* find the oldest clients and put them to destroy-buffer */
memset(destroy_buf, 0, sizeof(destroy_buf));
destroy_count = max_connections > CLIENT_DESTROY_OLDEST_COUNT*2 ?
CLIENT_DESTROY_OLDEST_COUNT : I_MIN(max_connections/2, 1);
iter = hash_iterate_init(clients);
while (hash_iterate(iter, &key, &value)) {
struct pop3_client *client = key;
for (i = 0; i < destroy_count; i++) {
if (destroy_buf[i] == NULL ||
destroy_buf[i]->created > client->created) {
/* @UNSAFE */
memmove(destroy_buf+i+1, destroy_buf+i,
sizeof(destroy_buf) -
(i+1) * sizeof(struct pop3_client *));
destroy_buf[i] = client;
break;
}
}
}
hash_iterate_deinit(iter);
/* then kill them */
for (i = 0; i < destroy_count; i++) {
if (destroy_buf[i] == NULL)
break;
client_destroy(destroy_buf[i],
"Disconnected: Connection queue full");
}
}
static char *get_apop_challenge(struct pop3_client *client)
{
struct auth_connect_id *id = &client->auth_id;
unsigned char buffer[16];
buffer_t *buf;
char *ret;
if (!auth_client_reserve_connection(auth_client, "APOP", id))
return NULL;
t_push();
random_fill(buffer, sizeof(buffer));
buf = buffer_create_static_hard(pool_datastack_create(),
MAX_BASE64_ENCODED_SIZE(sizeof(buffer)) + 1);
base64_encode(buffer, sizeof(buffer), buf);
buffer_append_c(buf, '\0');
ret = i_strdup_printf("<%x.%x.%lx.%s@%s>",
id->server_pid, id->connect_uid,
(unsigned long)ioloop_time,
(const char *)buf->data, my_hostname);
t_pop();
return ret;
}
static void client_auth_ready(struct pop3_client *client)
{
client->io = io_add(client->common.fd, IO_READ, client_input, client);
client->apop_challenge = get_apop_challenge(client);
client_send_line(client, t_strconcat("+OK ", greeting,
client->apop_challenge != NULL ?
" " : NULL,
client->apop_challenge, NULL));
}
struct client *client_create(int fd, bool ssl, const struct ip_addr *local_ip,
const struct ip_addr *ip)
{
struct pop3_client *client;
i_assert(fd != -1);
connection_queue_add(1);
/* always use nonblocking I/O */
net_set_nonblock(fd, TRUE);
client = i_new(struct pop3_client, 1);
client->created = ioloop_time;
client->refcount = 1;
client->common.tls = ssl;
client->common.secured = ssl || net_ip_compare(ip, local_ip);
client->common.local_ip = *local_ip;
client->common.ip = *ip;
client->common.fd = fd;
client_open_streams(client, fd);
client->last_input = ioloop_time;
hash_insert(clients, client, client);
main_ref();
client->auth_connected = auth_client_is_connected(auth_client);
if (client->auth_connected)
client_auth_ready(client);
client_set_title(client);
return &client->common;
}
void client_destroy(struct pop3_client *client, const char *reason)
{
if (client->destroyed)
return;
client->destroyed = TRUE;
if (reason != NULL)
client_syslog(&client->common, reason);
hash_remove(clients, client);
if (client->input != NULL)
i_stream_close(client->input);
if (client->output != NULL)
o_stream_close(client->output);
if (client->common.auth_request != NULL) {
i_assert(client->common.authenticating);
sasl_server_auth_client_error(&client->common, NULL);
} else {
i_assert(!client->common.authenticating);
}
if (client->common.master_tag != 0)
master_request_abort(&client->common);
if (client->io != NULL)
io_remove(&client->io);
if (client->common.fd != -1) {
net_disconnect(client->common.fd);
client->common.fd = -1;
}
if (client->proxy_password != NULL) {
safe_memset(client->proxy_password, 0,
strlen(client->proxy_password));
i_free(client->proxy_password);
client->proxy_password = NULL;
}
i_free(client->proxy_user);
client->proxy_user = NULL;
if (client->proxy != NULL) {
login_proxy_free(client->proxy);
client->proxy = NULL;
i_assert(client->refcount > 1);
client_unref(client);
}
if (client->common.proxy != NULL)
ssl_proxy_free(client->common.proxy);
client_unref(client);
main_listen_start();
main_unref();
}
void client_destroy_internal_failure(struct pop3_client *client)
{
client_send_line(client, "-ERR [IN-USE] Internal login failure. "
"Refer to server log for more information.");
client_destroy(client, "Internal login failure");
}
void client_ref(struct pop3_client *client)
{
client->refcount++;
}
bool client_unref(struct pop3_client *client)
{
i_assert(client->refcount > 0);
if (--client->refcount > 0)
return TRUE;
i_assert(client->destroyed);
if (client->input != NULL)
i_stream_unref(&client->input);
if (client->output != NULL)
o_stream_unref(&client->output);
i_free(client->last_user);
i_free(client->apop_challenge);
i_free(client->common.virtual_user);
i_free(client->common.auth_mech_name);
i_free(client);
return FALSE;
}
void client_send_line(struct pop3_client *client, const char *line)
{
struct const_iovec iov[2];
ssize_t ret;
iov[0].iov_base = line;
iov[0].iov_len = strlen(line);
iov[1].iov_base = "\r\n";
iov[1].iov_len = 2;
ret = o_stream_sendv(client->output, iov, 2);
if (ret < 0 || (size_t)ret != iov[0].iov_len + iov[1].iov_len) {
/* either disconnection or buffer full. in either case we
want this connection destroyed. however destroying it here
might break things if client is still tried to be accessed
without being referenced.. */
i_stream_close(client->input);
}
}
static void client_check_idle(struct pop3_client *client)
{
if (ioloop_time - client->last_input >= CLIENT_LOGIN_IDLE_TIMEOUT)
client_destroy(client, "Disconnected: Inactivity");
}
static void idle_timeout(void *context __attr_unused__)
{
struct hash_iterate_context *iter;
void *key, *value;
iter = hash_iterate_init(clients);
while (hash_iterate(iter, &key, &value)) {
struct pop3_client *client = key;
client_check_idle(client);
}
hash_iterate_deinit(iter);
}
unsigned int clients_get_count(void)
{
return hash_size(clients);
}
void clients_notify_auth_connected(void)
{
struct hash_iterate_context *iter;
void *key, *value;
iter = hash_iterate_init(clients);
while (hash_iterate(iter, &key, &value)) {
struct pop3_client *client = key;
if (!client->auth_connected) {
client->auth_connected = TRUE;
client_auth_ready(client);
}
}
hash_iterate_deinit(iter);
}
void clients_destroy_all(void)
{
struct hash_iterate_context *iter;
void *key, *value;
iter = hash_iterate_init(clients);
while (hash_iterate(iter, &key, &value)) {
struct pop3_client *client = key;
client_destroy(client, "Disconnected: Shutting down");
}
hash_iterate_deinit(iter);
}
void clients_init(void)
{
clients = hash_create(default_pool, default_pool, 128, NULL, NULL);
to_idle = timeout_add(1000, idle_timeout, NULL);
}
void clients_deinit(void)
{
clients_destroy_all();
hash_destroy(clients);
timeout_remove(&to_idle);
}