f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "auth-common.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "ioloop.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "istream.h"
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi#include "ostream.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "net.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "hex-binary.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "hostpid.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "llist.h"
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi#include "str.h"
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi#include "str-sanitize.h"
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi#include "randgen.h"
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi#include "safe-memset.h"
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi#include "master-service.h"
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi#include "mech.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "auth-fields.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "auth-request-handler.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "auth-client-interface.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "auth-client-connection.h"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch#include "auth-master-connection.h"
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi#define OUTBUF_THROTTLE_SIZE (1024*50)
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
22186f00440506c79548442402bce1e6742fbfa4Timo Sirainen#define AUTH_DEBUG_SENSITIVE_SUFFIX \
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch " (previous base64 data may contain sensitive data)"
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Boschstatic void auth_client_disconnected(struct auth_client_connection **_conn);
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Boschstatic void auth_client_connection_unref(struct auth_client_connection **_conn);
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Boschstatic void auth_client_input(struct auth_client_connection *conn);
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Boschstatic struct auth_client_connection *auth_client_connections;
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Boschstatic const char *reply_line_hide_pass(const char *line)
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi{
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch string_t *newline;
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch const char *p, *p2;
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch if (strstr(line, "pass") == NULL)
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch return line;
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi newline = t_str_new(strlen(line));
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch const char *const *fields = t_strsplit(line, "\t");
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch while(*fields != NULL) {
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch p = strstr(*fields, "pass");
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch p2 = strchr(*fields, '=');
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch if (p == NULL || p2 == NULL || p2 < p) {
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch str_append(newline, *fields);
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch } else {
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch /* include = */
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch str_append_data(newline, *fields, (p2 - *fields)+1);
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi str_append(newline, PASSWORD_HIDDEN_STR);
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch }
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch str_append_c(newline, '\t');
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch fields++;
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch }
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch return str_c(newline);
701a11e110c7a9857f908827612767fa0a9fdd37Timo Sirainen}
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Boschstatic void auth_client_send(struct auth_client_connection *conn,
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch const char *cmd)
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi{
b200bc3875fa06d42c8619865cc306c3297fcaccAki Tuomi struct const_iovec iov[2];
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch iov[0].iov_base = cmd;
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch iov[0].iov_len = strlen(cmd);
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch iov[1].iov_base = "\n";
7f50258fb5e71ca7965d8527f879114c39d0a0fbTimo Sirainen iov[1].iov_len = 1;
5ee7c64918f0f0856347c1e143e16d5110877df8Timo Sirainen o_stream_nsendv(conn->output, iov, 2);
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch if (o_stream_get_buffer_used_size(conn->output) >=
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch OUTBUF_THROTTLE_SIZE) {
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch /* stop reading new requests until client has read the pending
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch replies. */
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch io_remove(&conn->io);
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch }
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch if (conn->auth->set->debug) {
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch i_debug("client passdb out: %s",
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch conn->auth->set->debug_passwords ?
f9511e684858bf5f6ac77ab12254b85b737beae8Stephan Bosch cmd : reply_line_hide_pass(cmd));
}
}
static void auth_callback(const char *reply,
struct auth_client_connection *conn)
{
if (reply == NULL) {
/* handler destroyed */
auth_client_connection_unref(&conn);
} else {
auth_client_send(conn, reply);
}
}
static bool
auth_client_input_cpid(struct auth_client_connection *conn, const char *args)
{
struct auth_client_connection *old;
unsigned int pid;
i_assert(conn->pid == 0);
if (str_to_uint(args, &pid) < 0 || pid == 0) {
i_error("BUG: Authentication client said it's PID 0");
return FALSE;
}
if (conn->login_requests)
old = auth_client_connection_lookup(pid);
else {
/* the client is only authenticating, not logging in.
the PID isn't necessary, and since we allow authentication
via TCP sockets the PIDs may conflict, so ignore them. */
old = NULL;
pid = 0;
}
if (old != NULL) {
/* already exists. it's possible that it just reconnected,
see if the old connection is still there. */
i_assert(old != conn);
if (i_stream_read(old->input) == -1) {
auth_client_disconnected(&old);
old = NULL;
}
}
if (old != NULL) {
i_error("BUG: Authentication client gave a PID "
"%u of existing connection", pid);
return FALSE;
}
/* handshake complete, we can now actually start serving requests */
conn->refcount++;
conn->request_handler =
auth_request_handler_create(conn->token_auth, auth_callback, conn,
!conn->login_requests ? NULL :
auth_master_request_callback);
auth_request_handler_set(conn->request_handler, conn->connect_uid, pid);
conn->pid = pid;
if (conn->auth->set->debug)
i_debug("auth client connected (pid=%u)", conn->pid);
return TRUE;
}
static int auth_client_output(struct auth_client_connection *conn)
{
if (o_stream_flush(conn->output) < 0) {
auth_client_disconnected(&conn);
return 1;
}
if (o_stream_get_buffer_used_size(conn->output) <=
OUTBUF_THROTTLE_SIZE/3 && conn->io == NULL) {
/* allow input again */
conn->io = io_add(conn->fd, IO_READ, auth_client_input, conn);
}
return 1;
}
static const char *
auth_line_hide_pass(struct auth_client_connection *conn, const char *line)
{
const char *p, *p2;
p = strstr(line, "\tresp=");
if (p == NULL)
return line;
p += 6;
if (conn->auth->set->debug_passwords)
return t_strconcat(line, AUTH_DEBUG_SENSITIVE_SUFFIX, NULL);
p2 = strchr(p, '\t');
return t_strconcat(t_strdup_until(line, p), PASSWORD_HIDDEN_STR,
p2, NULL);
}
static const char *
cont_line_hide_pass(struct auth_client_connection *conn, const char *line)
{
const char *p;
if (conn->auth->set->debug_passwords)
return t_strconcat(line, AUTH_DEBUG_SENSITIVE_SUFFIX, NULL);
p = strchr(line, '\t');
if (p == NULL)
return line;
return t_strconcat(t_strdup_until(line, p), PASSWORD_HIDDEN_STR, NULL);
}
static bool
auth_client_cancel(struct auth_client_connection *conn, const char *line)
{
unsigned int client_id;
if (str_to_uint(line, &client_id) < 0) {
i_error("BUG: Authentication client sent broken CANCEL");
return FALSE;
}
auth_request_handler_cancel_request(conn->request_handler, client_id);
return TRUE;
}
static bool
auth_client_handle_line(struct auth_client_connection *conn, const char *line)
{
if (strncmp(line, "AUTH\t", 5) == 0) {
if (conn->auth->set->debug) {
i_debug("client in: %s",
auth_line_hide_pass(conn, line));
}
return auth_request_handler_auth_begin(conn->request_handler,
line + 5);
}
if (strncmp(line, "CONT\t", 5) == 0) {
if (conn->auth->set->debug) {
i_debug("client in: %s",
cont_line_hide_pass(conn, line));
}
return auth_request_handler_auth_continue(conn->request_handler,
line + 5);
}
if (strncmp(line, "CANCEL\t", 7) == 0) {
if (conn->auth->set->debug)
i_debug("client in: %s", line);
return auth_client_cancel(conn, line + 7);
}
i_error("BUG: Authentication client sent unknown command: %s",
str_sanitize(line, 80));
return FALSE;
}
static void auth_client_input(struct auth_client_connection *conn)
{
char *line;
bool ret;
switch (i_stream_read(conn->input)) {
case 0:
return;
case -1:
/* disconnected */
auth_client_disconnected(&conn);
return;
case -2:
/* buffer full */
i_error("BUG: Auth client %u sent us more than %d bytes",
conn->pid, (int)AUTH_CLIENT_MAX_LINE_LENGTH);
auth_client_connection_destroy(&conn);
return;
}
while (conn->request_handler == NULL) {
/* still handshaking */
line = i_stream_next_line(conn->input);
if (line == NULL)
return;
if (!conn->version_received) {
unsigned int vmajor, vminor;
const char *p;
/* split the version line */
if (strncmp(line, "VERSION\t", 8) != 0 ||
str_parse_uint(line + 8, &vmajor, &p) < 0 ||
*(p++) != '\t' || str_to_uint(p, &vminor) < 0) {
i_error("Authentication client "
"sent invalid VERSION line: %s", line);
auth_client_connection_destroy(&conn);
return;
}
/* make sure the major version matches */
if (vmajor != AUTH_MASTER_PROTOCOL_MAJOR_VERSION) {
i_error("Authentication client "
"not compatible with this server "
"(mixed old and new binaries?)");
auth_client_connection_destroy(&conn);
return;
}
conn->version_minor = vminor;
conn->version_received = TRUE;
continue;
}
if (strncmp(line, "CPID\t", 5) == 0) {
if (!auth_client_input_cpid(conn, line + 5)) {
auth_client_connection_destroy(&conn);
return;
}
} else {
i_error("BUG: Authentication client sent "
"unknown handshake command: %s",
str_sanitize(line, 80));
auth_client_connection_destroy(&conn);
return;
}
}
conn->refcount++;
while ((line = i_stream_next_line(conn->input)) != NULL) {
T_BEGIN {
ret = auth_client_handle_line(conn, line);
safe_memset(line, 0, strlen(line));
} T_END;
if (!ret) {
struct auth_client_connection *tmp_conn = conn;
auth_client_connection_destroy(&tmp_conn);
break;
}
}
auth_client_connection_unref(&conn);
}
void auth_client_connection_create(struct auth *auth, int fd,
bool login_requests, bool token_auth)
{
static unsigned int connect_uid_counter = 0;
struct auth_client_connection *conn;
const char *mechanisms;
string_t *str;
conn = i_new(struct auth_client_connection, 1);
conn->auth = auth;
conn->refcount = 1;
conn->connect_uid = ++connect_uid_counter;
conn->login_requests = login_requests;
conn->token_auth = token_auth;
random_fill(conn->cookie, sizeof(conn->cookie));
conn->fd = fd;
conn->input = i_stream_create_fd(fd, AUTH_CLIENT_MAX_LINE_LENGTH);
conn->output = o_stream_create_fd(fd, (size_t)-1);
o_stream_set_no_error_handling(conn->output, TRUE);
o_stream_set_flush_callback(conn->output, auth_client_output, conn);
conn->io = io_add(fd, IO_READ, auth_client_input, conn);
DLLIST_PREPEND(&auth_client_connections, conn);
if (token_auth) {
mechanisms = t_strconcat("MECH\t",
mech_dovecot_token.mech_name, "\n", NULL);
} else {
mechanisms = str_c(auth->reg->handshake);
}
str = t_str_new(128);
str_printfa(str, "VERSION\t%u\t%u\n%sSPID\t%s\nCUID\t%u\nCOOKIE\t",
AUTH_CLIENT_PROTOCOL_MAJOR_VERSION,
AUTH_CLIENT_PROTOCOL_MINOR_VERSION,
mechanisms, my_pid, conn->connect_uid);
binary_to_hex_append(str, conn->cookie, sizeof(conn->cookie));
str_append(str, "\nDONE\n");
if (o_stream_send(conn->output, str_data(str), str_len(str)) < 0)
auth_client_disconnected(&conn);
}
void auth_client_connection_destroy(struct auth_client_connection **_conn)
{
struct auth_client_connection *conn = *_conn;
*_conn = NULL;
if (conn->fd == -1)
return;
DLLIST_REMOVE(&auth_client_connections, conn);
i_stream_close(conn->input);
o_stream_close(conn->output);
io_remove(&conn->io);
net_disconnect(conn->fd);
conn->fd = -1;
if (conn->request_handler != NULL) {
auth_request_handler_abort_requests(conn->request_handler);
auth_request_handler_destroy(&conn->request_handler);
}
master_service_client_connection_destroyed(master_service);
auth_client_connection_unref(&conn);
}
static void auth_client_disconnected(struct auth_client_connection **_conn)
{
struct auth_client_connection *conn = *_conn;
unsigned int request_count;
int err;
*_conn = NULL;
if (conn->input->stream_errno != 0)
err = conn->input->stream_errno;
else if (conn->output->stream_errno != 0)
err = conn->output->stream_errno;
else
err = 0;
request_count = conn->request_handler == NULL ? 0 :
auth_request_handler_get_request_count(conn->request_handler);
if (request_count > 0) {
i_warning("auth client %u disconnected with %u "
"pending requests: %s", conn->pid, request_count,
err == 0 ? "EOF" : strerror(err));
}
auth_client_connection_destroy(&conn);
}
static void auth_client_connection_unref(struct auth_client_connection **_conn)
{
struct auth_client_connection *conn = *_conn;
*_conn = NULL;
if (--conn->refcount > 0)
return;
i_stream_unref(&conn->input);
o_stream_unref(&conn->output);
i_free(conn);
}
struct auth_client_connection *
auth_client_connection_lookup(unsigned int pid)
{
struct auth_client_connection *conn;
for (conn = auth_client_connections; conn != NULL; conn = conn->next) {
if (conn->pid == pid)
return conn;
}
return NULL;
}
void auth_client_connections_destroy_all(void)
{
struct auth_client_connection *conn;
while (auth_client_connections != NULL) {
conn = auth_client_connections;
auth_client_connection_destroy(&conn);
}
}