login-process.c revision 43d32cbe60fdaef2699d99f1ca259053e9350411
/* Copyright (C) 2002 Timo Sirainen */
#include "common.h"
#include "array.h"
#include "ioloop.h"
#include "hash.h"
#include "network.h"
#include "ostream.h"
#include "fdpass.h"
#include "fd-close-on-exec.h"
#include "env-util.h"
#include "restrict-access.h"
#include "restrict-process-size.h"
#include "dup2-array.h"
#include "login-process.h"
#include "auth-process.h"
#include "mail-process.h"
#include "master-login-interface.h"
#include "log.h"
#include "ssl-init.h"
#include <unistd.h>
#include <syslog.h>
struct login_process {
struct child_process process;
struct login_group *group;
int refcount;
int fd;
enum master_login_state state;
unsigned int initialized:1;
unsigned int destroyed:1;
unsigned int inetd_child:1;
};
struct login_auth_request {
struct login_process *process;
unsigned int tag;
unsigned int login_tag;
int fd;
};
static unsigned int auth_id_counter, login_pid_counter;
static bool logins_stalled = FALSE;
static struct login_group *login_groups;
static void login_processes_stall(void);
static void login_process_destroy(struct login_process *p);
static void login_process_unref(struct login_process *p);
static bool login_process_init_group(struct login_process *p);
static void login_processes_start_missing(void *context);
{
struct login_group *group;
}
{
return;
}
struct login_auth_request *request)
{
struct master_login_reply master_reply;
else {
t_push();
FALSE);
t_pop();
}
/* reply to login */
sizeof(master_reply));
if (ret != sizeof(master_reply)) {
if (ret >= 0) {
i_warning("Login process %s transmit buffer full, "
}
}
i_error("close(mail client) failed: %m");
}
static void process_remove_from_prelogin_lists(struct login_process *p)
{
if (p->state != LOGIN_STATE_FULL_PRELOGINS)
return;
if (p->prev_prelogin == NULL)
else
if (p->next_prelogin == NULL)
else
}
static void process_mark_nonlistening(struct login_process *p,
enum master_login_state new_state)
{
return;
if (p->state == LOGIN_STATE_LISTENING)
p->group->listening_processes--;
if (new_state == LOGIN_STATE_FULL_PRELOGINS) {
/* add to prelogin list */
p->group->oldest_prelogin_process = p;
else
p->group->newest_prelogin_process = p;
} else {
}
}
static void process_mark_listening(struct login_process *p)
{
return;
if (p->state != LOGIN_STATE_LISTENING)
p->group->listening_processes++;
}
static void login_process_set_initialized(struct login_process *p)
{
p->initialized = TRUE;
if (logins_stalled) {
/* processes were created successfully */
i_info("Created login processes successfully, unstalling");
timeout_remove(&to);
}
}
static void
{
i_error("login: tried to change state %d -> %d "
return;
}
if (state == LOGIN_STATE_LISTENING) {
} else {
}
}
static void login_process_groups_create(void)
{
struct server_settings *server;
}
}
static struct login_group *
{
struct login_group *group;
if (login_groups == NULL)
return group;
}
return NULL;
}
static bool login_process_read_group(struct login_process *p)
{
struct login_group *group;
unsigned char buf[256];
enum mail_protocol protocol;
unsigned int len;
/* read length */
if (ret != 1)
len = 0;
else {
i_error("login: Server name length too large");
return FALSE;
}
}
if (ret < 0)
i_error("login: read() failed: %m");
i_error("login: Server name wasn't sent");
else {
name = "default";
} else {
}
else {
return FALSE;
}
return FALSE;
}
return login_process_init_group(p);
}
return FALSE;
}
static int
int *client_fd_r)
{
*client_fd_r = -1;
i_error("login: Protocol version mismatch "
"(mixed old and new binaries?)");
return -1;
}
if (ret == 0) {
/* disconnected, ie. the login process died */
} else if (ret > 0) {
/* request wasn't fully read */
i_error("login: fd_read() returned partial %d",
(int)ret);
} else {
return 0;
i_error("login: fd_read() failed: %m");
}
return -1;
}
if (*client_fd_r != -1) {
i_error("login: Notification request sent "
"a file descriptor");
return -1;
}
return 1;
}
if (*client_fd_r == -1) {
i_error("login: Login request missing a file descriptor");
return -1;
}
i_error("login: fstat(mail client) failed: %m");
return -1;
}
i_error("login: Login request inode mismatch: %s != %s",
return -1;
}
return 1;
}
static void login_process_input(struct login_process *p)
{
struct auth_process *auth_process;
struct login_auth_request *authreq;
struct master_login_request req;
int client_fd;
/* we want to read the group */
if (!login_process_read_group(p))
return;
}
if (ret == 0)
return;
if (ret < 0) {
if (client_fd != -1) {
i_error("login: close(mail client) failed: %m");
}
return;
}
/* state notification */
if (!p->initialized) {
/* initialization notify */
} else {
/* change "listening for new connections" status */
}
return;
}
if (!p->initialized) {
i_error("login: trying to log in before initialization");
return;
}
/* ask the cookie from the auth process */
p->refcount++;
if (auth_process == NULL) {
i_error("login: Authentication process %u doesn't exist",
} else {
}
}
static struct login_process *
{
struct login_process *p;
FALSE);
p->state = LOGIN_STATE_LISTENING;
p->group->listening_processes++;
}
return p;
}
static void login_process_exited(struct login_process *p)
{
}
static void login_process_destroy(struct login_process *p)
{
if (p->destroyed)
return;
if (!p->initialized)
o_stream_close(p->output);
i_error("close(login) failed: %m");
if (p->inetd_child)
}
static void login_process_unref(struct login_process *p)
{
if (--p->refcount > 0)
return;
login_group_unref(p->group);
o_stream_unref(&p->output);
i_free(p);
}
{
/* setup access environment - needs to be done after
clean_child_process() since it clears environment. Don't set user
parameter since we don't want to call initgroups() for login
processes. */
0, 0, NULL);
env_put("DOVECOT_MASTER=1");
if (!set->ssl_disable) {
const char *ssl_key_password;
}
ssl_key_password, NULL));
}
if (set->ssl_verify_client_cert)
env_put("SSL_VERIFY_CLIENT_CERT=1");
}
if (set->disable_plaintext_auth)
env_put("DISABLE_PLAINTEXT_AUTH=1");
if (set->verbose_proctitle)
env_put("VERBOSE_PROCTITLE=1");
if (set->verbose_ssl)
env_put("VERBOSE_SSL=1");
env_put("VERBOSE_AUTH=1");
if (set->login_process_per_connection) {
env_put("PROCESS_PER_CONNECTION=1");
env_put("MAX_CONNECTIONS=1");
} else {
}
if (set->login_greeting_capability)
env_put("GREETING_CAPABILITY=1");
}
}
{
unsigned int max_log_lines_per_sec;
const char *prefix;
i_fatal("Login process must not run as root");
/* create communication to process with a socket pair */
i_error("socketpair() failed: %m");
return -1;
}
if (log_fd < 0)
pid = -1;
else {
if (pid < 0)
i_error("fork() failed: %m");
}
if (pid < 0) {
return -1;
}
if (pid != 0) {
/* master */
return pid;
}
/* redirect writes to stdout also to error log. For example OpenSSL
can be made to log its debug messages to stdout. */
/* redirect listener fds */
for (i = 0; i < listen_count; i++, cur_fd++)
}
for (i = 0; i < ssl_listen_count; i++, cur_fd++)
}
if (dup2_array(&dups) < 0)
i_fatal("Failed to dup2() fds");
/* don't close any of these */
/* no chrooting, but still change to the directory */
i_fatal("chdir(%s) failed: %m",
}
}
/* make sure we don't leak syslog fd, but do it last so that
any errors above will be logged */
closelog();
/* execv() needs at least one file descriptor. we might have all fds
up to fd_limit used already, so close one we don't care about.
either it succeeds or fails with EBADF, doesn't matter. */
return -1;
}
static void
{
i_assert(!p->inetd_child);
if (abnormal_exit) {
/* don't start raising the process count if they're dying all
the time */
p->group->wanted_processes_count = 0;
}
}
void login_processes_destroy_all(void)
{
struct hash_iterate_context *iter;
struct login_process *p = value;
}
while (login_groups != NULL) {
}
}
{
struct hash_iterate_context *iter;
struct master_login_reply reply;
struct login_process *p = value;
}
}
{
group->listening_processes == 0) {
/* destroy the oldest listening process. non-listening
processes are logged in users who we don't want to kick out
because someone's started flooding */
}
/* we want to respond fast when multiple clients are connecting
at once, but we also want to prevent fork-bombing. use the
same method as apache: check once a second if we need new
processes. if yes and we've used all the existing processes,
double their amount (unless we've hit the high limit).
Then for each second that didn't use all existing processes,
drop the max. process count by one. */
} else if (group->listening_processes == 0)
else if (group->wanted_processes_count >
if (create_login_process(group) < 0)
return -1;
}
if (group->listening_processes == 0 &&
/* we've reached our limit. notify the processes to start
listening again which makes them kill some of their
oldest clients when accepting the next connection */
}
return 0;
}
static void login_processes_stall(void)
{
if (logins_stalled || IS_INETD())
return;
i_error("Temporary failure in creating login processes, "
"slowing down for now");
timeout_remove(&to);
}
static void
{
struct login_group *group;
if (!have_initialized_auth_processes) {
/* don't create login processes before at least one auth
process has finished initializing */
return;
}
if (login_groups == NULL)
if (login_group_start_missings(group) < 0) {
return;
}
}
}
static int login_process_send_env(struct login_process *p)
{
extern char **environ;
char **env;
int ret = 0;
/* this will clear our environment. luckily we don't need it. */
ret = -1;
break;
}
}
/* if we're not chrooting, we need to tell login process
where its base directory is */
ret = -1;
}
ret = -1;
env_clean();
return ret;
}
static bool login_process_init_group(struct login_process *p)
{
p->group->listening_processes++;
if (login_process_send_env(p) < 0) {
i_error("login: Couldn't send environment");
return FALSE;
}
return TRUE;
}
{
struct login_process *p;
int fd;
if (fd < 0) {
if (fd < -1)
i_error("accept(inetd_login_fd) failed: %m");
} else {
p->initialized = TRUE;
p->inetd_child = TRUE;
}
}
void login_processes_init(void)
{
auth_id_counter = 0;
login_pid_counter = 0;
login_groups = NULL;
if (!IS_INETD()) {
} else {
}
}
void login_processes_deinit(void)
{
timeout_remove(&to);
}