view src/master/service-auth-server.c @ 9235:2e2b957f1cca HEAD

Implemented anvil service, which is used to implement mail_max_userip_connections.
author Timo Sirainen <tss@iki.fi>
date Tue, 05 May 2009 21:28:34 -0400
parents 7b3accdf44f8
children 1cdee825e414
line wrap: on
line source

/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */

#include "common.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "hash.h"
#include "service.h"
#include "service-process.h"
#include "service-auth-server.h"
#include "service-auth-source.h"
#include "../auth/auth-master-interface.h"

#include <stdlib.h>
#include <unistd.h>

#define AUTH_MAX_INBUF_SIZE 8192

static void
service_process_auth_request_free(struct service_process_auth_request *request)
{
	if (request->fd != -1) {
		if (close(request->fd) < 0)
			i_error("close(auth request fd) failed: %m");
	}
	i_free(request);
}

static void
service_process_auth_server_close(struct service_process_auth_server *process)
{
	struct hash_iterate_context *iter;
	void *key, *value;

	if (process->auth_requests != NULL) {
		iter = hash_table_iterate_init(process->auth_requests);
		while (hash_table_iterate(iter, &key, &value)) {
			struct service_process_auth_request *request = value;

			service_process_unref(&request->process->process);
			service_process_auth_request_free(request);
		}
		hash_table_iterate_deinit(&iter);
		hash_table_destroy(&process->auth_requests);
	}

	if (process->auth_input != NULL)
		i_stream_close(process->auth_input);
	if (process->auth_output != NULL)
		o_stream_close(process->auth_output);

	if (process->io_auth != NULL)
		io_remove(&process->io_auth);
	if (process->auth_fd != -1) {
		if (close(process->auth_fd) < 0)
			i_error("close(auth_fd) failed: %m");
		process->auth_fd = -1;
	}
}

static struct service_process_auth_request *
auth_process_lookup_request(struct service_process_auth_server *process,
			    unsigned int id)
{
        struct service_process_auth_request *request;

	request = hash_table_lookup(process->auth_requests, POINTER_CAST(id));
	if (request == NULL) {
		service_error(process->process.service,
			      "authentication service %s "
			      "sent reply with unknown ID %u",
			      dec2str(process->process.pid), id);
		return NULL;
	}

	hash_table_remove(process->auth_requests, POINTER_CAST(id));
	if (!service_process_unref(&request->process->process)) {
		/* process already died. */
		service_process_auth_request_free(request);
		return NULL;
	}

	return request;
}

static int
auth_process_input_user(struct service_process_auth_server *process, const char *args)
{
        struct service_process_auth_request *request;
	const char *const *list;
	enum master_auth_status status;
	unsigned int id;

	/* <id> <userid> [..] */

	list = t_strsplit(args, "\t");
	if (list[0] == NULL || list[1] == NULL) {
		i_error("BUG: Auth process %s sent corrupted USER line",
			dec2str(process->process.pid));
		return FALSE;
	}
	id = (unsigned int)strtoul(list[0], NULL, 10);

        request = auth_process_lookup_request(process, id);
	if (request != NULL) {
		struct service *dest_service =
			request->process->process.service->auth_dest_service;
		struct service_process *dest_process;

		dest_process = service_process_create(dest_service, list + 1,
						      request);
		status = dest_process != NULL ?
			MASTER_AUTH_STATUS_OK :
			MASTER_AUTH_STATUS_INTERNAL_ERROR;
		service_process_auth_source_send_reply(request->process,
						       request->process_tag,
						       status);
		service_process_auth_request_free(request);
	}
	return TRUE;
}

static int
auth_process_input_notfound(struct service_process_auth_server *process,
			    const char *args)
{
        struct service_process_auth_request *request;
	unsigned int id;

	id = (unsigned int)strtoul(args, NULL, 10);

        request = auth_process_lookup_request(process, id);
	if (request != NULL) {
		service_process_auth_source_send_reply(request->process,
						       request->process_tag,
						       FALSE);
		service_process_auth_request_free(request);
	}
	return TRUE;
}

static int
auth_process_input_fail(struct service_process_auth_server *process,
			const char *args)
{
        struct service_process_auth_request *request;
 	const char *error;
	unsigned int id;

	error = strchr(args, '\t');
	if (error != NULL)
		error++;

	id = (unsigned int)strtoul(args, NULL, 10);

        request = auth_process_lookup_request(process, id);
	if (request != NULL) {
		service_process_auth_source_send_reply(request->process,
						       request->process_tag,
						       FALSE);
		service_process_auth_request_free(request);
	}
	return TRUE;
}

static void
service_process_auth_server_input(struct service_process_auth_server *process)
{
	const char *line;
	int ret;

	switch (i_stream_read(process->auth_input)) {
	case 0:
		return;
	case -1:
		/* disconnected */
		service_process_auth_server_close(process);
		return;
	case -2:
		/* buffer full */
		service_error(process->process.service,
			      "authentication server process %s "
			      "sent us too long line",
			      dec2str(process->process.pid));
		service_process_auth_server_close(process);
		return;
	}

	if (!process->auth_version_received) {
		line = i_stream_next_line(process->auth_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) {
			service_error(process->process.service,
				      "authentication server process %s "
				      "not compatible with master process "
				      "(mixed old and new binaries?)",
				      dec2str(process->process.pid));
			service_process_auth_server_close(process);
			return;
		}
		process->auth_version_received = TRUE;
	}

	while ((line = i_stream_next_line(process->auth_input)) != NULL) {
		if (strncmp(line, "USER\t", 5) == 0)
			ret = auth_process_input_user(process, line + 5);
		else if (strncmp(line, "NOTFOUND\t", 9) == 0)
			ret = auth_process_input_notfound(process, line + 9);
		else if (strncmp(line, "FAIL\t", 5) == 0)
			ret = auth_process_input_fail(process, line + 5);
		else
			ret = TRUE;

		if (!ret) {
			service_process_auth_server_close(process);
			break;
		}
	}
}

void service_process_auth_server_init(struct service_process *_process, int fd)
{
	struct service_process_auth_server *process =
		(struct service_process_auth_server *)_process;

	i_assert(_process->service->type == SERVICE_TYPE_AUTH_SERVER);

	process->auth_fd = fd;
	process->auth_input = i_stream_create_fd(process->auth_fd,
						 AUTH_MAX_INBUF_SIZE, FALSE);
	process->auth_output =
		o_stream_create_fd(fd, (size_t)-1, FALSE);
	process->io_auth =
		io_add(process->auth_fd, IO_READ,
		       service_process_auth_server_input, process);
	process->auth_requests =
		hash_table_create(default_pool, default_pool, 0, NULL, NULL);
}

void service_process_auth_server_deinit(struct service_process *_process)
{
	struct service_process_auth_server *process =
		(struct service_process_auth_server *)_process;

	i_assert(_process->service->type == SERVICE_TYPE_AUTH_SERVER);

	service_process_auth_server_close(process);
}