client-common.c revision 2dfd08e8aa16dfcc975d8a62bc8d20b2ef849d71
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen/* Copyright (c) 2002-2015 Dovecot authors, see the included COPYING file */
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen#include "login-common.h"
767431e5084a037c4dbefdf30ebfa03c84b1f449Timo Sirainen#include "hostpid.h"
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen#include "llist.h"
1c633f71ec2060e5bfa500a97f34cd881a958ecdTimo Sirainen#include "istream.h"
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen#include "ostream.h"
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen#include "iostream-rawlog.h"
5fb3f13537dffd15a31e997da133a721c0728af8Timo Sirainen#include "process-title.h"
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen#include "buffer.h"
97437f768d1a3e6134fed1971202803fd250eef2Timo Sirainen#include "str.h"
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen#include "base64.h"
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen#include "str-sanitize.h"
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen#include "safe-memset.h"
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen#include "var-expand.h"
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen#include "master-interface.h"
e09c7dc961cb9cab04ec7cc79215c2f6318fbde0Timo Sirainen#include "master-service.h"
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen#include "master-service-ssl-settings.h"
23878bd03d1de531e3261a25598beec621351910Timo Sirainen#include "master-auth.h"
23878bd03d1de531e3261a25598beec621351910Timo Sirainen#include "auth-client.h"
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen#include "dsasl-client.h"
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen#include "login-proxy.h"
62d0db14d2c5008758983c28d242ec158baabf9eTimo Sirainen#include "ssl-proxy.h"
62d0db14d2c5008758983c28d242ec158baabf9eTimo Sirainen#include "client-common.h"
62d0db14d2c5008758983c28d242ec158baabf9eTimo Sirainen
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen#include <stdlib.h>
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainenstruct client *clients = NULL;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainenstatic struct client *last_client = NULL;
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainenstatic unsigned int clients_count = 0;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainenstatic void client_idle_disconnect_timeout(struct client *client)
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen{
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen const char *user_reason, *destroy_reason;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen unsigned int secs;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen if (client->master_tag != 0) {
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen secs = ioloop_time - client->auth_finished;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen user_reason = "Timeout while finishing login.";
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen destroy_reason = t_strdup_printf(
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen "Timeout while finishing login (waited %u secs)", secs);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen client_log_err(client, destroy_reason);
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen } else if (client->auth_request != NULL) {
10b8040903b1d1591f1d44552ff466c8789b8814Timo Sirainen user_reason =
10b8040903b1d1591f1d44552ff466c8789b8814Timo Sirainen "Disconnected for inactivity during authentication.";
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen destroy_reason =
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen "Disconnected: Inactivity during authentication";
72bc08129fb0aaec8144cc183a998ccc426fef9eTimo Sirainen } else if (client->login_proxy != NULL) {
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen secs = ioloop_time - client->created;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen user_reason = "Timeout while finishing login.";
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen destroy_reason = t_strdup_printf(
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen "proxy: Logging in to %s:%u timed out "
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen "(state=%u, duration=%us)",
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen login_proxy_get_host(client->login_proxy),
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen login_proxy_get_port(client->login_proxy),
7358272563d8ef77366447708ab0e58c0cff4151Timo Sirainen client->proxy_state, secs);
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen client_log_err(client, destroy_reason);
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen } else {
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen user_reason = "Disconnected for inactivity.";
7358272563d8ef77366447708ab0e58c0cff4151Timo Sirainen destroy_reason = "Disconnected: Inactivity";
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen }
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen client_notify_disconnect(client, CLIENT_DISCONNECT_TIMEOUT, user_reason);
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen client_destroy(client, destroy_reason);
29f138b4b9bc037b21dfaa6b8e458943a99d5db2Timo Sirainen}
29f138b4b9bc037b21dfaa6b8e458943a99d5db2Timo Sirainen
29f138b4b9bc037b21dfaa6b8e458943a99d5db2Timo Sirainenstatic void client_open_streams(struct client *client)
7358272563d8ef77366447708ab0e58c0cff4151Timo Sirainen{
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen client->input =
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen i_stream_create_fd(client->fd, LOGIN_MAX_INBUF_SIZE, FALSE);
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen client->output =
23878bd03d1de531e3261a25598beec621351910Timo Sirainen o_stream_create_fd(client->fd, LOGIN_MAX_OUTBUF_SIZE, FALSE);
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen o_stream_set_no_error_handling(client->output, TRUE);
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen if (login_rawlog_dir != NULL) {
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen if (iostream_rawlog_create(login_rawlog_dir, &client->input,
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen &client->output) < 0)
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen login_rawlog_dir = NULL;
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen }
1db62753d9e3b5d71018889c8ef0a3722a307455Timo Sirainen}
ad58b50aef8125981ebdbc89513236558bcccf60Timo Sirainen
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainenstatic bool client_is_trusted(struct client *client)
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen{
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen const char *const *net;
0779e926687b319fe1bcc0f1010ba7f88023e789Timo Sirainen struct ip_addr net_ip;
0f9a8663b0ff6fe30389d02284a2b002c40914ebTimo Sirainen unsigned int bits;
bd417d416988d11a6b555b9aa57779e7ed976951Timo Sirainen
a9efdb661eb7a8a33aacfdcc3486dcc675a21543Timo Sirainen if (client->set->login_trusted_networks == NULL)
a9efdb661eb7a8a33aacfdcc3486dcc675a21543Timo Sirainen return FALSE;
fab850a6aee4aaef4f4795bd7946807a3ba45041Timo Sirainen
bd417d416988d11a6b555b9aa57779e7ed976951Timo Sirainen net = t_strsplit_spaces(client->set->login_trusted_networks, ", ");
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen for (; *net != NULL; net++) {
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen if (net_parse_range(*net, &net_ip, &bits) < 0) {
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen i_error("login_trusted_networks: "
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen "Invalid network '%s'", *net);
0779e926687b319fe1bcc0f1010ba7f88023e789Timo Sirainen break;
72bc08129fb0aaec8144cc183a998ccc426fef9eTimo Sirainen }
72bc08129fb0aaec8144cc183a998ccc426fef9eTimo Sirainen
5230375627245d0c7ceb636ad10b985f4444e49eTimo Sirainen if (net_is_in_network(&client->ip, &net_ip, bits))
885a3c2287ae3e5827aa580ea06b231de38abb47Timo Sirainen return TRUE;
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen }
72bc08129fb0aaec8144cc183a998ccc426fef9eTimo Sirainen return FALSE;
2cc88ff507e244faa63683f804833b321a62c665Timo Sirainen}
71df09024cea5f2faa93da3bb9513ee96ba6bf22Timo Sirainen
0779e926687b319fe1bcc0f1010ba7f88023e789Timo Sirainenstruct client *
51327f2489a4e0e615eb9f7d921473cf8512bb79Timo Sirainenclient_create(int fd, bool ssl, pool_t pool,
51327f2489a4e0e615eb9f7d921473cf8512bb79Timo Sirainen const struct master_service_connection *conn,
97437f768d1a3e6134fed1971202803fd250eef2Timo Sirainen const struct login_settings *set,
97437f768d1a3e6134fed1971202803fd250eef2Timo Sirainen const struct master_service_ssl_settings *ssl_set,
97afa073e3e1e0301dc41173ec34beb08edcce50Timo Sirainen void **other_sets)
97afa073e3e1e0301dc41173ec34beb08edcce50Timo Sirainen{
636f017be100bce67d66fd3ae1544a47681efd33Timo Sirainen struct client *client;
b8b085f7bc6f1c0367802a9f00062bbbd981690dTimo Sirainen
b8b085f7bc6f1c0367802a9f00062bbbd981690dTimo Sirainen i_assert(fd != -1);
94ba4820927b906b333e39445c1508a29387c3aaTimo Sirainen
b932ee7fbbec6e79b777dcc7ba613b9e99f8337bTimo Sirainen client = login_binary->client_vfuncs->alloc(pool);
b932ee7fbbec6e79b777dcc7ba613b9e99f8337bTimo Sirainen client->v = *login_binary->client_vfuncs;
cf63dc8723b971cc80638fccbf494d961cbafc7fTimo Sirainen if (client->v.auth_send_challenge == NULL)
23878bd03d1de531e3261a25598beec621351910Timo Sirainen client->v.auth_send_challenge = client_auth_send_challenge;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen if (client->v.auth_parse_response == NULL)
23878bd03d1de531e3261a25598beec621351910Timo Sirainen client->v.auth_parse_response = client_auth_parse_response;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen client->created = ioloop_time;
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen client->refcount = 1;
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen client->pool = pool;
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen client->set = set;
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen client->ssl_set = ssl_set;
5fbccc935e3f7b916aa7c6e302a212821072e83aTimo Sirainen
23878bd03d1de531e3261a25598beec621351910Timo Sirainen client->fd = fd;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen client->tls = ssl;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen
88c816e8be4e1a29bca8b67d67a92c67a33f3795Timo Sirainen client->local_ip = conn->local_ip;
643a81fff9003cba13deb49a565a3c8171da524dTimo Sirainen client->local_port = conn->local_port;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen client->ip = conn->remote_ip;
b932ee7fbbec6e79b777dcc7ba613b9e99f8337bTimo Sirainen client->remote_port = conn->remote_port;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen client->real_local_ip = conn->real_local_ip;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen client->real_local_port = conn->real_local_port;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen client->real_remote_ip = conn->real_remote_ip;
23878bd03d1de531e3261a25598beec621351910Timo Sirainen client->real_remote_port = conn->real_remote_port;
ecbbdf594f9329fc15a182bd6c7c4a7fb144ed74Timo Sirainen client->listener_name = p_strdup(client->pool, conn->name);
ecbbdf594f9329fc15a182bd6c7c4a7fb144ed74Timo Sirainen
b8b085f7bc6f1c0367802a9f00062bbbd981690dTimo Sirainen client->trusted = client_is_trusted(client);
94ba4820927b906b333e39445c1508a29387c3aaTimo Sirainen client->secured = ssl || client->trusted ||
a6ab8f00351265e35b79f3a22b1f5a4978ae5c35Timo Sirainen net_ip_compare(&conn->real_remote_ip, &conn->real_local_ip);
client->proxy_ttl = LOGIN_PROXY_TTL;
if (last_client == NULL)
last_client = client;
DLLIST_PREPEND(&clients, client);
clients_count++;
client->to_disconnect =
timeout_add(CLIENT_LOGIN_TIMEOUT_MSECS,
client_idle_disconnect_timeout, client);
client_open_streams(client);
client->v.create(client, other_sets);
if (auth_client_is_connected(auth_client))
client_notify_auth_ready(client);
else
client_set_auth_waiting(client);
login_refresh_proctitle();
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);
if (last_client == client)
last_client = client->prev;
DLLIST_REMOVE(&clients, client);
if (client->output != NULL)
o_stream_uncork(client->output);
if (!client->login_success && client->ssl_proxy != NULL)
ssl_proxy_destroy(client->ssl_proxy);
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_auth, 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_disconnect != NULL)
timeout_remove(&client->to_disconnect);
if (client->to_auth_waiting != NULL)
timeout_remove(&client->to_auth_waiting);
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);
}
if (client->proxy_sasl_client != NULL)
dsasl_client_free(&client->proxy_sasl_client);
if (client->login_proxy != NULL)
login_proxy_free(&client->login_proxy);
if (client->v.destroy != NULL)
client->v.destroy(client);
if (client_unref(&client) && initial_service_count == 1) {
/* as soon as this connection is done with proxying
(or whatever), the process will die. there's no need for
authentication anymore, so close the connection.
do this only with initial service_count=1, in case there
are other clients with pending authentications */
auth_client_disconnect(auth_client, "unnecessary connection");
}
login_client_destroyed();
login_refresh_proctitle();
}
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_notify_disconnect(client, CLIENT_DISCONNECT_INTERNAL_ERROR,
"Internal login failure. "
"Refer to server log for more information.");
client_destroy(client, t_strdup_printf(
"Internal login failure (pid=%s id=%u)",
my_pid, client->master_auth_id));
}
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->login_proxy == NULL);
if (client->ssl_proxy != NULL)
ssl_proxy_free(&client->ssl_proxy);
if (client->input != NULL)
i_stream_unref(&client->input);
if (client->output != NULL)
o_stream_unref(&client->output);
i_free(client->proxy_user);
i_free(client->proxy_master_user);
i_free(client->virtual_user);
i_free(client->virtual_user_orig);
i_free(client->virtual_auth_user);
i_free(client->auth_mech_name);
i_free(client->master_data_prefix);
pool_unref(&client->pool);
i_assert(clients_count > 0);
clients_count--;
master_service_client_connection_destroyed(master_service);
login_refresh_proctitle();
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_notify_disconnect(client, CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
"Connection queue full");
client_destroy(client, "Disconnected: Connection queue full");
}
void clients_destroy_all_reason(const char *reason)
{
struct client *client, *next;
for (client = clients; client != NULL; client = next) {
next = client->next;
client_notify_disconnect(client,
CLIENT_DISCONNECT_SYSTEM_SHUTDOWN, reason);
client_destroy(client, reason);
}
}
void clients_destroy_all(void)
{
clients_destroy_all_reason("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_alloc(client->fd, &client->ip, client->pool,
client->set, client->ssl_set,
&client->ssl_proxy);
if (fd_ssl == -1) {
client_notify_disconnect(client,
CLIENT_DISCONNECT_INTERNAL_ERROR,
"TLS initialization failed.");
client_destroy(client,
"Disconnected: TLS initialization failed.");
return;
}
ssl_proxy_set_client(client->ssl_proxy, client);
ssl_proxy_start(client->ssl_proxy);
client->starttls = TRUE;
client->tls = TRUE;
client->secured = TRUE;
login_refresh_proctitle();
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->v.notify_starttls(client, FALSE, "TLS is already active.");
return;
}
if (!client_is_tls_enabled(client)) {
client->v.notify_starttls(client, FALSE, "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->v.notify_starttls(client, TRUE, "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;
}
const char *client_get_session_id(struct client *client)
{
buffer_t *buf, *base64_buf;
struct timeval tv;
uint64_t timestamp;
unsigned int i;
if (client->session_id != NULL)
return client->session_id;
buf = buffer_create_dynamic(pool_datastack_create(), 24);
base64_buf = buffer_create_dynamic(pool_datastack_create(), 24*2);
if (gettimeofday(&tv, NULL) < 0)
i_fatal("gettimeofday(): %m");
timestamp = tv.tv_usec + (long long)tv.tv_sec * 1000ULL*1000ULL;
/* add lowest 48 bits of the timestamp. this gives us a bit less than
9 years until it wraps */
for (i = 0; i < 48; i += 8)
buffer_append_c(buf, (timestamp >> i) & 0xff);
buffer_append_c(buf, client->remote_port & 0xff);
buffer_append_c(buf, (client->remote_port >> 16) & 0xff);
#ifdef HAVE_IPV6
if (IPADDR_IS_V6(&client->ip))
buffer_append(buf, &client->ip.u.ip6, sizeof(client->ip.u.ip6));
else
#endif
buffer_append(buf, &client->ip.u.ip4, sizeof(client->ip.u.ip4));
base64_encode(buf->data, buf->used, base64_buf);
client->session_id = p_strdup(client->pool, str_c(base64_buf));
return client->session_id;
}
static struct var_expand_table login_var_expand_empty_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, "session" },
{ '\0', NULL, "real_lip" },
{ '\0', NULL, "real_rip" },
{ '\0', NULL, "real_lport" },
{ '\0', NULL, "real_rport" },
{ '\0', NULL, "orig_user" },
{ '\0', NULL, "orig_username" },
{ '\0', NULL, "orig_domain" },
{ '\0', NULL, "auth_user" },
{ '\0', NULL, "auth_username" },
{ '\0', NULL, "auth_domain" },
{ '\0', NULL, "listener" },
{ '\0', NULL, NULL }
};
static void
get_var_expand_users(struct var_expand_table *tab, const char *user)
{
unsigned int i;
tab[0].value = user;
tab[1].value = t_strcut(user, '@');
tab[2].value = strchr(user, '@');
if (tab[2].value != NULL) tab[2].value++;
for (i = 0; i < 3; i++)
tab[i].value = str_sanitize(tab[i].value, 80);
}
static const struct var_expand_table *
get_var_expand_table(struct client *client)
{
struct var_expand_table *tab;
tab = t_malloc(sizeof(login_var_expand_empty_tab));
memcpy(tab, login_var_expand_empty_tab,
sizeof(login_var_expand_empty_tab));
if (client->virtual_user != NULL)
get_var_expand_users(tab, client->virtual_user);
tab[3].value = login_binary->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 = client->mail_pid == 0 ? "" :
dec2str(client->mail_pid);
tab[14].value = client_get_session_id(client);
tab[15].value = net_ip2addr(&client->real_local_ip);
tab[16].value = net_ip2addr(&client->real_remote_ip);
tab[17].value = dec2str(client->real_local_port);
tab[18].value = dec2str(client->real_remote_port);
if (client->virtual_user_orig != NULL)
get_var_expand_users(tab+19, client->virtual_user_orig);
else {
tab[19].value = tab[0].value;
tab[20].value = tab[1].value;
tab[21].value = tab[2].value;
}
if (client->virtual_auth_user != NULL)
get_var_expand_users(tab+22, client->virtual_auth_user);
else {
tab[22].value = tab[19].value;
tab[23].value = tab[20].value;
tab[24].value = tab[21].value;
}
tab[25].value = client->listener_name;
return tab;
}
static bool have_username_key(const char *str)
{
char key;
for (; *str != '\0'; str++) {
if (str[0] == '%' && str[1] != '\0') {
str++;
key = var_get_key(str);
if (key == 'u' || key == 'n')
return TRUE;
}
}
return FALSE;
}
static const char *
client_var_expand_func_passdb(const char *data, void *context)
{
struct client *client = context;
const char *field_name = data;
unsigned int i, field_name_len;
if (client->auth_passdb_args == NULL)
return NULL;
field_name_len = strlen(field_name);
for (i = 0; client->auth_passdb_args[i] != NULL; i++) {
if (strncmp(client->auth_passdb_args[i], field_name,
field_name_len) == 0 &&
client->auth_passdb_args[i][field_name_len] == '=')
return client->auth_passdb_args[i] + field_name_len+1;
}
return NULL;
}
static const char *
client_get_log_str(struct client *client, const char *msg)
{
static const struct var_expand_table static_tab[3] = {
{ 's', NULL, NULL },
{ '$', NULL, NULL },
{ '\0', NULL, NULL }
};
static const struct var_expand_func_table func_table[] = {
{ "passdb", client_var_expand_func_passdb },
{ NULL, NULL }
};
const struct var_expand_table *var_expand_table;
struct var_expand_table *tab;
char *const *e;
string_t *str, *str2;
unsigned int pos;
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);
str2 = t_str_new(128);
for (e = client->set->log_format_elements_split; *e != NULL; e++) {
pos = str_len(str);
var_expand_with_funcs(str, *e, var_expand_table,
func_table, client);
if (have_username_key(*e)) {
/* username is added even if it's empty */
} else {
str_truncate(str2, 0);
var_expand(str2, *e, login_var_expand_empty_tab);
if (strcmp(str_c(str)+pos, str_c(str2)) == 0) {
/* empty %variables, don't add */
str_truncate(str, pos);
continue;
}
}
if (str_len(str) > 0)
str_append(str, ", ");
}
if (str_len(str) > 0)
str_truncate(str, str_len(str)-2);
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;
}
void client_log_warn(struct client *client, const char *msg)
{
T_BEGIN {
i_warning("%s", client_get_log_str(client, msg));
} T_END;
}
bool client_is_tls_enabled(struct client *client)
{
return ssl_initialized && strcmp(client->ssl_set->ssl, "no") != 0;
}
const char *client_get_extra_disconnect_reason(struct client *client)
{
unsigned int auth_secs = client->auth_first_started == 0 ? 0 :
ioloop_time - client->auth_first_started;
if (client->set->auth_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->notified_auth_ready)
return t_strdup_printf(
"(disconnected before auth was ready, waited %u secs)",
(unsigned int)(ioloop_time - client->created));
if (client->auth_attempts == 0) {
return t_strdup_printf("(no auth attempts in %u secs)",
(unsigned int)(ioloop_time - client->created));
}
/* some auth attempts without SSL/TLS */
if (client->auth_tried_disabled_plaintext)
return "(tried to use disallowed plaintext auth)";
if (client->set->auth_ssl_require_client_cert &&
client->ssl_proxy == NULL)
return "(cert required, client didn't start TLS)";
if (client->auth_tried_unsupported_mech)
return "(tried to use unsupported auth mechanism)";
if (client->auth_waiting && client->auth_attempts == 1) {
return t_strdup_printf("(client didn't finish SASL auth, "
"waited %u secs)", auth_secs);
}
if (client->auth_request != NULL && client->auth_attempts == 1) {
return t_strdup_printf("(disconnected while authenticating, "
"waited %u secs)", auth_secs);
}
if (client->authenticating && client->auth_attempts == 1) {
return t_strdup_printf("(disconnected while finishing login, "
"waited %u secs)", auth_secs);
}
if (client->auth_try_aborted && client->auth_attempts == 1)
return "(aborted authentication)";
if (client->auth_process_comm_fail)
return "(auth process communication failure)";
if (client->proxy_auth_failed)
return "(proxy dest auth failed)";
if (client->auth_successes > 0) {
return t_strdup_printf("(internal failure, %u successful auths)",
client->auth_successes);
}
if (client->auth_user_disabled)
return "(user disabled)";
if (client->auth_pass_expired)
return "(password expired)";
return t_strdup_printf("(auth failed, %u attempts in %u secs)",
client->auth_attempts, auth_secs);
}
void client_notify_disconnect(struct client *client,
enum client_disconnect_reason reason,
const char *text)
{
if (!client->notified_disconnect) {
if (client->v.notify_disconnect != NULL)
client->v.notify_disconnect(client, reason, text);
client->notified_disconnect = TRUE;
}
}
void client_notify_auth_ready(struct client *client)
{
if (!client->notified_auth_ready) {
if (client->v.notify_auth_ready != NULL)
client->v.notify_auth_ready(client);
client->notified_auth_ready = TRUE;
}
}
void client_notify_status(struct client *client, bool bad, const char *text)
{
if (client->v.notify_status != NULL)
client->v.notify_status(client, bad, 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_notify_disconnect(client,
CLIENT_DISCONNECT_RESOURCE_CONSTRAINT,
"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 */
return TRUE;
}
}
void client_input(struct client *client)
{
client->v.input(client);
}