service-process.c revision c60178b2610a9b193ff72aa18448398ef72529a1
/* Copyright (c) 2005-2009 Dovecot authors, see the included COPYING file */
#include "common.h"
#include "array.h"
#include "aqueue.h"
#include "ioloop.h"
#include "istream.h"
#include "ostream.h"
#include "write-full.h"
#include "base64.h"
#include "hash.h"
#include "str.h"
#include "hostpid.h"
#include "env-util.h"
#include "fd-close-on-exec.h"
#include "restrict-access.h"
#include "restrict-process-size.h"
#include "eacces-error.h"
#include "master-service.h"
#include "master-service-settings.h"
#include "dup2-array.h"
#include "service.h"
#include "service-log.h"
#include "service-auth-server.h"
#include "service-auth-source.h"
#include "service-process-notify.h"
#include "service-process.h"
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
/* Timeout chdir() completely after this many seconds */
#define CHDIR_TIMEOUT 30
/* Give a warning about chdir() taking a while if it took longer than this
many seconds to finish. */
#define CHDIR_WARN_SECS 10
static void
bool give_anvil_fd)
{
struct service_listener *const *listeners;
should have been opened with fd_close_on_exec() so we don't have to
worry about them.
because the destination fd might be another one's source fd we have
to be careful not to overwrite anything. dup() the fd when needed */
case SERVICE_TYPE_LOG:
i_assert(n == 0);
n += socket_listener_count;
break;
case SERVICE_TYPE_ANVIL:
/* nonblocking anvil fd must be the first one. anvil treats it
as the master's fd */
MASTER_LISTEN_FD_FIRST + n++);
MASTER_LISTEN_FD_FIRST + n++);
socket_listener_count += 2;
break;
default:
break;
}
/* first add non-ssl listeners */
for (i = 0; i < count; i++) {
MASTER_LISTEN_FD_FIRST + n);
n++; socket_listener_count++;
}
}
/* then ssl-listeners */
ssl_socket_count = 0;
for (i = 0; i < count; i++) {
MASTER_LISTEN_FD_FIRST + n);
n++; socket_listener_count++;
}
}
if (!give_anvil_fd)
else {
}
case SERVICE_TYPE_AUTH_SOURCE:
case SERVICE_TYPE_AUTH_SERVER:
break;
default:
break;
}
if (std_fd != -1) {
env_put("LOGGED_IN=1");
}
/* set log file to stderr. dup2() here immediately so that
we can set up logging to it without causing any log messages
to be lost. */
env_put("LOG_SERVICE=1");
i_fatal("dup2(log fd) failed: %m");
} else {
}
/* make sure we don't leak syslog fd. try to do it as late as possible,
but also before dup2()s in case syslog fd is one of them. */
closelog();
if (dup2_array(&dups) < 0)
}
const char *user)
{
if (uid == 0) {
i_fatal("User %s not allowed to log in using UNIX UID 0 "
"(root logins are never allowed)", user);
}
i_fatal("User %s not allowed to log in using too %s "
"UNIX UID %s%s (see first_valid_uid in config file)",
user,
}
i_fatal("User %s not allowed to log in using too %s primary "
"UNIX group ID %s%s (see first_valid_gid in config file)",
user,
}
}
static void auth_args_apply(const char *const *args,
struct restrict_access_settings *rset,
const char **home)
{
rset->extra_groups =
} else {
/* unknown, set as environment */
/* boolean */
value = "=1";
} else {
/* FIXME: kind of ugly to have it
here.. */
key = "mail_location";
}
}
}
}
}
static void auth_success_write(void)
{
int fd;
if (auth_success_written)
return;
if (fd == -1)
else
}
{
unsigned int left;
int ret, chdir_errno;
if (*home != '/') {
i_fatal("user %s: Relative home directory paths not supported: "
}
/* if home directory is NFS-mounted, we might not have access to it as
root. Change the effective UID and GID temporarily to make it
work. */
}
chdir_errno = errno;
i_warning("user %s: chdir(%s) blocked for %u secs",
}
errno = chdir_errno;
if (ret == 0) {
/* chdir succeeded */
/* Not chrooted, fallback to using /tmp.
ENOENT: No home directory yet, but it might be automatically
created by the service process, so don't complain.
EINTR: chdir() timed out. */
} else {
i_fatal("user %s: chdir(%s) failed with uid %s: %m",
}
/* Change UID back. No need to change GID back, it doesn't
really matter. */
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");
}
}
const char *const *auth_args)
{
struct restrict_access_settings rset;
bool disallow_root;
env_put("DEBUG=1");
/* non-authenticating service. don't use *_valid_gid checks */
} else {
}
} else {
}
}
static void
{
const struct master_service_settings *set;
struct service_listener *const *listeners;
const char *const *p;
/* remove all environment, and put back what we need */
env_clean();
env_put(*p);
case SERVICE_TYPE_CONFIG:
break;
case SERVICE_TYPE_LOG:
/* give the log's configuration directly, so it won't depend
on config process */
env_put("DOVECONF_ENV=1");
break;
default:
&count);
break;
}
if (limit == 0) {
/* fallback to default limit */
}
}
{
"Initial status notification not received in %d "
"seconds, killing the process",
}
}
static void
{
return;
}
}
struct service_process *
const struct service_process_auth_request *request)
{
static unsigned int uid_counter = 0;
struct service_process *process;
unsigned int uid = ++uid_counter;
int fd[2];
/* probably throttling service, don't create new processes */
return NULL;
}
case SERVICE_TYPE_AUTH_SOURCE:
case SERVICE_TYPE_AUTH_SERVER:
return NULL;
}
break;
default:
break;
}
if (pid < 0) {
if (fd[0] != -1) {
}
return NULL;
}
if (pid == 0) {
/* child */
if (fd[0] != -1)
}
case SERVICE_TYPE_AUTH_SERVER:
break;
case SERVICE_TYPE_AUTH_SOURCE:
break;
default:
break;
}
if (process->available_count == 0) {
/* fallback to default limit */
}
service->process_count++;
service->process_avail++;
return process;
}
{
if (process->available_count > 0)
service->process_avail--;
service->process_count--;
case SERVICE_TYPE_AUTH_SERVER:
break;
case SERVICE_TYPE_AUTH_SOURCE:
break;
default:
break;
}
}
{
}
{
return TRUE;
return FALSE;
}
static const char *
{
switch (status) {
case FATAL_LOGOPEN:
return "Can't open log file";
case FATAL_LOGWRITE:
return "Can't write to log file";
case FATAL_LOGERROR:
return "Internal logging error";
case FATAL_OUTOFMEM:
return "Out of memory";
return t_strdup_printf("Out of memory (vsz_limit=%u MB, "
"you may need to increase it)",
case FATAL_EXEC:
return "exec() failed";
case FATAL_DEFAULT:
return "Fatal failure";
}
return NULL;
}
{
#ifdef WCOREDUMP
return;
}
return;
/* let's try to figure out why we didn't get a core dump */
if (core_dumps_disabled) {
return;
}
#ifdef HAVE_PR_SET_DUMPABLE
return;
}
return;
}
#endif
#endif
}
static void
int status, bool *default_fatal_r)
{
const char *msg;
*default_fatal_r = FALSE;
if (WIFSIGNALED(status)) {
return;
}
return;
}
if (status == 0) {
str_truncate(str, 0);
return;
}
if (status == FATAL_DEFAULT)
*default_fatal_r = TRUE;
}
bool default_fatal, const char *str)
{
const char *data;
return;
}
/* log it via the log process in charge of handling
this process's logging */
i_error("write(log process) failed: %m");
}
}
int status)
{
/* fast path */
return;
}
T_BEGIN {
bool default_fatal;
} T_END;
}