login-connection.c revision 0388fd5e0bf6654ea25299fd63f2ee8e5ce2913f
/* Copyright (c) 2010-2015 Dovecot authors, see the included COPYING file */
#include "lib.h"
#include "ioloop.h"
#include "net.h"
#include "istream.h"
#include "ostream.h"
#include "llist.h"
#include "str.h"
#include "master-service.h"
#include "director.h"
#include "director-request.h"
#include "auth-connection.h"
#include "login-connection.h"
#include <unistd.h>
struct login_connection {
struct login_connection *prev, *next;
int refcount;
int fd;
struct io *io;
struct istream *input;
struct ostream *output;
struct auth_connection *auth;
struct director *dir;
unsigned int destroyed:1;
unsigned int userdb:1;
unsigned int input_newline:1;
};
struct login_host_request {
struct login_connection *conn;
char *line, *username;
struct ip_addr local_ip;
unsigned int local_port;
unsigned int dest_port;
bool director_proxy_maybe;
};
static struct login_connection *login_connections;
static void auth_input_line(const char *line, void *context);
static void login_connection_unref(struct login_connection **_conn);
static void
login_connection_director_lookup(struct login_connection *conn,
const unsigned char *data, size_t size)
{
T_BEGIN {
string_t *line = t_str_new(128);
str_append(line, "OK\t");
str_append_n(line, data, size);
auth_input_line(str_c(line), conn);
} T_END;
}
static void login_connection_input(struct login_connection *conn)
{
const unsigned char *data, *p;
size_t size;
struct ostream *auth_output;
auth_output = auth_connection_send(conn->auth);
switch (i_stream_read(conn->input)) {
case -2:
data = i_stream_get_data(conn->input, &size);
o_stream_nsend(auth_output, data, size);
i_stream_skip(conn->input, size);
conn->input_newline = FALSE;
return;
case -1:
if (conn->input->stream_errno != 0 &&
conn->input->stream_errno != ECONNRESET) {
i_error("read(login connection) failed: %s",
i_stream_get_error(conn->input));
}
login_connection_deinit(&conn);
return;
case 0:
return;
default:
break;
}
o_stream_cork(auth_output);
data = i_stream_get_data(conn->input, &size);
while ((p = memchr(data, '\n', size)) != NULL) {
size_t linelen = p-data;
if (!conn->input_newline || linelen <= 16 ||
memcmp(data, "DIRECTOR-LOOKUP\t", 16) != 0) {
/* forward data to auth process */
o_stream_nsend(auth_output, data, linelen+1);
conn->input_newline = TRUE;
} else {
login_connection_director_lookup(conn, data+16, linelen-16);
}
i_stream_skip(conn->input, linelen+1);
data = i_stream_get_data(conn->input, &size);
}
o_stream_uncork(auth_output);
}
static void
login_connection_send_line(struct login_connection *conn, const char *line)
{
struct const_iovec iov[2];
if (conn->destroyed)
return;
iov[0].iov_base = line;
iov[0].iov_len = strlen(line);
iov[1].iov_base = "\n";
iov[1].iov_len = 1;
o_stream_nsendv(conn->output, iov, N_ELEMENTS(iov));
}
static bool login_host_request_is_self(struct login_host_request *request,
const struct ip_addr *dest_ip)
{
if (!net_ip_compare(dest_ip, &request->local_ip))
return FALSE;
if (request->dest_port != 0 && request->local_port != 0 &&
request->dest_port != request->local_port)
return FALSE;
return TRUE;
}
static void
login_host_callback(const struct ip_addr *ip, const char *errormsg,
void *context)
{
struct login_host_request *request = context;
struct director *dir = request->conn->dir;
const char *line, *line_params;
unsigned int secs;
if (ip == NULL) {
if (strncmp(request->line, "OK\t", 3) == 0)
line_params = request->line + 3;
else if (strncmp(request->line, "PASS\t", 5) == 0)
line_params = request->line + 5;
else
i_panic("BUG: Unexpected line: %s", request->line);
i_error("director: User %s host lookup failed: %s",
request->username, errormsg);
line = t_strconcat("FAIL\t", t_strcut(line_params, '\t'),
"\ttemp", NULL);
} else if (request->director_proxy_maybe &&
login_host_request_is_self(request, ip)) {
line = request->line;
} else {
secs = dir->set->director_user_expire / 2;
line = t_strdup_printf("%s\thost=%s\tproxy_refresh=%u",
request->line, net_ip2addr(ip), secs);
}
login_connection_send_line(request->conn, line);
login_connection_unref(&request->conn);
i_free(request->username);
i_free(request->line);
i_free(request);
}
static void auth_input_line(const char *line, void *context)
{
struct login_connection *conn = context;
struct login_host_request *request, temp_request;
const char *const *args, *line_params, *username = NULL, *tag = "";
bool proxy = FALSE, host = FALSE;
if (line == NULL) {
/* auth connection died -> kill also this login connection */
login_connection_deinit(&conn);
return;
}
if (!conn->userdb && strncmp(line, "OK\t", 3) == 0)
line_params = line + 3;
else if (conn->userdb && strncmp(line, "PASS\t", 5) == 0)
line_params = line + 5;
else {
login_connection_send_line(conn, line);
return;
}
/* OK <id> [<parameters>] */
args = t_strsplit_tab(line_params);
if (*args != NULL) {
/* we should always get here, but in case we don't just
forward as-is and let login process handle the error. */
args++;
}
memset(&temp_request, 0, sizeof(temp_request));
for (; *args != NULL; args++) {
if (strncmp(*args, "proxy", 5) == 0 &&
((*args)[5] == '=' || (*args)[5] == '\0'))
proxy = TRUE;
else if (strncmp(*args, "host=", 5) == 0)
host = TRUE;
else if (strncmp(*args, "lip=", 4) == 0) {
if (net_addr2ip((*args) + 4, &temp_request.local_ip) < 0)
i_error("auth sent invalid lip field: %s", (*args) + 6);
} else if (strncmp(*args, "lport=", 6) == 0) {
if (str_to_uint((*args) + 6, &temp_request.local_port) < 0)
i_error("auth sent invalid lport field: %s", (*args) + 6);
} else if (strncmp(*args, "port=", 5) == 0) {
if (str_to_uint((*args) + 5, &temp_request.dest_port) < 0)
i_error("auth sent invalid port field: %s", (*args) + 6);
} else if (strncmp(*args, "destuser=", 9) == 0)
username = *args + 9;
else if (strncmp(*args, "director_tag=", 13) == 0)
tag = *args + 13;
else if (strncmp(*args, "director_proxy_maybe", 20) == 0 &&
((*args)[20] == '=' || (*args)[20] == '\0'))
temp_request.director_proxy_maybe = TRUE;
else if (strncmp(*args, "user=", 5) == 0) {
if (username == NULL)
username = *args + 5;
}
}
if ((!proxy && !temp_request.director_proxy_maybe) ||
host || username == NULL) {
login_connection_send_line(conn, line);
return;
}
if (*conn->dir->set->master_user_separator != '\0') {
/* with master user logins we still want to use only the
login username */
username = t_strcut(username,
*conn->dir->set->master_user_separator);
}
/* we need to add the host. the lookup might be asynchronous */
request = i_new(struct login_host_request, 1);
*request = temp_request;
request->conn = conn;
request->line = i_strdup(line);
request->username = i_strdup(username);
conn->refcount++;
director_request(conn->dir, username, tag, login_host_callback, request);
}
struct login_connection *
login_connection_init(struct director *dir, int fd,
struct auth_connection *auth, bool userdb)
{
struct login_connection *conn;
conn = i_new(struct login_connection, 1);
conn->refcount = 1;
conn->fd = fd;
conn->auth = auth;
conn->dir = dir;
conn->input = i_stream_create_fd(conn->fd, IO_BLOCK_SIZE, FALSE);
conn->output = o_stream_create_fd(conn->fd, (size_t)-1, FALSE);
o_stream_set_no_error_handling(conn->output, TRUE);
conn->io = io_add(conn->fd, IO_READ, login_connection_input, conn);
conn->userdb = userdb;
conn->input_newline = TRUE;
auth_connection_set_callback(conn->auth, auth_input_line, conn);
DLLIST_PREPEND(&login_connections, conn);
return conn;
}
void login_connection_deinit(struct login_connection **_conn)
{
struct login_connection *conn = *_conn;
*_conn = NULL;
if (conn->destroyed)
return;
conn->destroyed = TRUE;
DLLIST_REMOVE(&login_connections, conn);
io_remove(&conn->io);
i_stream_destroy(&conn->input);
o_stream_destroy(&conn->output);
if (close(conn->fd) < 0)
i_error("close(login connection) failed: %m");
conn->fd = -1;
auth_connection_deinit(&conn->auth);
login_connection_unref(&conn);
master_service_client_connection_destroyed(master_service);
}
static void login_connection_unref(struct login_connection **_conn)
{
struct login_connection *conn = *_conn;
*_conn = NULL;
i_assert(conn->refcount > 0);
if (--conn->refcount == 0)
i_free(conn);
}
void login_connections_deinit(void)
{
while (login_connections != NULL) {
struct login_connection *conn = login_connections;
login_connection_deinit(&conn);
}
}