auth-process.c revision 395cfd9f83ada53f9d434943c062cbe1c80bae2a
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen/* Copyright (C) 2002 Timo Sirainen */
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "common.h"
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen#include "ioloop.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "env-util.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "fd-close-on-exec.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "network.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "ostream.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "restrict-access.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "restrict-process-size.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include "auth-process.h"
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen#include <stdlib.h>
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen#include <unistd.h>
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen#include <pwd.h>
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include <syslog.h>
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen#include <sys/stat.h>
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainenstruct auth_process {
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct auth_process *next;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen char *name;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen pid_t pid;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen int fd;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct io *io;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct ostream *output;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen unsigned int reply_pos;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen char reply_buf[sizeof(struct auth_cookie_reply_data)];
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen struct waiting_request *requests, **next_request;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen unsigned int initialized:1;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen};
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainenstruct waiting_request {
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct waiting_request *next;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen unsigned int id;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_callback_t callback;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen void *context;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen};
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic struct timeout *to;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic struct auth_process *processes;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainenstatic void auth_process_destroy(struct auth_process *p);
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainenstatic void push_request(struct auth_process *process, unsigned int id,
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen auth_callback_t callback, void *context)
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen{
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen struct waiting_request *req;
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen
c0435c854a0e7246373b9752d163095cc4fbe985Timo Sirainen req = i_new(struct waiting_request, 1);
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen req->id = id;
ecc81625167ed96c04c02aa190a1ea5baa65b474Timo Sirainen req->callback = callback;
req->context = context;
*process->next_request = req;
process->next_request = &req->next;
}
static void pop_request(struct auth_process *process,
struct auth_cookie_reply_data *reply)
{
struct waiting_request *req;
req = process->requests;
if (req == NULL) {
i_warning("imap-auth %s sent us unrequested reply for id %d",
dec2str(process->pid), reply->id);
return;
}
if (reply->id != req->id) {
i_fatal("imap-auth %s sent invalid id for reply "
"(got %d, expecting %d)",
dec2str(process->pid), reply->id, req->id);
}
/* auth process isn't trusted, validate all data to make sure
it's not trying to exploit us */
if (!VALIDATE_STR(reply->system_user) ||
!VALIDATE_STR(reply->virtual_user) || !VALIDATE_STR(reply->mail) ||
!VALIDATE_STR(reply->home)) {
i_error("auth: Received corrupted data");
auth_process_destroy(process);
return;
}
process->requests = req->next;
if (process->requests == NULL)
process->next_request = &process->requests;
req->callback(reply, req->context);
i_free(req);
}
static void auth_process_input(void *context, int fd,
struct io *io __attr_unused__)
{
struct auth_process *p = context;
int ret;
ret = net_receive(fd, p->reply_buf + p->reply_pos,
sizeof(p->reply_buf) - p->reply_pos);
if (ret < 0) {
/* disconnected */
auth_process_destroy(p);
return;
}
if (!p->initialized) {
if (p->reply_buf[0] != 'O') {
i_fatal("Auth process sent invalid initialization "
"notification");
}
p->initialized = TRUE;
ret--;
memmove(p->reply_buf, p->reply_buf + 1, ret);
}
p->reply_pos += ret;
if (p->reply_pos < sizeof(p->reply_buf))
return;
/* reply is now read */
pop_request(p, (struct auth_cookie_reply_data *) p->reply_buf);
p->reply_pos = 0;
}
static struct auth_process *
auth_process_new(pid_t pid, int fd, const char *name)
{
struct auth_process *p;
PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_AUTH);
p = i_new(struct auth_process, 1);
p->name = i_strdup(name);
p->pid = pid;
p->fd = fd;
p->io = io_add(fd, IO_READ, auth_process_input, p);
p->output = o_stream_create_file(fd, default_pool,
sizeof(struct auth_cookie_request_data)*100,
IO_PRIORITY_DEFAULT, FALSE);
p->next_request = &p->requests;
p->next = processes;
processes = p;
return p;
}
static void auth_process_destroy(struct auth_process *p)
{
struct auth_process **pos;
struct waiting_request *next;
if (!p->initialized)
i_fatal("Auth process died too early - shutting down");
for (pos = &processes; *pos != NULL; pos = &(*pos)->next) {
if (*pos == p) {
*pos = p->next;
break;
}
}
for (; p->requests != NULL; p->requests = next) {
next = p->requests->next;
p->requests->callback(NULL, p->requests->context);
i_free(p->requests);
}
(void)unlink(t_strconcat(set_login_dir, "/", p->name, NULL));
o_stream_unref(p->output);
io_remove(p->io);
if (close(p->fd) < 0)
i_error("close(auth) failed: %m");
i_free(p->name);
i_free(p);
}
static pid_t create_auth_process(struct auth_config *config)
{
static char *argv[] = { NULL, NULL };
const char *path;
struct passwd *pwd;
pid_t pid;
int fd[2], listen_fd, i;
if ((pwd = getpwnam(config->user)) == NULL)
i_fatal("Auth user doesn't exist: %s", config->user);
/* 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 */
fd_close_on_exec(fd[0], TRUE);
auth_process_new(pid, fd[0], config->name);
(void)close(fd[1]);
return pid;
}
/* create socket for listening auth requests from imap-login */
path = t_strconcat(set_login_dir, "/", config->name, NULL);
(void)unlink(path);
(void)umask(0177); /* we want 0600 mode for the socket */
listen_fd = net_listen_unix(path);
if (listen_fd < 0)
i_fatal("Can't listen in UNIX socket %s: %m", path);
i_assert(listen_fd > 2);
/* set correct permissions */
if (chown(path, set_login_uid, set_login_gid) < 0) {
i_fatal("login: chown(%s, %s, %s) failed: %m",
path, dec2str(set_login_uid), dec2str(set_login_gid));
}
/* move master communication handle to 0 */
if (dup2(fd[1], 0) < 0)
i_fatal("login: dup2(0) failed: %m");
(void)close(fd[0]);
(void)close(fd[1]);
/* set /dev/null handle into 1 and 2, so if something is printed into
stdout/stderr it can't go anywhere where it could cause harm */
if (dup2(null_fd, 1) < 0)
i_fatal("login: dup2(1) failed: %m");
if (dup2(null_fd, 2) < 0)
i_fatal("login: dup2(2) failed: %m");
clean_child_process();
/* move login communication handle to 3. do it last so we can be
sure it's not closed afterwards. */
if (listen_fd != 3) {
if (dup2(listen_fd, 3) < 0)
i_fatal("login: dup2() failed: %m");
(void)close(listen_fd);
}
for (i = 0; i <= 2; i++)
fd_close_on_exec(i, FALSE);
/* setup access environment - needs to be done after
clean_child_process() since it clears environment */
restrict_access_set_env(config->user, pwd->pw_uid, pwd->pw_gid,
config->chroot);
/* set other environment */
env_put(t_strconcat("AUTH_PROCESS=", dec2str(getpid()), NULL));
env_put(t_strconcat("MECHANISMS=", config->mechanisms, NULL));
env_put(t_strconcat("REALMS=", config->realms, NULL));
env_put(t_strconcat("USERINFO=", config->userinfo, NULL));
env_put(t_strconcat("USERINFO_ARGS=", config->userinfo_args, NULL));
if (config->use_cyrus_sasl)
env_put("USE_CYRUS_SASL=1");
if (config->verbose)
env_put("VERBOSE=1");
restrict_process_size(config->process_size);
/* make sure we don't leak syslog fd, but do it last so that
any errors above will be logged */
closelog();
/* hide the path, it's ugly */
argv[0] = strrchr(config->executable, '/');
if (argv[0] == NULL) argv[0] = config->executable; else argv[0]++;
execv(config->executable, (char **) argv);
i_fatal_status(FATAL_EXEC, "execv(%s) failed: %m", argv[0]);
return -1;
}
struct auth_process *auth_process_find(unsigned int id)
{
struct auth_process *p;
for (p = processes; p != NULL; p = p->next) {
if ((unsigned int)p->pid == id)
return p;
}
return NULL;
}
void auth_process_request(unsigned int login_pid,
struct auth_process *process, unsigned int id,
unsigned char cookie[AUTH_COOKIE_SIZE],
auth_callback_t callback, void *context)
{
struct auth_cookie_request_data req;
req.id = id;
req.login_pid = login_pid;
memcpy(req.cookie, cookie, AUTH_COOKIE_SIZE);
if (o_stream_send(process->output, &req, sizeof(req)) < 0)
auth_process_destroy(process);
push_request(process, id, callback, context);
}
static unsigned int auth_process_get_count(const char *name)
{
struct auth_process *p;
unsigned int count = 0;
for (p = processes; p != NULL; p = p->next) {
if (strcmp(p->name, name) == 0)
count++;
}
return count;
}
void auth_processes_destroy_all(void)
{
struct auth_process *next;
while (processes != NULL) {
next = processes->next;
auth_process_destroy(processes);
processes = next;
}
}
static void
auth_processes_start_missing(void *context __attr_unused__,
struct timeout *timeout __attr_unused__)
{
struct auth_config *config;
unsigned int count;
config = auth_processes_config;
for (; config != NULL; config = config->next) {
count = auth_process_get_count(config->name);
for (; count < config->count; count++)
(void)create_auth_process(config);
}
}
void auth_processes_init(void)
{
processes = NULL;
to = timeout_add(1000, auth_processes_start_missing, NULL);
}
void auth_processes_deinit(void)
{
timeout_remove(to);
auth_processes_destroy_all();
}