mail-storage-service.c revision 48ada47cce07fb7195a3437224c7c25f542326b0
a8c5a86d183db25a57bf193c06b41e092ec2e151Timo Sirainen/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "lib.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "ioloop.h"
08d6658a4e2ec8104cd1307f6baa75fdb07a24f8Mark Washenberger#include "array.h"
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen#include "hostpid.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "module-dir.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "restrict-access.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "eacces-error.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "str.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "var-expand.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include "dict.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "settings-parser.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "auth-master.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "master-service-private.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "master-service-settings.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "mail-user.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "mail-namespace.h"
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#include "mail-storage.h"
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen#include "mail-storage-service.h"
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include <stdlib.h>
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include <sys/stat.h>
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include <pwd.h>
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen#include <grp.h>
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen#ifdef HAVE_SYS_TIME_H
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen# include <sys/time.h>
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen#endif
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen#ifdef HAVE_SYS_RESOURCE_H
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen# include <sys/resource.h>
356303df200c991580bd24041996a070ad08c05eTimo Sirainen#endif
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen/* If time moves backwards more than this, kill ourself instead of sleeping. */
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen#define MAX_TIME_BACKWARDS_SLEEP 5
473080c7c0d25ddfdf77e7dfa0ba8f73c6c669d5Timo Sirainen#define MAX_NOWARN_FORWARD_SECS 10
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainenstruct mail_storage_service_ctx {
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen struct master_service *service;
00bde9ae9eab9e720462bf6ec9a4dd85e88c3bbfTimo Sirainen struct auth_master_connection *conn;
00bde9ae9eab9e720462bf6ec9a4dd85e88c3bbfTimo Sirainen struct auth_master_user_list_ctx *auth_list;
00bde9ae9eab9e720462bf6ec9a4dd85e88c3bbfTimo Sirainen const struct setting_parser_info **set_roots;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen enum mail_storage_service_flags flags;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen unsigned int debug:1;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen};
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainenstruct mail_storage_service_user {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen pool_t pool;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen struct mail_storage_service_input input;
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen const char *system_groups_user;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen const struct mail_user_settings *user_set;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen const struct setting_parser_info *user_info;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen struct setting_parser_context *set_parser;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen};
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainenstatic struct module *modules = NULL;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainenstatic void set_keyval(struct setting_parser_context *set_parser,
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen const char *key, const char *value)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen{
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen const char *str;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen str = t_strconcat(key, "=", value, NULL);
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen if (settings_parse_line(set_parser, str) < 0) {
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen i_fatal("Invalid userdb input '%s': %s", str,
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen settings_parser_get_error(set_parser));
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen }
3b94ff5951db4d4eddb7a80ed4e3f61207202635Timo Sirainen}
66d2db642fe24d555d113ba463e446b038d476efTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainenstatic bool validate_chroot(const struct mail_user_settings *user_set,
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen const char *dir)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen{
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen const char *const *chroot_dirs;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (*dir == '\0')
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen return FALSE;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (*user_set->valid_chroot_dirs == '\0')
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen return FALSE;
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen
22535a9e685e29214082878e37a267157044618eTimo Sirainen chroot_dirs = t_strsplit(user_set->valid_chroot_dirs, ":");
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen while (*chroot_dirs != NULL) {
22535a9e685e29214082878e37a267157044618eTimo Sirainen if (**chroot_dirs != '\0' &&
22535a9e685e29214082878e37a267157044618eTimo Sirainen strncmp(dir, *chroot_dirs, strlen(*chroot_dirs)) == 0)
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen return TRUE;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen chroot_dirs++;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen }
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen return FALSE;
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen}
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainenstatic int
22535a9e685e29214082878e37a267157044618eTimo Sirainenuser_reply_handle(struct mail_storage_service_user *user,
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen const struct auth_user_reply *reply,
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen const char **error_r)
e15f1d736c225c7ce6f3d08a37c1b2ae66b57c50Timo Sirainen{
e15f1d736c225c7ce6f3d08a37c1b2ae66b57c50Timo Sirainen struct setting_parser_context *set_parser = user->set_parser;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen const char *const *str, *p, *line, *key;
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen unsigned int i, count;
e15f1d736c225c7ce6f3d08a37c1b2ae66b57c50Timo Sirainen int ret = 0;
e15f1d736c225c7ce6f3d08a37c1b2ae66b57c50Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (reply->uid != (uid_t)-1) {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (reply->uid == 0) {
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen *error_r = "userdb returned 0 as uid";
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen return -1;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen }
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen set_keyval(set_parser, "mail_uid", dec2str(reply->uid));
6135260095e1704ed6edff9d00bdfc043c11429cTimo Sirainen }
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen if (reply->gid != (uid_t)-1)
49e513d090753ccbf95560b2f3a21f081a5b6c51Timo Sirainen set_keyval(set_parser, "mail_gid", dec2str(reply->gid));
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen
22535a9e685e29214082878e37a267157044618eTimo Sirainen if (reply->home != NULL)
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen set_keyval(set_parser, "mail_home", reply->home);
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (reply->chroot != NULL) {
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (!validate_chroot(user->user_set, reply->chroot)) {
22535a9e685e29214082878e37a267157044618eTimo Sirainen *error_r = t_strdup_printf(
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen "userdb returned invalid chroot directory: %s "
22535a9e685e29214082878e37a267157044618eTimo Sirainen "(see valid_chroot_dirs setting)",
22535a9e685e29214082878e37a267157044618eTimo Sirainen reply->chroot);
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen return -1;
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen }
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen set_keyval(set_parser, "mail_chroot", reply->chroot);
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen }
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen str = array_get(&reply->extra_fields, &count);
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen for (i = 0; i < count && ret == 0; i++) {
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen line = str[i];
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen if (strncmp(line, "system_groups_user=", 19) == 0) {
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen user->system_groups_user =
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen p_strdup(user->pool, line + 19);
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen } else if (strncmp(line, "nice=", 5) == 0) {
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen#ifdef HAVE_SETPRIORITY
6fabfb7bbfd88d0c1de66981e52850f26067623bTimo Sirainen int n = atoi(line + 5);
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen if (n != 0) {
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen if (setpriority(PRIO_PROCESS, 0, n) < 0)
849969f639a00eab26791db3cb1b66430420c0cdTimo Sirainen i_error("setpriority(%d) failed: %m", n);
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen }
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen#endif
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen } else T_BEGIN {
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen if (strncmp(line, "mail=", 5) == 0) {
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen line = t_strconcat("mail_location=",
c64e714193097f841691dcfa2902c270cb47bff8Timo Sirainen line + 5, NULL);
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen } else if ((p = strchr(str[i], '=')) == NULL)
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen line = t_strconcat(str[i], "=yes", NULL);
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen else
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen line = str[i];
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen key = t_strcut(line, '=');
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen if (!settings_parse_is_valid_key(set_parser, key)) {
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen /* assume it's a plugin setting */
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen line = t_strconcat("plugin/", line, NULL);
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen }
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen ret = settings_parse_line(set_parser, line);
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen } T_END;
c64e714193097f841691dcfa2902c270cb47bff8Timo Sirainen }
c64e714193097f841691dcfa2902c270cb47bff8Timo Sirainen
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen if (ret < 0) {
dd93aba1901a457346990f49c54a738947dc7128Timo Sirainen *error_r = t_strdup_printf("Invalid userdb input '%s': %s",
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen str[i], settings_parser_get_error(set_parser));
806cb455553b71934314da98f1b4a694a3aa152eTimo Sirainen }
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen return ret;
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen}
b42f37ae6f65ed986315b6885568d32115e589b1Timo Sirainen
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainenstatic int
1f1e81aab38d833d1c9cdc244c91fd762e0080d4Timo Sirainenservice_auth_userdb_lookup(struct mail_storage_service_ctx *ctx,
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen const struct mail_storage_service_input *input,
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen pool_t pool, const char **user,
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen const char *const **fields_r,
c5383a0ed56a188a7d5efaaa4c6f8243af432d65Timo Sirainen const char **error_r)
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen{
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen struct auth_user_info info;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen const char *new_username;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen int ret;
43d3ea2780b5f8557ede7b4c039e8f56cb8d357dTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen memset(&info, 0, sizeof(info));
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen info.service = ctx->service->name;
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen info.local_ip = input->local_ip;
7bafda1813454621e03615e83d55bccfa7cc56bdTimo Sirainen info.remote_ip = input->remote_ip;
d5abbb932a0a598f002da39a8b3326643b1b5efcTimo Sirainen
25757faf029c369a8318349dafe952e2358df1d8Timo Sirainen ret = auth_master_user_lookup(ctx->conn, *user, &info, pool,
&new_username, fields_r);
if (ret > 0) {
if (strcmp(*user, new_username) != 0) {
if (ctx->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;
}
static bool parse_uid(const char *str, uid_t *uid_r)
{
struct passwd *pw;
char *p;
if (*str >= '0' && *str <= '9') {
*uid_r = (uid_t)strtoul(str, &p, 10);
if (*p == '\0')
return TRUE;
}
pw = getpwnam(str);
if (pw == NULL)
return FALSE;
*uid_r = pw->pw_uid;
return TRUE;
}
static bool parse_gid(const char *str, gid_t *gid_r)
{
struct group *gr;
char *p;
if (*str >= '0' && *str <= '9') {
*gid_r = (gid_t)strtoul(str, &p, 10);
if (*p == '\0')
return TRUE;
}
gr = getgrnam(str);
if (gr == NULL)
return FALSE;
*gid_r = gr->gr_gid;
return TRUE;
}
static void
service_drop_privileges(const struct mail_user_settings *set,
const char *system_groups_user,
const char *home, const char *chroot,
bool disallow_root, bool keep_setuid_root)
{
struct restrict_access_settings rset;
uid_t current_euid, setuid_uid = 0;
current_euid = geteuid();
restrict_access_init(&rset);
if (*set->mail_uid != '\0') {
if (!parse_uid(set->mail_uid, &rset.uid))
i_fatal("Unknown mail_uid user: %s", set->mail_uid);
if (rset.uid < (uid_t)set->first_valid_uid ||
(set->last_valid_uid != 0 &&
rset.uid > (uid_t)set->last_valid_uid)) {
i_fatal("Mail access for users with UID %s "
"not permitted (see first_valid_uid in config file).",
dec2str(rset.uid));
}
}
if (*set->mail_gid != '\0') {
if (!parse_gid(set->mail_gid, &rset.gid))
i_fatal("Unknown mail_gid group: %s", set->mail_gid);
if (rset.gid < (gid_t)set->first_valid_gid ||
(set->last_valid_gid != 0 &&
rset.gid > (gid_t)set->last_valid_gid)) {
i_fatal("Mail access for users with GID %s "
"not permitted (see first_valid_gid in config file).",
dec2str(rset.gid));
}
}
if (*set->mail_privileged_group != '\0') {
if (!parse_gid(set->mail_privileged_group, &rset.privileged_gid)) {
i_fatal("Unknown mail_privileged_group: %s",
set->mail_gid);
}
}
if (*set->mail_access_groups != '\0')
rset.extra_groups = set->mail_access_groups;
rset.first_valid_gid = set->first_valid_gid;
rset.last_valid_gid = set->last_valid_gid;
/* we can't chroot if we want to switch between users. there's not
much point either (from security point of view) */
rset.chroot_dir = *chroot == '\0' || keep_setuid_root ? NULL : chroot;
rset.system_groups_user = system_groups_user;
if (disallow_root &&
(rset.uid == 0 || (rset.uid == (uid_t)-1 && current_euid == 0)))
i_fatal("Mail access not allowed for root");
if (keep_setuid_root) {
if (current_euid != rset.uid) {
if (current_euid != 0) {
/* we're changing the UID,
switch back to root first */
if (seteuid(0) < 0)
i_fatal("seteuid(0) failed: %m");
}
setuid_uid = rset.uid;
}
rset.uid = (uid_t)-1;
disallow_root = FALSE;
}
restrict_access(&rset, *home == '\0' ? NULL : home, disallow_root);
if (setuid_uid != 0) {
if (seteuid(setuid_uid) < 0)
i_fatal("seteuid(%s) failed: %m", dec2str(setuid_uid));
}
}
static int
mail_storage_service_init_post(struct mail_storage_service_ctx *ctx,
struct mail_storage_service_user *user,
const char *home, struct mail_user **mail_user_r,
const char **error_r)
{
const struct mail_storage_settings *mail_set;
struct mail_user *mail_user;
mail_user = mail_user_alloc(user->input.username, user->user_info,
user->user_set);
mail_user_set_home(mail_user, *home == '\0' ? NULL : home);
mail_user_set_vars(mail_user, geteuid(), ctx->service->name,
&user->input.local_ip, &user->input.remote_ip);
mail_set = mail_user_set_get_storage_set(mail_user);
if (mail_set->mail_debug) {
i_debug("Effective uid=%s, gid=%s, home=%s",
dec2str(geteuid()), dec2str(getegid()), home);
}
if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP) != 0 &&
(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS) == 0) {
/* we don't want to write core files to any users' home
directories since they could contain information about other
users' mails as well. so do no chdiring to home. */
} else if (*home != '\0' &&
(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_CHDIR) == 0) {
/* If possible chdir to home directory, so that core file
could be written in case we crash. */
if (chdir(home) < 0) {
if (errno == EACCES) {
i_error("%s", eacces_error_get("chdir",
t_strconcat(home, "/", NULL)));
} if (errno != ENOENT)
i_error("chdir(%s) failed: %m", home);
else if (mail_set->mail_debug)
i_debug("Home dir not found: %s", home);
}
}
if (mail_user_init(mail_user, error_r) < 0) {
mail_user_unref(&mail_user);
return -1;
}
if (mail_namespaces_init(mail_user, error_r) < 0) {
mail_user_unref(&mail_user);
return -1;
}
*mail_user_r = mail_user;
return 0;
}
static const struct var_expand_table *
get_var_expand_table(struct master_service *service,
struct mail_storage_service_input *input)
{
static struct var_expand_table static_tab[] = {
{ 'u', NULL, "user" },
{ 'n', NULL, "username" },
{ 'd', NULL, "domain" },
{ 's', NULL, "service" },
{ 'l', NULL, "lip" },
{ 'r', NULL, "rip" },
{ 'p', NULL, "pid" },
{ 'i', NULL, "uid" },
{ '\0', NULL, NULL }
};
struct var_expand_table *tab;
tab = t_malloc(sizeof(static_tab));
memcpy(tab, static_tab, sizeof(static_tab));
tab[0].value = input->username;
tab[1].value = t_strcut(input->username, '@');
tab[2].value = strchr(input->username, '@');
if (tab[2].value != NULL) tab[2].value++;
tab[3].value = service->name;
tab[4].value = net_ip2addr(&input->local_ip);
tab[5].value = net_ip2addr(&input->remote_ip);
tab[6].value = my_pid;
tab[7].value = dec2str(geteuid());
return tab;
}
static const char *
user_expand_varstr(struct master_service *service,
struct mail_storage_service_input *input, const char *str)
{
string_t *ret;
if (*str == SETTING_STRVAR_EXPANDED[0])
return str + 1;
i_assert(*str == SETTING_STRVAR_UNEXPANDED[0]);
ret = t_str_new(256);
var_expand(ret, str + 1, get_var_expand_table(service, input));
return str_c(ret);
}
static void
mail_storage_service_init_log(struct master_service *service,
struct mail_storage_service_user *user)
{
const struct mail_user_settings *user_set;
void **sets;
sets = master_service_settings_get_others(service);
user_set = sets[0];
T_BEGIN {
string_t *str;
str = t_str_new(256);
var_expand(str, user->user_set->mail_log_prefix,
get_var_expand_table(service, &user->input));
master_service_init_log(service, str_c(str));
} T_END;
}
static void mail_storage_service_time_moved(time_t old_time, time_t new_time)
{
long diff = new_time - old_time;
if (diff > 0) {
if (diff > MAX_NOWARN_FORWARD_SECS)
i_warning("Time jumped forwards %ld seconds", diff);
return;
}
diff = -diff;
if (diff > MAX_TIME_BACKWARDS_SLEEP) {
i_fatal("Time just moved backwards by %ld seconds. "
"This might cause a lot of problems, "
"so I'll just kill myself now. "
"http://wiki.dovecot.org/TimeMovedBackwards", diff);
} else {
i_error("Time just moved backwards by %ld seconds. "
"I'll sleep now until we're back in present. "
"http://wiki.dovecot.org/TimeMovedBackwards", diff);
/* Sleep extra second to make sure usecs also grows. */
diff++;
while (diff > 0 && sleep(diff) != 0) {
/* don't use sleep()'s return value, because
it could get us to a long loop in case
interrupts just keep coming */
diff = old_time - time(NULL) + 1;
}
}
}
struct mail_storage_service_ctx *
mail_storage_service_init(struct master_service *service,
const struct setting_parser_info *set_roots[],
enum mail_storage_service_flags flags)
{
struct mail_storage_service_ctx *ctx;
unsigned int count;
(void)umask(0077);
io_loop_set_time_moved_callback(current_ioloop,
mail_storage_service_time_moved);
mail_storage_init();
mail_storage_register_all();
mailbox_list_register_all();
ctx = i_new(struct mail_storage_service_ctx, 1);
ctx->service = service;
ctx->flags = flags;
/* @UNSAFE */
if (set_roots == NULL)
count = 0;
else
for (count = 0; set_roots[count] != NULL; count++) ;
ctx->set_roots = i_new(const struct setting_parser_info *, count + 2);
ctx->set_roots[0] = &mail_user_setting_parser_info;
memcpy(ctx->set_roots + 1, set_roots, sizeof(*ctx->set_roots) * count);
/* do all the global initialization. delay initializing plugins until
we drop privileges the first time. */
if ((flags & MAIL_STORAGE_SERVICE_NO_LOG_INIT) == 0) {
master_service_init_log(service,
t_strconcat(service->name, ": ", NULL));
}
dict_drivers_register_builtin();
return ctx;
}
struct auth_master_connection *
mail_storage_service_get_auth_conn(struct mail_storage_service_ctx *ctx)
{
i_assert(ctx->conn != NULL);
return ctx->conn;
}
static void
settings_parser_update_children_parent(struct setting_parser_info *parent,
pool_t pool)
{
struct setting_define *new_defs;
struct setting_parser_info *new_info;
unsigned int i, count;
for (count = 0; parent->defines[count].key != NULL; count++) ;
new_defs = p_new(pool, struct setting_define, count + 1);
memcpy(new_defs, parent->defines, sizeof(*new_defs) * count);
parent->defines = new_defs;
for (i = 0; i < count; i++) {
if (new_defs[i].list_info == NULL ||
new_defs[i].list_info->parent == NULL)
continue;
new_info = p_new(pool, struct setting_parser_info, 1);
*new_info = *new_defs[i].list_info;
new_info->parent = parent;
new_defs[i].list_info = new_info;
}
}
static struct setting_parser_info *
dyn_parsers_update_parent(pool_t pool,
struct master_service_settings_input *input)
{
const struct setting_parser_info *old_parent, **new_roots;
struct setting_parser_info *new_parent, *new_info;
struct dynamic_settings_parser *new_dyn_parsers;
unsigned int i, count;
/* settings_parser_info_update() modifies the parent structure.
since we may be using the same structure later, we want it to be
in its original state, so we'll have to copy all structures. */
old_parent = input->dyn_parsers[0].info->parent;
new_parent = p_new(pool, struct setting_parser_info, 1);
*new_parent = *old_parent;
settings_parser_update_children_parent(new_parent, pool);
/* update root */
for (count = 0; input->roots[count] != NULL; count++) ;
new_roots = p_new(pool, const struct setting_parser_info *, count + 1);
for (i = 0; i < count; i++) {
if (input->roots[i] == old_parent)
new_roots[i] = new_parent;
else
new_roots[i] = input->roots[i];
}
input->roots = new_roots;
/* update parent in dyn_parsers */
for (count = 0; input->dyn_parsers[count].name != NULL; count++) ;
new_dyn_parsers = p_new(pool, struct dynamic_settings_parser, count + 1);
for (i = 0; i < count; i++) {
new_dyn_parsers[i] = input->dyn_parsers[i];
new_info = p_new(pool, struct setting_parser_info, 1);
*new_info = *input->dyn_parsers[i].info;
new_info->parent = new_parent;
new_dyn_parsers[i].info = new_info;
}
input->dyn_parsers = new_dyn_parsers;
return new_parent;
}
int mail_storage_service_read_settings(struct mail_storage_service_ctx *ctx,
const struct mail_storage_service_input *input,
pool_t pool,
const struct setting_parser_info **user_info_r,
const char **error_r)
{
struct master_service_settings_input set_input;
unsigned int i;
memset(&set_input, 0, sizeof(set_input));
set_input.roots = ctx->set_roots;
set_input.dyn_parsers = mail_storage_get_dynamic_parsers();
/* settings reader may exec doveconf, which is going to clear
environment, and if we're not doing a userdb lookup we want to
use $HOME */
set_input.preserve_home =
(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) == 0;
set_input.dyn_parsers_parent =
dyn_parsers_update_parent(pool, &set_input);
if (input != NULL) {
set_input.module = input->module;
set_input.service = input->service;
set_input.username = input->username;
set_input.local_ip = input->local_ip;
set_input.remote_ip = input->remote_ip;
}
if (master_service_settings_read(ctx->service, &set_input,
error_r) < 0) {
*error_r = t_strdup_printf("Error reading configuration: %s",
*error_r);
return -1;
}
for (i = 0; ctx->set_roots[i] != NULL; i++) {
if (ctx->set_roots[i] == &mail_user_setting_parser_info) {
*user_info_r = set_input.roots[i];
return 0;
}
}
i_unreached();
return -1;
}
static void
mail_storage_service_first_init(struct mail_storage_service_ctx *ctx,
const struct setting_parser_info *user_info,
const struct mail_user_settings *user_set)
{
const struct mail_storage_settings *mail_set;
i_assert(ctx->conn == NULL);
mail_set = mail_user_set_get_driver_settings(user_info, user_set,
MAIL_STORAGE_SET_DRIVER_NAME);
ctx->debug = mail_set->mail_debug;
ctx->conn = auth_master_init(user_set->auth_socket_path,
ctx->debug);
i_assert(mail_user_auth_master_conn == NULL);
mail_user_auth_master_conn = ctx->conn;
}
static void
mail_storage_service_load_modules(struct mail_storage_service_ctx *ctx,
const struct mail_user_settings *user_set)
{
const char *version;
if (*user_set->mail_plugins == '\0')
return;
version = master_service_get_version_string(ctx->service);
modules = module_dir_load_missing(modules, user_set->mail_plugin_dir,
user_set->mail_plugins, TRUE,
version);
}
int mail_storage_service_lookup(struct mail_storage_service_ctx *ctx,
const struct mail_storage_service_input *input,
struct mail_storage_service_user **user_r,
const char **error_r)
{
struct mail_storage_service_user *user;
const char *username = input->username;
const struct setting_parser_info *user_info;
const struct mail_user_settings *user_set;
const char *const *userdb_fields;
struct auth_user_reply reply;
void **sets;
pool_t user_pool, temp_pool;
int ret = 1;
user_pool = pool_alloconly_create("mail storage service user", 1024*4);
if (mail_storage_service_read_settings(ctx, input, user_pool,
&user_info, error_r) < 0) {
pool_unref(&user_pool);
return -1;
}
sets = settings_parser_get_list(ctx->service->set_parser);
user_set = sets[1];
if (ctx->conn == NULL)
mail_storage_service_first_init(ctx, user_info, user_set);
/* load global plugins */
mail_storage_service_load_modules(ctx, user_set);
temp_pool = pool_alloconly_create("userdb lookup", 1024);
if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0) {
ret = service_auth_userdb_lookup(ctx, input, temp_pool,
&username, &userdb_fields,
error_r);
if (ret <= 0) {
pool_unref(&temp_pool);
pool_unref(&user_pool);
return ret;
}
} else {
userdb_fields = input->userdb_fields;
}
user = p_new(user_pool, struct mail_storage_service_user, 1);
memset(user_r, 0, sizeof(user_r));
user->pool = user_pool;
user->input = *input;
user->input.userdb_fields = NULL;
user->input.username = p_strdup(user_pool, username);
user->user_info = user_info;
user->set_parser =
settings_parser_dup(ctx->service->set_parser, user_pool);
sets = settings_parser_get_list(user->set_parser);
user->user_set = sets[1];
if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) == 0) {
const char *home = getenv("HOME");
if (home != NULL)
set_keyval(user->set_parser, "mail_home", home);
}
if (userdb_fields != NULL) {
auth_user_fields_parse(userdb_fields, temp_pool, &reply);
if (user_reply_handle(user, &reply, error_r) < 0)
ret = -1;
}
pool_unref(&temp_pool);
/* load per-user plugins */
mail_storage_service_load_modules(ctx, user->user_set);
*user_r = user;
return ret;
}
int mail_storage_service_next(struct mail_storage_service_ctx *ctx,
struct mail_storage_service_user *user,
struct mail_user **mail_user_r,
const char **error_r)
{
const struct mail_user_settings *user_set = user->user_set;
const char *home, *chroot;
unsigned int len;
bool temp_priv_drop =
(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_TEMP_PRIV_DROP);
/* variable strings are expanded in mail_user_init(),
but we need the home and chroot sooner so do them separately here. */
home = user_expand_varstr(ctx->service, &user->input,
user_set->mail_home);
chroot = user_expand_varstr(ctx->service, &user->input,
user_set->mail_chroot);
if (*home != '/' && *home != '\0') {
i_error("user %s: Relative home directory paths not supported: "
"%s", user->input.username, home);
return -1;
}
if ((ctx->flags & MAIL_STORAGE_SERVICE_NO_LOG_INIT) == 0)
mail_storage_service_init_log(ctx->service, user);
if ((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_NO_RESTRICT_ACCESS) == 0) {
service_drop_privileges(user_set, user->system_groups_user,
home, chroot,
(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_DISALLOW_ROOT) != 0,
temp_priv_drop);
if (!temp_priv_drop ||
(ctx->flags & MAIL_STORAGE_SERVICE_FLAG_ENABLE_CORE_DUMPS) != 0)
restrict_access_allow_coredumps(TRUE);
}
/* privileges are dropped. initialize plugins that haven't been
initialized yet. */
module_dir_init(modules);
/* we couldn't do chrooting, so if chrooting was enabled fix
the home directory */
len = strlen(chroot);
if (len > 2 && strcmp(chroot + len - 2, "/.") == 0 &&
strncmp(home, chroot, len - 2) == 0) {
/* home dir already contains the chroot dir */
if (!temp_priv_drop) {
home += len - 2;
if (*home == '\0')
home = "/";
set_keyval(user->set_parser, "mail_home", home);
chroot = t_strndup(chroot, len - 2);
}
} else if (len > 0 && temp_priv_drop) {
set_keyval(user->set_parser, "mail_home",
t_strconcat(chroot, "/", home, NULL));
}
if (mail_storage_service_init_post(ctx, user, home,
mail_user_r, error_r) < 0)
return -1;
return 0;
}
int mail_storage_service_lookup_next(struct mail_storage_service_ctx *ctx,
const struct mail_storage_service_input *input,
struct mail_storage_service_user **user_r,
struct mail_user **mail_user_r,
const char **error_r)
{
struct mail_storage_service_user *user;
const char *error;
int ret;
ret = mail_storage_service_lookup(ctx, input, &user, &error);
if (ret <= 0) {
*error_r = t_strdup_printf("User lookup failed: %s", error);
return ret;
}
if (mail_storage_service_next(ctx, user, mail_user_r, &error) < 0) {
mail_storage_service_user_free(&user);
*error_r = t_strdup_printf("User init failed: %s", error);
return -1;
}
*user_r = user;
return 1;
}
void mail_storage_service_user_free(struct mail_storage_service_user **_user)
{
struct mail_storage_service_user *user = *_user;
*_user = NULL;
settings_parser_deinit(&user->set_parser);
pool_unref(&user->pool);
}
void mail_storage_service_init_settings(struct mail_storage_service_ctx *ctx,
const struct mail_storage_service_input *input)
{
const struct setting_parser_info *user_info;
const struct mail_user_settings *user_set;
const char *error;
void **sets;
pool_t temp_pool;
if (ctx->conn != NULL)
return;
temp_pool = pool_alloconly_create("service all settings", 4096);
if (mail_storage_service_read_settings(ctx, input, temp_pool,
&user_info, &error) < 0)
i_fatal("%s", error);
sets = settings_parser_get_list(ctx->service->set_parser);
user_set = sets[1];
mail_storage_service_first_init(ctx, user_info, user_set);
pool_unref(&temp_pool);
}
unsigned int
mail_storage_service_all_init(struct mail_storage_service_ctx *ctx)
{
if (ctx->auth_list != NULL)
(void)auth_master_user_list_deinit(&ctx->auth_list);
mail_storage_service_init_settings(ctx, NULL);
ctx->auth_list = auth_master_user_list_init(ctx->conn);
return auth_master_user_list_count(ctx->auth_list);
}
int mail_storage_service_all_next(struct mail_storage_service_ctx *ctx,
const char **username_r)
{
i_assert((ctx->flags & MAIL_STORAGE_SERVICE_FLAG_USERDB_LOOKUP) != 0);
*username_r = auth_master_user_list_next(ctx->auth_list);
if (*username_r != NULL)
return 1;
return auth_master_user_list_deinit(&ctx->auth_list);
}
void mail_storage_service_deinit(struct mail_storage_service_ctx **_ctx)
{
struct mail_storage_service_ctx *ctx = *_ctx;
*_ctx = NULL;
if (ctx->auth_list != NULL)
(void)auth_master_user_list_deinit(&ctx->auth_list);
if (ctx->conn != NULL) {
if (mail_user_auth_master_conn == ctx->conn)
mail_user_auth_master_conn = NULL;
auth_master_deinit(&ctx->conn);
}
i_free(ctx);
module_dir_unload(&modules);
mail_storage_deinit();
dict_drivers_unregister_builtin();
}
void **mail_storage_service_user_get_set(struct mail_storage_service_user *user)
{
return settings_parser_get_list(user->set_parser) + 1;
}
const struct mail_storage_service_input *
mail_storage_service_user_get_input(struct mail_storage_service_user *user)
{
return &user->input;
}
struct setting_parser_context *
mail_storage_service_user_get_settings_parser(struct mail_storage_service_user *user)
{
return user->set_parser;
}
void *mail_storage_service_get_settings(struct master_service *service)
{
void **sets, *set;
T_BEGIN {
sets = master_service_settings_get_others(service);
set = sets[1];
} T_END;
return set;
}