auth-master-connection.c revision 1e21e6be70994b1aa9e52ca0e2f51afefca6d0df
/* Copyright (C) 2002 Timo Sirainen */
#include "common.h"
#include "buffer.h"
#include "hash.h"
#include "str.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "network.h"
#include "userdb.h"
#include "auth-request.h"
#include "auth-master-interface.h"
#include "auth-client-connection.h"
#include "auth-master-connection.h"
#include <unistd.h>
#include <stdlib.h>
#define MAX_INBUF_SIZE 1024
#define MAX_OUTBUF_SIZE (1024*50)
struct auth_listener {
struct auth_master_connection *master;
int client_listener;
int fd;
char *path;
struct io *io;
};
struct master_userdb_request {
struct auth_master_connection *conn;
unsigned int id;
struct auth_request *auth_request;
};
static int master_output(void *context);
static void auth_master_connection_close(struct auth_master_connection *conn);
static int auth_master_connection_unref(struct auth_master_connection *conn);
static void master_send(struct auth_master_connection *conn,
const char *fmt, ...) __attr_format__(2, 3);
static void master_send(struct auth_master_connection *conn,
const char *fmt, ...)
{
va_list args;
string_t *str;
t_push();
va_start(args, fmt);
str = t_str_new(256);
str_vprintfa(str, fmt, args);
str_append_c(str, '\n');
(void)o_stream_send(conn->output, str_data(str), str_len(str));
va_end(args);
t_pop();
}
static void append_user_reply(string_t *str, const struct user_data *user)
{
const char *p;
str_printfa(str, "%s\tuid=%s\tgid=%s", user->virtual_user,
dec2str(user->uid), dec2str(user->gid));
if (user->system_user != NULL)
str_printfa(str, "\tsystem_user=%s", user->system_user);
if (user->mail != NULL)
str_printfa(str, "\tmail=%s", user->mail);
p = user->home != NULL ? strstr(user->home, "/./") : NULL;
if (p == NULL) {
if (user->home != NULL)
str_printfa(str, "\thome=%s", user->home);
} else {
/* wu-ftpd like <chroot>/./<home> */
str_printfa(str, "\thome=%s\tchroot=%s",
p + 3, t_strdup_until(user->home, p));
}
}
static void userdb_callback(const struct user_data *user, void *context)
{
struct master_userdb_request *master_request = context;
string_t *str;
if (verbose_debug && user != NULL) {
i_info("userdb(%s): uid=%s gid=%s home=%s mail=%s",
get_log_prefix(master_request->auth_request),
dec2str(user->uid), dec2str(user->gid),
user->home != NULL ? user->home : "",
user->mail != NULL ? user->mail : "");
}
if (auth_master_connection_unref(master_request->conn)) {
if (user == NULL) {
master_send(master_request->conn, "NOTFOUND\t%u",
master_request->id);
} else {
str = t_str_new(256);
str_printfa(str, "USER\t%u\t", master_request->id);
append_user_reply(str, user);
master_send(master_request->conn, "%s", str_c(str));
}
}
auth_request_destroy(master_request->auth_request);
i_free(master_request);
}
static int
master_input_request(struct auth_master_connection *conn, const char *args)
{
struct auth_client_connection *client_conn;
struct master_userdb_request *master_request;
struct auth_request *request;
const char *const *list;
unsigned int id, client_pid, client_id;
/* <id> <client-pid> <client-id> */
list = t_strsplit(args, "\t");
if (list[0] == NULL || list[1] == NULL || list[2] == NULL) {
i_error("BUG: Master sent broken REQUEST");
return FALSE;
}
id = (unsigned int)strtoul(list[0], NULL, 10);
client_pid = (unsigned int)strtoul(list[1], NULL, 10);
client_id = (unsigned int)strtoul(list[2], NULL, 10);
client_conn = auth_client_connection_lookup(conn, client_pid);
request = client_conn == NULL ? NULL :
hash_lookup(client_conn->auth_requests,
POINTER_CAST(client_id));
if (request == NULL) {
if (verbose) {
i_info("Master request %u.%u not found",
client_pid, client_id);
}
master_send(conn, "NOTFOUND\t%u", id);
} else if (!request->successful) {
i_error("Master requested unfinished authentication request "
"%u.%u", client_pid, client_id);
master_send(conn, "NOTFOUND\t%u", id);
} else {
master_request = i_new(struct master_userdb_request, 1);
master_request->conn = conn;
master_request->id = id;
master_request->auth_request = request;
conn->refcount++;
auth_request_lookup_user(request, userdb_callback,
master_request);
}
return TRUE;
}
static int
master_input_die(struct auth_master_connection *conn)
{
return TRUE;
}
static void master_input(void *context)
{
struct auth_master_connection *conn = context;
char *line;
int ret;
switch (i_stream_read(conn->input)) {
case 0:
return;
case -1:
/* disconnected */
auth_master_connection_close(conn);
return;
case -2:
/* buffer full */
i_error("BUG: Master sent us more than %d bytes",
(int)MAX_INBUF_SIZE);
auth_master_connection_close(conn);
return;
}
if (!conn->version_received) {
line = i_stream_next_line(conn->input);
if (line == NULL)
return;
/* make sure the major version matches */
if (strncmp(line, "VERSION\t", 8) != 0 ||
atoi(t_strcut(line + 8, '\t')) !=
AUTH_MASTER_PROTOCOL_MAJOR_VERSION) {
i_error("Master not compatible with this server "
"(mixed old and new binaries?)");
auth_master_connection_close(conn);
return;
}
conn->version_received = TRUE;
}
while ((line = i_stream_next_line(conn->input)) != NULL) {
t_push();
if (strncmp(line, "REQUEST\t", 8) == 0)
ret = master_input_request(conn, line + 8);
else if (strcmp(line, "DIE") == 0)
ret = master_input_die(conn);
else {
/* ignore unknown command */
ret = TRUE;
}
t_pop();
if (!ret) {
auth_master_connection_close(conn);
return;
}
}
}
static int master_output(void *context)
{
struct auth_master_connection *conn = context;
int ret;
if ((ret = o_stream_flush(conn->output)) < 0) {
/* transmit error, probably master died */
auth_master_connection_close(conn);
return 1;
}
if (o_stream_get_buffer_used_size(conn->output) <= MAX_OUTBUF_SIZE/2) {
/* allow input again */
conn->io = io_add(conn->fd, IO_READ, master_input, conn);
}
return 1;
}
static void
auth_master_connection_set_fd(struct auth_master_connection *conn, int fd)
{
if (conn->input != NULL)
i_stream_unref(conn->input);
if (conn->output != NULL)
o_stream_unref(conn->output);
if (conn->io != NULL)
io_remove(conn->io);
conn->input = i_stream_create_file(fd, default_pool,
MAX_INBUF_SIZE, FALSE);
conn->output = o_stream_create_file(fd, default_pool,
(size_t)-1, FALSE);
o_stream_set_flush_callback(conn->output, master_output, conn);
conn->io = io_add(fd, IO_READ, master_input, conn);
conn->fd = fd;
}
struct auth_master_connection *
auth_master_connection_create(struct auth *auth, int fd, unsigned int pid)
{
struct auth_master_connection *conn;
conn = i_new(struct auth_master_connection, 1);
conn->auth = auth;
conn->refcount = 1;
conn->pid = pid;
conn->fd = fd;
conn->listeners_buf = buffer_create_dynamic(default_pool, 64);
if (fd != -1)
auth_master_connection_set_fd(conn, fd);
return conn;
}
void auth_master_connection_send_handshake(struct auth_master_connection *conn)
{
if (conn->output != NULL) {
master_send(conn, "VERSION\t%u\t%u\nSPID\t%u\n",
AUTH_MASTER_PROTOCOL_MAJOR_VERSION,
AUTH_MASTER_PROTOCOL_MINOR_VERSION, conn->pid);
}
}
static void auth_master_connection_close(struct auth_master_connection *conn)
{
if (!standalone)
io_loop_stop(ioloop);
if (close(conn->fd) < 0)
i_error("close(): %m");
conn->fd = -1;
o_stream_close(conn->output);
conn->output = NULL;
if (conn->io != NULL) {
io_remove(conn->io);
conn->io = NULL;
}
}
void auth_master_connection_destroy(struct auth_master_connection *conn)
{
struct auth_listener **l;
size_t i, size;
if (conn->destroyed)
return;
conn->destroyed = TRUE;
auth_client_connections_deinit(conn);
if (conn->fd != -1)
auth_master_connection_close(conn);
l = buffer_get_modifyable_data(conn->listeners_buf, &size);
size /= sizeof(*l);
for (i = 0; i < size; i++) {
net_disconnect(l[i]->fd);
io_remove(l[i]->io);
if (l[i]->path != NULL) {
(void)unlink(l[i]->path);
i_free(l[i]->path);
}
i_free(l[i]);
}
buffer_free(conn->listeners_buf);
conn->listeners_buf = NULL;
auth_master_connection_unref(conn);
}
static int auth_master_connection_unref(struct auth_master_connection *conn)
{
if (--conn->refcount > 0)
return TRUE;
if (conn->output != NULL)
o_stream_unref(conn->output);
i_free(conn);
return FALSE;
}
static void auth_accept(void *context)
{
struct auth_listener *l = context;
int fd;
fd = net_accept(l->fd, NULL, NULL);
if (fd < 0) {
if (fd < -1)
i_fatal("accept() failed: %m");
} else {
net_set_nonblock(fd, TRUE);
if (l->client_listener)
(void)auth_client_connection_create(l->master, fd);
else {
/* we'll just replace the previous master.. */
auth_master_connection_set_fd(l->master, fd);
auth_master_connection_send_handshake(l->master);
}
}
}
void auth_master_connection_add_listener(struct auth_master_connection *conn,
int fd, const char *path, int client)
{
struct auth_listener *l;
l = i_new(struct auth_listener, 1);
l->master = conn;
l->client_listener = client;
l->fd = fd;
l->path = i_strdup(path);
l->io = io_add(fd, IO_READ, auth_accept, l);
buffer_append(conn->listeners_buf, &l, sizeof(l));
}