client-common.c revision 5f1d689131a75c39f064cbd4202373e7edf78f18
a8c5a86d183db25a57bf193c06b41e092ec2e151Timo Sirainen/* Copyright (c) 2002-2017 Dovecot authors, see the included COPYING file */
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen#include "login-common.h"
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen#include "array.h"
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen#include "hostpid.h"
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen#include "llist.h"
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen#include "istream.h"
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen#include "ostream.h"
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen#include "iostream-rawlog.h"
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen#include "process-title.h"
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen#include "hook-build.h"
93ae7fcd39c6982f7e338adfe71139942d9bbad1Timo Sirainen#include "buffer.h"
93ae7fcd39c6982f7e338adfe71139942d9bbad1Timo Sirainen#include "str.h"
93ae7fcd39c6982f7e338adfe71139942d9bbad1Timo Sirainen#include "strescape.h"
93ae7fcd39c6982f7e338adfe71139942d9bbad1Timo Sirainen#include "base64.h"
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen#include "str-sanitize.h"
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen#include "safe-memset.h"
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen#include "var-expand.h"
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen#include "master-interface.h"
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen#include "master-service.h"
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen#include "master-service-ssl-settings.h"
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen#include "master-auth.h"
8e361d2906b0e44f7175a20981f8d2280645b58bTimo Sirainen#include "auth-client.h"
8e361d2906b0e44f7175a20981f8d2280645b58bTimo Sirainen#include "dsasl-client.h"
3281669db44d09a087a203201248abbc81b3cc1aTimo Sirainen#include "login-proxy.h"
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen#include "ssl-proxy.h"
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen#include "client-common.h"
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainenstruct client *clients = NULL;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainenstatic struct client *last_client = NULL;
cd466fe7b84b0223735a6469c7f7bc225f65996dTimo Sirainenstatic unsigned int clients_count = 0;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainenstruct login_client_module_hooks {
0e3f8c6edad565112d91f0a53568c0313d657e48Timo Sirainen struct module *module;
0e3f8c6edad565112d91f0a53568c0313d657e48Timo Sirainen const struct login_client_hooks *hooks;
0e3f8c6edad565112d91f0a53568c0313d657e48Timo Sirainen};
0e3f8c6edad565112d91f0a53568c0313d657e48Timo Sirainen
0e3f8c6edad565112d91f0a53568c0313d657e48Timo Sirainenstatic ARRAY(struct login_client_module_hooks) module_hooks = ARRAY_INIT;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen
d56384d5226c8860079d0d0b08b83404e8c42986Timo Sirainenvoid login_client_hooks_add(struct module *module,
0f66f12eb4cdbf47670975044c88d8f388bf92dfTimo Sirainen const struct login_client_hooks *hooks)
d56384d5226c8860079d0d0b08b83404e8c42986Timo Sirainen{
d56384d5226c8860079d0d0b08b83404e8c42986Timo Sirainen struct login_client_module_hooks *hook;
4b058f90f9e8a2c6b2eed275de4eb8cc5195a71dTimo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen hook = array_append_space(&module_hooks);
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen hook->module = module;
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen hook->hooks = hooks;
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen}
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainenvoid login_client_hooks_remove(const struct login_client_hooks *hooks)
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen{
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen const struct login_client_module_hooks *module_hook;
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen unsigned int idx = UINT_MAX;
d6af1e63bc7824f1cc5b9b73a1c5f8f8789788d6Timo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen array_foreach(&module_hooks, module_hook) {
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen if (module_hook->hooks == hooks) {
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen idx = array_foreach_idx(&module_hooks, module_hook);
6a9f9a5101b665fd2ef80c9e048a5eace78e01efTimo Sirainen break;
d5cebe7f98e63d4e2822863ef2faa4971e8b3a5dTimo Sirainen }
0f66f12eb4cdbf47670975044c88d8f388bf92dfTimo Sirainen }
6a9f9a5101b665fd2ef80c9e048a5eace78e01efTimo Sirainen i_assert(idx != UINT_MAX);
6a9f9a5101b665fd2ef80c9e048a5eace78e01efTimo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen array_delete(&module_hooks, idx, 1);
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen}
4b058f90f9e8a2c6b2eed275de4eb8cc5195a71dTimo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainenstatic void hook_login_client_allocated(struct client *client)
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen{
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen const struct login_client_module_hooks *module_hook;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct hook_build_context *ctx;
a3ea111cfdbfd4f32baeb0bd7f1d72568c60a023Timo Sirainen
a3ea111cfdbfd4f32baeb0bd7f1d72568c60a023Timo Sirainen ctx = hook_build_init((void *)&client->v, sizeof(client->v));
a3ea111cfdbfd4f32baeb0bd7f1d72568c60a023Timo Sirainen client->vlast = &client->v;
a3ea111cfdbfd4f32baeb0bd7f1d72568c60a023Timo Sirainen array_foreach(&module_hooks, module_hook) {
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen if (module_hook->hooks->client_allocated != NULL) T_BEGIN {
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen module_hook->hooks->client_allocated(client);
4b058f90f9e8a2c6b2eed275de4eb8cc5195a71dTimo Sirainen hook_build_update(ctx, client->vlast);
8d80659e504ffb34bb0c6a633184fece35751b18Timo Sirainen } T_END;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen }
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client->vlast = NULL;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen hook_build_deinit(&ctx);
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen}
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainenstatic void client_idle_disconnect_timeout(struct client *client)
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen{
1f57716285d4c5bc9bf2fd5569e3c85fd496afd9Timo Sirainen const char *user_reason, *destroy_reason;
1f57716285d4c5bc9bf2fd5569e3c85fd496afd9Timo Sirainen unsigned int secs;
1f57716285d4c5bc9bf2fd5569e3c85fd496afd9Timo Sirainen
1f57716285d4c5bc9bf2fd5569e3c85fd496afd9Timo Sirainen if (client->master_tag != 0) {
1f57716285d4c5bc9bf2fd5569e3c85fd496afd9Timo Sirainen secs = ioloop_time - client->auth_finished;
1f57716285d4c5bc9bf2fd5569e3c85fd496afd9Timo Sirainen user_reason = "Timeout while finishing login.";
1f57716285d4c5bc9bf2fd5569e3c85fd496afd9Timo Sirainen destroy_reason = t_strdup_printf(
1f57716285d4c5bc9bf2fd5569e3c85fd496afd9Timo Sirainen "Timeout while finishing login (waited %u secs)", secs);
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen client_log_err(client, destroy_reason);
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen } else if (client->auth_request != NULL) {
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen user_reason =
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen "Disconnected for inactivity during authentication.";
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen destroy_reason =
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen "Disconnected: Inactivity during authentication";
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen } else if (client->login_proxy != NULL) {
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen secs = ioloop_time - client->created;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen user_reason = "Timeout while finishing login.";
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen destroy_reason = t_strdup_printf(
a3ea111cfdbfd4f32baeb0bd7f1d72568c60a023Timo Sirainen "proxy: Logging in to %s:%u timed out "
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen "(state=%s, duration=%us)",
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen login_proxy_get_host(client->login_proxy),
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen login_proxy_get_port(client->login_proxy),
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client_proxy_get_state(client), secs);
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client_log_err(client, destroy_reason);
a29a5b7520f7b8d6cdaf97e66d184b6a9e4f4ecfTimo Sirainen } else {
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen user_reason = "Disconnected for inactivity.";
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen destroy_reason = "Disconnected: Inactivity";
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen }
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client_notify_disconnect(client, CLIENT_DISCONNECT_TIMEOUT, user_reason);
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen client_destroy(client, destroy_reason);
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen}
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainenstatic void client_open_streams(struct client *client)
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen{
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client->input = i_stream_create_fd(client->fd, LOGIN_MAX_INBUF_SIZE);
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client->output = o_stream_create_fd(client->fd, LOGIN_MAX_OUTBUF_SIZE);
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen o_stream_set_no_error_handling(client->output, TRUE);
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen if (login_rawlog_dir != NULL) {
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen if (iostream_rawlog_create(login_rawlog_dir, &client->input,
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen &client->output) < 0)
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen login_rawlog_dir = NULL;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen }
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen}
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainenstatic bool client_is_trusted(struct client *client)
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen{
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen const char *const *net;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen struct ip_addr net_ip;
af208f4a43a81a39a2ec44e68d65d12b95f1b386Timo Sirainen unsigned int bits;
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen if (client->set->login_trusted_networks == NULL)
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen return FALSE;
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen net = t_strsplit_spaces(client->set->login_trusted_networks, ", ");
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen for (; *net != NULL; net++) {
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen if (net_parse_range(*net, &net_ip, &bits) < 0) {
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen i_error("login_trusted_networks: "
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen "Invalid network '%s'", *net);
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen break;
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen }
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen if (net_is_in_network(&client->ip, &net_ip, bits))
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen return TRUE;
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen }
743c3ba9d4b06d4797d06136ec53a4a652422a57Timo Sirainen return FALSE;
743c3ba9d4b06d4797d06136ec53a4a652422a57Timo Sirainen}
22c1ec434d7323e125c150e3fd237316c74de6d5Timo Sirainen
22c1ec434d7323e125c150e3fd237316c74de6d5Timo Sirainenstruct client *
743c3ba9d4b06d4797d06136ec53a4a652422a57Timo Sirainenclient_create(int fd, bool ssl, pool_t pool,
22c1ec434d7323e125c150e3fd237316c74de6d5Timo Sirainen const struct master_service_connection *conn,
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen const struct login_settings *set,
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen const struct master_service_ssl_settings *ssl_set,
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen void **other_sets)
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen{
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen struct client *client;
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen i_assert(fd != -1);
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen client = login_binary->client_vfuncs->alloc(pool);
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen client->v = *login_binary->client_vfuncs;
5dd05e966ffd69181ab3067f6939b03ced68ebc3Timo Sirainen if (client->v.auth_send_challenge == NULL)
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen client->v.auth_send_challenge = client_auth_send_challenge;
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen if (client->v.auth_parse_response == NULL)
b09be485e9373be4288f5615bbce6ebed65a425aTimo Sirainen client->v.auth_parse_response = client_auth_parse_response;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client->created = ioloop_time;
9453e8d75cfd8fab2232cf772e9b120f308fb3eeTimo Sirainen client->refcount = 1;
9453e8d75cfd8fab2232cf772e9b120f308fb3eeTimo Sirainen
9453e8d75cfd8fab2232cf772e9b120f308fb3eeTimo Sirainen client->pool = pool;
9453e8d75cfd8fab2232cf772e9b120f308fb3eeTimo Sirainen client->preproxy_pool = pool_alloconly_create(MEMPOOL_GROWING"preproxy pool", 256);
9453e8d75cfd8fab2232cf772e9b120f308fb3eeTimo Sirainen client->set = set;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen client->ssl_set = ssl_set;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen p_array_init(&client->module_contexts, client->pool, 5);
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client->fd = fd;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client->tls = ssl;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client->local_ip = conn->local_ip;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client->local_port = conn->local_port;
33ca6b017b6ebbd048651b5e3d16915001dbc291Timo Sirainen client->ip = conn->remote_ip;
client->remote_port = conn->remote_port;
client->real_local_ip = conn->real_local_ip;
client->real_local_port = conn->real_local_port;
client->real_remote_ip = conn->real_remote_ip;
client->real_remote_port = conn->real_remote_port;
client->listener_name = p_strdup(client->pool, conn->name);
client->trusted = client_is_trusted(client);
client->secured = ssl || client->trusted ||
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);
hook_login_client_allocated(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->preproxy_pool != NULL)
pool_unref(&client->preproxy_pool);
if (!client->login_success && reason != NULL) {
const char *extra_reason =
client_get_extra_disconnect_reason(client);
if (extra_reason[0] != '\0')
reason = t_strconcat(reason, " ", extra_reason, 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);
}
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->v.free != NULL)
client->v.free(client);
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_common_default_free(struct client *client ATTR_UNUSED)
{
}
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 */
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;
}
void client_add_forward_field(struct client *client, const char *key,
const char *value)
{
if (client->forward_fields == NULL)
client->forward_fields = str_new(client->preproxy_pool, 32);
else
str_append_c(client->forward_fields, '\t');
/* prefixing is done by auth process */
str_append_tabescaped(client->forward_fields, key);
str_append_c(client->forward_fields, '=');
str_append_tabescaped(client->forward_fields, value);
}
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 >> 8) & 0xff);
if (IPADDR_IS_V6(&client->ip))
buffer_append(buf, &client->ip.u.ip6, sizeof(client->ip.u.ip6));
else
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, "local_name" },
{ '\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 = i_strchr_to_next(user, '@');
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_no0(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;
tab[26].value = str_sanitize(client->local_name, 256);
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 int
client_var_expand_func_passdb(const char *data, void *context,
const char **value_r,
const char **error_r ATTR_UNUSED)
{
struct client *client = context;
const char *field_name = data;
unsigned int i;
size_t field_name_len;
*value_r = NULL;
if (client->auth_passdb_args == NULL)
return 1;
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] == '=') {
*value_r = client->auth_passdb_args[i] + field_name_len+1;
return 1;
}
}
return 1;
}
static const char *
client_get_log_str(struct client *client, const char *msg)
{
static const struct var_expand_func_table func_table[] = {
{ "passdb", client_var_expand_func_passdb },
{ NULL, NULL }
};
static bool expand_error_logged = FALSE;
const struct var_expand_table *var_expand_table;
char *const *e;
const char *error;
string_t *str, *str2;
unsigned int pos;
var_expand_table = get_var_expand_table(client);
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);
if (var_expand_with_funcs(str, *e, var_expand_table,
func_table, client, &error) <= 0 &&
!expand_error_logged) {
i_error("Failed to expand log_format_elements=%s: %s",
*e, error);
expand_error_logged = TRUE;
}
if (have_username_key(*e)) {
/* username is added even if it's empty */
} else {
str_truncate(str2, 0);
if (var_expand(str2, *e, login_var_expand_empty_tab,
&error) <= 0) {
/* we just logged this error above. no need
to do it again. */
}
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);
const struct var_expand_table tab[3] = {
{ 's', t_strdup(str_c(str)), NULL },
{ '$', msg, NULL },
{ '\0', NULL, NULL }
};
str_truncate(str, 0);
if (var_expand(str, client->set->login_log_format, tab, &error) <= 0) {
i_error("Failed to expand login_log_format=%s: %s",
client->set->login_log_format, error);
expand_error_logged = TRUE;
}
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) {
if (!client->banner_sent) {
/* disconnected by a plugin */
return "";
}
return t_strdup_printf("(no auth attempts in %u secs)",
(unsigned int)(ioloop_time - client->created));
}
/* some auth attempts without SSL/TLS */
if (client->set->auth_ssl_require_client_cert &&
client->ssl_proxy == NULL)
return "(cert required, client didn't start TLS)";
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);
}
switch (client->last_auth_fail) {
case CLIENT_AUTH_FAIL_CODE_AUTHZFAILED:
return t_strdup_printf(
"(authorization failed, %u attempts in %u secs)",
client->auth_attempts, auth_secs);
case CLIENT_AUTH_FAIL_CODE_TEMPFAIL:
return "(auth service reported temporary failure)";
case CLIENT_AUTH_FAIL_CODE_USER_DISABLED:
return "(user disabled)";
case CLIENT_AUTH_FAIL_CODE_PASS_EXPIRED:
return "(password expired)";
case CLIENT_AUTH_FAIL_CODE_INVALID_BASE64:
return "(sent invalid base64 in response)";
case CLIENT_AUTH_FAIL_CODE_LOGIN_DISABLED:
return "(login disabled)";
case CLIENT_AUTH_FAIL_CODE_MECH_INVALID:
return "(tried to use unsupported auth mechanism)";
case CLIENT_AUTH_FAIL_CODE_MECH_SSL_REQUIRED:
return "(tried to use disallowed plaintext auth)";
default:
break;
}
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_common_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_data(struct client *client, const void *data, size_t size)
{
client->v.send_raw_data(client, data, size);
}
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);
}
void client_common_init(void)
{
i_array_init(&module_hooks, 32);
}
void client_common_deinit(void)
{
array_free(&module_hooks);
}