client-common.c revision d873ff0b3e44137e712ed274e9b0e0fd3baea521
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen/* Copyright (c) 2002-2009 Dovecot authors, see the included COPYING file */
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "common.h"
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen#include "hostpid.h"
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen#include "llist.h"
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen#include "istream.h"
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen#include "ostream.h"
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen#include "process-title.h"
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen#include "str.h"
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen#include "str-sanitize.h"
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen#include "safe-memset.h"
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen#include "var-expand.h"
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen#include "master-service.h"
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen#include "master-auth.h"
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen#include "auth-client.h"
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen#include "login-proxy.h"
16c89b1260c9d07c01c83a9219424d3727069b2eTimo Sirainen#include "ssl-proxy.h"
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen#include "client-common.h"
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen#include <stdlib.h>
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstruct client *clients = NULL, *last_client = NULL;
31ddc75584c5cde53d2e78a737587f2e7fdcb0d2Timo Sirainenstatic unsigned int clients_count = 0;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstatic void client_idle_disconnect_timeout(struct client *client)
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen{
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen client_send_line(client, CLIENT_CMD_REPLY_BYE,
c251a38df327599a62d341bf5c2282f31352faa5Timo Sirainen "Disconnected for inactivity.");
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen client_destroy(client, "Disconnected: Inactivity");
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen}
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainenstatic void client_open_streams(struct client *client)
7797aa2479e99aeb71057b7a2584b2cb72e4d3f8Timo Sirainen{
bbf796c17f02538058d7559bfe96d677e5b55015Timo Sirainen client->input =
6a19e109ee8c5a6f688da83a86a7f6abeb71abddTimo Sirainen i_stream_create_fd(client->fd, LOGIN_MAX_INBUF_SIZE, FALSE);
41e1c7380edda701719d8ce1fb4d465d2ec4c84dTimo Sirainen client->output =
1175f27441385a7011629f295f42708f9a3a4ffcTimo Sirainen o_stream_create_fd(client->fd, LOGIN_MAX_OUTBUF_SIZE, FALSE);
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen}
72cbf33ae81fde08384d30c779ff540752d9256cTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainenstruct client *client_create(int fd, bool ssl, pool_t pool,
1b3bb8d39686ed24730cbc31cc9a33dc62c8c6c3Timo Sirainen const struct login_settings *set,
7c95b03620a03a43dd72d39608cea5fc77393ad6Timo Sirainen const struct ip_addr *local_ip,
e06c0b65c16ccce69bbee009ead14d7d3d17a256Timo Sirainen const struct ip_addr *remote_ip)
73e7998716853b5b7621c06aea0022dccda70ad1Timo Sirainen{
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen struct client *client;
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen i_assert(fd != -1);
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen
24fc71a693331ffe77e2b6d81c70aca6fa055e47Timo Sirainen client = client_vfuncs.alloc(pool);
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen client->v = client_vfuncs;
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen if (client->v.auth_send_challenge == NULL)
09c3a491f4f6ccebe290c7709bdc0d79a187610bTimo Sirainen client->v.auth_send_challenge = client_auth_send_challenge;
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen if (client->v.auth_parse_response == NULL)
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen client->v.auth_parse_response = client_auth_parse_response;
5a07b37a9df398b5189c14872a600384208ab74bTimo Sirainen
0cb2e8eb55e70f8ebe1e8349bdf49e4cbe5d8834Timo Sirainen client->created = ioloop_time;
client->refcount = 1;
client->pool = pool;
client->set = set;
client->local_ip = *local_ip;
client->ip = *remote_ip;
client->fd = fd;
client->tls = ssl;
client->trusted = client_is_trusted(client);
client->secured = ssl || client->trusted ||
net_ip_compare(remote_ip, local_ip);
if (last_client == NULL)
last_client = client;
DLLIST_PREPEND(&clients, client);
clients_count++;
client_set_title(client);
client->to_idle_disconnect =
timeout_add(CLIENT_LOGIN_IDLE_TIMEOUT_MSECS,
client_idle_disconnect_timeout, client);
client_open_streams(client);
client->v.create(client);
if (auth_client_is_connected(auth_client))
client->v.send_greeting(client);
else
client_set_auth_waiting(client);
return client;
}
void client_destroy(struct client *client, const char *reason)
{
if (client->destroyed)
return;
client->destroyed = TRUE;
if (!client->login_success && reason != NULL) {
reason = t_strconcat(reason, " ",
client_get_extra_disconnect_reason(client), NULL);
}
if (reason != NULL)
client_log(client, reason);
i_assert(clients_count > 0);
clients_count--;
if (last_client == client) {
i_assert(client->prev != NULL || clients_count == 0);
last_client = client->prev;
}
DLLIST_REMOVE(&clients, client);
if (client->input != NULL)
i_stream_close(client->input);
if (client->output != NULL)
o_stream_close(client->output);
if (client->master_tag != 0) {
i_assert(client->auth_request == NULL);
i_assert(client->authenticating);
i_assert(client->refcount > 1);
client->authenticating = FALSE;
master_auth_request_abort(master_service, client->master_tag);
client->refcount--;
} else if (client->auth_request != NULL) {
i_assert(client->authenticating);
sasl_server_auth_abort(client);
} else {
i_assert(!client->authenticating);
}
if (client->io != NULL)
io_remove(&client->io);
if (client->to_idle_disconnect != NULL)
timeout_remove(&client->to_idle_disconnect);
if (client->to_auth_waiting != NULL)
timeout_remove(&client->to_auth_waiting);
if (client->to_authfail_delay != NULL)
timeout_remove(&client->to_authfail_delay);
if (client->auth_response != NULL)
str_free(&client->auth_response);
if (client->fd != -1) {
net_disconnect(client->fd);
client->fd = -1;
}
if (client->proxy_password != NULL) {
safe_memset(client->proxy_password, 0,
strlen(client->proxy_password));
i_free_and_null(client->proxy_password);
}
i_free_and_null(client->proxy_user);
i_free_and_null(client->proxy_master_user);
if (client->login_proxy != NULL)
login_proxy_free(&client->login_proxy);
if (client->ssl_proxy != NULL)
ssl_proxy_free(&client->ssl_proxy);
client->v.destroy(client);
client_unref(&client);
}
void client_destroy_success(struct client *client, const char *reason)
{
client->login_success = TRUE;
client_destroy(client, reason);
}
void client_destroy_internal_failure(struct client *client)
{
client_send_line(client, CLIENT_CMD_REPLY_AUTH_FAIL_TEMP,
"Internal login failure. "
"Refer to server log for more information.");
client_destroy(client, "Internal login failure");
}
void client_ref(struct client *client)
{
client->refcount++;
}
bool client_unref(struct client **_client)
{
struct client *client = *_client;
i_assert(client->refcount > 0);
if (--client->refcount > 0)
return TRUE;
*_client = NULL;
i_assert(client->destroyed);
i_assert(client->ssl_proxy == NULL);
i_assert(client->login_proxy == NULL);
if (client->input != NULL)
i_stream_unref(&client->input);
if (client->output != NULL)
o_stream_unref(&client->output);
i_free(client->virtual_user);
i_free(client->auth_mech_name);
pool_unref(&client->pool);
master_service_client_connection_destroyed(master_service);
return FALSE;
}
void client_destroy_oldest(void)
{
struct client *client;
if (last_client == NULL) {
/* we have no clients */
return;
}
/* destroy the last client that hasn't successfully authenticated yet.
this is usually the last client, but don't kill it if it's just
waiting for master to finish its job. */
for (client = last_client; client != NULL; client = client->prev) {
if (client->master_tag == 0)
break;
}
if (client == NULL)
client = last_client;
client_destroy(client, "Disconnected: Connection queue full");
}
void clients_destroy_all(void)
{
struct client *client, *next;
for (client = clients; client != NULL; client = next) {
next = client->next;
client_destroy(client, "Disconnected: Shutting down");
}
}
static void client_start_tls(struct client *client)
{
int fd_ssl;
client_ref(client);
if (!client_unref(&client) || client->destroyed)
return;
fd_ssl = ssl_proxy_new(client->fd, &client->ip,
client->set, &client->ssl_proxy);
if (fd_ssl == -1) {
client_send_line(client, CLIENT_CMD_REPLY_BYE,
"TLS initialization failed.");
client_destroy(client,
"Disconnected: TLS initialization failed.");
return;
}
ssl_proxy_set_client(client->ssl_proxy, client);
client->starttls = TRUE;
client->tls = TRUE;
client->secured = TRUE;
client_set_title(client);
client->fd = fd_ssl;
client->io = io_add(client->fd, IO_READ, client_input, client);
i_stream_unref(&client->input);
o_stream_unref(&client->output);
client_open_streams(client);
client->v.starttls(client);
}
static int client_output_starttls(struct client *client)
{
int ret;
if ((ret = o_stream_flush(client->output)) < 0) {
client_destroy(client, "Disconnected");
return 1;
}
if (ret > 0) {
o_stream_unset_flush_callback(client->output);
client_start_tls(client);
}
return 1;
}
void client_cmd_starttls(struct client *client)
{
if (client->tls) {
client_send_line(client, CLIENT_CMD_REPLY_BAD,
"TLS is already active.");
return;
}
if (!ssl_initialized) {
client_send_line(client, CLIENT_CMD_REPLY_BAD,
"TLS support isn't enabled.");
return;
}
/* remove input handler, SSL proxy gives us a new fd. we also have to
remove it in case we have to wait for buffer to be flushed */
if (client->io != NULL)
io_remove(&client->io);
client_send_line(client, CLIENT_CMD_REPLY_OK,
"Begin TLS negotiation now.");
/* uncork the old fd */
o_stream_uncork(client->output);
if (o_stream_flush(client->output) <= 0) {
/* the buffer has to be flushed */
o_stream_set_flush_pending(client->output, TRUE);
o_stream_set_flush_callback(client->output,
client_output_starttls, client);
} else {
client_start_tls(client);
}
}
unsigned int clients_get_count(void)
{
return clients_count;
}
void client_set_title(struct client *client)
{
const char *addr;
if (!client->set->verbose_proctitle ||
master_service_get_client_limit(master_service) > 1)
return;
addr = net_ip2addr(&client->ip);
if (addr == NULL)
addr = "??";
process_title_set(t_strdup_printf(client->tls ?
"[%s TLS]" : "[%s]", addr));
}
static const struct var_expand_table *
get_var_expand_table(struct client *client)
{
static struct var_expand_table static_tab[] = {
{ 'u', NULL, "user" },
{ 'n', NULL, "username" },
{ 'd', NULL, "domain" },
{ 's', NULL, "service" },
{ 'h', NULL, "home" },
{ 'l', NULL, "lip" },
{ 'r', NULL, "rip" },
{ 'p', NULL, "pid" },
{ 'm', NULL, "mech" },
{ 'a', NULL, "lport" },
{ 'b', NULL, "rport" },
{ 'c', NULL, "secured" },
{ 'k', NULL, "ssl_security" },
{ 'e', NULL, "mail_pid" },
{ '\0', NULL, NULL }
};
struct var_expand_table *tab;
unsigned int i;
tab = t_malloc(sizeof(static_tab));
memcpy(tab, static_tab, sizeof(static_tab));
if (client->virtual_user != NULL) {
tab[0].value = client->virtual_user;
tab[1].value = t_strcut(client->virtual_user, '@');
tab[2].value = strchr(client->virtual_user, '@');
if (tab[2].value != NULL) tab[2].value++;
for (i = 0; i < 3; i++)
tab[i].value = str_sanitize(tab[i].value, 80);
}
tab[3].value = login_protocol;
tab[4].value = getenv("HOME");
tab[5].value = net_ip2addr(&client->local_ip);
tab[6].value = net_ip2addr(&client->ip);
tab[7].value = my_pid;
tab[8].value = client->auth_mech_name == NULL ? NULL :
str_sanitize(client->auth_mech_name, MAX_MECH_NAME);
tab[9].value = dec2str(client->local_port);
tab[10].value = dec2str(client->remote_port);
if (!client->tls) {
tab[11].value = client->secured ? "secured" : NULL;
tab[12].value = "";
} else {
const char *ssl_state =
ssl_proxy_is_handshaked(client->ssl_proxy) ?
"TLS" : "TLS handshaking";
const char *ssl_error =
ssl_proxy_get_last_error(client->ssl_proxy);
tab[11].value = ssl_error == NULL ? ssl_state :
t_strdup_printf("%s: %s", ssl_state, ssl_error);
tab[12].value =
ssl_proxy_get_security_string(client->ssl_proxy);
}
tab[13].value = dec2str(client->mail_pid);
return tab;
}
static bool have_key(const struct var_expand_table *table, const char *str)
{
char key;
unsigned int i;
key = var_get_key(str);
for (i = 0; table[i].key != '\0'; i++) {
if (table[i].key == key) {
return table[i].value != NULL &&
table[i].value[0] != '\0';
}
}
return FALSE;
}
static const char *
client_get_log_str(struct client *client, const char *msg)
{
static struct var_expand_table static_tab[3] = {
{ 's', NULL, NULL },
{ '$', NULL, NULL },
{ '\0', NULL, NULL }
};
const struct var_expand_table *var_expand_table;
struct var_expand_table *tab;
const char *p;
char *const *e;
string_t *str;
var_expand_table = get_var_expand_table(client);
tab = t_malloc(sizeof(static_tab));
memcpy(tab, static_tab, sizeof(static_tab));
str = t_str_new(256);
for (e = client->set->log_format_elements_split; *e != NULL; e++) {
for (p = *e; *p != '\0'; p++) {
if (*p != '%' || p[1] == '\0')
continue;
p++;
if (have_key(var_expand_table, p)) {
if (str_len(str) > 0)
str_append(str, ", ");
var_expand(str, *e, var_expand_table);
break;
}
}
}
tab[0].value = t_strdup(str_c(str));
tab[1].value = msg;
str_truncate(str, 0);
var_expand(str, client->set->login_log_format, tab);
return str_c(str);
}
void client_log(struct client *client, const char *msg)
{
T_BEGIN {
i_info("%s", client_get_log_str(client, msg));
} T_END;
}
void client_log_err(struct client *client, const char *msg)
{
T_BEGIN {
i_error("%s", client_get_log_str(client, msg));
} T_END;
}
bool client_is_trusted(struct client *client)
{
const char *const *net;
struct ip_addr net_ip;
unsigned int bits;
if (client->set->login_trusted_networks == NULL)
return FALSE;
net = t_strsplit_spaces(client->set->login_trusted_networks, ", ");
for (; *net != NULL; net++) {
if (net_parse_range(*net, &net_ip, &bits) < 0) {
i_error("login_trusted_networks: "
"Invalid network '%s'", *net);
break;
}
if (net_is_in_network(&client->ip, &net_ip, bits))
return TRUE;
}
return FALSE;
}
const char *client_get_extra_disconnect_reason(struct client *client)
{
if (client->set->ssl_require_client_cert && client->ssl_proxy != NULL) {
if (ssl_proxy_has_broken_client_cert(client->ssl_proxy))
return "(client sent an invalid cert)";
if (!ssl_proxy_has_valid_client_cert(client->ssl_proxy))
return "(client didn't send a cert)";
}
if (client->auth_attempts == 0)
return "(no auth attempts)";
/* some auth attempts without SSL/TLS */
if (client->auth_tried_disabled_plaintext)
return "(tried to use disabled plaintext auth)";
if (client->set->ssl_require_client_cert)
return "(cert required, client didn't start TLS)";
if (client->auth_tried_unsupported_mech)
return "(tried to use unsupported auth mechanism)";
if (client->auth_request != NULL && client->auth_attempts == 1)
return "(disconnected while authenticating)";
if (client->auth_try_aborted && client->auth_attempts == 1)
return "(aborted authentication)";
return t_strdup_printf("(auth failed, %u attempts)",
client->auth_attempts);
}
void client_send_line(struct client *client, enum client_cmd_reply reply,
const char *text)
{
client->v.send_line(client, reply, text);
}
void client_send_raw_data(struct client *client, const void *data, size_t size)
{
ssize_t ret;
ret = o_stream_send(client->output, data, size);
if (ret < 0 || (size_t)ret != size) {
/* 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);
}
}
void client_send_raw(struct client *client, const char *data)
{
client_send_raw_data(client, data, strlen(data));
}
bool client_read(struct client *client)
{
switch (i_stream_read(client->input)) {
case -2:
/* buffer full */
client_send_line(client, CLIENT_CMD_REPLY_BYE,
"Input buffer full, aborting");
client_destroy(client, "Disconnected: Input buffer full");
return FALSE;
case -1:
/* disconnected */
client_destroy(client, "Disconnected");
return FALSE;
case 0:
/* nothing new read */
return TRUE;
default:
/* something was read */
timeout_reset(client->to_idle_disconnect);
return TRUE;
}
}
void client_input(struct client *client)
{
client->v.input(client);
}