view src/master/mail-process.c @ 2000:c7c19f5071c3 HEAD

Write all logging through master process. Fixes problems with log rotation, chrooting, etc. Master process also allows max. 10 log messages per second per child process, it then begins throttling them (eventually making the child process start blocking on stderr).
author Timo Sirainen <tss@iki.fi>
date Mon, 10 May 2004 19:05:09 +0300
parents 50e24ce4f9c3
children f0925b2271e1
line wrap: on
line source

/* Copyright (C) 2002 Timo Sirainen */

#include "common.h"
#include "fd-close-on-exec.h"
#include "env-util.h"
#include "str.h"
#include "network.h"
#include "restrict-access.h"
#include "restrict-process-size.h"
#include "var-expand.h"
#include "mail-process.h"
#include "login-process.h"
#include "log.h"

#include <stdlib.h>
#include <unistd.h>
#include <grp.h>
#include <syslog.h>
#include <sys/stat.h>

static unsigned int mail_process_count = 0;

static int validate_uid_gid(struct settings *set, uid_t uid, gid_t gid,
			    const char *user)
{
	if (uid == 0) {
		i_error("Logins with UID 0 not permitted (user %s)", user);
		return FALSE;
	}

	if (set->login_uid == uid && master_uid != uid) {
		i_error("Can't log in using login processes UID %s (user %s) "
			"(see login_user in config file).",
			dec2str(uid), user);
	}

	if (uid < (uid_t)set->first_valid_uid ||
	    (set->last_valid_uid != 0 && uid > (uid_t)set->last_valid_uid)) {
		i_error("Logins with UID %s (user %s) not permitted "
			"(modify first_valid_uid in config file)",
			dec2str(uid), user);
		return FALSE;
	}

	if (gid < (gid_t)set->first_valid_gid ||
	    (set->last_valid_gid != 0 && gid > (gid_t)set->last_valid_gid)) {
		i_error("Logins for users with primary group ID %s (user %s) "
			"not permitted (see first_valid_gid in config file).",
			dec2str(gid), user);
		return FALSE;
	}

	return TRUE;
}

static int validate_chroot(struct settings *set, const char *dir)
{
	const char *const *chroot_dirs;

	if (*dir == '\0')
		return FALSE;

	if (set->valid_chroot_dirs == NULL)
		return FALSE;

	chroot_dirs = t_strsplit(set->valid_chroot_dirs, ":");
	while (*chroot_dirs != NULL) {
		if (**chroot_dirs != '\0' &&
		    strncmp(dir, *chroot_dirs, strlen(*chroot_dirs)) == 0)
			return TRUE;
		chroot_dirs++;
	}

	return FALSE;
}

static const char *expand_mail_env(const char *env, const char *user,
				   const char *home)
{
	string_t *str;
	const char *p;

	str = t_str_new(256);

	/* it's either type:data or just data */
	p = strchr(env, ':');
	if (p != NULL) {
		while (env != p) {
			str_append_c(str, *env);
			env++;
		}

		str_append_c(str, *env++);
	}

	if (env[0] == '~' && env[1] == '/') {
		/* expand home */
		str_append(str, home);
		env++;
	}

	/* expand %vars */
        var_expand(str, env, user, home);
	return str_c(str);
}

static void env_put_namespace(struct namespace_settings *ns,
			      const char *default_location,
			      const char *user, const char *home)
{
	const char *location;
	unsigned int i;
	string_t *str;

	if (default_location == NULL)
		default_location = "";

	for (i = 1; ns != NULL; i++, ns = ns->next) {
		t_push();

		location = ns->location != NULL ? ns->location :
			default_location;
		location = expand_mail_env(location, user, home);
		env_put(t_strdup_printf("NAMESPACE_%u=%s", i, location));

		if (ns->separator != NULL) {
			env_put(t_strdup_printf("NAMESPACE_%u_SEP=%s",
						i, ns->separator));
		}
		if (ns->type != NULL) {
			env_put(t_strdup_printf("NAMESPACE_%u_TYPE=%s",
						i, ns->type));
		}
		if (ns->prefix != NULL) {
			/* expand variables, eg. ~%u/ can be useful */
			str = t_str_new(256);
			str_printfa(str, "NAMESPACE_%u_PREFIX=", i);
			var_expand(str, ns->prefix, user, home);
			env_put(str_c(str));
		}
		if (ns->inbox)
			env_put(t_strdup_printf("NAMESPACE_%u_INBOX=1", i));
		if (ns->hidden)
			env_put(t_strdup_printf("NAMESPACE_%u_HIDDEN=1", i));
		t_pop();
	}
}

int create_mail_process(struct login_group *group, int socket,
			struct ip_addr *ip,
			struct auth_master_reply *reply, const char *data)
{
	const char *argv[4];
	struct settings *set = group->set;
	const char *addr, *mail, *user, *chroot_dir, *home_dir, *full_home_dir;
	const char *executable, *p, *prefix;
	char title[1024];
	pid_t pid;
	int i, err, ret, log_fd;

	// FIXME: per-group
	if (mail_process_count == set->max_mail_processes) {
		i_error("Maximum number of mail processes exceeded");
		return FALSE;
	}

	if (!validate_uid_gid(set, reply->uid, reply->gid,
			      data + reply->virtual_user_idx))
		return FALSE;

	home_dir = data + reply->home_idx;
	chroot_dir = data + reply->chroot_idx;

	if (*chroot_dir == '\0' && set->mail_chroot != NULL)
		chroot_dir = set->mail_chroot;

	if (*chroot_dir != '\0' && !validate_chroot(set, chroot_dir)) {
		i_error("Invalid chroot directory: %s", chroot_dir);
		return FALSE;
	}

	prefix = t_strdup_printf("%s(%s): ", process_names[group->process_type],
				 data + reply->virtual_user_idx);
	log_fd = log_create_pipe(prefix);

	pid = fork();
	if (pid < 0) {
		i_error("fork() failed: %m");
		(void)close(log_fd);
		return FALSE;
	}

	if (pid != 0) {
		/* master */
		mail_process_count++;
		PID_ADD_PROCESS_TYPE(pid, group->process_type);
		(void)close(log_fd);
		return TRUE;
	}

	child_process_init_env();

	/* move the client socket into stdin and stdout fds */
	fd_close_on_exec(socket, FALSE);
	if (dup2(socket, 0) < 0)
		i_fatal("mail: dup2(stdin) failed: %m");
	if (dup2(socket, 1) < 0)
		i_fatal("mail: dup2(stdout) failed: %m");
	if (dup2(log_fd, 2) < 0)
		i_fatal("mail: dup2(stderr) failed: %m");

	if (close(socket) < 0)
		i_error("mail: close(mail client) failed: %m");

	/* setup environment - set the most important environment first
	   (paranoia about filling up environment without noticing) */
	restrict_access_set_env(data + reply->system_user_idx,
				reply->uid, reply->gid, chroot_dir,
				set->first_valid_gid, set->last_valid_gid);

	restrict_process_size(group->set->mail_process_size, (unsigned int)-1);

	if (*home_dir == '\0')
		ret = -1;
	else {
		full_home_dir = *chroot_dir == '\0' ? home_dir :
			t_strconcat(chroot_dir, "/", home_dir, NULL);
		/* NOTE: if home directory is NFS-mounted, we might not
		   have access to it as root. Change the effective UID
		   temporarily to make it work. */
		if (reply->uid != master_uid && seteuid(reply->uid) < 0)
			i_fatal("seteuid(%s) failed: %m", dec2str(reply->uid));
		ret = chdir(full_home_dir);
		if (reply->uid != master_uid && seteuid(master_uid) < 0)
			i_fatal("seteuid(%s) failed: %m", dec2str(master_uid));

		/* If user's home directory doesn't exist and we're not
		   trying to chroot anywhere, fallback to /tmp as the mails
		   could be stored elsewhere. */
		if (ret < 0 && (errno != ENOENT || *chroot_dir != '\0')) {
			i_fatal("chdir(%s) failed with uid %s: %m",
				full_home_dir, dec2str(reply->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");
	}

	env_put("LOGGED_IN=1");
	env_put(t_strconcat("HOME=", home_dir, NULL));
	env_put(t_strconcat("MAIL_CACHE_FIELDS=",
			    set->mail_cache_fields, NULL));
	env_put(t_strconcat("MAIL_NEVER_CACHE_FIELDS=",
			    set->mail_never_cache_fields, NULL));
	env_put(t_strdup_printf("MAILBOX_CHECK_INTERVAL=%u",
				set->mailbox_check_interval));
	env_put(t_strdup_printf("MAILBOX_IDLE_CHECK_INTERVAL=%u",
				set->mailbox_idle_check_interval));
	env_put(t_strconcat("CLIENT_WORKAROUNDS=",
			    set->client_workarounds, NULL));
	env_put(t_strdup_printf("MAIL_MAX_KEYWORD_LENGTH=%u",
				set->mail_max_keyword_length));
	env_put(t_strdup_printf("IMAP_MAX_LINE_LENGTH=%u",
				set->imap_max_line_length));
	env_put(t_strconcat("IMAP_CAPABILITY=",
			    set->imap_capability, NULL));

	if (set->mail_save_crlf)
		env_put("MAIL_SAVE_CRLF=1");
	if (set->mail_read_mmaped)
		env_put("MAIL_READ_MMAPED=1");
	if (set->mmap_disable)
		env_put("MMAP_DISABLE=1");
	if (set->mmap_no_write)
		env_put("MMAP_NO_WRITE=1");
	if (set->fcntl_locks_disable)
		env_put("FCNTL_LOCKS_DISABLE=1");
	if (set->maildir_copy_with_hardlinks)
		env_put("MAILDIR_COPY_WITH_HARDLINKS=1");
	if (set->maildir_check_content_changes)
		env_put("MAILDIR_CHECK_CONTENT_CHANGES=1");
	if (set->mail_full_filesystem_access)
		env_put("FULL_FILESYSTEM_ACCESS=1");
	(void)umask(set->umask);

	env_put(t_strconcat("MBOX_LOCKS=", set->mbox_locks, NULL));
	env_put(t_strdup_printf("MBOX_LOCK_TIMEOUT=%u",
				set->mbox_lock_timeout));
	env_put(t_strdup_printf("MBOX_DOTLOCK_CHANGE_TIMEOUT=%u",
				set->mbox_dotlock_change_timeout));
	if (set->mbox_read_dotlock)
		env_put("MBOX_READ_DOTLOCK=1");

	if (group->set->mail_use_modules &&
	    group->set->mail_modules != NULL &&
	    *group->set->mail_modules != '\0') {
		env_put(t_strconcat("MODULE_DIR=",
				    group->set->mail_modules, NULL));
	}

	/* user given environment - may be malicious. virtual_user comes from
	   auth process, but don't trust that too much either. Some auth
	   mechanism might allow leaving extra data there. */
	mail = data + reply->mail_idx;
	user = data + reply->virtual_user_idx;
	if (*mail == '\0' && set->default_mail_env != NULL)
		mail = expand_mail_env(set->default_mail_env, user, home_dir);

	if (set->server->namespaces != NULL) {
		env_put_namespace(set->server->namespaces,
				  mail, user, home_dir);
	}

	env_put(t_strconcat("MAIL=", mail, NULL));
	env_put(t_strconcat("USER=", data + reply->virtual_user_idx, NULL));

	addr = net_ip2addr(ip);
	env_put(t_strconcat("IP=", addr, NULL));

	if (!set->verbose_proctitle)
		title[0] = '\0';
	else {
		if (addr == NULL)
			addr = "??";

		i_snprintf(title, sizeof(title), "[%s %s]",
			   data + reply->virtual_user_idx, addr);
	}

	/* make sure we don't leak syslog fd, but do it last so that
	   any errors above will be logged */
	closelog();

	if (set->mail_drop_priv_before_exec)
		restrict_access_by_env(TRUE);

	/* very simple argument splitting. */
	i = 0;
	argv[i++] = executable = t_strcut(group->set->mail_executable, ' ');
	argv[i] = strchr(group->set->mail_executable, ' ');
	if (argv[i] != NULL) {
		argv[i]++;
		i++;
	}
	if (title[0] != '\0')
		argv[i++] = title;
	argv[i] = NULL;

	/* hide the path, it's ugly */
	p = strrchr(argv[0], '/');
	if (p != NULL) argv[0] = p+1;

	execv(executable, (char **) argv);
	err = errno;

	for (i = 0; i < 3; i++)
		(void)close(i);

	errno = err;
	i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m",
		       group->set->mail_executable);

	/* not reached */
	return FALSE;
}

void mail_process_destroyed(pid_t pid __attr_unused__)
{
	mail_process_count--;
}