Master process is no longer in the middle.
--HG--
branch : HEAD
}
service auth {
- type = auth
executable = dovecot-auth
# default
path = auth-userdb
mode = 0600
}
+
+ unix_listener {
+ path = auth-master
+ mode = 0600
+ }
}
service auth-worker {
service imap-login {
protocol = imap
- type = auth-source
+ type = login
executable = imap-login
- auth_dest_service = imap
inet_listener {
port = 143
# Most of the memory goes to mmap()ing files. You may need to increase this
# limit if you have huge mailboxes.
#vsz_limit = 256
+
+ service_count = 1
+ unix_listener {
+ path = login/imap
+ mode = 0666
+ }
}
service pop3-login {
protocol = pop3
- type = auth-source
+ type = login
executable = pop3-login
- auth_dest_service = pop3
inet_listener {
port = 110
service pop3 {
protocol = pop3
executable = pop3
+
+ service_count = 1
+ unix_listener {
+ path = login/pop3
+ mode = 0666
+ }
}
service lmtp {
auth-cache.h \
auth-client-connection.h \
auth-common.h \
- auth-master-interface.h \
auth-master-connection.h \
mech-otp-skey-common.h \
mech-plain-common.h \
conn->refcount++;
conn->request_handler =
auth_request_handler_create(conn->auth,
- auth_callback, conn,
- array_count(&auth_master_connections) != 0 ?
- auth_master_request_callback : NULL);
+ auth_callback, conn, auth_master_request_callback);
auth_request_handler_set(conn->request_handler, conn->connect_uid, pid);
conn->pid = pid;
#ifndef AUTH_CLIENT_CONNECTION_H
#define AUTH_CLIENT_CONNECTION_H
-#include "master-interface.h"
+#include "master-auth.h"
struct auth_client_connection {
struct auth *auth;
#include "master-service.h"
#include "userdb.h"
#include "userdb-blocking.h"
+#include "master-interface.h"
#include "auth-request-handler.h"
-#include "auth-master-interface.h"
#include "auth-client-connection.h"
#include "auth-master-connection.h"
struct auth_master_connection *conn = *_conn;
struct auth_master_connection *const *masters;
unsigned int i, count;
- bool service_connection = conn->fd != MASTER_AUTH_FD;
*_conn = NULL;
if (conn->destroyed)
conn->fd = -1;
}
- if (service_connection)
- master_service_client_connection_destroyed(master_service);
+ master_service_client_connection_destroyed(master_service);
auth_master_connection_unref(&conn);
}
+++ /dev/null
-#ifndef AUTH_MASTER_INTERFACE_H
-#define AUTH_MASTER_INTERFACE_H
-
-#include "master-interface.h"
-
-/* Major version changes are not backwards compatible,
- minor version numbers can be ignored. */
-#define AUTH_MASTER_PROTOCOL_MAJOR_VERSION 1
-#define AUTH_MASTER_PROTOCOL_MINOR_VERSION 1
-
-#endif
#include "module-dir.h"
#include "randgen.h"
#include "master-service.h"
+#include "master-interface.h"
#include "password-scheme.h"
#include "mech.h"
#include "auth.h"
#include "auth-request-handler.h"
#include "auth-worker-server.h"
#include "auth-worker-client.h"
-#include "auth-master-interface.h"
#include "auth-master-connection.h"
#include "auth-client-connection.h"
/* workers have only a single connection from the master
auth process */
master_service_set_client_limit(master_service, 1);
- } else if (getenv("MASTER_AUTH_FD") != NULL) {
- (void)auth_master_connection_create(auth, MASTER_AUTH_FD,
- FALSE);
}
}
argc -= optind;
argv += optind;
+ master_service_init_finish(master_service);
if (!doveadm_try_run(cmd_name, argc, argv) &&
!doveadm_mail_try_run(cmd_name, argc, argv))
usage();
}
if (optind != argc)
usage();
+ master_service_init_finish(master_service);
memset(&input, 0, sizeof(input));
input.username = username;
extern struct mail_storage_callbacks mail_storage_callbacks;
struct imap_module_register imap_module_register = { 0 };
-static struct client *imap_clients = NULL;
+struct client *imap_clients = NULL;
static void client_idle_timeout(struct client *client)
{
unsigned int modseqs_sent_since_sync:1;
};
+extern struct client *imap_clients;
+
/* Create new client with specified input/output handles. socket specifies
if the handle is a socket. */
struct client *client_create(int fd_in, int fd_out, struct mail_user *user,
#include "restrict-access.h"
#include "fd-close-on-exec.h"
#include "process-title.h"
-#include "master-service.h"
#include "master-interface.h"
+#include "master-service.h"
+#include "master-login.h"
#include "mail-user.h"
#include "mail-storage-service.h"
#include "imap-commands.h"
#include <unistd.h>
#define IS_STANDALONE() \
- (getenv("CLIENT_INPUT") == NULL)
+ (getenv(MASTER_UID_ENV) == NULL)
+
+static const struct setting_parser_info *set_roots[] = {
+ &imap_setting_parser_info,
+ NULL
+};
+static struct master_login *master_login = NULL;
+static enum mail_storage_service_flags storage_service_flags = 0;
+static bool user_initialized = FALSE;
void (*hook_client_created)(struct client **client) = NULL;
-static void client_add_input(struct client *client, const char *input)
+static void client_add_input(struct client *client, const buffer_t *buf)
{
- buffer_t *buf;
+ struct ostream *output;
const char *tag;
unsigned int data_pos;
bool send_untagged_capability = FALSE;
- buf = input == NULL ? NULL : t_base64_decode_str(input);
if (buf != NULL && buf->used > 0) {
tag = t_strndup(buf->data, buf->used);
switch (*tag) {
tag = getenv("IMAPLOGINTAG");
}
+ output = client->output;
+ o_stream_ref(output);
+ o_stream_cork(output);
if (tag == NULL) {
client_send_line(client, t_strconcat(
"* PREAUTH [CAPABILITY ",
} else if (send_untagged_capability) {
/* client doesn't seem to understand tagged capabilities. send
untagged instead and hope that it works. */
- o_stream_cork(client->output);
client_send_line(client, t_strconcat("* CAPABILITY ",
str_c(client->capability_string), NULL));
client_send_line(client, t_strconcat(tag, " OK Logged in", NULL));
- o_stream_uncork(client->output);
} else {
client_send_line(client, t_strconcat(
tag, " OK [CAPABILITY ",
str_c(client->capability_string), "] Logged in", NULL));
}
(void)client_handle_input(client);
+ o_stream_uncork(output);
+ o_stream_unref(&output);
}
-static void main_init(const struct imap_settings *set, struct mail_user *user,
- bool dump_capability)
+static void
+main_stdio_init_user(const struct imap_settings *set, struct mail_user *user)
{
struct client *client;
- struct ostream *output;
+ buffer_t *input_buf;
+ const char *input_base64;
- if (set->shutdown_clients && !dump_capability)
- master_service_set_die_with_master(master_service, TRUE);
+ input_base64 = getenv("CLIENT_INPUT");
+ input_buf = input_base64 == NULL ? NULL :
+ t_base64_decode_str(input_base64);
- client = client_create(0, 1, user, set);
+ client = client_create(STDIN_FILENO, STDOUT_FILENO, user, set);
+ client_add_input(client, input_buf);
+}
+
+static void
+main_stdio_run(bool dump_capability)
+{
+ struct mail_storage_service_input input;
+ struct mail_user *mail_user;
+ const struct imap_settings *set;
+ const char *value;
+
+ memset(&input, 0, sizeof(input));
+ input.module = input.service = "imap";
+ input.username = getenv("USER");
+ if (input.username == NULL && IS_STANDALONE())
+ input.username = getlogin();
+ if (input.username == NULL)
+ i_fatal("USER environment missing");
+ if ((value = getenv("IP")) != NULL)
+ net_addr2ip(value, &input.remote_ip);
+ if ((value = getenv("LOCAL_IP")) != NULL)
+ net_addr2ip(value, &input.local_ip);
+
+ user_initialized = TRUE;
+ mail_user = mail_storage_service_init_user(master_service,
+ &input, set_roots,
+ storage_service_flags);
+ set = mail_storage_service_get_settings(master_service);
+ restrict_access_allow_coredumps(TRUE);
+ if (set->shutdown_clients)
+ master_service_set_die_with_master(master_service, TRUE);
if (dump_capability) {
+ struct client *client = client_create(0, 1, mail_user, set);
printf("%s\n", str_c(client->capability_string));
exit(0);
}
- output = client->output;
- o_stream_ref(output);
- o_stream_cork(output);
- client_add_input(client, getenv("CLIENT_INPUT"));
- o_stream_uncork(output);
- o_stream_unref(&output);
+ /* fake that we're running, so we know if client was destroyed
+ while handling its initial input */
+ io_loop_set_running(current_ioloop);
+ main_stdio_init_user(set, mail_user);
}
-static void main_deinit(void)
+static void
+login_client_connected(const struct master_login_client *client,
+ const char *username, const char *const *extra_fields)
{
- clients_destroy_all();
+ struct mail_storage_service_input input;
+ struct mail_user *mail_user;
+ struct client *imap_client;
+ const struct imap_settings *set;
+ buffer_t input_buf;
+
+ if (imap_clients != NULL) {
+ i_error("Can't handle more than one connection currently");
+ (void)close(client->fd);
+ return;
+ }
+ i_assert(!user_initialized);
+
+ memset(&input, 0, sizeof(input));
+ input.module = input.service = "imap";
+ input.local_ip = client->auth_req.local_ip;
+ input.remote_ip = client->auth_req.remote_ip;
+ input.username = username;
+ input.userdb_fields = extra_fields;
+
+ if (input.username == NULL) {
+ i_error("login client: Username missing from auth reply");
+ (void)close(client->fd);
+ return;
+ }
+ user_initialized = TRUE;
+ master_login_deinit(&master_login);
+
+ mail_user = mail_storage_service_init_user(master_service,
+ &input, set_roots,
+ storage_service_flags);
+ set = mail_storage_service_get_settings(master_service);
+ restrict_access_allow_coredumps(TRUE);
+ if (set->shutdown_clients)
+ master_service_set_die_with_master(master_service, TRUE);
+
+ /* fake that we're running, so we know if client was destroyed
+ while handling its initial input */
+ io_loop_set_running(current_ioloop);
+
+ buffer_create_const_data(&input_buf, client->data,
+ client->auth_req.data_size);
+ imap_client = client_create(client->fd, client->fd, mail_user, set);
+ T_BEGIN {
+ client_add_input(imap_client, &input_buf);
+ } T_END;
}
static void client_connected(const struct master_service_connection *conn)
{
- /* FIXME: we can't handle this yet */
- (void)close(conn->fd);
+ if (master_login == NULL) {
+ /* running standalone, we shouldn't even get here */
+ (void)close(conn->fd);
+ } else {
+ master_login_add(master_login, conn->fd);
+ }
}
int main(int argc, char *argv[], char *envp[])
{
- const struct setting_parser_info *set_roots[] = {
- &imap_setting_parser_info,
- NULL
- };
- enum master_service_flags service_flags =
- MASTER_SERVICE_FLAG_STD_CLIENT;
- enum mail_storage_service_flags storage_service_flags = 0;
- struct mail_storage_service_input input;
- struct mail_user *mail_user;
- const struct imap_settings *set;
+ enum master_service_flags service_flags = 0;
bool dump_capability;
- const char *value;
int c;
if (IS_STANDALONE() && getuid() == 0 &&
return 1;
}
- if (IS_STANDALONE())
- service_flags |= MASTER_SERVICE_FLAG_STANDALONE;
- else {
+ if (IS_STANDALONE()) {
+ service_flags |= MASTER_SERVICE_FLAG_STANDALONE |
+ MASTER_SERVICE_FLAG_STD_CLIENT;
+ } else {
storage_service_flags |=
- MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT |
- MAIL_STORAGE_SERVICE_FLAG_RESTRICT_BY_ENV;
+ MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT;
}
dump_capability = getenv("DUMP_CAPABILITY") != NULL;
if (!master_service_parse_option(master_service, c, optarg))
exit(FATAL_DEFAULT);
}
-
- memset(&input, 0, sizeof(input));
- input.module = "imap";
- input.service = "imap";
- input.username = getenv("USER");
- if (input.username == NULL && IS_STANDALONE())
- input.username = getlogin();
- if (input.username == NULL) {
- if (getenv(MASTER_UID_ENV) == NULL)
- i_fatal("USER environment missing");
- else {
- i_fatal("login_executable setting must be imap-login, "
- "not imap");
- }
- }
- if ((value = getenv("IP")) != NULL)
- net_addr2ip(value, &input.remote_ip);
- if ((value = getenv("LOCAL_IP")) != NULL)
- net_addr2ip(value, &input.local_ip);
+ process_title_init(argv, envp);
+ master_service_init_finish(master_service);
/* plugins may want to add commands, so this needs to be called early */
commands_init();
imap_fetch_handlers_init();
- mail_user = mail_storage_service_init_user(master_service,
- &input, set_roots,
- storage_service_flags);
- set = mail_storage_service_get_settings(master_service);
- restrict_access_allow_coredumps(TRUE);
-
- process_title_init(argv, envp);
-
- /* fake that we're running, so we know if client was destroyed
- while initializing */
- io_loop_set_running(current_ioloop);
+ if (IS_STANDALONE() || dump_capability) {
+ T_BEGIN {
+ main_stdio_run(dump_capability);
+ } T_END;
+ } else {
+ master_login = master_login_init("auth-master",
+ login_client_connected);
+ io_loop_set_running(current_ioloop);
+ }
- T_BEGIN {
- main_init(set, mail_user, dump_capability);
- } T_END;
if (io_loop_is_running(current_ioloop))
master_service_run(master_service, client_connected);
+ clients_destroy_all();
- main_deinit();
- mail_storage_service_deinit_user();
+ if (master_login != NULL)
+ master_login_deinit(&master_login);
+ if (user_initialized)
+ mail_storage_service_deinit_user();
imap_fetch_handlers_deinit();
commands_deinit();
i_fatal_status(EX_USAGE,
"destination user parameter (-d user) not given");
}
+ master_service_init_finish(master_service);
memset(&service_input, 0, sizeof(service_input));
service_input.module = "lda";
libmaster_la_SOURCES = \
master-auth.c \
+ master-login.c \
+ master-login-auth.c \
master-service.c \
master-service-settings.c \
syslog-util.c
noinst_HEADERS = \
master-auth.h \
master-interface.h \
+ master-login.h \
+ master-login-auth.h \
master-service.h \
master-service-private.h \
master-service-settings.h \
#include <unistd.h>
#include <sys/stat.h>
-struct master_auth {
- struct master_service *service;
- pool_t pool;
+struct master_auth_connection {
+ struct master_auth *auth;
+ unsigned int tag;
int fd;
struct io *io;
- unsigned int tag_counter;
- struct hash_table *requests;
-
char buf[sizeof(struct master_auth_reply)];
unsigned int buf_pos;
- /* linked list, node->context is the next pointer */
- struct master_auth_request_node *free_nodes;
-};
-
-struct master_auth_request_node {
master_auth_callback_t *callback;
void *context;
};
-static struct master_auth_request_node aborted_node;
-
-unsigned int master_auth_request(struct master_service *service, int fd,
- const struct master_auth_request *request,
- const unsigned char *data,
- master_auth_callback_t *callback,
- void *context)
-{
- struct master_auth *auth = service->auth;
- struct master_auth_request_node *node;
- struct master_auth_request req;
- buffer_t *buf;
- struct stat st;
- ssize_t ret;
-
- i_assert(request->auth_pid != 0);
-
- req = *request;
- req.tag = ++auth->tag_counter;
- if (req.tag == 0)
- req.tag = ++auth->tag_counter;
-
- if (fstat(fd, &st) < 0)
- i_fatal("fstat(auth dest fd) failed: %m");
- req.ino = st.st_ino;
+struct master_auth {
+ struct master_service *service;
+ pool_t pool;
- buf = buffer_create_dynamic(pool_datastack_create(),
- sizeof(req) + req.data_size);
- buffer_append(buf, &req, sizeof(req));
- buffer_append(buf, data, req.data_size);
+ const char *path;
- ret = fd_send(auth->fd, fd, buf->data, buf->used);
- if (ret < 0)
- i_fatal("fd_send(%d) failed: %m", fd);
- if ((size_t)ret != buf->used) {
- i_fatal("fd_send() sent only %d of %d bytes",
- (int)ret, (int)buf->used);
- }
+ unsigned int tag_counter;
+ struct hash_table *connections;
+};
- if (auth->free_nodes == NULL)
- node = p_new(auth->pool, struct master_auth_request_node, 1);
- else {
- node = auth->free_nodes;
- auth->free_nodes = node->context;
- }
- node->callback = callback;
- node->context = context;
+struct master_auth *
+master_auth_init(struct master_service *service, const char *path)
+{
+ struct master_auth *auth;
+ pool_t pool;
- hash_table_insert(auth->requests, POINTER_CAST(req.tag), node);
- return req.tag;
+ pool = pool_alloconly_create("master auth", 1024);
+ auth = p_new(pool, struct master_auth, 1);
+ auth->pool = pool;
+ auth->service = service;
+ auth->path = p_strdup(pool, path);
+ auth->connections = hash_table_create(default_pool, pool,
+ 0, NULL, NULL);
+ return auth;
}
-void master_auth_request_abort(struct master_service *service, unsigned int tag)
+static void
+master_auth_connection_deinit(struct master_auth_connection **_conn)
{
- struct master_auth *auth = service->auth;
- struct master_auth_request_node *node;
+ struct master_auth_connection *conn = *_conn;
+ struct master_auth_reply reply;
- node = hash_table_lookup(auth->requests, POINTER_CAST(tag));
- if (node == NULL)
- i_panic("master_auth_request_abort(): tag %u not found", tag);
+ *_conn = NULL;
- i_assert(node != &aborted_node);
- hash_table_update(auth->requests, POINTER_CAST(tag), &aborted_node);
+ if (conn->tag != 0) {
+ hash_table_remove(conn->auth->connections,
+ POINTER_CAST(conn->tag));
+ }
- node->callback = NULL;
- node->context = auth->free_nodes;
- auth->free_nodes = node;
-}
+ if (conn->callback != NULL) {
+ memset(&reply, 0, sizeof(reply));
+ reply.status = MASTER_AUTH_STATUS_INTERNAL_ERROR;
+ conn->callback(&reply, conn->context);
+ }
-static void
-master_notify_have_more_avail_processes(struct master_service *service,
- bool have_more)
-{
- if (!have_more) {
- /* make sure we're listening for more connections */
- master_service_io_listeners_add(service);
+ if (conn->io != NULL)
+ io_remove(&conn->io);
+ if (conn->fd != -1) {
+ if (close(conn->fd) < 0)
+ i_fatal("close(master auth conn) failed: %m");
+ conn->fd = -1;
}
- service->call_avail_overflow = !have_more;
+ i_free(conn);
}
-static void request_handle(struct master_auth *auth,
- struct master_auth_reply *reply)
+void master_auth_deinit(struct master_auth **_auth)
{
- struct master_auth_request_node *node;
+ struct master_auth *auth = *_auth;
+ struct hash_iterate_context *iter;
+ void *key, *value;
- if (reply->tag == 0) {
- /* notification from master */
- master_notify_have_more_avail_processes(auth->service,
- reply->status == 0);
- return;
- }
-
- node = hash_table_lookup(auth->requests, POINTER_CAST(reply->tag));
- if (node == NULL)
- i_error("Master sent reply with unknown tag %u", reply->tag);
+ *_auth = NULL;
- if (node != &aborted_node) {
- node->callback(reply, node->context);
+ iter = hash_table_iterate_init(auth->connections);
+ while (hash_table_iterate(iter, &key, &value)) {
+ struct master_auth_connection *conn = value;
- /* the callback may have called master_auth_request_abort(),
- which would have put the node to free_nodes list already */
- if (node->callback != NULL) {
- node->callback = NULL;
- node->context = auth->free_nodes;
- auth->free_nodes = node;
- }
+ conn->tag = 0;
+ master_auth_connection_deinit(&conn);
}
-
- hash_table_remove(auth->requests, POINTER_CAST(reply->tag));
+ hash_table_iterate_deinit(&iter);
+ hash_table_destroy(&auth->connections);
+ pool_unref(&auth->pool);
}
-static void master_auth_input(void *context)
+static void master_auth_connection_input(struct master_auth_connection *conn)
{
- struct master_auth *auth = context;
+ const struct master_auth_reply *reply;
int ret;
- ret = net_receive(auth->fd, auth->buf + auth->buf_pos,
- sizeof(auth->buf) - auth->buf_pos);
- if (ret < 0) {
- /* master died, kill all clients logging in */
- master_service_stop(auth->service);
+ ret = read(conn->fd, conn->buf + conn->buf_pos,
+ sizeof(conn->buf) - conn->buf_pos);
+ if (ret <= 0) {
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ return;
+ i_error("read(master auth conn) failed: %m");
+ } else {
+ i_error("read(master auth conn) failed: "
+ "Remote closed connection");
+ }
+ master_auth_connection_deinit(&conn);
return;
}
- auth->buf_pos += ret;
- if (auth->buf_pos < sizeof(auth->buf))
+ conn->buf_pos += ret;
+ if (conn->buf_pos < sizeof(conn->buf))
return;
/* reply is now read */
- request_handle(auth, (struct master_auth_reply *) auth->buf);
- auth->buf_pos = 0;
+ reply = (struct master_auth_reply *)conn->buf;
+ conn->buf_pos = 0;
+
+ if (conn->tag != reply->tag)
+ i_error("Master sent reply with unknown tag %u", reply->tag);
+ else {
+ conn->callback(reply, conn->context);
+ conn->callback = NULL;
+ }
+ master_auth_connection_deinit(&conn);
}
-void master_auth_init(struct master_service *service)
+void master_auth_request(struct master_auth *auth, int fd,
+ const struct master_auth_request *request,
+ const unsigned char *data,
+ master_auth_callback_t *callback,
+ void *context, unsigned int *tag_r)
{
- struct master_auth *auth;
- struct ip_addr ip;
- pool_t pool;
+ struct master_auth_connection *conn;
+ struct master_auth_request req;
+ buffer_t *buf;
+ struct stat st;
+ ssize_t ret;
- i_assert(service->auth == NULL);
+ i_assert(request->client_pid != 0);
+ i_assert(request->auth_pid != 0);
- if (getenv("MASTER_AUTH_FD") == NULL)
- i_fatal("auth_dest_service setting not set");
+ conn = i_new(struct master_auth_connection, 1);
+ conn->auth = auth;
+ conn->callback = callback;
+ conn->context = context;
- if (net_getsockname(MASTER_AUTH_FD, &ip, NULL) < 0 ||
- ip.family != AF_UNIX)
- i_fatal("MASTER_AUTH_FD not given");
+ req = *request;
+ req.tag = ++auth->tag_counter;
+ if (req.tag == 0)
+ req.tag = ++auth->tag_counter;
- pool = pool_alloconly_create("master auth", 1024);
- auth = p_new(pool, struct master_auth, 1);
- auth->pool = pool;
- auth->service = service;
- auth->fd = MASTER_AUTH_FD;
- auth->requests = hash_table_create(default_pool, pool, 0, NULL, NULL);
- auth->io = io_add(auth->fd, IO_READ, master_auth_input, auth);
+ if (fstat(fd, &st) < 0)
+ i_fatal("fstat(auth dest fd) failed: %m");
+ req.ino = st.st_ino;
+
+ buf = buffer_create_dynamic(pool_datastack_create(),
+ sizeof(req) + req.data_size);
+ buffer_append(buf, &req, sizeof(req));
+ buffer_append(buf, data, req.data_size);
+
+ conn->fd = net_connect_unix(auth->path);
+ if (conn->fd == -1)
+ i_error("net_connect_unix(%s) failed: %m", auth->path);
+
+ ret = conn->fd == -1 ? -1 :
+ fd_send(conn->fd, fd, buf->data, buf->used);
+ if (ret < 0)
+ i_error("fd_send(%d) failed: %m", fd);
+ else if ((size_t)ret != buf->used) {
+ i_error("fd_send() sent only %d of %d bytes",
+ (int)ret, (int)buf->used);
+ ret = -1;
+ }
+ if (ret < 0) {
+ master_auth_connection_deinit(&conn);
+ return;
+ }
- service->auth = auth;
+ conn->tag = req.tag;
+ conn->io = io_add(conn->fd, IO_READ,
+ master_auth_connection_input, conn);
+ hash_table_insert(auth->connections, POINTER_CAST(req.tag), conn);
+ *tag_r = req.tag;
}
-void master_auth_deinit(struct master_service *service)
+void master_auth_request_abort(struct master_auth *auth, unsigned int tag)
{
- struct master_auth *auth = service->auth;
+ struct master_auth_connection *conn;
- i_assert(service->auth != NULL);
+ conn = hash_table_lookup(auth->connections, POINTER_CAST(tag));
+ if (conn == NULL)
+ i_panic("master_auth_request_abort(): tag %u not found", tag);
- hash_table_destroy(&auth->requests);
- if (auth->io != NULL)
- io_remove(&auth->io);
- if (close(auth->fd) < 0)
- i_fatal("close(master auth) failed: %m");
- pool_unref(&auth->pool);
- service->auth = NULL;
+ conn->callback = NULL;
}
#ifndef MASTER_AUTH_H
#define MASTER_AUTH_H
+#include "network.h"
+
struct master_service;
-struct master_auth_request;
-struct master_auth_reply;
+
+/* Major version changes are not backwards compatible,
+ minor version numbers can be ignored. */
+#define AUTH_MASTER_PROTOCOL_MAJOR_VERSION 1
+#define AUTH_MASTER_PROTOCOL_MINOR_VERSION 1
+
+/* Authentication client process's cookie size */
+#define MASTER_AUTH_COOKIE_SIZE (128/8)
+
+/* This should be kept in sync with LOGIN_MAX_INBUF_SIZE. Multiply it by two
+ to make sure there's space to transfer the command tag */
+#define MASTER_AUTH_MAX_DATA_SIZE (1024*2)
+
+/* Authentication request. File descriptor may be sent along with the
+ request. */
+struct master_auth_request {
+ /* Request tag. Reply is sent back using same tag. */
+ unsigned int tag;
+
+ /* Authentication process, authentication ID and auth cookie. */
+ pid_t auth_pid;
+ unsigned int auth_id;
+ unsigned int client_pid;
+ uint8_t cookie[MASTER_AUTH_COOKIE_SIZE];
+
+ /* Local and remote IPs of the connection. The file descriptor
+ itself may be a local socketpair. */
+ struct ip_addr local_ip, remote_ip;
+
+ /* request follows this many bytes of client input */
+ uint32_t data_size;
+ /* inode of the transferred fd. verified just to be sure that the
+ correct fd is mapped to the correct struct. */
+ ino_t ino;
+};
+
+enum master_auth_status {
+ MASTER_AUTH_STATUS_OK,
+ MASTER_AUTH_STATUS_INTERNAL_ERROR
+};
+
+struct master_auth_reply {
+ /* tag=0 are notifications from master */
+ unsigned int tag;
+ enum master_auth_status status;
+ /* PID of the post-login mail process handling this connection */
+ pid_t mail_pid;
+};
typedef void master_auth_callback_t(const struct master_auth_reply *reply,
void *context);
+struct master_auth *
+master_auth_init(struct master_service *service, const char *path);
+void master_auth_deinit(struct master_auth **auth);
+
/* Send an authentication request. The fd contains the file descriptor to
transfer, or -1 if no fd is wanted to be transferred. Returns tag which can
be used to abort the request (ie. ignore the reply from master).
request->tag is ignored. */
-unsigned int master_auth_request(struct master_service *service, int fd,
- const struct master_auth_request *request,
- const unsigned char *data,
- master_auth_callback_t *callback,
- void *context);
-void master_auth_request_abort(struct master_service *service,
- unsigned int tag);
-
-void master_auth_init(struct master_service *service);
-void master_auth_deinit(struct master_service *service);
+void master_auth_request(struct master_auth *auth, int fd,
+ const struct master_auth_request *request,
+ const unsigned char *data,
+ master_auth_callback_t *callback,
+ void *context, unsigned int *tag_r);
+void master_auth_request_abort(struct master_auth *auth, unsigned int tag);
#endif
#ifndef MASTER_INTERFACE_H
#define MASTER_INTERFACE_H
-#include "network.h"
-
/* We are attempting semi-compatibility with Postfix's master process here.
Whether this is useful or not remains to be seen. */
/* unsigned char prefix[]; */
};
-/* This should be kept in sync with LOGIN_MAX_INBUF_SIZE. Multiply it by two
- to make sure there's space to transfer the command tag */
-#define MASTER_AUTH_MAX_DATA_SIZE (1024*2)
-
-/* Authentication client process's cookie size */
-#define MASTER_AUTH_COOKIE_SIZE (128/8)
-
-/* Authentication request. File descriptor may be sent along with the
- request. */
-struct master_auth_request {
- /* Request tag. Reply is sent back using same tag. */
- unsigned int tag;
-
- /* Authentication process, authentication ID and auth cookie. */
- pid_t auth_pid;
- unsigned int auth_id;
- uint8_t cookie[MASTER_AUTH_COOKIE_SIZE];
-
- /* Local and remote IPs of the connection. The file descriptor
- itself may be a local socketpair. */
- struct ip_addr local_ip, remote_ip;
-
- /* request follows this many bytes of client input */
- uint32_t data_size;
- /* inode of the transferred fd. verified just to be sure that the
- correct fd is mapped to the correct struct. */
- ino_t ino;
-};
-
-enum master_auth_status {
- MASTER_AUTH_STATUS_OK,
- MASTER_AUTH_STATUS_INTERNAL_ERROR
-};
-
-struct master_auth_reply {
- /* tag=0 are notifications from master */
- unsigned int tag;
- enum master_auth_status status;
- /* PID of the post-login mail process handling this connection */
- pid_t mail_pid;
+enum master_login_state {
+ MASTER_LOGIN_STATE_NONFULL = 0,
+ MASTER_LOGIN_STATE_FULL
};
/* getenv(MASTER_UID_ENV) provides master_status.uid value */
if dovecot was started with -p parameter. */
#define MASTER_SSL_KEY_PASSWORD_ENV "SSL_KEY_PASSWORD"
-/* Write pipe to anvil. Currently available only for auth destination
- services, for others it's /dev/null. */
+/* Write pipe to anvil. */
#define MASTER_ANVIL_FD 3
-
-/* Socket for sending master_auth_requests. Also used by auth server process
- as a master socket. */
-#define MASTER_AUTH_FD 4
+/* Master's "all processes full" notification fd for login processes */
+#define MASTER_LOGIN_NOTIFY_FD 4
/* Shared pipe to master, used to send master_status reports */
#define MASTER_STATUS_FD 5
--- /dev/null
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "network.h"
+#include "ioloop.h"
+#include "istream.h"
+#include "ostream.h"
+#include "hex-binary.h"
+#include "hash.h"
+#include "str.h"
+#include "master-auth.h"
+#include "master-login-auth.h"
+
+#include <stdlib.h>
+
+#define AUTH_MAX_INBUF_SIZE 8192
+
+struct master_login_auth_request {
+ master_login_auth_request_callback_t *callback;
+ void *context;
+};
+
+struct master_login_auth {
+ pool_t pool;
+ const char *auth_socket_path;
+ int refcount;
+
+ int fd;
+ struct io *io;
+ struct istream *input;
+ struct ostream *output;
+
+ unsigned int id_counter;
+ struct hash_table *requests;
+
+ unsigned int version_received:1;
+};
+
+struct master_login_auth *master_login_auth_init(const char *auth_socket_path)
+{
+ struct master_login_auth *auth;
+ pool_t pool;
+
+ pool = pool_alloconly_create("master login auth", 1024);
+ auth = p_new(pool, struct master_login_auth, 1);
+ auth->pool = pool;
+ auth->auth_socket_path = p_strdup(pool, auth_socket_path);
+ auth->refcount = 1;
+ auth->fd = -1;
+ auth->requests = hash_table_create(default_pool, pool, 0, NULL, NULL);
+ return auth;
+}
+
+static void master_login_auth_disconnect(struct master_login_auth *auth)
+{
+ struct hash_iterate_context *iter;
+ void *key, *value;
+
+ iter = hash_table_iterate_init(auth->requests);
+ while (hash_table_iterate(iter, &key, &value)) {
+ struct master_login_auth_request *request = value;
+ request->callback(NULL, request->context);
+ i_free(request);
+ }
+ hash_table_iterate_deinit(&iter);
+ hash_table_clear(auth->requests, FALSE);
+
+ if (auth->io != NULL)
+ io_remove(&auth->io);
+ if (auth->fd != -1) {
+ i_stream_destroy(&auth->input);
+ o_stream_destroy(&auth->output);
+
+ net_disconnect(auth->fd);
+ auth->fd = -1;
+ }
+ auth->version_received = FALSE;
+}
+
+static void master_login_auth_unref(struct master_login_auth **_auth)
+{
+ struct master_login_auth *auth = *_auth;
+
+ *_auth = NULL;
+
+ i_assert(auth->refcount > 0);
+ if (--auth->refcount > 0)
+ return;
+
+ hash_table_destroy(&auth->requests);
+ pool_unref(&auth->pool);
+}
+
+void master_login_auth_deinit(struct master_login_auth **_auth)
+{
+ struct master_login_auth *auth = *_auth;
+
+ *_auth = NULL;
+
+ master_login_auth_disconnect(auth);
+ master_login_auth_unref(&auth);
+}
+
+static struct master_login_auth_request *
+master_login_auth_lookup_request(struct master_login_auth *auth,
+ unsigned int id)
+{
+ struct master_login_auth_request *request;
+
+ request = hash_table_lookup(auth->requests, POINTER_CAST(id));
+ if (request == NULL) {
+ i_error("Auth server sent reply with unknown ID %u", id);
+ return NULL;
+ }
+
+ hash_table_remove(auth->requests, POINTER_CAST(id));
+ return request;
+}
+
+static bool
+master_login_auth_input_user(struct master_login_auth *auth, const char *args)
+{
+ struct master_login_auth_request *request;
+ const char *const *list;
+ unsigned int id;
+
+ /* <id> <userid> [..] */
+
+ list = t_strsplit(args, "\t");
+ if (list[0] == NULL || list[1] == NULL) {
+ i_error("Auth server sent corrupted USER line");
+ return FALSE;
+ }
+ id = (unsigned int)strtoul(list[0], NULL, 10);
+
+ request = master_login_auth_lookup_request(auth, id);
+ if (request != NULL) {
+ request->callback(list + 1, request->context);
+ i_free(request);
+ }
+ return TRUE;
+}
+
+static bool
+master_login_auth_input_notfound(struct master_login_auth *auth,
+ const char *args)
+{
+ struct master_login_auth_request *request;
+ unsigned int id;
+
+ id = (unsigned int)strtoul(args, NULL, 10);
+ request = master_login_auth_lookup_request(auth, id);
+ if (request != NULL) {
+ i_error("Auth request not found (timed out?): %u", id);
+ request->callback(NULL, request->context);
+ i_free(request);
+ }
+ return TRUE;
+}
+
+static bool
+master_login_auth_input_fail(struct master_login_auth *auth, const char *args)
+{
+ struct master_login_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 = master_login_auth_lookup_request(auth, id);
+ if (request != NULL) {
+ request->callback(NULL, request->context);
+ i_free(request);
+ }
+ return TRUE;
+}
+
+static void master_login_auth_input(struct master_login_auth *auth)
+{
+ const char *line;
+ bool ret;
+
+ switch (i_stream_read(auth->input)) {
+ case 0:
+ return;
+ case -1:
+ /* disconnected */
+ master_login_auth_disconnect(auth);
+ return;
+ case -2:
+ /* buffer full */
+ i_error("Auth server sent us too long line");
+ master_login_auth_disconnect(auth);
+ return;
+ }
+
+ if (!auth->version_received) {
+ line = i_stream_next_line(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) {
+ i_error("Authentication server not compatible with "
+ "master process (mixed old and new binaries?)");
+ master_login_auth_disconnect(auth);
+ return;
+ }
+ auth->version_received = TRUE;
+ }
+
+ auth->refcount++;
+ while ((line = i_stream_next_line(auth->input)) != NULL) {
+ if (strncmp(line, "USER\t", 5) == 0)
+ ret = master_login_auth_input_user(auth, line + 5);
+ else if (strncmp(line, "NOTFOUND\t", 9) == 0)
+ ret = master_login_auth_input_notfound(auth, line + 9);
+ else if (strncmp(line, "FAIL\t", 5) == 0)
+ ret = master_login_auth_input_fail(auth, line + 5);
+ else
+ ret = TRUE;
+
+ if (!ret || auth->input == NULL) {
+ master_login_auth_disconnect(auth);
+ break;
+ }
+ }
+ master_login_auth_unref(&auth);
+}
+
+static int
+master_login_auth_connect(struct master_login_auth *auth)
+{
+ int fd;
+
+ i_assert(auth->fd == -1);
+
+ fd = net_connect_unix(auth->auth_socket_path);
+ if (fd == -1) {
+ i_error("net_connect_unix(%s) failed: %m",
+ auth->auth_socket_path);
+ return -1;
+ }
+ auth->fd = fd;
+ auth->input = i_stream_create_fd(fd, AUTH_MAX_INBUF_SIZE, FALSE);
+ auth->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
+ auth->io = io_add(fd, IO_READ, master_login_auth_input, auth);
+ return 0;
+}
+
+void master_login_auth_request(struct master_login_auth *auth,
+ const struct master_auth_request *req,
+ master_login_auth_request_callback_t *callback,
+ void *context)
+{
+ struct master_login_auth_request *login_req;
+ unsigned int id;
+ string_t *str;
+
+ str = t_str_new(128);
+ if (auth->fd == -1) {
+ if (master_login_auth_connect(auth) < 0) {
+ callback(NULL, context);
+ return;
+ }
+ str_printfa(str, "VERSION\t%u\t%u\n",
+ AUTH_MASTER_PROTOCOL_MAJOR_VERSION,
+ AUTH_MASTER_PROTOCOL_MINOR_VERSION);
+ }
+
+ id = ++auth->id_counter;
+ if (id == 0)
+ id++;
+
+ str_printfa(str, "REQUEST\t%u\t%u\t%u\t", id,
+ req->client_pid, req->auth_id);
+ binary_to_hex_append(str, req->cookie, sizeof(req->cookie));
+ str_append_c(str, '\n');
+ o_stream_send(auth->output, str_data(str), str_len(str));
+
+ login_req = i_new(struct master_login_auth_request, 1);
+ login_req->callback = callback;
+ login_req->context = context;
+ hash_table_insert(auth->requests, POINTER_CAST(id), login_req);
+}
--- /dev/null
+#ifndef MASTER_LOGIN_AUTH_H
+#define MASTER_LOGIN_AUTH_H
+
+struct master_auth_request;
+
+typedef void
+master_login_auth_request_callback_t(const char *const *auth_args,
+ void *context);
+
+struct master_login_auth *master_login_auth_init(const char *auth_socket_path);
+void master_login_auth_deinit(struct master_login_auth **auth);
+
+void master_login_auth_request(struct master_login_auth *auth,
+ const struct master_auth_request *req,
+ master_login_auth_request_callback_t *callback,
+ void *context);
+
+#endif
--- /dev/null
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "ostream.h"
+#include "fdpass.h"
+#include "fd-close-on-exec.h"
+#include "llist.h"
+#include "master-login.h"
+#include "master-login-auth.h"
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+struct master_login_connection {
+ struct master_login_connection *prev, *next;
+
+ struct master_login *login;
+ int fd;
+ struct io *io;
+ struct ostream *output;
+};
+
+struct master_login {
+ master_login_callback_t *callback;
+ struct master_login_connection *conns;
+ struct master_login_auth *auth;
+};
+
+static void master_login_conn_deinit(struct master_login_connection **_conn);
+
+struct master_login *
+master_login_init(const char *auth_socket_path,
+ master_login_callback_t *callback)
+{
+ struct master_login *login;
+
+ login = i_new(struct master_login, 1);
+ login->callback = callback;
+ login->auth = master_login_auth_init(auth_socket_path);
+ return login;
+}
+
+void master_login_deinit(struct master_login **_login)
+{
+ struct master_login *login = *_login;
+
+ *_login = NULL;
+
+ master_login_auth_deinit(&login->auth);
+ while (login->conns != NULL) {
+ struct master_login_connection *conn = login->conns;
+
+ master_login_conn_deinit(&conn);
+ }
+ i_free(login);
+}
+
+static int
+master_login_conn_read_request(struct master_login_connection *conn,
+ struct master_auth_request *req_r,
+ unsigned char data[MASTER_AUTH_MAX_DATA_SIZE],
+ int *client_fd_r)
+{
+ struct stat st;
+ ssize_t ret;
+
+ *client_fd_r = -1;
+
+ ret = fd_read(conn->fd, req_r, sizeof(*req_r), client_fd_r);
+ if (ret != sizeof(*req_r)) {
+ if (ret == 0) {
+ /* disconnected */
+ } else if (ret > 0) {
+ /* request wasn't fully read */
+ i_error("fd_read() partial input (%d/%d)",
+ (int)ret, (int)sizeof(*req_r));
+ } else {
+ if (errno == EAGAIN)
+ return 0;
+
+ i_error("fd_read() failed: %m");
+ }
+ return -1;
+ }
+
+ if (req_r->data_size != 0) {
+ if (req_r->data_size > MASTER_AUTH_MAX_DATA_SIZE) {
+ i_error("Too large auth data_size sent");
+ return -1;
+ }
+ /* @UNSAFE */
+ ret = read(conn->fd, data, req_r->data_size);
+ if (ret != (ssize_t)req_r->data_size) {
+ if (ret == 0) {
+ /* disconnected */
+ } else if (ret > 0) {
+ /* request wasn't fully read */
+ i_error("Data read partially %d/%u",
+ (int)ret, req_r->data_size);
+ } else {
+ i_error("read(data) failed: %m");
+ }
+ return -1;
+ }
+ }
+
+ if (*client_fd_r == -1) {
+ i_error("Auth request missing a file descriptor");
+ return -1;
+ }
+
+ if (fstat(*client_fd_r, &st) < 0) {
+ i_error("fstat(fd_recv client) failed: %m");
+ return -1;
+ }
+ if (st.st_ino != req_r->ino) {
+ i_error("Auth request inode mismatch: %s != %s",
+ dec2str(st.st_ino), dec2str(req_r->ino));
+ return -1;
+ }
+ return 1;
+}
+
+static void
+master_login_auth_callback(const char *const *auth_args, void *context)
+{
+ struct master_login_client *client = context;
+ struct master_auth_reply reply;
+
+ memset(&reply, 0, sizeof(reply));
+ reply.tag = client->auth_req.tag;
+ reply.status = auth_args != NULL ? MASTER_AUTH_STATUS_OK :
+ MASTER_AUTH_STATUS_INTERNAL_ERROR;
+ reply.mail_pid = getpid();
+ o_stream_send(client->conn->output, &reply, sizeof(reply));
+
+ if (auth_args == NULL) {
+ if (close(client->fd) < 0)
+ i_error("close(fd_recv client) failed: %m");
+ i_free(client);
+ return;
+ }
+
+ client->conn->login->callback(client, auth_args[0], auth_args+1);
+ i_free(client);
+}
+
+static void master_login_conn_input(struct master_login_connection *conn)
+{
+ struct master_auth_request req;
+ struct master_login_client *client;
+ unsigned char data[MASTER_AUTH_MAX_DATA_SIZE];
+ int ret, client_fd;
+
+ ret = master_login_conn_read_request(conn, &req, data, &client_fd);
+ if (ret <= 0) {
+ if (ret < 0)
+ master_login_conn_deinit(&conn);
+ if (client_fd != -1) {
+ if (close(client_fd) < 0)
+ i_error("close(fd_recv client) failed: %m");
+ }
+ return;
+ }
+ fd_close_on_exec(client_fd, TRUE);
+
+ /* @UNSAFE: we have a request. do userdb lookup for it. */
+ client = i_malloc(sizeof(struct master_login_client) + req.data_size);
+ client->conn = conn;
+ client->fd = client_fd;
+ client->auth_req = req;
+ memcpy(client->data, data, req.data_size);
+
+ master_login_auth_request(conn->login->auth, &req,
+ master_login_auth_callback, client);
+}
+
+void master_login_add(struct master_login *login, int fd)
+{
+ struct master_login_connection *conn;
+
+ conn = i_new(struct master_login_connection, 1);
+ conn->login = login;
+ conn->fd = fd;
+ conn->io = io_add(conn->fd, IO_READ, master_login_conn_input, conn);
+ conn->output = o_stream_create_fd(fd, (size_t)-1, FALSE);
+
+ DLLIST_PREPEND(&login->conns, conn);
+}
+
+static void master_login_conn_deinit(struct master_login_connection **_conn)
+{
+ struct master_login_connection *conn = *_conn;
+
+ *_conn = NULL;
+
+ DLLIST_REMOVE(&conn->login->conns, conn);
+
+ io_remove(&conn->io);
+ o_stream_unref(&conn->output);
+ if (close(conn->fd) < 0)
+ i_error("close(master login) failed: %m");
+ i_free(conn);
+}
--- /dev/null
+#ifndef MASTER_LOGIN_H
+#define MASTER_LOGIN_H
+
+#include "master-auth.h"
+
+struct master_login_client {
+ struct master_login_connection *conn;
+ int fd;
+
+ struct master_auth_request auth_req;
+ unsigned char data[FLEXIBLE_ARRAY_MEMBER];
+};
+
+typedef void
+master_login_callback_t(const struct master_login_client *client,
+ const char *username, const char *const *extra_fields);
+
+struct master_login *
+master_login_init(const char *auth_socket_path,
+ master_login_callback_t *callback);
+void master_login_deinit(struct master_login **login);
+
+void master_login_add(struct master_login *login, int fd);
+
+#endif
struct master_status master_status;
void (*avail_overflow_callback)(void);
+ struct timeout *to_overflow_state;
- struct master_auth *auth;
master_service_connection_callback_t *callback;
pool_t set_pool;
unsigned int initial_status_sent:1;
unsigned int die_with_master:1;
unsigned int call_avail_overflow:1;
+ unsigned int delay_status_updates:1;
};
void master_service_io_listeners_add(struct master_service *service);
/* getenv(MASTER_DOVECOT_VERSION_ENV) provides master's version number */
#define MASTER_DOVECOT_VERSION_ENV "DOVECOT_VERSION"
+/* when we're full of connections, how often to check if login state has
+ changed. we normally notice it immediately because of a signal, so this is
+ just a fallback against race conditions. */
+#define MASTER_SERVICE_STATE_CHECK_MSECS 1000
+
struct master_service *master_service;
+static void master_service_refresh_login_state(struct master_service *service);
static void io_listeners_remove(struct master_service *service);
static void master_status_update(struct master_service *service);
io_loop_stop(service->ioloop);
}
+static void
+sig_state_changed(const siginfo_t *si ATTR_UNUSED, void *context)
+{
+ struct master_service *service = context;
+
+ master_service_refresh_login_state(service);
+}
+
static void master_service_verify_version(struct master_service *service)
{
if (service->version_string != NULL &&
lib_signals_ignore(SIGALRM, FALSE);
lib_signals_set_handler(SIGINT, TRUE, sig_die, service);
lib_signals_set_handler(SIGTERM, TRUE, sig_die, service);
+ if ((service->flags & MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE) != 0) {
+ lib_signals_set_handler(SIGUSR1, TRUE,
+ sig_state_changed, service);
+ }
if ((service->flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
if (fstat(MASTER_STATUS_FD, &st) < 0 || !S_ISFIFO(st.st_mode))
}
}
+static void master_service_set_login_state(struct master_service *service,
+ enum master_login_state state)
+{
+ if (service->to_overflow_state != NULL)
+ timeout_remove(&service->to_overflow_state);
+
+ switch (state) {
+ case MASTER_LOGIN_STATE_NONFULL:
+ service->call_avail_overflow = FALSE;
+ if (service->master_status.available_count > 0)
+ return;
+
+ /* some processes should now be able to handle new connections,
+ although we can't. but there may be race conditions, so
+ make sure that we'll check again soon if the state has
+ changed to "full" without our knowledge. */
+ service->to_overflow_state =
+ timeout_add(MASTER_SERVICE_STATE_CHECK_MSECS,
+ master_service_refresh_login_state,
+ service);
+ return;
+ case MASTER_LOGIN_STATE_FULL:
+ /* make sure we're listening for more connections */
+ service->call_avail_overflow = TRUE;
+ master_service_io_listeners_add(service);
+ return;
+ }
+ i_error("Invalid master login state: %d", state);
+}
+
+static void master_service_refresh_login_state(struct master_service *service)
+{
+ int ret;
+
+ ret = lseek(MASTER_LOGIN_NOTIFY_FD, 0, SEEK_CUR);
+ if (ret < 0)
+ i_error("lseek(login notify fd) failed: %m");
+ else
+ master_service_set_login_state(service, ret);
+}
+
static void master_service_close_config_fd(struct master_service *service)
{
if (service->config_fd != -1) {
io_listeners_remove(service);
master_service_close_config_fd(service);
+ if (service->to_overflow_state != NULL)
+ timeout_remove(&service->to_overflow_state);
if (service->io_status_error != NULL)
io_remove(&service->io_status_error);
if (service->io_status_write != NULL)
/* we are full. stop listening for now, unless overflow
callback destroys one of the existing connections */
if (service->call_avail_overflow &&
- service->avail_overflow_callback != NULL)
+ service->avail_overflow_callback != NULL) {
+ service->delay_status_updates = TRUE;
service->avail_overflow_callback();
+ service->delay_status_updates = FALSE;
+ }
if (service->master_status.available_count == 0) {
io_listeners_remove(service);
{
ssize_t ret;
- if (service->master_status.pid == 0)
+ if (service->master_status.pid == 0 || service->delay_status_updates)
return; /* closed */
ret = write(MASTER_STATUS_FD, &service->master_status,
/* Don't read settings by executing config binary */
MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS = 0x10,
/* Don't read settings from environment */
- MASTER_SERVICE_FLAG_NO_ENV_SETTINGS = 0x20
+ MASTER_SERVICE_FLAG_NO_ENV_SETTINGS = 0x20,
+ /* Use MASTER_LOGIN_NOTIFY_FD to track login overflow state */
+ MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE = 0x40
};
struct master_service_connection {
for (i = 0; i < count && ret == 0; i++) {
line = str[i];
if (strncmp(line, "system_groups_user=", 19) == 0)
- *system_groups_user_r = line + 19;
+ *system_groups_user_r = t_strdup(line + 19);
else T_BEGIN {
if (strncmp(line, "mail=", 5) == 0) {
line = t_strconcat("mail_location=",
static int
service_auth_userdb_lookup(struct auth_master_connection *conn,
- struct setting_parser_context *set_parser,
const char *service_name,
const struct mail_storage_service_input *input,
const struct mail_user_settings *user_set,
- const char **user, const char **system_groups_user_r,
+ pool_t pool, const char **user,
+ const char *const **fields_r,
const char **error_r)
{
struct auth_user_info info;
- struct auth_user_reply reply;
- const char *system_groups_user, *orig_user = *user;
- const char *new_username, *const *fields;
- unsigned int len;
- pool_t pool;
+ const char *new_username;
int ret;
memset(&info, 0, sizeof(info));
info.local_ip = input->local_ip;
info.remote_ip = input->remote_ip;
- pool = pool_alloconly_create("userdb lookup", 1024);
ret = auth_master_user_lookup(conn, *user, &info, pool,
- &new_username, &fields);
+ &new_username, fields_r);
if (ret > 0) {
- auth_user_fields_parse(fields, pool, &reply);
- len = reply.chroot == NULL ? 0 : strlen(reply.chroot);
-
- *user = t_strdup(new_username);
- if (user_reply_handle(set_parser, user_set, &reply,
- &system_groups_user, error_r) < 0)
- ret = -1;
- *system_groups_user_r = t_strdup(system_groups_user);
- } else {
- if (ret == 0)
- *error_r = "unknown user";
- else
- *error_r = "userdb lookup failed";
- *system_groups_user_r = NULL;
- }
-
- if (ret > 0 && strcmp(*user, orig_user) != 0) {
- if (mail_user_set_get_storage_set(user_set)->mail_debug)
- i_debug("changed username to %s", *user);
- }
-
- pool_unref(&pool);
+ if (strcmp(*user, new_username) != 0) {
+ if (mail_user_set_get_storage_set(user_set)->mail_debug)
+ i_debug("changed username to %s", new_username);
+ *user = t_strdup(new_username);
+ }
+ *user = new_username;
+ } else if (ret == 0)
+ *error_r = "unknown user";
+ else
+ *error_r = "userdb lookup failed";
return ret;
}
struct mail_user *mail_user;
struct auth_master_connection *conn;
void **sets;
- const char *user, *orig_user, *home, *system_groups_user, *error;
+ const char *user, *orig_user, *home, *error;
+ const char *system_groups_user = NULL, *const *userdb_fields = NULL;
unsigned int len;
bool userdb_lookup;
+ pool_t userdb_pool = NULL;
io_loop_set_time_moved_callback(current_ioloop,
mail_storage_service_time_moved);
- master_service_init_finish(service);
userdb_lookup = (flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0;
mail_storage_service_init_settings(service, &input, set_roots,
orig_user = user = input.username;
conn = auth_master_init(user_set->auth_socket_path,
mail_set->mail_debug);
- if (service_auth_userdb_lookup(conn, service->set_parser,
- service->name, &input,
- user_set, &user,
- &system_groups_user,
- &error) <= 0)
+ userdb_pool = pool_alloconly_create("userdb lookup", 1024);
+ if (service_auth_userdb_lookup(conn, service->name, &input,
+ user_set, userdb_pool, &user,
+ &userdb_fields, &error) <= 0)
i_fatal("%s", error);
auth_master_deinit(&conn);
input.username = user;
/* set up logging again in case username changed */
mail_storage_service_init_log(service, &input);
+ } else if (input.userdb_fields != NULL) {
+ userdb_fields = input.userdb_fields;
+ userdb_pool = pool_alloconly_create("userdb fields", 1024);
+ }
+ if (userdb_fields != NULL) {
+ struct auth_user_reply reply;
+
+ auth_user_fields_parse(userdb_fields, userdb_pool, &reply);
+ if (user_reply_handle(service->set_parser, user_set, &reply,
+ &system_groups_user, &error) < 0)
+ i_fatal("%s", error);
}
+ if (userdb_pool != NULL)
+ pool_unref(&userdb_pool);
/* variable strings are expanded in mail_user_init(),
but we need the home sooner so do it separately here. */
home = user_expand_varstr(service, &input, user_set->mail_home);
if (!userdb_lookup) {
- system_groups_user = NULL;
if (*home == '\0' && getenv("HOME") != NULL) {
home = getenv("HOME");
set_keyval(service->set_parser, "mail_home", home);
io_loop_set_time_moved_callback(current_ioloop,
mail_storage_service_time_moved);
- master_service_init_finish(service);
ctx = i_new(struct mail_storage_service_multi_ctx, 1);
ctx->service = service;
return ctx->conn;
}
+static int multi_userdb_lookup(struct mail_storage_service_multi_ctx *ctx,
+ struct mail_storage_service_multi_user *user,
+ pool_t userdb_pool, const char **error_r)
+{
+ struct auth_user_reply reply;
+ const char *username, *system_groups_user, *const *userdb_fields;
+ int ret;
+
+ username = user->input.username;
+ ret = service_auth_userdb_lookup(ctx->conn, ctx->service->name,
+ &user->input, user->user_set,
+ userdb_pool, &username,
+ &userdb_fields, error_r);
+ if (ret <= 0)
+ return ret;
+
+ auth_user_fields_parse(userdb_fields, userdb_pool, &reply);
+ ret = user_reply_handle(ctx->service->set_parser, user->user_set,
+ &reply, &system_groups_user, error_r);
+ if (ret <= 0)
+ return ret;
+
+ user->input.username = p_strdup(user->pool, username);
+ user->system_groups_user = p_strdup(user->pool, system_groups_user);
+ return 1;
+}
+
int mail_storage_service_multi_lookup(struct mail_storage_service_multi_ctx *ctx,
const struct mail_storage_service_input *input,
pool_t pool,
const char **error_r)
{
struct mail_storage_service_multi_user *user;
- const char *orig_user, *username;
void **sets;
- int ret;
+ pool_t userdb_pool;
+ int ret = 1;
user = p_new(pool, struct mail_storage_service_multi_user, 1);
memset(user_r, 0, sizeof(user_r));
user->user_set = sets[1];
if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0) {
- orig_user = username = user->input.username;
- ret = service_auth_userdb_lookup(ctx->conn, user->set_parser,
- ctx->service->name, input,
- user->user_set, &username,
- &user->system_groups_user,
- error_r);
- if (ret <= 0)
- return ret;
- user->input.username = p_strdup(pool, username);
+ userdb_pool = pool_alloconly_create("userdb lookup", 1024);
+ ret = multi_userdb_lookup(ctx, user, userdb_pool, error_r);
+ pool_unref(&userdb_pool);
}
*user_r = user;
return 1;
const char *service;
const char *username;
struct ip_addr local_ip, remote_ip;
+
+ const char *const *userdb_fields;
};
struct setting_parser_info;
i_assert(client->authenticating);
i_assert(client->refcount > 1);
client->authenticating = FALSE;
- master_auth_request_abort(master_service, client->master_tag);
+ master_auth_request_abort(master_auth, client->master_tag);
client->refcount--;
} else if (client->auth_request != NULL) {
i_assert(client->authenticating);
extern unsigned int login_default_port;
extern struct auth_client *auth_client;
+extern struct master_auth *master_auth;
extern bool closing_down;
extern int anvil_fd;
#include <syslog.h>
struct auth_client *auth_client;
+struct master_auth *master_auth;
bool closing_down;
int anvil_fd = -1;
auth_client = auth_client_init("auth", (unsigned int)getpid(), FALSE);
auth_client_set_connect_notify(auth_client, auth_connect_notify, NULL);
+ master_auth = master_auth_init(master_service, login_protocol);
clients_init();
login_proxy_init();
- master_auth_init(master_service);
}
static void main_deinit(void)
if (auth_client != NULL)
auth_client_deinit(&auth_client);
clients_deinit();
+ master_auth_deinit(&master_auth);
if (anvil_fd != -1) {
if (close(anvil_fd) < 0)
i_error("close(anvil) failed: %m");
}
- master_auth_deinit(master_service);
}
int main(int argc, char *argv[], char *envp[])
{
+ enum master_service_flags service_flags =
+ MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN |
+ MASTER_SERVICE_FLAG_TRACK_LOGIN_STATE;
const char *getopt_str;
pool_t set_pool;
int c;
- master_service = master_service_init(login_process_name,
- MASTER_SERVICE_FLAG_KEEP_CONFIG_OPEN,
- argc, argv);
+ master_service = master_service_init(login_process_name, service_flags,
+ argc, argv);
master_service_init_log(master_service, t_strconcat(
login_process_name, ": ", NULL));
req.auth_id = auth_client_request_get_id(request);
req.local_ip = client->local_ip;
req.remote_ip = client->ip;
+ req.client_pid = getpid();
cookie = auth_client_get_cookie(auth_client);
if (hex_to_binary(cookie, buf) == 0 && buf->used == sizeof(req.cookie))
buffer_append(buf, data, size);
req.data_size = buf->used;
- client->master_tag =
- master_auth_request(master_service, client->fd, &req, buf->data,
- master_auth_callback, client);
+ master_auth_request(master_auth, client->fd, &req, buf->data,
+ master_auth_callback, client, &client->master_tag);
}
static bool anvil_has_too_many_connections(struct client *client)
main.c \
master-settings.c \
service-anvil.c \
- service-auth-server.c \
- service-auth-source.c \
service-listen.c \
service-log.c \
service-monitor.c \
dup2-array.h \
master-settings.h \
service-anvil.h \
- service-auth-server.h \
- service-auth-source.h \
service-listen.h \
service-log.h \
service-monitor.h \
i_error("unlink(%s) failed: %m", path);
}
-static bool
-services_has_name(const struct master_settings *set, const char *name)
-{
- struct service_settings *const *services;
- unsigned int i, count;
-
- services = array_get(&set->services, &count);
- for (i = 0; i < count; i++) {
- if (strcmp(services[i]->name, name) == 0)
- return TRUE;
- }
- return FALSE;
-}
-
-static bool services_have_auth_destinations(const struct master_settings *set)
-{
- struct service_settings *const *services;
- unsigned int i, count;
-
- services = array_get(&set->services, &count);
- for (i = 0; i < count; i++) {
- if (strcmp(services[i]->type, "auth-source") == 0) {
- if (services_has_name(set, services[i]->auth_dest_service))
- return TRUE;
- }
- }
- return FALSE;
-}
-
static void auth_warning_print(const struct master_settings *set)
{
struct stat st;
auth_success_written = stat(AUTH_SUCCESS_PATH, &st) == 0;
- if (!auth_success_written && !set->auth_debug &&
- services_have_auth_destinations(set)) {
+ if (!auth_success_written && !set->auth_debug) {
fprintf(stderr,
"If you have trouble with authentication failures,\n"
"enable auth_debug setting. See http://wiki.dovecot.org/WhyDoesItNotWork\n"
if (services->config->process_avail == 0) {
/* we can't reload config if there's no config process. */
- if (service_process_create(services->config,
- NULL, NULL) == NULL) {
+ if (service_process_create(services->config) == NULL) {
i_error("Can't reload configuration because "
"we couldn't create a config process");
return;
#include "array.h"
#include "env-util.h"
#include "istream.h"
+#include "network.h"
#include "str.h"
#include "mkdir-parents.h"
#include "safe-mkdir.h"
DEF(SET_STR, privileged_group),
DEF(SET_STR, extra_groups),
DEF(SET_STR, chroot),
- DEF(SET_STR, auth_dest_service),
DEF(SET_BOOL, drop_priv_before_exec),
MEMBER(privileged_group) "",
MEMBER(extra_groups) "",
MEMBER(chroot) "",
- MEMBER(auth_dest_service) "",
MEMBER(drop_priv_before_exec) FALSE,
}
}
+static bool master_settings_parse_type(struct service_settings *set,
+ const char **error_r)
+{
+ if (*set->type == '\0')
+ set->parsed_type = SERVICE_TYPE_UNKNOWN;
+ else if (strcmp(set->type, "log") == 0)
+ set->parsed_type = SERVICE_TYPE_LOG;
+ else if (strcmp(set->type, "config") == 0)
+ set->parsed_type = SERVICE_TYPE_CONFIG;
+ else if (strcmp(set->type, "anvil") == 0)
+ set->parsed_type = SERVICE_TYPE_ANVIL;
+ else if (strcmp(set->type, "login") == 0)
+ set->parsed_type = SERVICE_TYPE_LOGIN;
+ else {
+ *error_r = t_strconcat("Unknown service type: ",
+ set->type, NULL);
+ return FALSE;
+ }
+ return TRUE;
+}
+
static bool
master_settings_verify(void *_set, pool_t pool, const char **error_r)
{
"Service #%d is missing name", i);
return FALSE;
}
- if (*service->type != '\0' &&
- strcmp(service->type, "log") != 0 &&
- strcmp(service->type, "config") != 0 &&
- strcmp(service->type, "anvil") != 0 &&
- strcmp(service->type, "auth") != 0 &&
- strcmp(service->type, "auth-source") != 0) {
- *error_r = t_strconcat("Unknown service type: ",
- service->type, NULL);
+ if (!master_settings_parse_type(service, error_r))
return FALSE;
- }
for (j = 0; j < i; j++) {
if (strcmp(service->name, services[j]->name) == 0) {
*error_r = t_strdup_printf(
services = array_get(&set->services, &count);
for (i = 0; i < count; i++) {
- if (strcmp(services[i]->type, "auth-source") == 0 &&
- strstr(services[i]->name, "-login") != NULL) {
+ if (strcmp(services[i]->type, "login") == 0) {
if (strstr(services[i]->executable, " -D") != NULL)
cores = TRUE;
(void)get_uidgid(services[i]->user, &uid, gid_r, &error);
services = array_get(&set->services, &count);
for (i = 0; i < count; i++) {
- if (strcmp(services[i]->type, "auth") == 0 &&
- array_is_created(&services[i]->unix_listeners)) {
+ if (array_is_created(&services[i]->unix_listeners)) {
u = array_get(&services[i]->unix_listeners, &count2);
for (j = 0; j < count2; j++) {
if (strncmp(u[j]->path, dir, strlen(dir)) == 0)
#ifndef MASTER_SETTINGS_H
#define MASTER_SETTINGS_H
+/* <settings checks> */
+enum service_type {
+ SERVICE_TYPE_UNKNOWN,
+ SERVICE_TYPE_LOG,
+ SERVICE_TYPE_ANVIL,
+ SERVICE_TYPE_CONFIG,
+ SERVICE_TYPE_LOGIN
+};
+/* </settings checks> */
+
struct file_listener_settings {
const char *path;
unsigned int mode;
const char *privileged_group;
const char *extra_groups;
const char *chroot;
- const char *auth_dest_service;
bool drop_priv_before_exec;
ARRAY_TYPE(file_listener_settings) unix_listeners;
ARRAY_TYPE(file_listener_settings) fifo_listeners;
ARRAY_DEFINE(inet_listeners, struct inet_listener_settings *);
+
+ enum service_type parsed_type;
};
struct master_settings {
+++ /dev/null
-/* 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 struct service *
-auth_process_get_dest_service(struct service_process_auth_source *process)
-{
- struct service *service = process->process.service;
-
- if (!service->list->destroyed)
- return service->auth_dest_service;
-
- service = service_lookup(services, service->set->auth_dest_service);
- if (service == NULL) {
- i_warning("service(%s): Lost destination service %s",
- service->set->name, service->set->auth_dest_service);
- }
- return service;
-}
-
-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;
- struct service_process *dest_process;
-
- dest_service = auth_process_get_dest_service(request->process);
- dest_process = dest_service == NULL ? NULL :
- 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,
- MASTER_AUTH_STATUS_INTERNAL_ERROR);
- 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,
- MASTER_AUTH_STATUS_INTERNAL_ERROR);
- 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);
-}
+++ /dev/null
-#ifndef SERVICE_AUTH_SERVER_H
-#define SERVICE_AUTH_SERVER_H
-
-struct service_process;
-
-void service_process_auth_server_init(struct service_process *process, int fd);
-void service_process_auth_server_deinit(struct service_process *process);
-
-#endif
+++ /dev/null
-/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
-
-#include "common.h"
-#include "hash.h"
-#include "str.h"
-#include "hex-binary.h"
-#include "ioloop.h"
-#include "ostream.h"
-#include "fdpass.h"
-#include "fd-close-on-exec.h"
-#include "../auth/auth-master-interface.h"
-#include "service.h"
-#include "service-process.h"
-#include "service-auth-source.h"
-
-#include <unistd.h>
-#include <sys/stat.h>
-
-#define AUTH_SOURCE_OUTBUF_THROTTLE_THRESHOLD (1024 - 256)
-#define AUTH_SERVER_MAX_OUTBUF_SIZE (1024*64)
-#define AUTH_BUSY_LOG_INTERVAL 30
-
-static void
-service_process_auth_source_input(struct service_process_auth_source *process);
-
-static void
-service_process_auth_source_close(struct service_process_auth_source *process)
-{
- 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 int
-process_auth_source_output(struct service_process_auth_source *process)
-{
- int ret;
-
- if ((ret = o_stream_flush(process->auth_output)) < 0)
- return -1;
-
- if (process->io_auth == NULL &&
- o_stream_get_buffer_used_size(process->auth_output) <
- AUTH_SOURCE_OUTBUF_THROTTLE_THRESHOLD) {
- /* enable parsing input again */
- o_stream_unset_flush_callback(process->auth_output);
- process->io_auth = io_add(process->auth_fd, IO_READ,
- service_process_auth_source_input,
- process);
- }
- return ret;
-}
-
-void service_process_auth_source_send_reply(struct service_process_auth_source *process,
- unsigned int tag,
- enum master_auth_status status)
-{
- struct master_auth_reply reply;
-
- if (o_stream_get_buffer_used_size(process->auth_output) >=
- AUTH_SOURCE_OUTBUF_THROTTLE_THRESHOLD) {
- /* not reading our output. stop parsing input until it will. */
- if (process->io_auth != NULL) {
- io_remove(&process->io_auth);
-
- o_stream_set_flush_callback(process->auth_output,
- process_auth_source_output,
- process);
- }
- }
-
- /* Reply to login process */
- memset(&reply, 0, sizeof(reply));
- reply.tag = tag;
- reply.status = status;
-
- o_stream_send(process->auth_output, &reply, sizeof(reply));
-}
-
-static unsigned int
-auth_server_send_request(struct service_process_auth_server *server_process,
- struct service_process_auth_source *source_process,
- unsigned int auth_id,
- const uint8_t cookie[MASTER_AUTH_COOKIE_SIZE])
-{
- unsigned int tag = 0;
- string_t *str;
-
- while (tag == 0)
- tag = ++server_process->auth_tag_counter;
-
- str = t_str_new(256);
- if (!server_process->auth_version_sent) {
- server_process->auth_version_sent = TRUE;
- str_printfa(str, "VERSION\t%u\t%u\n",
- AUTH_MASTER_PROTOCOL_MAJOR_VERSION,
- AUTH_MASTER_PROTOCOL_MINOR_VERSION);
- o_stream_send(server_process->auth_output,
- str_data(str), str_len(str));
- str_truncate(str, 0);
- }
-
- str_printfa(str, "REQUEST\t%u\t%s\t%u\t",
- tag, dec2str(source_process->process.pid), auth_id);
- binary_to_hex_append(str, cookie, MASTER_AUTH_COOKIE_SIZE);
- str_append_c(str, '\n');
-
- o_stream_send(server_process->auth_output, str_data(str), str_len(str));
- return tag;
-}
-
-static int
-auth_read_request(struct service_process_auth_source *process,
- struct master_auth_request *req,
- unsigned char data[MASTER_AUTH_MAX_DATA_SIZE],
- int *client_fd_r)
-{
- struct service *service = process->process.service;
- struct stat st;
- ssize_t ret;
-
- *client_fd_r = -1;
-
- ret = fd_read(process->auth_fd, req, sizeof(*req), client_fd_r);
- if (ret != sizeof(*req)) {
- if (ret == 0) {
- /* disconnected */
- } else if (ret > 0) {
- /* request wasn't fully read */
- service_error(service, "fd_read() partial input (%d/%d)",
- (int)ret, (int)sizeof(*req));
- } else {
- if (errno == EAGAIN)
- return 0;
-
- service_error(service, "fd_read() failed: %m");
- }
- return -1;
- }
-
- if (req->data_size != 0) {
- if (req->data_size > MASTER_AUTH_MAX_DATA_SIZE) {
- service_error(service, "Too large auth data_size sent");
- return -1;
- }
- /* @UNSAFE */
- ret = read(process->auth_fd, data, req->data_size);
- if (ret != (ssize_t)req->data_size) {
- if (ret == 0) {
- /* disconnected */
- } else if (ret > 0) {
- /* request wasn't fully read */
- service_error(service,
- "Data read partially %d/%u",
- (int)ret, req->data_size);
- } else {
- service_error(service, "read(data) failed: %m");
- }
- return -1;
- }
- }
-
- if (*client_fd_r == -1) {
- service_error(service, "Auth request missing a file descriptor");
- return -1;
- }
-
- if (fstat(*client_fd_r, &st) < 0) {
- service_error(service, "fstat(auth dest fd) failed: %m");
- return -1;
- }
- if (st.st_ino != req->ino) {
- service_error(service, "Auth request inode mismatch: %s != %s",
- dec2str(st.st_ino), dec2str(req->ino));
- return -1;
- }
- return 1;
-}
-
-static void
-service_process_auth_source_input(struct service_process_auth_source *process)
-{
- struct service *service = process->process.service;
- struct service_process_auth_server *auth_process;
- struct service_process_auth_request *auth_req;
- struct master_auth_request req;
- unsigned char data[MASTER_AUTH_MAX_DATA_SIZE];
- unsigned int tag;
- ssize_t ret;
- int client_fd;
-
- ret = auth_read_request(process, &req, data, &client_fd);
- if (ret == 0)
- return;
- if (ret < 0) {
- if (client_fd != -1) {
- if (close(client_fd) < 0)
- i_error("login: close(mail client) failed: %m");
- }
- service_process_auth_source_close(process);
- return;
- }
- fd_close_on_exec(client_fd, TRUE);
-
- /* we have a request. check its validity. */
- auth_process = hash_table_lookup(service_pids, &req.auth_pid);
- if (auth_process == NULL) {
- service_error(service, "authentication request for unknown "
- "auth server PID %s", dec2str(req.auth_pid));
- service_process_auth_source_send_reply(process, req.tag,
- MASTER_AUTH_STATUS_INTERNAL_ERROR);
- (void)close(client_fd);
- return;
- }
-
- if (o_stream_get_buffer_used_size(auth_process->auth_output) >=
- AUTH_SERVER_MAX_OUTBUF_SIZE) {
- if (auth_process->auth_busy_stamp <=
- ioloop_time - AUTH_BUSY_LOG_INTERVAL) {
- i_warning("service(%s): authentication server PID "
- "%s too busy",
- auth_process->process.service->set->name,
- dec2str(req.auth_pid));
- auth_process->auth_busy_stamp = ioloop_time;
- }
- service_process_auth_source_send_reply(process, req.tag,
- MASTER_AUTH_STATUS_INTERNAL_ERROR);
- (void)close(client_fd);
- return;
- }
-
- /* ok, we have a non-busy authentication server.
- send a request to it. */
- auth_req = i_malloc(sizeof(*auth_req) + req.data_size);
- auth_req->process = process;
- auth_req->process_tag = req.tag;
- auth_req->fd = client_fd;
- auth_req->local_ip = req.local_ip;
- auth_req->remote_ip = req.remote_ip;
- auth_req->data_size = req.data_size;
- memcpy(auth_req->data, data, req.data_size);
-
- tag = auth_server_send_request(auth_process, process, req.auth_id,
- req.cookie);
-
- service_process_ref(&process->process);
- hash_table_insert(auth_process->auth_requests,
- POINTER_CAST(tag), auth_req);
-}
-
-void service_process_auth_source_init(struct service_process *_process, int fd)
-{
- struct service_process_auth_source *process =
- (struct service_process_auth_source *)_process;
-
- i_assert(_process->service->type == SERVICE_TYPE_AUTH_SOURCE);
-
- process->auth_fd = fd;
- process->io_auth = io_add(process->auth_fd, IO_READ,
- service_process_auth_source_input, process);
- process->auth_output =
- o_stream_create_fd(fd, (size_t)-1, FALSE);
-}
-
-void service_process_auth_source_deinit(struct service_process *_process)
-{
- struct service_process_auth_source *process =
- (struct service_process_auth_source *)_process;
-
- i_assert(_process->service->type == SERVICE_TYPE_AUTH_SOURCE);
-
- service_process_auth_source_close(process);
-}
-
-void service_processes_auth_source_notify(struct service *service,
- bool all_processes_created)
-{
- struct hash_iterate_context *iter;
- void *key, *value;
- enum master_auth_status status;
-
- i_assert(service->type == SERVICE_TYPE_AUTH_SOURCE);
-
- status = all_processes_created ? 1 : 0;
-
- iter = hash_table_iterate_init(service_pids);
- while (hash_table_iterate(iter, &key, &value)) {
- struct service_process *process = value;
- struct service_process_auth_source *auth_process;
-
- if (process->service != service)
- continue;
-
- auth_process = (struct service_process_auth_source *)process;
- if (auth_process->last_notify_status != (int)status) {
- auth_process->last_notify_status = (int)status;
- service_process_auth_source_send_reply(auth_process,
- 0, status);
- }
- }
- hash_table_iterate_deinit(&iter);
-}
+++ /dev/null
-#ifndef SERVICE_AUTH_SOURCE_H
-#define SERVICE_AUTH_SOURCE_H
-
-struct service_process;
-struct service_process_auth_source;
-
-void service_process_auth_source_init(struct service_process *process, int fd);
-void service_process_auth_source_deinit(struct service_process *process);
-
-void service_process_auth_source_send_reply(struct service_process_auth_source *process,
- unsigned int tag,
- enum master_auth_status status);
-
-void service_processes_auth_source_notify(struct service *service,
- bool all_processes_created);
-
-#endif
#include "ioloop.h"
#include "fd-close-on-exec.h"
#include "hash.h"
+#include "str.h"
+#include "safe-mkstemp.h"
#include "service.h"
-#include "service-auth-source.h"
#include "service-process.h"
#include "service-process-notify.h"
#include "service-anvil.h"
i_assert(service->process_avail > 0);
service->process_avail--;
+ if (service->type == SERVICE_TYPE_LOGIN &&
+ service->process_avail == 0 &&
+ service->process_count == service->process_limit)
+ service_login_notify(service, TRUE);
+
/* we may need to start more */
service_monitor_start_extra_avail(service);
service_monitor_listen_start(service);
process);
}
}
+ if (service->type == SERVICE_TYPE_LOGIN)
+ service_login_notify(service, FALSE);
}
static void service_status_input(struct service *service)
struct service_process *process;
ssize_t ret;
+ status.pid = 0;
ret = read(service->status_fd[0], &status, sizeof(status));
switch (ret) {
case 0:
service->listen_pending = TRUE;
service_monitor_listen_stop(service);
- if (service->type == SERVICE_TYPE_AUTH_SOURCE) {
+ if (service->type == SERVICE_TYPE_LOGIN) {
/* reached process limit, notify processes that they
need to start killing existing connections if they
reach connection limit */
- service_processes_auth_source_notify(service, TRUE);
+ service_login_notify(service, TRUE);
}
}
}
/* create a child process and let it accept() this connection */
- if (service_process_create(service, NULL, NULL) == NULL)
+ if (service_process_create(service) == NULL)
service_monitor_throttle(service);
else
service_monitor_listen_stop(service);
count = service->process_limit - service->process_count;
for (i = 0; i < count; i++) {
- if (service_process_create(service, NULL, NULL) == NULL) {
+ if (service_process_create(service) == NULL) {
service_monitor_throttle(service);
break;
}
service->listening = FALSE;
}
+static int service_login_create_notify_fd(struct service *service)
+{
+ int fd;
+
+ if (service->login_notify_fd != -1)
+ return 0;
+
+ T_BEGIN {
+ string_t *prefix = t_str_new(128);
+ const char *path;
+
+ str_append(prefix, "/tmp/dovecot-master");
+
+ fd = safe_mkstemp(prefix, 0600, (uid_t)-1, (gid_t)-1);
+ path = str_c(prefix);
+
+ if (fd == -1) {
+ service_error(service, "safe_mkstemp(%s) failed: %m",
+ path);
+ } else if (unlink(path) < 0) {
+ service_error(service, "unlink(%s) failed: %m", path);
+ } else {
+ fd_close_on_exec(fd, TRUE);
+ service->login_notify_fd = fd;
+ }
+ } T_END;
+
+ if (fd != service->login_notify_fd)
+ (void)close(fd);
+ return fd == -1 ? -1 : 0;
+}
+
void services_monitor_start(struct service_list *service_list)
{
struct service *const *services;
services = array_get(&service_list->services, &count);
for (i = 0; i < count; i++) {
+ if (services[i]->type == SERVICE_TYPE_LOGIN) {
+ if (service_login_create_notify_fd(services[i]) < 0)
+ continue;
+ }
if (services[i]->status_fd[0] == -1) {
/* we haven't yet created status pipe */
if (pipe(services[i]->status_fd) < 0) {
io_add(services[i]->status_fd[0], IO_READ,
service_status_input, services[i]);
}
-
if (services[i]->status_fd[0] != -1) {
service_monitor_start_extra_avail(services[i]);
service_monitor_listen_start(services[i]);
}
}
- if (service_process_create(service_list->log, NULL, NULL) != NULL)
+ if (service_process_create(service_list->log) != NULL)
service_monitor_listen_stop(service_list->log);
}
service->status_fd[i] = -1;
}
}
+ if (service->login_notify_fd != -1) {
+ if (close(service->login_notify_fd) < 0) {
+ service_error(service,
+ "close(login notify fd) failed: %m");
+ }
+ service->login_notify_fd = -1;
+ }
+ if (service->to_login_notify != NULL)
+ timeout_remove(&service->to_login_notify);
service_monitor_listen_stop(service);
if (service->to_throttle != NULL)
#include "service.h"
#include "service-anvil.h"
#include "service-log.h"
-#include "service-auth-server.h"
-#include "service-auth-source.h"
#include "service-process-notify.h"
#include "service-process.h"
#define CHDIR_WARN_SECS 10
static void
-service_dup_fds(struct service *service, int auth_fd, int std_fd,
- bool give_anvil_fd)
+service_dup_fds(struct service *service)
{
struct service_listener *const *listeners;
ARRAY_TYPE(dup2) dups;
}
}
- if (!give_anvil_fd)
- dup2_append(&dups, null_fd, MASTER_ANVIL_FD);
- else {
- dup2_append(&dups, service->list->blocking_anvil_fd[1],
- MASTER_ANVIL_FD);
+ if (service->login_notify_fd != -1) {
+ dup2_append(&dups, service->login_notify_fd,
+ MASTER_LOGIN_NOTIFY_FD);
}
+ dup2_append(&dups, service->list->blocking_anvil_fd[1],
+ MASTER_ANVIL_FD);
dup2_append(&dups, service->status_fd[1], MASTER_STATUS_FD);
- switch (service->type) {
- case SERVICE_TYPE_AUTH_SOURCE:
- case SERVICE_TYPE_AUTH_SERVER:
- i_assert(auth_fd != -1);
- dup2_append(&dups, auth_fd, MASTER_AUTH_FD);
- env_put(t_strdup_printf("MASTER_AUTH_FD=%d", MASTER_AUTH_FD));
- break;
- default:
- i_assert(auth_fd == -1);
- dup2_append(&dups, null_fd, MASTER_AUTH_FD);
- break;
- }
-
- if (std_fd != -1) {
- dup2_append(&dups, std_fd, STDIN_FILENO);
- dup2_append(&dups, std_fd, STDOUT_FILENO);
- env_put("LOGGED_IN=1");
- }
-
if (service->type != SERVICE_TYPE_LOG) {
/* set log file to stderr. dup2() here immediately so that
we can set up logging to it without causing any log messages
}
static void
-validate_uid_gid(struct master_settings *set,
- uid_t uid, gid_t gid, const char *user,
- const struct service_process_auth_request *request)
-{
- struct service_process *request_process =
- request == NULL ? NULL : &request->process->process;
-
- if (uid == 0) {
- i_fatal("User %s not allowed to log in using UNIX UID 0 "
- "(root logins are never allowed)", user);
- }
-
- if (request != NULL && request_process->service->uid == uid &&
- master_uid != uid) {
- struct passwd *pw;
-
- pw = getpwuid(uid);
- i_fatal("User %s not allowed to log in using %s's "
- "UNIX UID %s%s (see http://wiki.dovecot.org/UserIds)",
- user, request_process->service->set->name,
- dec2str(uid), pw == NULL ? "" :
- t_strdup_printf("(%s)", pw->pw_name));
- }
-
- if (uid < (uid_t)set->first_valid_uid ||
- (set->last_valid_uid != 0 && uid > (uid_t)set->last_valid_uid)) {
- struct passwd *pw;
- bool low = uid < (uid_t)set->first_valid_uid;
-
- pw = getpwuid(uid);
- i_fatal("User %s not allowed to log in using too %s "
- "UNIX UID %s%s (see %s in config file)",
- user, low ? "low" : "high",
- dec2str(uid), pw == NULL ? "" :
- t_strdup_printf("(%s)", pw->pw_name),
- low ? "first_valid_uid" : "last_valid_uid");
- }
-
- if (gid < (gid_t)set->first_valid_gid ||
- (set->last_valid_gid != 0 && gid > (gid_t)set->last_valid_gid)) {
- struct group *gr;
- bool low = gid < (gid_t)set->first_valid_gid;
-
- gr = getgrgid(gid);
- i_fatal("User %s not allowed to log in using too %s primary "
- "UNIX group ID %s%s (see %s in config file)",
- user, low ? "low" : "high",
- dec2str(gid), gr == NULL ? "" :
- t_strdup_printf("(%s)", gr->gr_name),
- low ? "first_valid_gid" : "last_valid_gid");
- }
-}
-
-static void auth_args_apply(const char *const *args,
- struct restrict_access_settings *rset,
- const char **home)
-{
- const char *key, *value;
- string_t *expanded_vars;
-
- expanded_vars = t_str_new(128);
- str_append(expanded_vars, "VARS_EXPANDED=");
- for (; *args != NULL; args++) {
- if (strncmp(*args, "uid=", 4) == 0)
- rset->uid = (uid_t)strtoul(*args + 4, NULL, 10);
- else if (strncmp(*args, "gid=", 4) == 0)
- rset->gid = (gid_t)strtoul(*args + 4, NULL, 10);
- else if (strncmp(*args, "home=", 5) == 0) {
- *home = *args + 5;
- env_put(t_strconcat("HOME=", *args + 5, NULL));
- } else if (strncmp(*args, "chroot=", 7) == 0)
- rset->chroot_dir = *args + 7;
- else if (strncmp(*args, "system_groups_user=", 19) == 0)
- rset->system_groups_user = *args + 19;
- else if (strncmp(*args, "mail_access_groups=", 19) == 0) {
- rset->extra_groups =
- rset->extra_groups == NULL ? *args + 19 :
- t_strconcat(*args + 19, ",",
- rset->extra_groups, NULL);
- } else {
- /* unknown, set as environment */
- value = strchr(*args, '=');
- if (value == NULL) {
- /* boolean */
- key = *args;
- value = "=1";
- } else {
- key = t_strdup_until(*args, value);
- if (strcmp(key, "mail") == 0) {
- /* FIXME: kind of ugly to have it
- here.. */
- key = "mail_location";
- }
- }
- str_append(expanded_vars, key);
- str_append_c(expanded_vars, ' ');
- env_put(t_strconcat(t_str_ucase(key), value, NULL));
- }
- }
- env_put(str_c(expanded_vars));
-}
-
-static void auth_success_write(void)
-{
- int fd;
-
- if (auth_success_written)
- return;
-
- fd = creat(AUTH_SUCCESS_PATH, 0666);
- if (fd == -1)
- i_error("creat(%s) failed: %m", AUTH_SUCCESS_PATH);
- else
- (void)close(fd);
- auth_success_written = TRUE;
-}
-
-static void chdir_to_home(const struct restrict_access_settings *rset,
- const char *user, const char *home)
-{
- unsigned int left;
- int ret, chdir_errno;
-
- if (*home != '/') {
- i_fatal("user %s: Relative home directory paths not supported: "
- "%s", user, home);
- }
-
- /* if home directory is NFS-mounted, we might not have access to it as
- root. Change the effective UID and GID temporarily to make it
- work. */
- if (rset->uid != master_uid) {
- if (setegid(rset->gid) < 0)
- i_fatal("setegid(%s) failed: %m", dec2str(rset->gid));
- if (seteuid(rset->uid) < 0)
- i_fatal("seteuid(%s) failed: %m", dec2str(rset->uid));
- }
-
- alarm(CHDIR_TIMEOUT);
- ret = chdir(home);
- chdir_errno = errno;
- if ((left = alarm(0)) < CHDIR_TIMEOUT - CHDIR_WARN_SECS) {
- i_warning("user %s: chdir(%s) blocked for %u secs",
- user, home, CHDIR_TIMEOUT - left);
- }
-
- errno = chdir_errno;
- if (ret == 0) {
- /* chdir succeeded */
- } else if ((errno == ENOENT || errno == ENOTDIR || errno == EINTR) &&
- rset->chroot_dir == NULL) {
- /* Not chrooted, fallback to using /tmp.
-
- ENOENT: No home directory yet, but it might be automatically
- created by the service process, so don't complain.
- ENOTDIR: This check is mainly for /dev/null home directory.
- EINTR: chdir() timed out. */
- } else if (errno == EACCES) {
- i_fatal("user %s: %s", user, eacces_error_get("chdir", home));
- } else {
- i_fatal("user %s: chdir(%s) failed with uid %s: %m",
- user, home, dec2str(rset->uid));
- }
- /* Change UID back. No need to change GID back, it doesn't
- really matter. */
- if (rset->uid != master_uid && seteuid(master_uid) < 0)
- i_fatal("seteuid(%s) failed: %m", dec2str(master_uid));
-
- if (ret < 0) {
- /* We still have to change to some directory where we have
- rx-access. /tmp should exist everywhere. */
- if (chdir("/tmp") < 0)
- i_fatal("chdir(/tmp) failed: %m");
- }
-}
-
-static void
-drop_privileges(struct service *service, const char *const *auth_args,
- const struct service_process_auth_request *request)
+drop_privileges(struct service *service)
{
- struct master_settings *master_set = service->set->master_set;
struct restrict_access_settings rset;
- const char *user, *home = NULL;
bool disallow_root;
- if (auth_args != NULL && service->set->master_set->mail_debug)
- env_put("DEBUG=1");
-
if (service->vsz_limit != 0)
restrict_process_size(service->vsz_limit, -1U);
service->set->chroot;
rset.extra_groups = service->extra_gids;
- if (auth_args == NULL) {
- /* non-authenticating service. don't use *_valid_gid checks */
- } else {
- i_assert(auth_args[0] != NULL);
-
- rset.first_valid_gid = master_set->first_valid_gid;
- rset.last_valid_gid = master_set->last_valid_gid;
-
- user = auth_args[0];
- env_put(t_strconcat("USER=", user, NULL));
-
- auth_success_write();
- auth_args_apply(auth_args + 1, &rset, &home);
-
- validate_uid_gid(master_set, rset.uid, rset.gid, user,
- request);
- }
-
- if (home != NULL)
- chdir_to_home(&rset, user, home);
-
if (service->set->drop_priv_before_exec) {
- disallow_root = service->type == SERVICE_TYPE_AUTH_SERVER ||
- service->type == SERVICE_TYPE_AUTH_SOURCE;
- restrict_access(&rset, home, disallow_root);
+ disallow_root = service->type == SERVICE_TYPE_LOGIN;
+ restrict_access(&rset, NULL, disallow_root);
} else {
restrict_access_set_env(&rset);
}
timeout_remove(&process->to_status);
}
-static void
-handle_request(const struct service_process_auth_request *request)
-{
- string_t *str;
-
- if (request == NULL)
- return;
-
- if (request->data_size > 0) {
- str = t_str_new(request->data_size*3);
- str_append(str, "CLIENT_INPUT=");
- base64_encode(request->data, request->data_size, str);
- env_put(str_c(str));
- }
-
- env_put(t_strconcat("LOCAL_IP=", net_ip2addr(&request->local_ip), NULL));
- env_put(t_strconcat("IP=", net_ip2addr(&request->remote_ip), NULL));
-}
-
-static const char **
-get_extra_args(struct service *service,
- const struct service_process_auth_request *request,
- const char *const *auth_args)
-{
- const char **extra;
-
- if (!service->set->master_set->verbose_proctitle || request == NULL)
- return NULL;
-
- extra = t_new(const char *, 2);
- extra[0] = t_strdup_printf("[%s %s]", auth_args[0],
- net_ip2addr(&request->remote_ip));
- return extra;
-}
-
-struct service_process *
-service_process_create(struct service *service, const char *const *auth_args,
- const struct service_process_auth_request *request)
+struct service_process *service_process_create(struct service *service)
{
static unsigned int uid_counter = 0;
struct service_process *process;
unsigned int uid = ++uid_counter;
- int fd[2];
pid_t pid;
if (service->to_throttle != NULL) {
/* throttling service, don't create new processes */
return NULL;
}
- if (service->process_count >= service->process_limit) {
- /* we should get here only with auth dest services */
- i_warning("service(%s): process_limit reached, "
- "dropping this client connection",
- service->set->name);
- return NULL;
- }
-
- switch (service->type) {
- case SERVICE_TYPE_AUTH_SOURCE:
- case SERVICE_TYPE_AUTH_SERVER:
- if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) {
- service_error(service, "socketpair() failed: %m");
- return NULL;
- }
- fd_close_on_exec(fd[0], TRUE);
- fd_close_on_exec(fd[1], TRUE);
- break;
- default:
- fd[0] = fd[1] = -1;
- break;
- }
pid = fork();
if (pid < 0) {
service_error(service, "fork() failed: %m");
- if (fd[0] != -1) {
- (void)close(fd[0]);
- (void)close(fd[1]);
- }
return NULL;
}
if (pid == 0) {
/* child */
- if (fd[0] != -1)
- (void)close(fd[0]);
service_process_setup_environment(service, uid);
- handle_request(request);
- service_dup_fds(service, fd[1], request == NULL ? -1 :
- request->fd, auth_args != NULL);
- drop_privileges(service, auth_args, request);
- process_exec(service->executable,
- get_extra_args(service, request, auth_args));
+ service_dup_fds(service);
+ drop_privileges(service);
+ process_exec(service->executable, NULL);
}
switch (service->type) {
- case SERVICE_TYPE_AUTH_SERVER:
- process = i_malloc(sizeof(struct service_process_auth_server));
- process->service = service;
- service_process_auth_server_init(process, fd[0]);
- (void)close(fd[1]);
- break;
- case SERVICE_TYPE_AUTH_SOURCE:
- process = i_malloc(sizeof(struct service_process_auth_source));
- process->service = service;
- service_process_auth_source_init(process, fd[0]);
- (void)close(fd[1]);
- break;
case SERVICE_TYPE_ANVIL:
service_anvil_process_created(service);
/* fall through */
default:
process = i_new(struct service_process, 1);
process->service = service;
- i_assert(fd[0] == -1);
break;
}
timeout_remove(&process->to_idle);
switch (process->service->type) {
- case SERVICE_TYPE_AUTH_SERVER:
- service_process_auth_server_deinit(process);
- break;
- case SERVICE_TYPE_AUTH_SOURCE:
- service_process_auth_source_deinit(process);
- break;
case SERVICE_TYPE_ANVIL:
service_anvil_process_destroyed(service);
break;
service_process_unref(process);
if (service->process_count < service->process_limit &&
- service->type == SERVICE_TYPE_AUTH_SOURCE)
- service_processes_auth_source_notify(service, FALSE);
+ service->type == SERVICE_TYPE_LOGIN)
+ service_login_notify(service, FALSE);
service_list_unref(service_list);
}
unsigned int destroyed:1;
};
-struct service_process_auth_server {
- struct service_process process;
+#define SERVICE_PROCESS_IS_INITIALIZED(process) \
+ ((process)->to_status == NULL)
- int auth_fd;
- struct io *io_auth;
- struct ostream *auth_output;
- struct istream *auth_input;
-
- /* pending authentication requests that are being verified from
- auth server. */
- struct hash_table *auth_requests;
- /* Last time we wrote "authentication server is too busy" to log */
- time_t auth_busy_stamp;
- /* Tag counter for outgoing requests */
- unsigned int auth_tag_counter;
-
- unsigned int auth_version_sent:1;
- unsigned int auth_version_received:1;
-};
-
-struct service_process_auth_source {
- struct service_process process;
-
- int last_notify_status;
-
- int auth_fd;
- struct io *io_auth;
- struct ostream *auth_output;
-};
-
-struct service_process_auth_request {
- struct service_process_auth_source *process;
-
- unsigned int process_tag;
- int fd;
-
- struct ip_addr local_ip, remote_ip;
- unsigned int data_size;
- unsigned char data[FLEXIBLE_ARRAY_MEMBER];
-};
-
-struct service_process *
-service_process_create(struct service *service, const char *const *auth_args,
- const struct service_process_auth_request *request);
+struct service_process *service_process_create(struct service *service);
void service_process_destroy(struct service_process *process);
void service_process_ref(struct service_process *process);
#include <signal.h>
#define SERVICE_DIE_TIMEOUT_MSECS (1000*10)
+#define SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS 2
struct hash_table *service_pids;
service->vsz_limit = set->vsz_limit != 0 ? set->vsz_limit :
set->master_set->default_vsz_limit;
-
- service->type = SERVICE_TYPE_UNKNOWN;
- if (*set->type != '\0') {
- if (strcmp(set->type, "log") == 0)
- service->type = SERVICE_TYPE_LOG;
- else if (strcmp(set->type, "config") == 0)
- service->type = SERVICE_TYPE_CONFIG;
- else if (strcmp(set->type, "anvil") == 0)
- service->type = SERVICE_TYPE_ANVIL;
- else if (strcmp(set->type, "auth") == 0)
- service->type = SERVICE_TYPE_AUTH_SERVER;
- else if (strcmp(set->type, "auth-source") == 0)
- service->type = SERVICE_TYPE_AUTH_SOURCE;
- }
-
- if (*set->auth_dest_service != '\0')
- service->type = SERVICE_TYPE_AUTH_SOURCE;
+ service->type = service->set->parsed_type;
if (set->process_limit == 0) {
/* unlimited */
service->status_fd[0] = -1;
service->status_fd[1] = -1;
service->log_process_internal_fd = -1;
+ service->login_notify_fd = -1;
if (array_is_created(&set->unix_listeners))
unix_listeners = array_get(&set->unix_listeners, &unix_count);
struct service_list **services_r, const char **error_r)
{
struct service_list *service_list;
- struct service *service, *const *services;
+ struct service *service;
struct service_settings *const *service_settings;
pool_t pool;
const char *error;
array_append(&service_list->services, &service, 1);
}
- /* resolve service dependencies */
- services = array_get(&service_list->services, &count);
- for (i = 0; i < count; i++) {
- if (services[i]->type == SERVICE_TYPE_AUTH_SOURCE) {
- const char *dest_service =
- services[i]->set->auth_dest_service;
- services[i]->auth_dest_service =
- service_lookup(service_list, dest_service);
- if (services[i]->auth_dest_service == NULL) {
- *error_r = t_strdup_printf(
- "auth_dest_service doesn't exist: %s",
- dest_service);
- return -1;
- }
- }
- }
-
if (service_list->log == NULL) {
*error_r = "log service not specified";
return -1;
for (; process != NULL; process = process->next) {
i_assert(process->service == service);
+ if (!SERVICE_PROCESS_IS_INITIALIZED(process) &&
+ signo != SIGKILL) {
+ /* too early to signal it */
+ continue;
+ }
+
if (kill(process->pid, signo) < 0 && errno != ESRCH) {
service_error(service, "kill(%s, %d) failed: %m",
dec2str(process->pid), signo);
}
}
+static void service_login_notify_send(struct service *service)
+{
+ service->last_login_notify_time = ioloop_time;
+ if (service->to_login_notify != NULL)
+ timeout_remove(&service->to_login_notify);
+
+ service_signal(service, SIGUSR1);
+}
+
+static void service_login_notify_timeout(struct service *service)
+{
+ service_login_notify_send(service);
+}
+
+void service_login_notify(struct service *service, bool all_processes_full)
+{
+ enum master_login_state state;
+ int diff;
+
+ if (service->last_login_full_notify == all_processes_full ||
+ service->login_notify_fd == -1)
+ return;
+
+ /* change the state always immediately. it's cheap. */
+ service->last_login_full_notify = all_processes_full;
+ state = all_processes_full ? MASTER_LOGIN_STATE_FULL :
+ MASTER_LOGIN_STATE_NONFULL;
+ if (lseek(service->login_notify_fd, state, SEEK_SET) < 0)
+ service_error(service, "lseek(notify fd) failed: %m");
+
+ /* but don't send signal to processes too often */
+ diff = ioloop_time - service->last_login_notify_time;
+ if (diff < SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS) {
+ if (service->to_login_notify != NULL)
+ return;
+
+ diff = (SERVICE_LOGIN_NOTIFY_MIN_INTERVAL_SECS - diff) * 1000;
+ service->to_login_notify =
+ timeout_add(diff, service_login_notify_timeout,
+ service);
+ } else {
+ service_login_notify_send(service);
+ }
+}
+
static void services_kill_timeout(struct service_list *service_list)
{
struct service *const *services, *log_service;
#define SERVICE_H
#include "network.h"
-
-struct master_settings;
+#include "master-settings.h"
/* If a service process doesn't send its first status notification in
this many seconds, kill the process */
#define SERVICE_FIRST_STATUS_TIMEOUT_SECS 30
-enum service_type {
- SERVICE_TYPE_UNKNOWN,
- SERVICE_TYPE_LOG,
- SERVICE_TYPE_ANVIL,
- SERVICE_TYPE_CONFIG,
- SERVICE_TYPE_AUTH_SERVER,
- SERVICE_TYPE_AUTH_SOURCE
-};
-
enum service_listener_type {
SERVICE_LISTENER_UNIX,
SERVICE_LISTENER_FIFO,
int status_fd[2];
struct io *io_status;
+ /* Login process's notify fd. We change its seek position to
+ communicate state to login processes. */
+ int login_notify_fd;
+ time_t last_login_notify_time;
+ struct timeout *to_login_notify;
+
/* if a process fails before servicing its first request, assume it's
broken and start throtting new process creations */
struct timeout *to_throttle;
- /* SERVICE_TYPE_AUTH_SOURCE: Destination service to run after
- successful authentication. */
- struct service *auth_dest_service;
-
/* Last time a "dropping client connections" warning was logged */
time_t last_drop_warning;
unsigned int listening:1;
/* TRUE if service has at least one inet_listener */
unsigned int have_inet_listeners:1;
+ /* service_login_notify()'s last notification state */
+ unsigned int last_login_full_notify:1;
};
struct service_list {
int master_log_fd[2];
struct service_process_notify *log_byes;
- /* passed to auth destination processes */
+ /* passed to child processes */
int blocking_anvil_fd[2];
/* used by master process to notify about dying processes */
int nonblocking_anvil_fd[2];
/* Send a signal to all processes in a given service */
void service_signal(struct service *service, int signo);
+/* Notify all processes (if necessary) that no more connections can be handled
+ by the service without killing existing connections (TRUE) or that they
+ can be (FALSE). */
+void service_login_notify(struct service *service, bool all_processes_full);
/* Prevent service from launching new processes for a while. */
void service_throttle(struct service *service, unsigned int secs);
#include "pop3-common.h"
#include "ioloop.h"
-#include "istream.h"
#include "buffer.h"
+#include "istream.h"
+#include "ostream.h"
#include "base64.h"
#include "restrict-access.h"
#include "process-title.h"
#include "master-service.h"
+#include "master-login.h"
#include "master-interface.h"
#include "var-expand.h"
#include "mail-storage-service.h"
#include <unistd.h>
#define IS_STANDALONE() \
- (getenv("LOGGED_IN") == NULL)
+ (getenv(MASTER_UID_ENV) == NULL)
+
+static const struct setting_parser_info *set_roots[] = {
+ &pop3_setting_parser_info,
+ NULL
+};
+static struct master_login *master_login = NULL;
+static enum mail_storage_service_flags storage_service_flags = 0;
+static bool user_initialized = FALSE;
void (*hook_client_created)(struct client **client) = NULL;
-static bool main_init(const struct pop3_settings *set, struct mail_user *user)
+static void client_add_input(struct client *client, const buffer_t *buf)
{
- struct client *client;
- const char *str;
- bool ret = TRUE;
-
- if (set->shutdown_clients)
- master_service_set_die_with_master(master_service, TRUE);
+ struct ostream *output;
- client = client_create(0, 1, user, set);
- if (client == NULL)
- return FALSE;
+ if (buf != NULL && buf->used > 0) {
+ if (!i_stream_add_data(client->input, buf->data, buf->used))
+ i_panic("Couldn't add client input to stream");
+ }
+ output = client->output;
+ o_stream_ref(output);
+ o_stream_cork(output);
if (!IS_STANDALONE())
client_send_line(client, "+OK Logged in.");
-
- str = getenv("CLIENT_INPUT");
- if (str != NULL) T_BEGIN {
- buffer_t *buf = t_base64_decode_str(str);
- if (buf->used > 0) {
- if (!i_stream_add_data(client->input, buf->data,
- buf->used))
- i_panic("Couldn't add client input to stream");
- ret = client_handle_input(client);
- }
- } T_END;
- return ret;
+ (void)client_handle_input(client);
+ o_stream_uncork(output);
+ o_stream_unref(&output);
}
-static void main_deinit(void)
+static void
+main_stdio_init_user(const struct pop3_settings *set, struct mail_user *user)
{
- clients_destroy_all();
-}
+ struct client *client;
+ buffer_t *input_buf;
+ const char *input_base64;
-static void client_connected(const struct master_service_connection *conn)
-{
- /* we can't handle this yet */
- (void)close(conn->fd);
+ input_base64 = getenv("CLIENT_INPUT");
+ input_buf = input_base64 == NULL ? NULL :
+ t_base64_decode_str(input_base64);
+
+ client = client_create(STDIN_FILENO, STDOUT_FILENO, user, set);
+ client_add_input(client, input_buf);
}
-int main(int argc, char *argv[], char *envp[])
+static void main_stdio_run(void)
{
- const struct setting_parser_info *set_roots[] = {
- &pop3_setting_parser_info,
- NULL
- };
- enum master_service_flags service_flags =
- MASTER_SERVICE_FLAG_STD_CLIENT;
- enum mail_storage_service_flags storage_service_flags = 0;
struct mail_storage_service_input input;
struct mail_user *mail_user;
const struct pop3_settings *set;
const char *value;
- int c;
-
- if (IS_STANDALONE() && getuid() == 0 &&
- net_getpeername(1, NULL, NULL) == 0) {
- printf("-ERR pop3 binary must not be started from "
- "inetd, use pop3-login instead.\n");
- return 1;
- }
-
- if (IS_STANDALONE())
- service_flags |= MASTER_SERVICE_FLAG_STANDALONE;
- else {
- storage_service_flags |=
- MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT |
- MAIL_STORAGE_SERVICE_FLAG_RESTRICT_BY_ENV;
- }
-
- master_service = master_service_init("pop3", service_flags, argc, argv);
- while ((c = getopt(argc, argv, master_service_getopt_string())) > 0) {
- if (!master_service_parse_option(master_service, c, optarg))
- exit(FATAL_DEFAULT);
- }
memset(&input, 0, sizeof(input));
- input.module = "pop3";
- input.service = "pop3";
+ input.module = input.service = "pop3";
input.username = getenv("USER");
if (input.username == NULL && IS_STANDALONE())
input.username = getlogin();
- if (input.username == NULL) {
- if (getenv(MASTER_UID_ENV) == NULL)
- i_fatal("USER environment missing");
- else {
- i_fatal("login_executable setting must be pop3-login, "
- "not pop3");
- }
- }
+ if (input.username == NULL)
+ i_fatal("USER environment missing");
if ((value = getenv("IP")) != NULL)
net_addr2ip(value, &input.remote_ip);
if ((value = getenv("LOCAL_IP")) != NULL)
net_addr2ip(value, &input.local_ip);
+ user_initialized = TRUE;
mail_user = mail_storage_service_init_user(master_service,
&input, set_roots,
storage_service_flags);
set = mail_storage_service_get_settings(master_service);
restrict_access_allow_coredumps(TRUE);
+ if (set->shutdown_clients)
+ master_service_set_die_with_master(master_service, TRUE);
- process_title_init(argv, envp);
+ /* fake that we're running, so we know if client was destroyed
+ while handling its initial input */
+ io_loop_set_running(current_ioloop);
+ main_stdio_init_user(set, mail_user);
+}
+
+static void
+login_client_connected(const struct master_login_client *client,
+ const char *username, const char *const *extra_fields)
+{
+ struct mail_storage_service_input input;
+ struct mail_user *mail_user;
+ struct client *pop3_client;
+ const struct pop3_settings *set;
+ buffer_t input_buf;
+
+ if (pop3_clients != NULL) {
+ i_error("Can't handle more than one connection currently");
+ (void)close(client->fd);
+ return;
+ }
+ i_assert(!user_initialized);
+
+ memset(&input, 0, sizeof(input));
+ input.module = input.service = "pop3";
+ input.local_ip = client->auth_req.local_ip;
+ input.remote_ip = client->auth_req.remote_ip;
+ input.username = username;
+ input.userdb_fields = extra_fields;
+
+ if (input.username == NULL) {
+ i_error("login client: Username missing from auth reply");
+ (void)close(client->fd);
+ return;
+ }
+ user_initialized = TRUE;
+ master_login_deinit(&master_login);
+
+ mail_user = mail_storage_service_init_user(master_service,
+ &input, set_roots,
+ storage_service_flags);
+ set = mail_storage_service_get_settings(master_service);
+ restrict_access_allow_coredumps(TRUE);
+ if (set->shutdown_clients)
+ master_service_set_die_with_master(master_service, TRUE);
/* fake that we're running, so we know if client was destroyed
- while initializing */
+ while handling its initial input */
io_loop_set_running(current_ioloop);
- if (main_init(set, mail_user))
+ buffer_create_const_data(&input_buf, client->data,
+ client->auth_req.data_size);
+ pop3_client = client_create(client->fd, client->fd, mail_user, set);
+ T_BEGIN {
+ client_add_input(pop3_client, &input_buf);
+ } T_END;
+}
+
+static void client_connected(const struct master_service_connection *conn)
+{
+ if (master_login == NULL) {
+ /* running standalone, we shouldn't even get here */
+ (void)close(conn->fd);
+ } else {
+ master_login_add(master_login, conn->fd);
+ }
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+ enum master_service_flags service_flags = 0;
+ int c;
+
+ if (IS_STANDALONE() && getuid() == 0 &&
+ net_getpeername(1, NULL, NULL) == 0) {
+ printf("-ERR pop3 binary must not be started from "
+ "inetd, use pop3-login instead.\n");
+ return 1;
+ }
+
+ if (IS_STANDALONE()) {
+ service_flags |= MASTER_SERVICE_FLAG_STANDALONE |
+ MASTER_SERVICE_FLAG_STD_CLIENT;
+ } else {
+ storage_service_flags |=
+ MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT;
+ }
+
+ master_service = master_service_init("pop3", service_flags, argc, argv);
+ while ((c = getopt(argc, argv, master_service_getopt_string())) > 0) {
+ if (!master_service_parse_option(master_service, c, optarg))
+ exit(FATAL_DEFAULT);
+ }
+ process_title_init(argv, envp);
+ master_service_init_finish(master_service);
+
+ if (IS_STANDALONE()) {
+ T_BEGIN {
+ main_stdio_run();
+ } T_END;
+ } else {
+ master_login = master_login_init("auth-master",
+ login_client_connected);
+ io_loop_set_running(current_ioloop);
+ }
+
+ if (io_loop_is_running(current_ioloop))
master_service_run(master_service, client_connected);
+ clients_destroy_all();
- main_deinit();
- mail_storage_service_deinit_user();
+ if (master_login != NULL)
+ master_login_deinit(&master_login);
+ if (user_initialized)
+ mail_storage_service_deinit_user();
master_service_deinit(&master_service);
return 0;
}
transaction. This allows the mailbox to become unlocked. */
#define CLIENT_COMMIT_TIMEOUT_MSECS (10*1000)
-static struct client *pop3_clients;
+struct client *pop3_clients;
static void client_input(struct client *client);
static int client_output(struct client *client);
unsigned int anvil_sent:1;
};
+extern struct client *pop3_clients;
+
/* Create new client with specified input/output handles. socket specifies
if the handle is a socket. */
struct client *client_create(int fd_in, int fd_out, struct mail_user *user,