OpenBSD-4.6/usr.sbin/smtpd/mda.c
/* $OpenBSD: mda.c,v 1.23 2009/06/07 05:56:25 eric Exp $ */
/*
* Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
* Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/queue.h>
#include <sys/tree.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <event.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "smtpd.h"
__dead void mda_shutdown(void);
void mda_sig_handler(int, short, void *);
void mda_dispatch_parent(int, short, void *);
void mda_dispatch_queue(int, short, void *);
void mda_dispatch_runner(int, short, void *);
void mda_setup_events(struct smtpd *);
void mda_disable_events(struct smtpd *);
void mda_remove_message(struct smtpd *, struct batch *, struct message *x);
void
mda_sig_handler(int sig, short event, void *p)
{
switch (sig) {
case SIGINT:
case SIGTERM:
mda_shutdown();
break;
default:
fatalx("mda_sig_handler: unexpected signal");
}
}
void
mda_dispatch_parent(int sig, short event, void *p)
{
struct smtpd *env = p;
struct imsgev *iev;
struct imsgbuf *ibuf;
struct imsg imsg;
ssize_t n;
iev = env->sc_ievs[PROC_PARENT];
ibuf = &iev->ibuf;
if (event & EV_READ) {
if ((n = imsg_read(ibuf)) == -1)
fatal("imsg_read_error");
if (n == 0) {
/* this pipe is dead, so remove the event handler */
event_del(&iev->ev);
event_loopexit(NULL);
return;
}
}
if (event & EV_WRITE) {
if (msgbuf_write(&ibuf->w) == -1)
fatal("msgbuf_write");
}
for (;;) {
if ((n = imsg_get(ibuf, &imsg)) == -1)
fatalx("mda_dispatch_parent: imsg_get error");
if (n == 0)
break;
switch (imsg.hdr.type) {
case IMSG_MDA_MAILBOX_FILE: {
struct batch *batchp = imsg.data;
struct session *s;
struct message *messagep;
enum message_status status;
IMSG_SIZE_CHECK(batchp);
messagep = &batchp->message;
status = messagep->status;
batchp = batch_by_id(env, batchp->id);
if (batchp == NULL)
fatalx("mda_dispatch_parent: internal inconsistency.");
s = batchp->sessionp;
messagep = message_by_id(env, batchp, messagep->id);
if (messagep == NULL)
fatalx("mda_dispatch_parent: internal inconsistency.");
messagep->status = status;
s->mboxfd = imsg.fd;
if (s->mboxfd == -1) {
mda_remove_message(env, batchp, messagep);
break;
}
batchp->message = *messagep;
imsg_compose_event(env->sc_ievs[PROC_PARENT],
IMSG_PARENT_MESSAGE_OPEN, 0, 0, -1, batchp,
sizeof(struct batch));
break;
}
case IMSG_MDA_MESSAGE_FILE: {
struct batch *batchp = imsg.data;
struct session *s;
struct message *messagep;
enum message_status status;
int (*store)(struct batch *, struct message *) = store_write_message;
IMSG_SIZE_CHECK(batchp);
messagep = &batchp->message;
status = messagep->status;
batchp = batch_by_id(env, batchp->id);
if (batchp == NULL)
fatalx("mda_dispatch_parent: internal inconsistency.");
s = batchp->sessionp;
messagep = message_by_id(env, batchp, messagep->id);
if (messagep == NULL)
fatalx("mda_dispatch_parent: internal inconsistency.");
messagep->status = status;
s->messagefd = imsg.fd;
if (s->messagefd == -1) {
if (s->mboxfd != -1)
close(s->mboxfd);
mda_remove_message(env, batchp, messagep);
break;
}
/* If batch is a daemon message, override the default store function */
if (batchp->type & T_DAEMON_BATCH) {
store = store_write_daemon;
}
if (store_message(batchp, messagep, store)) {
if (batchp->message.recipient.rule.r_action == A_MAILDIR)
imsg_compose_event(env->sc_ievs[PROC_PARENT],
IMSG_PARENT_MAILBOX_RENAME, 0, 0, -1, batchp,
sizeof(struct batch));
}
if (s->mboxfd != -1)
close(s->mboxfd);
if (s->messagefd != -1)
close(s->messagefd);
mda_remove_message(env, batchp, messagep);
break;
}
default:
log_warnx("mda_dispatch_parent: got imsg %d",
imsg.hdr.type);
fatalx("mda_dispatch_parent: unexpected imsg");
}
imsg_free(&imsg);
}
imsg_event_add(iev);
}
void
mda_dispatch_queue(int sig, short event, void *p)
{
struct smtpd *env = p;
struct imsgev *iev;
struct imsgbuf *ibuf;
struct imsg imsg;
ssize_t n;
iev = env->sc_ievs[PROC_QUEUE];
ibuf = &iev->ibuf;
if (event & EV_READ) {
if ((n = imsg_read(ibuf)) == -1)
fatal("imsg_read_error");
if (n == 0) {
/* this pipe is dead, so remove the event handler */
event_del(&iev->ev);
event_loopexit(NULL);
return;
}
}
if (event & EV_WRITE) {
if (msgbuf_write(&ibuf->w) == -1)
fatal("msgbuf_write");
}
for (;;) {
if ((n = imsg_get(ibuf, &imsg)) == -1)
fatalx("mda_dispatch_queue: imsg_get error");
if (n == 0)
break;
switch (imsg.hdr.type) {
default:
log_warnx("mda_dispatch_queue: got imsg %d",
imsg.hdr.type);
fatalx("mda_dispatch_queue: unexpected imsg");
}
imsg_free(&imsg);
}
imsg_event_add(iev);
}
void
mda_dispatch_runner(int sig, short event, void *p)
{
struct smtpd *env = p;
struct imsgev *iev;
struct imsgbuf *ibuf;
struct imsg imsg;
ssize_t n;
iev = env->sc_ievs[PROC_RUNNER];
ibuf = &iev->ibuf;
if (event & EV_READ) {
if ((n = imsg_read(ibuf)) == -1)
fatal("imsg_read_error");
if (n == 0) {
/* this pipe is dead, so remove the event handler */
event_del(&iev->ev);
event_loopexit(NULL);
return;
}
}
if (event & EV_WRITE) {
if (msgbuf_write(&ibuf->w) == -1)
fatal("msgbuf_write");
}
for (;;) {
if ((n = imsg_get(ibuf, &imsg)) == -1)
fatalx("mda_dispatch_runner: imsg_get error");
if (n == 0)
break;
switch (imsg.hdr.type) {
case IMSG_BATCH_CREATE: {
struct batch *request = imsg.data;
struct batch *batchp;
struct session *s;
IMSG_SIZE_CHECK(request);
/* create a client session */
if ((s = calloc(1, sizeof(*s))) == NULL)
fatal(NULL);
s->s_state = S_INIT;
s->s_env = env;
s->s_id = queue_generate_id();
SPLAY_INSERT(sessiontree, &s->s_env->sc_sessions, s);
/* create the batch for this session */
batchp = calloc(1, sizeof (struct batch));
if (batchp == NULL)
fatal("mda_dispatch_runner: calloc");
*batchp = *request;
batchp->env = env;
batchp->sessionp = s;
s->batch = batchp;
TAILQ_INIT(&batchp->messages);
SPLAY_INSERT(batchtree, &env->batch_queue, batchp);
break;
}
case IMSG_BATCH_APPEND: {
struct message *append = imsg.data;
struct message *messagep;
struct batch *batchp;
IMSG_SIZE_CHECK(append);
messagep = calloc(1, sizeof (struct message));
if (messagep == NULL)
fatal("mda_dispatch_runner: calloc");
*messagep = *append;
batchp = batch_by_id(env, messagep->batch_id);
if (batchp == NULL)
fatalx("mda_dispatch_runner: internal inconsistency.");
TAILQ_INSERT_TAIL(&batchp->messages, messagep, entry);
break;
}
case IMSG_BATCH_CLOSE: {
struct batch *batchp = imsg.data;
struct session *s;
struct batch lookup;
struct message *messagep;
IMSG_SIZE_CHECK(batchp);
batchp = batch_by_id(env, batchp->id);
if (batchp == NULL)
fatalx("mda_dispatch_runner: internal inconsistency.");
s = batchp->sessionp;
lookup = *batchp;
TAILQ_FOREACH(messagep, &batchp->messages, entry) {
lookup.message = *messagep;
imsg_compose_event(env->sc_ievs[PROC_PARENT],
IMSG_PARENT_MAILBOX_OPEN, 0, 0, -1, &lookup,
sizeof(struct batch));
}
break;
}
default:
log_warnx("mda_dispatch_runner: got imsg %d",
imsg.hdr.type);
fatalx("mda_dispatch_runner: unexpected imsg");
}
imsg_free(&imsg);
}
imsg_event_add(iev);
}
void
mda_shutdown(void)
{
log_info("mail delivery agent exiting");
_exit(0);
}
void
mda_setup_events(struct smtpd *env)
{
}
void
mda_disable_events(struct smtpd *env)
{
}
pid_t
mda(struct smtpd *env)
{
pid_t pid;
struct passwd *pw;
struct event ev_sigint;
struct event ev_sigterm;
struct peer peers[] = {
{ PROC_PARENT, mda_dispatch_parent },
{ PROC_QUEUE, mda_dispatch_queue },
{ PROC_RUNNER, mda_dispatch_runner }
};
switch (pid = fork()) {
case -1:
fatal("mda: cannot fork");
case 0:
break;
default:
return (pid);
}
purge_config(env, PURGE_EVERYTHING);
pw = env->sc_pw;
#ifndef DEBUG
if (chroot(pw->pw_dir) == -1)
fatal("mda: chroot");
if (chdir("/") == -1)
fatal("mda: chdir(\"/\")");
#else
#warning disabling privilege revocation and chroot in DEBUG MODE
#endif
smtpd_process = PROC_MDA;
setproctitle("%s", env->sc_title[smtpd_process]);
#ifndef DEBUG
if (setgroups(1, &pw->pw_gid) ||
setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
fatal("mda: cannot drop privileges");
#endif
SPLAY_INIT(&env->batch_queue);
event_init();
signal_set(&ev_sigint, SIGINT, mda_sig_handler, env);
signal_set(&ev_sigterm, SIGTERM, mda_sig_handler, env);
signal_add(&ev_sigint, NULL);
signal_add(&ev_sigterm, NULL);
signal(SIGPIPE, SIG_IGN);
signal(SIGHUP, SIG_IGN);
config_pipes(env, peers, nitems(peers));
config_peers(env, peers, nitems(peers));
mda_setup_events(env);
event_dispatch();
mda_shutdown();
return (0);
}
void
mda_remove_message(struct smtpd *env, struct batch *batchp, struct message *messagep)
{
imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_MESSAGE_UPDATE, 0, 0,
-1, messagep, sizeof (struct message));
if ((batchp->message.status & S_MESSAGE_TEMPFAILURE) == 0 &&
(batchp->message.status & S_MESSAGE_PERMFAILURE) == 0) {
log_info("%s: to=<%s@%s>, delay=%d, stat=Sent",
messagep->message_uid,
messagep->recipient.user,
messagep->recipient.domain,
time(NULL) - messagep->creation);
}
SPLAY_REMOVE(sessiontree, &env->sc_sessions, batchp->sessionp);
free(batchp->sessionp);
queue_remove_batch_message(env, batchp, messagep);
}