script-login.c revision d482079eb385cd071bbc9637cacee225e4aff968
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen/* Copyright (c) 2009-2010 Dovecot authors, see the included COPYING file */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "lib.h"
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "env-util.h"
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "execv-const.h"
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "fdpass.h"
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "restrict-access.h"
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "str.h"
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "strescape.h"
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "settings-parser.h"
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "mail-storage-service.h"
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#include "master-interface.h"
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen#include "master-service.h"
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen#include "master-service-settings.h"
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen#include <stdlib.h>
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen#include <unistd.h>
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen#define SCRIPT_LOGIN_PROTOCOL_VERSION_MAJOR 1
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen#define SCRIPT_LOGIN_READ_TIMEOUT_SECS 10
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen#define ENV_USERDB_KEYS "USERDB_KEYS"
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen#define SCRIPT_COMM_FD 3
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainenstatic const char **exec_args;
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainenstatic bool drop_privileges = FALSE;
58bc77731bb25e900498a28409337e747f622722Timo Sirainen
c2f24d55319fad0b6c03425f402f0cb0cb1a318bTimo Sirainenstatic void client_connected(struct master_service_connection *conn)
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen{
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen enum mail_storage_service_flags flags =
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS;
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen string_t *instr, *keys;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen const char **args, *key, *value, *error, *version_line, *data_line;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen struct mail_storage_service_ctx *service_ctx;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen struct mail_storage_service_input input;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen struct mail_storage_service_user *user;
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen char buf[1024];
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen unsigned int i, socket_count;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen int fd = -1;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen ssize_t ret;
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen
alarm(SCRIPT_LOGIN_READ_TIMEOUT_SECS);
net_set_nonblock(conn->fd, FALSE);
instr = t_str_new(1024);
ret = fd_read(conn->fd, buf, sizeof(buf), &fd);
while (ret > 0) {
str_append_n(instr, buf, ret);
if (buf[ret-1] == '\n' &&
strchr(str_c(instr), '\n')[1] != '\0') {
str_truncate(instr, str_len(instr)-1);
break;
}
ret = read(conn->fd, buf, sizeof(buf));
}
version_line = str_c(instr);
data_line = strchr(version_line, '\n');
if (data_line != NULL)
version_line = t_strdup_until(version_line, data_line++);
else
version_line = NULL;
if (ret > 0 || version_line != NULL) {
if (version_line == NULL ||
!version_string_verify(version_line, "script-login",
SCRIPT_LOGIN_PROTOCOL_VERSION_MAJOR)) {
i_fatal("Client not compatible with this binary "
"(connecting to wrong socket?)");
}
}
if (ret <= 0) {
if (ret < 0)
i_fatal("read() failed: %m");
else
i_fatal("read() failed: disconnected");
}
if (fd == -1)
i_fatal("client fd not received");
/* put everything to environment */
env_clean();
keys = t_str_new(256);
args = t_strsplit(data_line, "\t");
if (str_array_length(args) < 3)
i_fatal("Missing input fields");
i = 0;
memset(&input, 0, sizeof(input));
input.module = "mail"; /* need to get mail_uid, mail_gid */
input.service = "script-login";
(void)net_addr2ip(args[i++], &input.local_ip);
(void)net_addr2ip(args[i++], &input.remote_ip);
input.username = args[i++];
input.userdb_fields = args + i;
env_put(t_strconcat("LOCAL_IP=", net_ip2addr(&input.local_ip), NULL));
env_put(t_strconcat("IP=", net_ip2addr(&input.remote_ip), NULL));
env_put(t_strconcat("USER=", input.username, NULL));
for (; args[i] != '\0'; i++) {
args[i] = str_tabunescape(t_strdup_noconst(args[i]));
value = strchr(args[i], '=');
if (value != NULL) {
key = t_str_ucase(t_strdup_until(args[i], value));
env_put(t_strconcat(key, value, NULL));
str_printfa(keys, "%s ", key);
}
}
env_put(t_strconcat(ENV_USERDB_KEYS"=", str_c(keys), NULL));
master_service_init_log(master_service,
t_strdup_printf("script-login(%s): ", input.username));
service_ctx = mail_storage_service_init(master_service, NULL, flags);
if (mail_storage_service_lookup(service_ctx, &input, &user, &error) <= 0)
i_fatal("%s", error);
mail_storage_service_restrict_setenv(service_ctx, user);
if (drop_privileges)
restrict_access_by_env(getenv("HOME"), TRUE);
if (dup2(fd, STDIN_FILENO) < 0)
i_fatal("dup2() failed: %m");
if (dup2(fd, STDOUT_FILENO) < 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");
}
/* close all listener sockets */
socket_count = master_service_get_socket_count(master_service);
for (i = 0; i < socket_count; i++) {
if (close(MASTER_LISTEN_FD_FIRST + i) < 0)
i_error("close(listener) failed: %m");
}
if (close(MASTER_STATUS_FD) < 0)
i_error("close(status) failed: %m");
execvp_const(exec_args[0], exec_args);
}
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, STDOUT_FILENO,
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, c;
if (getenv(MASTER_IS_PARENT_ENV) == NULL)
flags |= MASTER_SERVICE_FLAG_STANDALONE;
master_service = master_service_init("script-login", flags,
&argc, &argv, "+d");
while ((c = master_getopt(master_service)) > 0) {
switch (c) {
case 'd':
drop_privileges = TRUE;
break;
default:
return FATAL_DEFAULT;
}
}
argc -= optind;
argv += optind;
master_service_init_log(master_service, "script-login: ");
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[0] == NULL)
i_fatal("Missing script path");
exec_args = i_new(const char *, argc + 2);
for (i = 0; i < argc; i++)
exec_args[i] = argv[i];
exec_args[i] = PKG_LIBEXECDIR"/script-login";
exec_args[i+1] = NULL;
if (exec_args[0][0] != '/') {
exec_args[0] = t_strconcat(PKG_LIBEXECDIR"/",
exec_args[0], NULL);
}
master_service_run(master_service, client_connected);
}
master_service_deinit(&master_service);
return 0;
}