script-login.c revision 02c335c23bf5fa225a467c19f2c063fb0dc7b8c3
5f5870385cff47efd2f58e7892f251cf13761528Timo Sirainen/* Copyright (c) 2009-2016 Dovecot authors, see the included COPYING file */
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen
08d6658a4e2ec8104cd1307f6baa75fdb07a24f8Mark Washenberger#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"
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#include "mail-storage-service.h"
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#include "master-interface.h"
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#include "master-service.h"
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#include "master-service-settings.h"
91e2dc36b9c0c91f0af716be81dc2aa6cbbed6c2Timo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#include <unistd.h>
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#define SCRIPT_LOGIN_PROTOCOL_VERSION_MAJOR 1
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen#define SCRIPT_LOGIN_READ_TIMEOUT_SECS 10
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen#define ENV_USERDB_KEYS "USERDB_KEYS"
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen#define SCRIPT_COMM_FD 3
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainenstatic const char **exec_args;
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainenstatic bool drop_to_userdb_privileges = FALSE;
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainenstatic void client_connected(struct master_service_connection *conn)
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen{
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen enum mail_storage_service_flags flags =
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen MAIL_STORAGE_SERVICE_FLAG_NO_PLUGINS;
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen string_t *instr, *keys;
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen const char **args, *key, *value, *error, *version_line, *data_line;
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen struct mail_storage_service_ctx *service_ctx;
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen struct mail_storage_service_input input;
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen struct mail_storage_service_user *user;
0f39a57760d93cddbce3ca43096d78e0fe2f42fdTimo Sirainen char buf[1024];
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen unsigned int i, socket_count;
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen int fd = -1;
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen ssize_t ret;
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen alarm(SCRIPT_LOGIN_READ_TIMEOUT_SECS);
3313a51ef9b245248d672c20f930c52a577a42f7Timo Sirainen
b4205cfdf6eb4ca8ea7d01fbdcf77d9c9ad6ae72Timo Sirainen net_set_nonblock(conn->fd, FALSE);
88e9835c4d8973c62cd4db1ec7324ff46dd3ff15Timo Sirainen instr = t_str_new(1024);
58bc77731bb25e900498a28409337e747f622722Timo Sirainen ret = fd_read(conn->fd, buf, sizeof(buf), &fd);
c2f24d55319fad0b6c03425f402f0cb0cb1a318bTimo Sirainen while (ret > 0) {
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen str_append_n(instr, buf, ret);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (buf[ret-1] == '\n' &&
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen strchr(str_c(instr), '\n')[1] != '\0') {
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen str_truncate(instr, str_len(instr)-1);
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen break;
e9503210d3521a6833ed62dc332fc42ffb0e7a13Timo Sirainen }
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen ret = read(conn->fd, buf, sizeof(buf));
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen }
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen version_line = str_c(instr);
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen data_line = strchr(version_line, '\n');
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen if (data_line != NULL)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen version_line = t_strdup_until(version_line, data_line++);
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen else
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen version_line = NULL;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (ret > 0 || version_line != NULL) {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (version_line == NULL ||
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen !version_string_verify(version_line, "script-login",
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen SCRIPT_LOGIN_PROTOCOL_VERSION_MAJOR)) {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen i_fatal("Client not compatible with this binary "
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen "(connecting to wrong socket?)");
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen }
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen }
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
1aad8ad0590bee2d09d5fdb5413af72e2a8e156aTimo Sirainen if (ret <= 0) {
1aad8ad0590bee2d09d5fdb5413af72e2a8e156aTimo Sirainen if (ret < 0)
1aad8ad0590bee2d09d5fdb5413af72e2a8e156aTimo Sirainen i_fatal("read() failed: %m");
91e2dc36b9c0c91f0af716be81dc2aa6cbbed6c2Timo Sirainen else
91e2dc36b9c0c91f0af716be81dc2aa6cbbed6c2Timo Sirainen i_fatal("read() failed: disconnected");
e9371f899a3d4207a0ffd3923ea5ec7250cf5e75Timo Sirainen }
1aad8ad0590bee2d09d5fdb5413af72e2a8e156aTimo Sirainen if (fd == -1)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen i_fatal("client fd not received");
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen alarm(0);
e9371f899a3d4207a0ffd3923ea5ec7250cf5e75Timo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen /* put everything to environment */
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen env_clean();
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen keys = t_str_new(256);
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen args = t_strsplit_tab(data_line);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen if (str_array_length(args) < 3)
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen i_fatal("Missing input fields");
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen i = 0;
e9371f899a3d4207a0ffd3923ea5ec7250cf5e75Timo Sirainen memset(&input, 0, sizeof(input));
e9371f899a3d4207a0ffd3923ea5ec7250cf5e75Timo Sirainen input.module = "mail"; /* need to get mail_uid, mail_gid */
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen input.service = "script-login";
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen (void)net_addr2ip(args[i++], &input.local_ip);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen (void)net_addr2ip(args[i++], &input.remote_ip);
e9371f899a3d4207a0ffd3923ea5ec7250cf5e75Timo Sirainen input.username = args[i++];
b0e9375a1ff97c9c7d40655922af5ccc73ecaa76Timo Sirainen input.userdb_fields = args + i;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen env_put(t_strconcat("LOCAL_IP=", net_ip2addr(&input.local_ip), NULL));
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen env_put(t_strconcat("IP=", net_ip2addr(&input.remote_ip), NULL));
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen env_put(t_strconcat("USER=", input.username, NULL));
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen for (; args[i] != NULL; i++) {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen args[i] = str_tabunescape(t_strdup_noconst(args[i]));
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen value = strchr(args[i], '=');
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen if (value != NULL) {
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen key = t_str_ucase(t_strdup_until(args[i], value));
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen env_put(t_strconcat(key, value, NULL));
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen str_printfa(keys, "%s ", key);
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen }
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen }
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen env_put(t_strconcat(ENV_USERDB_KEYS"=", str_c(keys), NULL));
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen master_service_init_log(master_service,
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen t_strdup_printf("script-login(%s): ", input.username));
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
91e2dc36b9c0c91f0af716be81dc2aa6cbbed6c2Timo Sirainen if (drop_to_userdb_privileges) {
e9371f899a3d4207a0ffd3923ea5ec7250cf5e75Timo Sirainen service_ctx = mail_storage_service_init(master_service, NULL, flags);
91e2dc36b9c0c91f0af716be81dc2aa6cbbed6c2Timo Sirainen if (mail_storage_service_lookup(service_ctx, &input, &user, &error) <= 0)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen i_fatal("%s", error);
2e29e4797a48d78d669821722bdb54fd0a1d3b94Timo Sirainen mail_storage_service_restrict_setenv(service_ctx, user);
/* we can't exec anything in a chroot */
env_remove("RESTRICT_CHROOT");
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 (close(fd) < 0)
i_fatal("close() failed: %m");
if (conn->fd != SCRIPT_COMM_FD) {
if (dup2(conn->fd, SCRIPT_COMM_FD) < 0)
i_fatal("dup2() failed: %m");
if (close(conn->fd) < 0)
i_fatal("close() 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_append_tabescaped(reply,
t_strconcat(t_str_lcase(*keys), "=",
value, NULL));
}
}
str_append_c(reply, '\n');
/* finish by sending the fd to the mail process */
ret = fd_send(SCRIPT_COMM_FD, STDOUT_FILENO,
str_data(reply), str_len(reply));
if (ret == (ssize_t)str_len(reply)) {
/* success */
} else {
if (ret < 0)
i_error("fd_send() failed: %m");
else
i_error("fd_send() sent partial output");
/* exit with 0 even though we failed. non-0 exit just makes
master log an unnecessary error. */
}
}
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_to_userdb_privileges = TRUE;
break;
default:
return FATAL_DEFAULT;
}
}
argc -= optind;
argv += optind;
master_service_init_log(master_service, "script-login: ");
if (!drop_to_userdb_privileges &&
(flags & MASTER_SERVICE_FLAG_STANDALONE) == 0) {
/* drop to privileges defined by service settings */
restrict_access_by_env(NULL, FALSE);
}
master_service_init_finish(master_service);
master_service_set_service_count(master_service, 1);
if ((flags & MASTER_SERVICE_FLAG_STANDALONE) != 0) {
/* The last post-login script is calling us to finish login */
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;
}