Mercurial > dovecot > original-hg > dovecot-1.2
diff src/master/login-process.c @ 0:3b1985cbc908 HEAD
Initial revision
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Fri, 09 Aug 2002 12:15:38 +0300 |
parents | |
children | 82b7de533f98 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/login-process.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,301 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "network.h" +#include "iobuffer.h" +#include "fdpass.h" +#include "restrict-access.h" +#include "login-process.h" +#include "auth-process.h" +#include "master-interface.h" + +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> + +typedef struct { + int refcount; + + pid_t pid; + int fd; + IO io; + IOBuffer *outbuf; + unsigned int destroyed:1; +} LoginProcess; + +typedef struct { + LoginProcess *process; + int login_id; + int auth_id; + int fd; + + char login_tag[LOGIN_TAG_SIZE]; +} LoginAuthRequest; + +static int auth_id_counter; +static Timeout to; +static HashTable *processes = NULL; + +static void login_process_destroy(LoginProcess *p); +static void login_process_unref(LoginProcess *p); + +static void auth_callback(AuthCookieReplyData *cookie_reply, void *user_data) +{ + const char *env[] = { + "MAIL", NULL, + "LOGIN_TAG", NULL, + NULL + }; + LoginAuthRequest *request = user_data; + LoginProcess *process; + MasterReply reply; + + env[1] = cookie_reply->mail; + env[3] = request->login_tag; + + if (cookie_reply == NULL || !cookie_reply->success) + reply.result = MASTER_RESULT_FAILURE; + else { + reply.result = create_imap_process(request->fd, + cookie_reply->user, + cookie_reply->uid, + cookie_reply->gid, + cookie_reply->home, + cookie_reply->chroot, env); + } + + /* reply to login */ + reply.id = request->login_id; + + process = request->process; + if (io_buffer_send(process->outbuf, &reply, sizeof(reply)) < 0) + login_process_destroy(process); + + (void)close(request->fd); + login_process_unref(process); + i_free(request); +} + +static void login_process_input(void *user_data, int fd __attr_unused__, + IO io __attr_unused__) +{ + LoginProcess *p = user_data; + AuthProcess *auth_process; + LoginAuthRequest *authreq; + MasterRequest req; + int client_fd, ret; + + ret = fd_read(p->fd, &req, sizeof(req), &client_fd); + if (ret != sizeof(req)) { + if (ret == 0) { + /* disconnected, ie. the login process died */ + } else if (ret > 0) { + /* req wasn't fully read */ + i_error("login: fd_read() couldn't read all req"); + } else { + i_error("login: fd_read() failed: %m"); + } + + login_process_destroy(p); + return; + } + + /* login process isn't trusted, validate all data to make sure + it's not trying to exploit us */ + if (!VALIDATE_STR(req.login_tag)) { + i_error("login: Received corrupted data"); + login_process_destroy(p); + return; + } + + /* ask the cookie from the auth process */ + authreq = i_new(LoginAuthRequest, 1); + p->refcount++; + authreq->process = p; + authreq->login_id = req.id; + authreq->auth_id = ++auth_id_counter; + authreq->fd = client_fd; + strcpy(authreq->login_tag, req.login_tag); + + auth_process = auth_process_find(req.auth_process); + if (auth_process == NULL) { + i_error("login: Authentication process %u doesn't exist", + req.auth_process); + auth_callback(NULL, &authreq); + } else { + auth_process_request(auth_process, authreq->auth_id, req.cookie, + auth_callback, authreq); + } +} + +static LoginProcess *login_process_new(pid_t pid, int fd) +{ + LoginProcess *p; + + PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_LOGIN); + + p = i_new(LoginProcess, 1); + p->refcount = 1; + p->pid = pid; + p->fd = fd; + p->io = io_add(fd, IO_READ, login_process_input, p); + p->outbuf = io_buffer_create(fd, default_pool, IO_PRIORITY_DEFAULT, + sizeof(MasterReply)*10); + + hash_insert(processes, INT_TO_POINTER(pid), p); + return p; +} + +static void login_process_destroy(LoginProcess *p) +{ + if (p->destroyed) + return; + p->destroyed = TRUE; + + io_buffer_close(p->outbuf); + io_remove(p->io); + (void)close(p->fd); + + hash_remove(processes, INT_TO_POINTER(p->pid)); + login_process_unref(p); +} + +static void login_process_unref(LoginProcess *p) +{ + if (--p->refcount > 0) + return; + + io_buffer_destroy(p->outbuf); + i_free(p); +} + +static pid_t create_login_process(void) +{ + static const char *argv[] = { NULL, NULL }; + pid_t pid; + int fd[2]; + + if (set_login_uid == 0) + i_fatal("Login process must not run as root"); + + /* create communication to process with a socket pair */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) { + i_error("socketpair() failed: %m"); + return -1; + } + + pid = fork(); + if (pid < 0) { + (void)close(fd[0]); + (void)close(fd[1]); + i_error("fork() failed: %m"); + return -1; + } + + if (pid != 0) { + /* master */ + login_process_new(pid, fd[0]); + (void)close(fd[1]); + return pid; + } + + /* move communication handle */ + if (dup2(fd[1], LOGIN_MASTER_SOCKET_FD) < 0) + i_fatal("login: dup2() failed: %m"); + + /* move the listen handle */ + if (dup2(imap_fd, LOGIN_IMAP_LISTEN_FD) < 0) + i_fatal("login: dup2() failed: %m"); + + /* move the SSL listen handle */ + if (dup2(imaps_fd, LOGIN_IMAPS_LISTEN_FD) < 0) + i_fatal("login: dup2() failed: %m"); + + /* imap_fd and imaps_fd are closed by clean_child_process() */ + + (void)close(fd[0]); + (void)close(fd[1]); + + clean_child_process(); + + /* setup access environment - needs to be done after + clean_child_process() since it clears environment */ + restrict_access_set_env(set_login_user, set_login_uid, set_login_gid, + set_login_chroot ? set_login_dir : NULL); + + if (!set_login_chroot) { + /* no chrooting, but still change to the directory */ + if (chdir(set_login_dir) < 0) { + i_fatal("chdir(%s) failed: %m", + set_login_dir); + } + } + + if (set_ssl_cert_file != NULL) { + putenv((char *) t_strconcat("SSL_CERT_FILE=", + set_ssl_cert_file, NULL)); + } + + if (set_ssl_key_file != NULL) { + putenv((char *) t_strconcat("SSL_KEY_FILE=", + set_ssl_key_file, NULL)); + } + + if (set_disable_plaintext_auth) + putenv("DISABLE_PLAINTEXT_AUTH=1"); + + putenv((char *) t_strdup_printf("MAX_LOGGING_USERS=%d", + set_max_logging_users)); + + /* hide the path, it's ugly */ + argv[0] = strrchr(set_login_executable, '/'); + if (argv[0] == NULL) argv[0] = set_login_executable; else argv[0]++; + + execv(set_login_executable, (char **) argv); + + i_fatal("execv(%s) failed: %m", argv[0]); + return -1; +} + +static void login_hash_cleanup(void *key __attr_unused__, void *value, + void *user_data __attr_unused__) +{ + LoginProcess *p = value; + + (void)close(p->fd); +} + +void login_processes_cleanup(void) +{ + hash_foreach(processes, login_hash_cleanup, NULL); +} + +static void login_processes_start_missing(void *user_data __attr_unused__, + Timeout timeout __attr_unused__) +{ + /* create max. one process every second, that way if it keeps + dying all the time we don't eat all cpu with fork()ing. */ + if (hash_size(processes) < set_login_processes_count) + (void)create_login_process(); +} + +void login_processes_init(void) +{ + auth_id_counter = 0; + processes = hash_create(default_pool, 128, NULL, NULL); + to = timeout_add(1000, login_processes_start_missing, NULL); +} + +static void login_hash_destroy(void *key __attr_unused__, void *value, + void *user_data __attr_unused__) +{ + login_process_destroy(value); +} + +void login_processes_deinit(void) +{ + timeout_remove(to); + + hash_foreach(processes, login_hash_destroy, NULL); + hash_destroy(processes); +}