auth-master.c revision aba994a4e79a020b4748e0ceffc194e5a18e1d1a
0N/A/* Copyright (c) 2005-2008 Dovecot authors, see the included COPYING file */
2362N/A
0N/A#include "lib.h"
0N/A#include "lib-signals.h"
0N/A#include "array.h"
0N/A#include "ioloop.h"
0N/A#include "network.h"
0N/A#include "istream.h"
0N/A#include "ostream.h"
0N/A#include "auth-master.h"
0N/A
0N/A#include <stdlib.h>
0N/A#include <unistd.h>
0N/A
0N/A#define AUTH_PROTOCOL_MAJOR 1
0N/A#define AUTH_PROTOCOL_MINOR 0
0N/A
0N/A#define AUTH_REQUEST_TIMEOUT_SECS 30
2362N/A#define AUTH_MASTER_IDLE_SECS 60
2362N/A
2362N/A#define MAX_INBUF_SIZE 8192
0N/A#define MAX_OUTBUF_SIZE 1024
0N/A
0N/Astruct auth_connection {
0N/A char *auth_socket_path;
0N/A
0N/A int fd;
0N/A struct ioloop *ioloop;
0N/A struct io *io;
0N/A struct istream *input;
0N/A struct ostream *output;
0N/A struct timeout *to;
0N/A
0N/A unsigned int request_counter;
0N/A pool_t pool;
0N/A const char *user;
0N/A struct auth_user_reply *user_reply;
0N/A int return_value;
0N/A
0N/A unsigned int debug:1;
0N/A unsigned int sent_handshake:1;
0N/A unsigned int handshaked:1;
0N/A unsigned int aborted:1;
0N/A};
0N/A
0N/Astatic void auth_input(struct auth_connection *conn);
0N/A
0N/Astruct auth_connection *
0N/Aauth_master_init(const char *auth_socket_path, bool debug)
0N/A{
0N/A struct auth_connection *conn;
0N/A
0N/A conn = i_new(struct auth_connection, 1);
0N/A conn->auth_socket_path = i_strdup(auth_socket_path);
0N/A conn->fd = -1;
0N/A conn->debug = debug;
0N/A return conn;
0N/A}
0N/A
0N/Astatic void auth_connection_close(struct auth_connection *conn)
0N/A{
0N/A if (conn->to != NULL)
0N/A timeout_remove(&conn->to);
0N/A if (conn->fd != -1) {
0N/A if (close(conn->fd) < 0)
0N/A i_error("close(%s) failed: %m", conn->auth_socket_path);
0N/A conn->fd = -1;
0N/A }
0N/A
0N/A conn->sent_handshake = FALSE;
0N/A conn->handshaked = FALSE;
0N/A}
0N/A
0N/Avoid auth_master_deinit(struct auth_connection *conn)
0N/A{
0N/A auth_connection_close(conn);
0N/A i_free(conn->auth_socket_path);
0N/A i_free(conn);
0N/A}
0N/A
0N/Astatic void auth_request_lookup_abort(struct auth_connection *conn)
0N/A{
0N/A io_loop_stop(conn->ioloop);
0N/A conn->aborted = TRUE;
0N/A}
0N/A
0N/Astatic void auth_parse_input(struct auth_connection *conn,
0N/A const char *const *args)
0N/A{
0N/A struct auth_user_reply *reply = conn->user_reply;
0N/A
0N/A memset(reply, 0, sizeof(*reply));
0N/A reply->uid = (uid_t)-1;
0N/A reply->gid = (gid_t)-1;
0N/A p_array_init(&reply->extra_fields, conn->pool, 64);
0N/A
0N/A for (; *args != NULL; args++) {
0N/A if (conn->debug)
0N/A i_info("auth input: %s", *args);
0N/A
0N/A if (strncmp(*args, "uid=", 4) == 0)
0N/A reply->uid = strtoul(*args + 4, NULL, 10);
0N/A else if (strncmp(*args, "gid=", 4) == 0)
0N/A reply->gid = strtoul(*args + 4, NULL, 10);
0N/A else if (strncmp(*args, "home=", 5) == 0)
0N/A reply->home = p_strdup(conn->pool, *args + 5);
0N/A else if (strncmp(*args, "chroot=", 7) == 0)
0N/A reply->chroot = p_strdup(conn->pool, *args + 7);
0N/A else {
0N/A const char *field = p_strdup(conn->pool, *args);
0N/A array_append(&reply->extra_fields, &field, 1);
0N/A }
0N/A }
0N/A}
0N/A
0N/Astatic int auth_input_handshake(struct auth_connection *conn)
0N/A{
0N/A const char *line, *const *tmp;
0N/A
0N/A while ((line = i_stream_next_line(conn->input)) != NULL) {
0N/A tmp = t_strsplit(line, "\t");
0N/A if (strcmp(tmp[0], "VERSION") == 0 &&
0N/A tmp[1] != NULL && tmp[2] != NULL) {
0N/A if (strcmp(tmp[1], dec2str(AUTH_PROTOCOL_MAJOR)) != 0) {
0N/A i_error("userdb lookup(%s): "
0N/A "Auth protocol version mismatch "
0N/A "(%s vs %d)", conn->user, tmp[1],
0N/A AUTH_PROTOCOL_MAJOR);
0N/A auth_request_lookup_abort(conn);
0N/A return -1;
0N/A }
0N/A } else if (strcmp(tmp[0], "SPID") == 0) {
0N/A conn->handshaked = TRUE;
0N/A break;
0N/A }
0N/A }
0N/A return 0;
0N/A}
0N/A
0N/Astatic void auth_input(struct auth_connection *conn)
0N/A{
0N/A const char *line, *cmd, *const *args, *id, *wanted_id;
0N/A
0N/A switch (i_stream_read(conn->input)) {
0N/A case 0:
0N/A return;
0N/A case -1:
0N/A /* disconnected */
0N/A i_error("userdb lookup(%s): Disconnected unexpectedly",
0N/A conn->user);
0N/A auth_request_lookup_abort(conn);
0N/A return;
0N/A case -2:
0N/A /* buffer full */
0N/A i_error("userdb lookup(%s): BUG: Received more than %d bytes",
0N/A conn->user, MAX_INBUF_SIZE);
0N/A auth_request_lookup_abort(conn);
0N/A return;
0N/A }
0N/A
0N/A if (!conn->handshaked) {
0N/A if (auth_input_handshake(conn) < 0)
0N/A return;
0N/A }
0N/A
0N/A line = i_stream_next_line(conn->input);
0N/A if (line == NULL)
0N/A return;
0N/A
0N/A args = t_strsplit(line, "\t");
0N/A cmd = *args; args++;
0N/A if (*args == NULL)
0N/A id = "";
0N/A else {
0N/A id = *args;
0N/A args++;
0N/A }
0N/A
0N/A wanted_id = dec2str(conn->request_counter);
0N/A if (strcmp(id, wanted_id) == 0) {
0N/A io_loop_stop(conn->ioloop);
0N/A if (strcmp(cmd, "USER") == 0) {
0N/A auth_parse_input(conn, args);
0N/A conn->return_value = 1;
0N/A return;
0N/A }
0N/A if (strcmp(cmd, "NOTFOUND") == 0) {
0N/A conn->return_value = 0;
0N/A return;
0N/A }
0N/A if (strcmp(cmd, "FAIL") == 0) {
0N/A i_error("userdb lookup(%s) failed: %s",
0N/A conn->user, *args != NULL ? *args :
0N/A "Internal failure");
0N/A return;
0N/A }
0N/A }
0N/A
0N/A if (strcmp(cmd, "CUID") == 0) {
0N/A i_error("userdb lookup(%s): %s is an auth client socket. "
0N/A "It should be a master socket.",
0N/A conn->user, conn->auth_socket_path);
0N/A } else {
0N/A i_error("userdb lookup(%s): BUG: Unexpected input: %s",
0N/A conn->user, line);
0N/A }
0N/A auth_request_lookup_abort(conn);
0N/A}
0N/A
0N/Astatic int auth_master_connect(struct auth_connection *conn)
0N/A{
0N/A int fd, try;
0N/A
0N/A i_assert(conn->fd == -1);
0N/A
0N/A /* max. 1 second wait here. */
0N/A for (try = 0; try < 10; try++) {
0N/A fd = net_connect_unix(conn->auth_socket_path);
0N/A if (fd != -1 || (errno != EAGAIN && errno != ECONNREFUSED))
0N/A break;
0N/A
0N/A /* busy. wait for a while. */
0N/A usleep(((rand() % 10) + 1) * 10000);
0N/A }
0N/A if (fd == -1) {
0N/A i_error("userdb lookup: connect(%s) failed: %m",
0N/A conn->auth_socket_path);
0N/A return -1;
0N/A }
0N/A conn->fd = fd;
0N/A return 0;
0N/A}
0N/A
0N/Astatic void auth_request_timeout(struct auth_connection *conn)
0N/A{
0N/A if (!conn->handshaked)
0N/A i_error("userdb lookup(%s): Connecting timed out", conn->user);
0N/A else
0N/A i_error("userdb lookup(%s): Request timed out", conn->user);
0N/A auth_request_lookup_abort(conn);
0N/A}
0N/A
0N/Astatic void auth_idle_timeout(struct auth_connection *conn)
0N/A{
auth_connection_close(conn);
}
static void auth_master_set_io(struct auth_connection *conn)
{
if (conn->to != NULL)
timeout_remove(&conn->to);
conn->ioloop = io_loop_create();
conn->input = i_stream_create_fd(conn->fd, MAX_INBUF_SIZE, FALSE);
conn->output = o_stream_create_fd(conn->fd, MAX_OUTBUF_SIZE, FALSE);
conn->io = io_add(conn->fd, IO_READ, auth_input, conn);
conn->to = timeout_add(1000*AUTH_REQUEST_TIMEOUT_SECS,
auth_request_timeout, conn);
lib_signals_reset_ioloop();
}
static void auth_master_unset_io(struct auth_connection *conn,
struct ioloop *prev_ioloop)
{
io_loop_set_current(prev_ioloop);
lib_signals_reset_ioloop();
io_loop_set_current(conn->ioloop);
timeout_remove(&conn->to);
io_remove(&conn->io);
i_stream_unref(&conn->input);
o_stream_unref(&conn->output);
io_loop_destroy(&conn->ioloop);
conn->to = timeout_add(1000*AUTH_MASTER_IDLE_SECS,
auth_idle_timeout, conn);
}
static bool is_valid_string(const char *str)
{
const char *p;
/* make sure we're not sending any characters that have a special
meaning. */
for (p = str; *p != '\0'; p++) {
if (*p == '\t' || *p == '\n' || *p == '\r')
return FALSE;
}
return TRUE;
}
int auth_master_user_lookup(struct auth_connection *conn,
const char *user, const char *service,
pool_t pool, struct auth_user_reply *reply_r)
{
struct ioloop *prev_ioloop;
const char *str;
if (!is_valid_string(user) || !is_valid_string(service)) {
/* non-allowed characters, the user can't exist */
return 0;
}
if (conn->fd == -1) {
if (auth_master_connect(conn) < 0)
return -1;
}
prev_ioloop = current_ioloop;
auth_master_set_io(conn);
conn->return_value = -1;
conn->pool = pool;
conn->user = user;
conn->user_reply = reply_r;
if (++conn->request_counter == 0) {
/* avoid zero */
conn->request_counter++;
}
o_stream_cork(conn->output);
if (!conn->sent_handshake) {
str = t_strdup_printf("VERSION\t%d\t%d\n",
AUTH_PROTOCOL_MAJOR, AUTH_PROTOCOL_MINOR);
o_stream_send_str(conn->output, str);
conn->sent_handshake = TRUE;
}
str = t_strdup_printf("USER\t%u\t%s\tservice=%s\n",
conn->request_counter, user, service);
o_stream_send_str(conn->output, str);
o_stream_uncork(conn->output);
if (conn->output->stream_errno != 0) {
errno = conn->output->stream_errno;
i_error("write(auth socket) failed: %m");
} else {
io_loop_run(conn->ioloop);
}
auth_master_unset_io(conn, prev_ioloop);
if (conn->aborted) {
conn->aborted = FALSE;
auth_connection_close(conn);
}
conn->user = NULL;
conn->pool = NULL;
conn->user_reply = NULL;
return conn->return_value;
}