#include "fdpass.h"
#include "fd-close-on-exec.h"
#include "llist.h"
+#include "str.h"
+#include "strescape.h"
#include "master-service-private.h"
#include "master-login.h"
#include "master-login-auth.h"
#include <sys/stat.h>
#include <unistd.h>
+#define MASTER_LOGIN_POSTLOGIN_TIMEOUT_MSECS (60*1000)
+
struct master_login_connection {
struct master_login_connection *prev, *next;
struct ostream *output;
};
+struct master_login_postlogin {
+ struct master_login_client *client;
+
+ int fd;
+ struct io *io;
+ struct timeout *to;
+ string_t *input;
+};
+
struct master_login {
struct master_service *service;
master_login_callback_t *callback;
struct master_login_connection *conns;
struct master_login_auth *auth;
+ char *postlogin_socket_path;
};
static void master_login_conn_deinit(struct master_login_connection **_conn);
struct master_login *
master_login_init(struct master_service *service, const char *auth_socket_path,
+ const char *postlogin_socket_path,
master_login_callback_t *callback)
{
struct master_login *login;
login->service = service;
login->callback = callback;
login->auth = master_login_auth_init(auth_socket_path);
+ login->postlogin_socket_path = i_strdup(postlogin_socket_path);
service->login_connections = TRUE;
return login;
}
master_login_conn_deinit(&conn);
}
+ i_free(login->postlogin_socket_path);
i_free(login);
}
return 1;
}
+static void master_login_auth_finish(struct master_login_client *client,
+ const char *const *auth_args)
+{
+ struct master_login_connection *conn = client->conn;
+ struct master_service *service = conn->login->service;
+ bool close_config;
+
+ close_config = service->master_status.available_count == 0 &&
+ service->service_count_left == 1;
+
+ conn->login->callback(client, auth_args[0], auth_args+1);
+ i_free(client);
+
+ if (close_config) {
+ /* we're dying as soon as this connection closes. */
+ master_service_close_config_fd(service);
+ master_login_conn_deinit(&conn);
+ }
+}
+
+static void master_login_client_free(struct master_login_client *client)
+{
+ if (close(client->fd) < 0)
+ i_error("close(fd_read client) failed: %m");
+ i_free(client);
+}
+
+static void master_login_postlogin_free(struct master_login_postlogin *pl)
+{
+ timeout_remove(&pl->to);
+ io_remove(&pl->io);
+ if (close(pl->fd) < 0)
+ i_error("close(postlogin) failed: %m");
+ str_free(&pl->input);
+ i_free(pl);
+}
+
+static void master_login_postlogin_input(struct master_login_postlogin *pl)
+{
+ struct master_login *login = pl->client->conn->login;
+ char buf[1024];
+ const char **auth_args, **p;
+ unsigned int len;
+ ssize_t ret;
+ int fd = -1;
+
+ while ((ret = fd_read(pl->fd, buf, sizeof(buf), &fd)) > 0) {
+ if (fd != -1) {
+ /* post-login script replaced fd */
+ if (close(pl->client->fd) < 0)
+ i_error("close(client) failed: %m");
+ pl->client->fd = fd;
+ }
+ str_append_n(pl->input, buf, ret);
+ }
+
+ len = str_len(pl->input);
+ if (len > 0 && str_c(pl->input)[len-1] == '\n') {
+ /* finished reading the input */
+ str_truncate(pl->input, len-1);
+ } else {
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ return;
+
+ i_error("fd_read(%s) failed: %m",
+ login->postlogin_socket_path);
+ } else {
+ i_error("fd_read(%s) failed: disconnected",
+ login->postlogin_socket_path);
+ }
+ master_login_client_free(pl->client);
+ master_login_postlogin_free(pl);
+ master_service_client_connection_destroyed(login->service);
+ return;
+ }
+
+ auth_args = t_strsplit(str_c(pl->input), "\t");
+ for (p = auth_args; *p != NULL; p++)
+ *p = str_tabunescape(t_strdup_noconst(*p));
+
+ master_login_auth_finish(pl->client, auth_args);
+ master_login_postlogin_free(pl);
+}
+
+static void master_login_postlogin_timeout(struct master_login_postlogin *pl)
+{
+ struct master_login *login = pl->client->conn->login;
+
+ i_error("%s: Timeout waiting for post-login script to finish, aborting",
+ login->postlogin_socket_path);
+
+ master_login_client_free(pl->client);
+ master_login_postlogin_free(pl);
+ master_service_client_connection_destroyed(login->service);
+}
+
+static int master_login_postlogin(struct master_login_client *client,
+ const char *const *auth_args)
+{
+ struct master_login *login = client->conn->login;
+ struct master_login_postlogin *pl;
+ string_t *str;
+ unsigned int i;
+ int fd;
+ ssize_t ret;
+
+ fd = net_connect_unix_with_retries(login->postlogin_socket_path, 1000);
+ if (fd == -1) {
+ i_error("net_connect_unix(%s) failed: %m",
+ login->postlogin_socket_path);
+ return -1;
+ }
+
+ str = t_str_new(256);
+ str_printfa(str, "%s\t%s", net_ip2addr(&client->auth_req.local_ip),
+ net_ip2addr(&client->auth_req.remote_ip));
+ for (i = 0; auth_args[i] != NULL; i++) {
+ str_append_c(str, '\t');
+ str_tabescape_write(str, auth_args[i]);
+ }
+ str_append_c(str, '\n');
+ ret = fd_send(fd, client->fd, str_data(str), str_len(str));
+ if (ret != (ssize_t)str_len(str)) {
+ if (ret < 0) {
+ i_error("write(%s) failed: %m",
+ login->postlogin_socket_path);
+ } else {
+ i_error("write(%s) failed: partial write",
+ login->postlogin_socket_path);
+ }
+ (void)close(fd);
+ return -1;
+ }
+ net_set_nonblock(fd, TRUE);
+
+ pl = i_new(struct master_login_postlogin, 1);
+ pl->client = client;
+ pl->fd = fd;
+ pl->io = io_add(fd, IO_READ, master_login_postlogin_input, pl);
+ pl->to = timeout_add(MASTER_LOGIN_POSTLOGIN_TIMEOUT_MSECS,
+ master_login_postlogin_timeout, pl);
+ pl->input = str_new(default_pool, 512);
+ return 0;
+}
+
static void
master_login_auth_callback(const char *const *auth_args, void *context)
{
struct master_login_client *client = context;
struct master_auth_reply reply;
- struct master_login_connection *conn = client->conn;
- struct master_service *service = conn->login->service;
- bool close_config;
+ struct master_service *service = client->conn->login->service;
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(conn->output, &reply, sizeof(reply));
+ o_stream_send(client->conn->output, &reply, sizeof(reply));
if (auth_args == NULL || auth_args[0] == NULL) {
if (auth_args != NULL)
i_error("login client: Username missing from auth reply");
- if (close(client->fd) < 0)
- i_error("close(fd_read client) failed: %m");
- i_free(client);
+ master_login_client_free(client);
return;
}
i_assert(service->master_status.available_count > 0);
service->master_status.available_count--;
master_status_update(service);
- close_config = service->master_status.available_count == 0 &&
- service->service_count_left == 1;
- conn->login->callback(client, auth_args[0], auth_args+1);
- i_free(client);
-
- if (close_config) {
- /* we're dying as soon as this connection closes. */
- master_service_close_config_fd(service);
- master_login_conn_deinit(&conn);
+ if (client->conn->login->postlogin_socket_path == NULL)
+ master_login_auth_finish(client, auth_args);
+ else {
+ /* execute post-login scripts before finishing auth */
+ if (master_login_postlogin(client, auth_args) < 0) {
+ master_login_client_free(client);
+ master_service_client_connection_destroyed(service);
+ }
}
}
--- /dev/null
+/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "env-util.h"
+#include "fdpass.h"
+#include "str.h"
+#include "strescape.h"
+#include "master-interface.h"
+#include "master-service.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#define ENV_USERDB_KEYS "USERDB_KEYS"
+#define SCRIPT_COMM_FD 3
+#define SCRIPT_CLIENT_FD 1
+
+static char **exec_args;
+
+static void client_connected(const struct master_service_connection *conn)
+{
+ string_t *input, *keys;
+ const char **args, *arg, *key, *value, *username;
+ char buf[1024];
+ unsigned int i;
+ int fd = -1;
+ ssize_t ret;
+
+ input = t_str_new(1024);
+ ret = fd_read(conn->fd, buf, sizeof(buf), &fd);
+ while (ret > 0) {
+ str_append_n(input, buf, ret);
+ if (buf[ret-1] == '\n') {
+ str_truncate(input, str_len(input)-1);
+ break;
+ }
+
+ ret = read(conn->fd, buf, sizeof(buf));
+ }
+ if (ret <= 0) {
+ if (ret < 0)
+ i_fatal("read() failed: %m");
+ else
+ i_fatal("read() failed: disconnected");
+ (void)close(conn->fd);
+ return;
+ }
+ if (fd == -1)
+ i_fatal("client fd not received");
+
+ /* put everything to environment */
+ env_clean();
+ keys = t_str_new(256);
+ args = t_strsplit(str_c(input), "\t");
+
+ if (str_array_length(args) < 3)
+ i_fatal("Missing input fields");
+
+ i = 0;
+ env_put(t_strconcat("LOCAL_IP=", args[i++], NULL));
+ env_put(t_strconcat("IP=", args[i++], NULL));
+ username = args[i++];
+ env_put(t_strconcat("USER=", username, NULL));
+
+ for (; args[i] != '\0'; i++) {
+ arg = str_tabunescape(t_strdup_noconst(args[i]));
+ value = strchr(arg, '=');
+ if (value != NULL) {
+ key = t_str_ucase(t_strdup_until(arg, value));
+ env_put(t_strconcat(key, value, NULL));
+ str_printfa(keys, "%s ", key);
+ }
+ }
+ env_put(t_strconcat(ENV_USERDB_KEYS"=", str_c(keys), NULL));
+
+ if (dup2(fd, SCRIPT_CLIENT_FD) < 0)
+ i_fatal("dup2() failed: %m");
+ if (conn->fd != SCRIPT_COMM_FD) {
+ if (dup2(conn->fd, SCRIPT_COMM_FD) < 0)
+ i_fatal("dup2() failed: %m");
+ }
+
+ master_service_init_log(master_service,
+ t_strdup_printf("script(%s): ", username));
+
+ (void)execvp(exec_args[0], exec_args);
+ i_fatal("execvp(%s) failed: %m", exec_args[0]);
+}
+
+static void script_execute_finish(void)
+{
+ const char *keys_str, *username, *const *keys, *value;
+ string_t *reply = t_str_new(512);
+ ssize_t ret;
+
+ keys_str = getenv(ENV_USERDB_KEYS);
+ if (keys_str == NULL)
+ i_fatal(ENV_USERDB_KEYS" environment missing");
+
+ username = getenv("USER");
+ if (username == NULL)
+ i_fatal("USER environment missing");
+ str_append(reply, username);
+
+ for (keys = t_strsplit_spaces(keys_str, " "); *keys != NULL; keys++) {
+ value = getenv(t_str_ucase(*keys));
+ if (value != NULL) {
+ str_append_c(reply, '\t');
+ str_tabescape_write(reply,
+ t_strconcat(t_str_lcase(*keys), "=",
+ value, NULL));
+ }
+ }
+ str_append_c(reply, '\n');
+
+ ret = fd_send(SCRIPT_COMM_FD, SCRIPT_CLIENT_FD,
+ str_data(reply), str_len(reply));
+ if (ret < 0)
+ i_fatal("fd_send() failed: %m");
+ else if (ret != (ssize_t)str_len(reply))
+ i_fatal("fd_send() sent partial output");
+}
+
+int main(int argc, char *argv[])
+{
+ enum master_service_flags flags = 0;
+ int i;
+
+ if (getenv(MASTER_UID_ENV) == NULL)
+ flags |= MASTER_SERVICE_FLAG_STANDALONE;
+
+ master_service = master_service_init("script", flags,
+ &argc, &argv, NULL);
+ if (master_getopt(master_service) > 0)
+ return FATAL_DEFAULT;
+
+ master_service_init_log(master_service, "script: ");
+ master_service_init_finish(master_service);
+ master_service_set_service_count(master_service, 1);
+
+ if ((flags & MASTER_SERVICE_FLAG_STANDALONE) != 0)
+ script_execute_finish();
+ else {
+ if (argv[1] == NULL)
+ i_fatal("Missing script path");
+ exec_args = i_new(char *, argc + 1);
+ for (i = 1; i < argc; i++)
+ exec_args[i-1] = argv[i];
+ exec_args[i-1] = PKG_LIBEXECDIR"/script";
+ exec_args[i] = NULL;
+
+ master_service_run(master_service, client_connected);
+ }
+ master_service_deinit(&master_service);
+ return 0;
+}