Commit the remainder of Max's dma work (with minor modifications).
authorMatthias Schmidt <matthias@dragonflybsd.org>
Tue, 16 Sep 2008 17:57:23 +0000 (17:57 +0000)
committerMatthias Schmidt <matthias@dragonflybsd.org>
Tue, 16 Sep 2008 17:57:23 +0000 (17:57 +0000)
See Max mail on kernel@ [1] for further details.

* Support of .forward files (Note: dma is now setuid root)
* Send multiple mails at once
* Fix some style(9) issues (mostly return())

Some style(9) issues are still in the code.  I take care if I have some
spare time :)  Please test!

Submitted-by: Max Lindner <gisanka@gmail.com>
Sponsored-by: Google Summer of Code 2008
[1] http://leaf.dragonflybsd.org/mailarchive/kernel/2008-08/msg00045.html

libexec/dma/Makefile
libexec/dma/dma.c
libexec/dma/dma.h
libexec/dma/net.c

index 0d30c4e..b0fb82e 100644 (file)
@@ -1,4 +1,4 @@
-# $DragonFly: src/libexec/dma/Makefile,v 1.3 2008/02/04 08:58:54 matthias Exp $
+# $DragonFly: src/libexec/dma/Makefile,v 1.4 2008/09/16 17:57:22 matthias Exp $
 #
 
 CFLAGS+= -DHAVE_CRYPTO
@@ -13,7 +13,7 @@ MAN=  dma.8
 
 BINOWN= root
 BINGRP= mail
-BINMODE=2555
+BINMODE=4555
 WARNS?=        1
 
 .include <bsd.prog.mk>
index 6c910a0..598b629 100644 (file)
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * $DragonFly: src/libexec/dma/dma.c,v 1.3 2008/02/04 08:58:54 matthias Exp $
+ * $DragonFly: src/libexec/dma/dma.c,v 1.4 2008/09/16 17:57:22 matthias Exp $
  */
 
+#include <sys/ipc.h>
 #include <sys/param.h>
 #include <sys/queue.h>
+#include <sys/sem.h>
+#include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 
@@ -47,6 +50,7 @@
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <grp.h>
 #include <inttypes.h>
 #include <netdb.h>
 #include <paths.h>
@@ -63,7 +67,8 @@
 
 
 
-static void deliver(struct qitem *);
+static void deliver(struct qitem *, int);
+static void deliver_smarthost(struct queue *, int);
 static int add_recp(struct queue *, const char *, const char *, int);
 
 struct aliases aliases = LIST_HEAD_INITIALIZER(aliases);
@@ -72,6 +77,41 @@ struct virtusers virtusers = LIST_HEAD_INITIALIZER(virtusers);
 struct authusers authusers = LIST_HEAD_INITIALIZER(authusers);
 static int daemonize = 1;
 struct config *config;
+int controlsocket_df, clientsocket_df, controlsocket_wl, clientsocket_wl, semkey;
+
+static void *
+release_children()
+{
+       struct sembuf sema;
+       int null = 0;
+
+       /* 
+        * Try to decrement semaphore as we start communicating with
+        * write_to_local_user() 
+        */
+       sema.sem_num = SEM_WL;
+       sema.sem_op = -1;
+       sema.sem_flg = 0;
+       if (semop(semkey, &sema, 1) == -1) {
+               err(1, "semaphore decrement failed");
+       }
+
+       /*
+        * write_to_local_user() will exit and kill dotforwardhandler(), too
+        * if the corresponding semaphore is zero
+        * otherwise nothing happens
+        */
+       write(controlsocket_wl, &null, sizeof(null));
+
+       /* 
+        * Increment semaphore as we stop communicating with 
+        * write_to_local_user()
+        */
+       sema.sem_op = 1;
+       if (semop(semkey, &sema, 1) == -1) {
+               err(1, "semaphore decrement failed");
+       }
+}
 
 char *
 hostname(void)
@@ -104,19 +144,19 @@ set_from(const char *osender)
        if (osender) {
                sender = strdup(osender);
                if (sender == NULL)
-                       return (NULL);
+                       return(NULL);
        } else {
                if (asprintf(&sender, "%s@%s", getlogin(), hostname()) <= 0)
-                       return (NULL);
+                       return(NULL);
        }
 
        if (strchr(sender, '\n') != NULL) {
                errno = EINVAL;
-               return (NULL);
+               return(NULL);
        }
 
 out:
-       return (sender);
+       return(sender);
 }
 
 static int
@@ -124,11 +164,11 @@ read_aliases(void)
 {
        yyin = fopen(config->aliases, "r");
        if (yyin == NULL)
-               return (0);     /* not fatal */
+               return(0);      /* not fatal */
        if (yyparse())
-               return (-1);    /* fatal error, probably malloc() */
+               return(-1);     /* fatal error, probably malloc() */
        fclose(yyin);
-       return (0);
+       return(0);
 }
 
 static int
@@ -143,10 +183,10 @@ add_recp(struct queue *queue, const char *str, const char *sender, int expand)
 
        it = calloc(1, sizeof(*it));
        if (it == NULL)
-               return (-1);
+               return(-1);
        it->addr = strdup(str);
        if (it->addr == NULL)
-               return (-1);
+               return(-1);
 
        it->sender = sender;
        host = strrchr(it->addr, '@');
@@ -160,42 +200,160 @@ add_recp(struct queue *queue, const char *str, const char *sender, int expand)
                if (strcmp(tit->addr, it->addr) == 0) {
                        free(it->addr);
                        free(it);
-                       return (0);
+                       return(0);
                }
        }
        LIST_INSERT_HEAD(&queue->queue, it, next);
        if (strrchr(it->addr, '@') == NULL) {
-               it->remote = 0;
+               /* local = 1 means its a username or mailbox */
+               it->local = 1;
+               /* only search for aliases and .forward if asked for */
+               /* needed to have the possibility to add an mailbox directly */
                if (expand) {
+                       /* first check /etc/aliases */
                        LIST_FOREACH(al, &aliases, next) {
                                if (strcmp(al->alias, it->addr) != 0)
                                        continue;
                                SLIST_FOREACH(sit, &al->dests, next) {
-                                       if (add_recp(queue, sit->str, sender, 1) != 0)
-                                               return (-1);
+                                       if (add_recp(queue, sit->str,
+                                           sender, 1) != 0)
+                                               return(-1);
                                }
                                aliased = 1;
                        }
                        if (aliased) {
                                LIST_REMOVE(it, next);
                        } else {
-                               /* Local destination, check */
+                               /* then check .forward of user */
+                               fd_set rfds;
+                               int ret;
+                               uint8_t len, type;
+                               struct sembuf sema;
+                               /* is the username valid */
                                pw = getpwnam(it->addr);
+                               endpwent();
                                if (pw == NULL)
                                        goto out;
-                               endpwent();
+
+                               /*
+                                * Try to decrement semaphore as we start
+                                * communicating with dotforwardhandler()
+                                */
+                               sema.sem_num = SEM_DF;
+                               sema.sem_op = -1;
+                               sema.sem_flg = 0;
+                               if (semop(semkey, &sema, 1) == -1) {
+                                       err(1, "semaphore decrement failed");
+                               }
+
+                               /* write username to dotforwardhandler */
+                               len = strlen(it->addr);
+                               write(controlsocket_df, &len, sizeof(len));
+                               write(controlsocket_df, it->addr, len);
+                               FD_ZERO(&rfds);
+                               FD_SET(controlsocket_df, &rfds);
+
+                               /* wait for incoming redirects and pipes */
+                               while (ret =select(controlsocket_df + 1,
+                                   &rfds, NULL, NULL, NULL)) {
+                                       /*
+                                        * Receive back list of mailboxnames
+                                        * and/or emailadresses
+                                        */
+                                       if (ret == -1) {
+                                               /*
+                                                * increment semaphore because
+                                                * we stopped communicating
+                                                * with dotforwardhandler()
+                                                */
+                                               sema.sem_op = 1;
+                                               semop(semkey, &sema, 1);
+                                               return(-1);
+                                       }
+                                       /* read type of .forward entry */
+                                       read(controlsocket_df, &type, 1);
+                                       if (type & ENDOFDOTFORWARD) {
+                                               /* end of .forward */
+                                               /*
+                                                * If there are redirects, then
+                                                * we do not need the original
+                                                * qitem any longer
+                                                */
+                                               if (aliased) {
+                                                       LIST_REMOVE(it, next);
+                                               }
+                                               break;
+                                       } else if (type & ISMAILBOX) {
+                                               /* redirect -> user/emailaddress */
+                                               /*
+                                                * FIXME shall there be the possibility to use
+                                                * usernames instead of mailboxes?
+                                                */
+                                               char *username;
+                                               read(controlsocket_df, &len, sizeof(len));
+                                               username = calloc(1, len + 1);
+                                               read(controlsocket_df, username, len);
+                                               /*
+                                                * Do not further expand since
+                                                * its remote or local mailbox
+                                                */
+                                               if (add_recp(queue, username, sender, 0) != 0) {
+                                                       aliased = 1;
+                                               }
+                                       } else if (type & ISPIPE) {
+                                               /* redirect to a pipe */
+                                               /*
+                                                * Create new qitem and save
+                                                * information in it
+                                                */
+                                               struct qitem *pit;
+                                               pit = calloc(1, sizeof(*pit));
+                                               if (pit == NULL) {
+                                                       /*
+                                                        * Increment semaphore
+                                                        * because we stopped
+                                                        * communicating with
+                                                        * dotforwardhandler()
+                                                        */
+                                                       sema.sem_op = 1;
+                                                       semop(semkey, &sema, 1);
+                                                       return(-1);
+                                               }
+                                               LIST_INSERT_HEAD(&queue->queue, pit, next);
+                                               /*
+                                                * Save username to qitem,
+                                                * because its overwritten by
+                                                * pipe command
+                                                */
+                                               pit->pipeuser = strdup(it->addr);
+                                               pit->sender = sender;
+                                               /* local = 2 means redirect to pipe */
+                                               pit->local = 2;
+                                               read(controlsocket_df, &len, sizeof(len));
+                                               pit->addr = realloc(pit->addr, len + 1);
+                                               memset(pit->addr, 0, len + 1);
+                                               read(controlsocket_df, pit->addr, len);
+                                               aliased = 1;
+                                       }
+                               }
+                               /*
+                                * Increment semaphore because we stopped
+                                * communicating with dotforwardhandler()
+                                */
+                               sema.sem_op = 1;
+                               semop(semkey, &sema, 1);
                        }
                }
        } else {
-               it->remote = 1;
+               it->local = 0;
        }
 
-       return (0);
+       return(0);
 
 out:
        free(it->addr);
        free(it);
-       return (-1);
+       return(-1);
 }
 
 static void
@@ -216,22 +374,23 @@ gentempf(struct queue *queue)
        int fd;
 
        if (snprintf(fn, sizeof(fn), "%s/%s", config->spooldir, "tmp_XXXXXXXXXX") <= 0)
-               return (-1);
+               return(-1);
        fd = mkstemp(fn);
        if (fd < 0)
-               return (-1);
+               return(-1);
        queue->mailfd = fd;
+
        queue->tmpf = strdup(fn);
        if (queue->tmpf == NULL) {
                unlink(fn);
-               return (-1);
+               return(-1);
        }
        t = malloc(sizeof(*t));
        if (t != NULL) {
                t->str = queue->tmpf;
                SLIST_INSERT_HEAD(&tmpfs, t, next);
        }
-       return (0);
+       return(0);
 }
 
 /*
@@ -260,48 +419,52 @@ preparespool(struct queue *queue, const char *sender)
        error = snprintf(line, sizeof(line), "%s\n", sender);
        if (error < 0 || (size_t)error >= sizeof(line)) {
                errno = E2BIG;
-               return (-1);
+               return(-1);
        }
        if (write(queue->mailfd, line, error) != error)
-               return (-1);
+               return(-1);
 
        queuef = fdopen(queue->mailfd, "r+");
        if (queuef == NULL)
-               return (-1);
+               return(-1);
 
        /*
         * Assign queue id to each dest.
         */
        if (fstat(queue->mailfd, &st) != 0)
-               return (-1);
+               return(-1);
        queue->id = st.st_ino;
        LIST_FOREACH(it, &queue->queue, next) {
                if (asprintf(&it->queueid, "%"PRIxMAX".%"PRIxPTR,
                             queue->id, (uintptr_t)it) <= 0)
-                       return (-1);
+                       return(-1);
                if (asprintf(&it->queuefn, "%s/%s",
                             config->spooldir, it->queueid) <= 0)
-                       return (-1);
-               /* File may not exist yet */
-               if (stat(it->queuefn, &st) == 0)
-                       return (-1);
+                       return(-1);
+               /* File may already exist */
+               if (stat(it->queuefn, &st) == 0) {
+                       warn("Spoolfile already exists: %s", it->queuefn);
+                       return(-1);
+               }
+               /* Reset errno to avoid confusion */
+               errno = 0;
                it->queuef = queuef;
                error = snprintf(line, sizeof(line), "%s %s\n",
                               it->queueid, it->addr);
                if (error < 0 || (size_t)error >= sizeof(line))
-                       return (-1);
+                       return(-1);
                if (write(queue->mailfd, line, error) != error)
-                       return (-1);
+                       return(-1);
        }
        line[0] = '\n';
        if (write(queue->mailfd, line, 1) != 1)
-               return (-1);
+               return(-1);
 
        hdrlen = lseek(queue->mailfd, 0, SEEK_CUR);
        LIST_FOREACH(it, &queue->queue, next) {
                it->hdrlen = hdrlen;
        }
-       return (0);
+       return(0);
 }
 
 static char *
@@ -316,7 +479,7 @@ rfc822date(void)
                       localtime(&now));
        if (error == 0)
                strcpy(str, "(date fail)");
-       return (str);
+       return(str);
 }
 
 static int
@@ -338,9 +501,9 @@ Received: from %s (uid %d)\n\
                hostname(), VERSION,
                rfc822date());
        if (error < 0 || (size_t)error >= sizeof(line))
-               return (-1);
+               return(-1);
        if (write(queue->mailfd, line, error) != error)
-               return (-1);
+               return(-1);
 
        while (!feof(stdin)) {
                if (fgets(line, sizeof(line), stdin) == NULL)
@@ -348,16 +511,16 @@ Received: from %s (uid %d)\n\
                linelen = strlen(line);
                if (linelen == 0 || line[linelen - 1] != '\n') {
                        errno = EINVAL;         /* XXX mark permanent errors */
-                       return (-1);
+                       return(-1);
                }
                if (!nodot && linelen == 2 && line[0] == '.')
                        break;
                if ((size_t)write(queue->mailfd, line, linelen) != linelen)
-                       return (-1);
+                       return(-1);
        }
        if (fsync(queue->mailfd) != 0)
-               return (-1);
-       return (0);
+               return(-1);
+       return(0);
 }
 
 static int
@@ -365,43 +528,81 @@ linkspool(struct queue *queue)
 {
        struct qitem *it;
 
+       /*
+        * Only if it is not a pipe delivery
+        * pipe deliveries are only tried once so there
+        * is no need for a spool-file, they use the
+        * original tempfile
+        */
+
        LIST_FOREACH(it, &queue->queue, next) {
+               /* 
+                * There shall be no files for pipe deliveries since not all
+                * information is saved in the header, so pipe delivery is
+                * tried once and forgotten thereafter.
+                */
+               if (it->local == 2) 
+                       continue;
                if (link(queue->tmpf, it->queuefn) != 0)
                        goto delfiles;
        }
-       unlink(queue->tmpf);
-       return (0);
+       return(0);
 
 delfiles:
        LIST_FOREACH(it, &queue->queue, next) {
+               /*
+                * There are no files for pipe delivery, so they can't be
+                * deleted.
+                */
+               if (it->local == 2) 
+                       continue;
                unlink(it->queuefn);
        }
-       return (-1);
+       return(-1);
 }
 
-static struct qitem *
-go_background(struct queue *queue)
+static void
+go_background(struct queue *queue, int leavesemaphore)
 {
        struct sigaction sa;
        struct qitem *it;
        pid_t pid;
+       int seen_remote_address = 0;
 
        if (daemonize && daemon(0, 0) != 0) {
-               syslog(LOG_ERR, "can not daemonize: %m");
+               syslog(LOG_ERR, "[go_background] can not daemonize: %m");
                exit(1);
        }
        daemonize = 0;
-
        bzero(&sa, sizeof(sa));
        sa.sa_flags = SA_NOCLDWAIT;
        sa.sa_handler = SIG_IGN;
        sigaction(SIGCHLD, &sa, NULL);
 
-       LIST_FOREACH(it, &queue->queue, next) {
-               /* No need to fork for the last dest */
-               if (LIST_NEXT(it, next) == NULL)
-                       return (it);
 
+       LIST_FOREACH(it, &queue->queue, next) {
+               /* 
+                * If smarthost is enabled, the address is remote
+                * set smarthost delivery flag, otherwise deliver it 'normal'.
+                */
+               if (config->smarthost != NULL && strlen(config->smarthost) > 0
+                   && it->local == 0
+                  ) {
+                       seen_remote_address = 1;
+                       /*
+                        * if it is not the last entry, continue
+                        * (if it is the last, start delivery in parent
+                        */
+                       if (LIST_NEXT(it, next) != NULL) {
+                               continue;
+                       }
+               } else {
+                       /*
+                        * If item is local, we do not need it in the list any
+                        * more, so delete it.
+                        */
+                       LIST_REMOVE(it, next);
+               }
                pid = fork();
                switch (pid) {
                case -1:
@@ -415,7 +616,15 @@ go_background(struct queue *queue)
                         *
                         * return and deliver mail
                         */
-                       return (it);
+
+                       if (config->smarthost == NULL || strlen(config->smarthost) == 0 || it->local)
+                               if (LIST_NEXT(it, next) == NULL && !seen_remote_address)
+                                       /* if there is no smarthost-delivery and we are the last item */
+                                       deliver(it, leavesemaphore);
+                               else 
+                                       deliver(it, 0);
+                       else
+                               _exit(0);
 
                default:
                        /*
@@ -423,6 +632,19 @@ go_background(struct queue *queue)
                         *
                         * fork next child
                         */
+                       /*
+                        * If it is the last loop and there were remote
+                        * addresses, start smarthost delivery.
+                        * No need to doublecheck if smarthost is
+                        * activated in config file.
+                        */
+                       if (LIST_NEXT(it, next) == NULL) {
+                               if (seen_remote_address) {
+                                       deliver_smarthost(queue, leavesemaphore);
+                               } else {
+                                       _exit(0);
+                               }
+                       } 
                        break;
                }
        }
@@ -432,17 +654,33 @@ go_background(struct queue *queue)
 }
 
 static void
-bounce(struct qitem *it, const char *reason)
+bounce(struct qitem *it, const char *reason, int leavesemaphore)
 {
        struct queue bounceq;
        struct qitem *bit;
        char line[1000];
        int error;
+       struct sembuf sema;
 
        /* Don't bounce bounced mails */
        if (it->sender[0] == 0) {
+               /*
+                * If we are the last bounce, then decrement semaphore
+                * and release children.
+                */
+               if (leavesemaphore) {
+                       /* semaphore-- (MUST NOT BLOCK BECAUSE ITS POSITIVE) */
+                       sema.sem_num = SEM_SIGHUP;
+                       sema.sem_op = -1;
+                       sema.sem_flg = IPC_NOWAIT;
+                       if (semop(semkey, &sema, 1) == -1) {
+                               err(1, "[deliver] semaphore decrement failed");
+                       }
+                       /* release child processes */
+                       release_children();
+               }
                syslog(LOG_CRIT, "%s: delivery panic: can't bounce a bounce",
-                      it->queueid);
+                       it->queueid);
                exit(1);
        }
 
@@ -512,11 +750,25 @@ Message headers follow.\n\
        unlink(it->queuefn);
        fclose(it->queuef);
 
-       bit = go_background(&bounceq);
-       deliver(bit);
+       go_background(&bounceq, leavesemaphore);
        /* NOTREACHED */
 
 fail:
+       /*
+        * If we are the last bounce, then decrement semaphore
+        * and release children.
+        */
+       if (leavesemaphore) {
+               /* semaphore-- (MUST NOT BLOCK BECAUSE ITS POSITIVE) */
+               sema.sem_num = SEM_SIGHUP;
+               sema.sem_op = -1;
+               sema.sem_flg = IPC_NOWAIT;
+               if (semop(semkey, &sema, 1) == -1) {
+                       err(1, "[deliver] semaphore decrement failed");
+               }
+               /* release child processes */
+               release_children();
+       }
        syslog(LOG_CRIT, "%s: error creating bounce: %m", it->queueid);
        unlink(it->queuefn);
        exit(1);
@@ -525,114 +777,308 @@ fail:
 static int
 deliver_local(struct qitem *it, const char **errmsg)
 {
-       char fn[PATH_MAX+1];
        char line[1000];
+       char fn[PATH_MAX+1];
+       int len;
+       uint8_t mode = 0, fail = 0;
        size_t linelen;
-       int mbox;
-       int error;
-       off_t mboxlen;
        time_t now = time(NULL);
+       char *username;
+       struct sembuf sema;
 
-       error = snprintf(fn, sizeof(fn), "%s/%s", _PATH_MAILDIR, it->addr);
-       if (error < 0 || (size_t)error >= sizeof(fn)) {
-               syslog(LOG_ERR, "%s: local delivery deferred: %m",
-                      it->queueid);
-               return (1);
+
+       /*
+        * Try to decrement semaphore as we start communicating with
+        * write_to_local_user()
+        */
+       sema.sem_num = SEM_WL;
+       sema.sem_op = -1;
+       sema.sem_flg = 0;
+       if (semop(semkey, &sema, 1) == -1) {
+               err(1, "semaphore decrement failed");
+       }
+
+
+       /* Tell write_to_local_user() the username to drop the privileges */
+       if (it->local == 1) { /* mailbox delivery */
+               username = it->addr;
+       } else if (it->local == 2) { /* pipe delivery */
+               username = it->pipeuser;
+       }
+       len = strlen(username);
+       write(controlsocket_wl, &len, sizeof(len));
+       write(controlsocket_wl, username, len);
+       read(controlsocket_wl, &fail, sizeof(fail));
+       if (fail) {
+               syslog(LOG_ERR,
+                       "%s: local delivery deferred: can not fork and drop privileges `%s': %m",
+                       it->queueid, username);
+               /*
+                * Increment semaphore because we stopped communicating with
+                * write_to_local_user().
+                */
+               sema.sem_op = 1;
+               semop(semkey, &sema, 1);
+               return(1);
        }
 
-       /* mailx removes users mailspool file if empty, so open with O_CREAT */
-       mbox = open(fn, O_WRONLY | O_EXLOCK | O_APPEND | O_CREAT);
-       if (mbox < 0) {
-               syslog(LOG_ERR, "%s: local delivery deferred: can not open `%s': %m",
-                      it->queueid, fn);
-               return (1);
+
+       /* Tell write_to_local_user() the delivery mode (write to mailbox || pipe) */
+       if (it->local == 1) { /* mailbox delivery */
+               mode = ISMAILBOX;
+               len = snprintf(fn, sizeof(fn), "%s/%s", _PATH_MAILDIR, it->addr);
+               if (len < 0 || (size_t)len >= sizeof(fn)) {
+                       syslog(LOG_ERR, "%s: local delivery deferred: %m",
+                                       it->queueid);
+                       /*
+                        * Increment semaphore because we stopped communicating
+                        * with write_to_local_user().
+                        */
+                       sema.sem_op = 1;
+                       semop(semkey, &sema, 1);
+                       return(1);
+               }
+       } else if (it->local == 2) { /* pipe delivery */
+               mode = ISPIPE;
+               strncpy(fn, it->addr, sizeof(fn));
+               len = strlen(fn);
        }
-       mboxlen = lseek(mbox, 0, SEEK_CUR);
+       write(controlsocket_wl, &len, sizeof(len));
+       write(controlsocket_wl, fn, len);
+       write(controlsocket_wl, &mode, sizeof(mode));
+       read(controlsocket_wl, &fail, sizeof(fail));
+       if (fail) {
+               errno = fail;
+               syslog(LOG_ERR,
+                       "%s: local delivery deferred: can not (p)open `%s': %m",
+                       it->queueid, it->addr);
+               /*
+                * Increment semaphore because we stopped communicating
+                * with write_to_local_user().
+                */
+               sema.sem_op = 1;
+               semop(semkey, &sema, 1);
+               return(1);
+       }
+
 
+       /* Prepare transfer of mail-data */
        if (fseek(it->queuef, it->hdrlen, SEEK_SET) != 0) {
                syslog(LOG_ERR, "%s: local delivery deferred: can not seek: %m",
                       it->queueid);
-               return (1);
+               /*
+                * Increment semaphore because we stopped communicating
+                * with write_to_local_user().
+                */
+               sema.sem_op = 1;
+               semop(semkey, &sema, 1);
+               return(1);
        }
 
-       error = snprintf(line, sizeof(line), "From %s\t%s", it->sender, ctime(&now));
-       if (error < 0 || (size_t)error >= sizeof(line)) {
+
+       /* Send first header line. */
+       linelen = snprintf(line, sizeof(line), "From %s\t%s", it->sender, ctime(&now));
+       if (linelen < 0 || (size_t)linelen >= sizeof(line)) {
                syslog(LOG_ERR, "%s: local delivery deferred: can not write header: %m",
                       it->queueid);
-               return (1);
+               /*
+                * Increment semaphore because we stopped communicating
+                * with write_to_local_user().
+                */
+               sema.sem_op = 1;
+               semop(semkey, &sema, 1);
+               return(1);
        }
-       if (write(mbox, line, error) != error)
+
+       write(controlsocket_wl, &linelen, sizeof(linelen));
+       write(controlsocket_wl, line, linelen);
+
+       read(controlsocket_wl, &fail, sizeof(fail));
+       if (fail) {
                goto wrerror;
+       }
 
+
+       /* Read mail data and transfer it to write_to_local_user(). */
        while (!feof(it->queuef)) {
                if (fgets(line, sizeof(line), it->queuef) == NULL)
                        break;
                linelen = strlen(line);
                if (linelen == 0 || line[linelen - 1] != '\n') {
-                       syslog(LOG_CRIT, "%s: local delivery failed: corrupted queue file",
-                              it->queueid);
+                       syslog(LOG_CRIT,
+                               "%s: local delivery failed: corrupted queue file",
+                               it->queueid);
                        *errmsg = "corrupted queue file";
-                       error = -1;
+                       len = -1;
+                       /* break receive and write loop at write_to_local_user() */
+                       linelen = 0;
+                       write(controlsocket_wl, &linelen, sizeof(linelen)); 
+                       /* and send error state */
+                       linelen = 1;
+                       write(controlsocket_wl, &linelen, sizeof(linelen)); 
                        goto chop;
                }
 
                if (strncmp(line, "From ", 5) == 0) {
                        const char *gt = ">";
+                       size_t sizeofchar = 1;
 
-                       if (write(mbox, gt, 1) != 1)
+                       write(controlsocket_wl, &sizeofchar, sizeof(sizeofchar));
+                       write(controlsocket_wl, gt, 1);
+                       read(controlsocket_wl, &fail, sizeof(fail));
+                       if (fail) {
                                goto wrerror;
+                       }
                }
-               if ((size_t)write(mbox, line, linelen) != linelen)
+               write(controlsocket_wl, &linelen, sizeof(linelen)); 
+               write(controlsocket_wl, line, linelen); 
+               read(controlsocket_wl, &fail, sizeof(fail));
+               if (fail) {
                        goto wrerror;
+               }
        }
+
+       /* Send final linebreak */
        line[0] = '\n';
-       if (write(mbox, line, 1) != 1)
+       linelen = 1;
+       write(controlsocket_wl, &linelen, sizeof(linelen)); 
+       write(controlsocket_wl, line, linelen); 
+       read(controlsocket_wl, &fail, sizeof(fail));
+       if (fail) {
                goto wrerror;
-       close(mbox);
-       return (0);
+       }
+
+
+       /* break receive and write loop in write_to_local_user() */
+       linelen = 0;
+       /* send '0' twice, because above we send '0' '1' in case of error */
+       write(controlsocket_wl, &linelen, sizeof(linelen)); 
+       write(controlsocket_wl, &linelen, sizeof(linelen)); 
+       read(controlsocket_wl, &fail, sizeof(fail));
+       if (fail) {
+               goto wrerror;
+       }
+
+
+       /*
+        * Increment semaphore because we stopped communicating
+        * with write_to_local_user().
+        */
+       sema.sem_op = 1;
+       semop(semkey, &sema, 1);
+       return(0);
 
 wrerror:
+       errno = fail;
        syslog(LOG_ERR, "%s: local delivery failed: write error: %m",
               it->queueid);
-       error = 1;
+       len = 1;
 chop:
-       if (ftruncate(mbox, mboxlen) != 0)
+       read(controlsocket_wl, &fail, sizeof(fail));
+       if (fail == 2) {
                syslog(LOG_WARNING, "%s: error recovering mbox `%s': %m",
-                      it->queueid, fn);
-       close(mbox);
-       return (error);
+                       it->queueid, fn);
+       }
+       /*
+        * Increment semaphore because we stopped communicating
+        * with write_to_local_user().
+        */
+       sema.sem_op = 1;
+       semop(semkey, &sema, 1);
+       return(len);
 }
 
 static void
-deliver(struct qitem *it)
+deliver(struct qitem *it, int leavesemaphore)
 {
        int error;
        unsigned int backoff = MIN_RETRY;
        const char *errmsg = "unknown bounce reason";
        struct timeval now;
        struct stat st;
+       struct sembuf sema;
 
-       syslog(LOG_INFO, "%s: mail from=<%s> to=<%s>",
-              it->queueid, it->sender, it->addr);
+       if (it->local == 2) {
+               syslog(LOG_INFO, "%s: mail from=<%s> to=<%s> command=<%s>",
+                               it->queueid, it->sender, it->pipeuser, it->addr);
+       } else {
+               syslog(LOG_INFO, "%s: mail from=<%s> to=<%s>",
+                               it->queueid, it->sender, it->addr);
+       }
 
 retry:
        syslog(LOG_INFO, "%s: trying delivery",
               it->queueid);
 
-       if (it->remote)
-               error = deliver_remote(it, &errmsg);
-       else
+       /*
+        * Only increment semaphore, if we are not the last bounce
+        * because there is still a incremented semaphore from
+        * the bounced delivery
+        */
+       if (!leavesemaphore) {
+               /*
+                * Increment semaphore for each mail we try to deliver.
+                * When completing the transmit, the semaphore is decremented.
+                * If the semaphore is zero the other childs know that they
+                * can terminate.
+                */
+               sema.sem_num = SEM_SIGHUP;
+               sema.sem_op = 1;
+               sema.sem_flg = 0;
+               if (semop(semkey, &sema, 1) == -1) {
+                       err(1, "[deliver] semaphore increment failed");
+               }
+       }
+       if (it->local) {
                error = deliver_local(it, &errmsg);
+       } else {
+               error = deliver_remote(it, &errmsg, NULL);
+       }
 
        switch (error) {
        case 0:
-               unlink(it->queuefn);
+               /* semaphore-- (MUST NOT BLOCK BECAUSE ITS POSITIVE) */
+               sema.sem_num = SEM_SIGHUP;
+               sema.sem_op = -1;
+               sema.sem_flg = IPC_NOWAIT;
+               if (semop(semkey, &sema, 1) == -1) {
+                       err(1, "[deliver] semaphore decrement failed");
+               }
+               /* release child processes */
+               release_children();
+               /* Do not try to delete the spool file: pipe mode */
+               if (it->local != 2) 
+                       unlink(it->queuefn);
                syslog(LOG_INFO, "%s: delivery successful",
                       it->queueid);
                exit(0);
 
        case 1:
+               /* pipe delivery only tries once, then gives up */
+               if (it->local == 2) {
+                       /* decrement-- (MUST NOT BLOCK BECAUSE ITS POSITIVE) */
+                       sema.sem_num = SEM_SIGHUP;
+                       sema.sem_op = -1;
+                       sema.sem_flg = IPC_NOWAIT;
+                       if (semop(semkey, &sema, 1) == -1) {
+                               err(1, "[deliver] semaphore decrement failed");
+                       }
+                       /* release child processes */
+                       release_children();
+                       syslog(LOG_ERR, "%s: delivery to pipe `%s' failed, giving up",
+                              it->queueid, it->addr);
+                       exit(1);
+               }
                if (stat(it->queuefn, &st) != 0) {
+                       /* semaphore-- (MUST NOT BLOCK BECAUSE ITS POSITIVE) */
+                       sema.sem_num = SEM_SIGHUP;
+                       sema.sem_op = -1;
+                       sema.sem_flg = IPC_NOWAIT;
+                       if (semop(semkey, &sema, 1) == -1) {
+                               err(1, "[deliver] semaphore decrement failed");
+                       }
+                       /* release child processes */
+                       release_children();
                        syslog(LOG_ERR, "%s: lost queue file `%s'",
                               it->queueid, it->queuefn);
                        exit(1);
@@ -642,8 +1088,8 @@ retry:
                        char *msg;
 
                        if (asprintf(&msg,
-                                "Could not deliver for the last %d seconds. Giving up.",
-                                MAX_TIMEOUT) > 0)
+                           "Could not deliver for the last %d seconds. Giving up.",
+                           MAX_TIMEOUT) > 0)
                                errmsg = msg;
                        goto bounce;
                }
@@ -659,10 +1105,253 @@ retry:
        }
 
 bounce:
-       bounce(it, errmsg);
+       bounce(it, errmsg, 1);
        /* NOTREACHED */
 }
 
+/*
+ * deliver_smarthost() is similar to deliver(), but has some differences:
+ * -deliver_smarthost() works with a queue
+ * -each entry in this queue has a corresponding file in the spooldir
+ * -if the mail is sent correctly to a address, delete the corresponding file,
+ *     even if there were errors with other addresses
+ * -so deliver_remote must tell deliver_smarthost to which addresses it has
+ *  successfully sent the mail
+ *  -this can be done with 3 queues:
+ *   -one queue for sent mails
+ *   -one queue for 4xx addresses (tempfail)
+ *   -one queue for 5xx addresses (permfail)
+ *  -the sent mails are deleted
+ *  -the 4xx are tried again
+ *  -the 5xx are bounced
+ */
+
+static void
+deliver_smarthost(struct queue *queue, int leavesemaphore)
+{
+       int error, bounces = 0;
+       unsigned int backoff = MIN_RETRY;
+       const char *errmsg = "unknown bounce reason";
+       struct timeval now;
+       struct stat st;
+       struct sembuf sema;
+       struct qitem *it, *tit;
+       struct queue *queues[4], *bouncequeue, successqueue, tempfailqueue,
+               permfailqueue;
+
+       /*
+        * only increment semaphore, if we are not the last bounce
+        * because there is still a incremented semaphore from
+        * the bounced delivery
+        */
+       if (!leavesemaphore) {
+               /*
+                * Increment semaphore for each mail we try to deliver.
+                * When completing the transmit, the semaphore is decremented.
+                * If the semaphore is zero the other childs know that they
+                * can terminate.
+                */
+               sema.sem_num = SEM_SIGHUP;
+               sema.sem_op = 1;
+               sema.sem_flg = 0;
+               if (semop(semkey, &sema, 1) == -1) {
+                       err(1, "[deliver] semaphore increment failed");
+               }
+       }
+
+       queues[0] = queue;
+       queues[1] = &successqueue;
+       queues[2] = &tempfailqueue;
+       queues[3] = &permfailqueue;
+
+retry:
+       /* initialise 3 empty queues and link it in queues[] */
+       LIST_INIT(&queues[1]->queue); /* successful sent items */
+       LIST_INIT(&queues[2]->queue); /* temporary error items */
+       LIST_INIT(&queues[3]->queue); /* permanent error items */
+
+       it = LIST_FIRST(&queues[0]->queue);
+
+       syslog(LOG_INFO, "%s: trying delivery",
+              it->queueid);
+
+       /* if queuefile of first qitem is gone, the mail can't be sended out */
+       if (stat(it->queuefn, &st) != 0) {
+                       syslog(LOG_ERR, "%s: lost queue file `%s'",
+                              it->queueid, it->queuefn);
+                       /* semaphore-- (MUST NOT BLOCK BECAUSE ITS POSITIVE) */
+                       sema.sem_num = SEM_SIGHUP;
+                       sema.sem_op = -1;
+                       sema.sem_flg = IPC_NOWAIT;
+                       if (semop(semkey, &sema, 1) == -1) {
+                               err(1, "[deliver] semaphore decrement failed");
+                       }
+                       release_children();
+                       exit(1);
+       }
+
+       error = deliver_remote(it, &errmsg, queues);
+
+       /* if there was an error, do nothing with the other 3 queues! */
+       if (error == 0) {
+
+               /*
+                * If there are permanent errors, bounce items in permanent
+                * error queue.
+                */
+               if (!LIST_EMPTY(&queues[3]->queue)) {
+                       bounces = 1;
+                       pid_t pid;
+                       pid = fork();
+                       switch (pid) {
+                               case -1:
+                                       syslog(LOG_ERR, "can not fork: %m");
+                                       exit(1);
+                                       break;
+
+                               case 0:
+                                       /*
+                                        * Child:
+                                        *
+                                        * Tell which queue to bounce and set 
+                                        * errmsg.  Child will exit as soon as
+                                        * all childs for bounces are spawned.
+                                        * So no need to set up a signal handler.
+                                        */
+                                       bouncequeue = queues[3];
+                                       errmsg = "smarthost sent permanent error (5xx)";
+                                       goto bounce;
+
+                               default:
+                                       /*
+                                        * Parent:
+                                        *
+                                        * continue with stuff
+                                        */
+                                       break;
+                       }
+               }
+
+               /* delete successfully sent items */
+               if (!LIST_EMPTY(&queues[1]->queue)) {
+                       LIST_FOREACH(tit, &queues[1]->queue, next) {
+                               unlink(tit->queuefn);
+                               LIST_REMOVE(tit, next);
+                               syslog(LOG_INFO, "%s: delivery successful",
+                                               tit->queueid);
+                       }
+               }
+       }
+
+       /* If the temporary error queue is empty and there was no error, finish */
+       if (LIST_EMPTY(&queues[2]->queue) && error == 0) {
+               /* only decrement semaphore if there were no bounces! */
+               if (!bounces) {
+                       /* semaphore-- (MUST NOT BLOCK BECAUSE ITS POSITIVE) */
+                       sema.sem_num = SEM_SIGHUP;
+                       sema.sem_op = -1;
+                       sema.sem_flg = IPC_NOWAIT;
+                       if (semop(semkey, &sema, 1) == -1) {
+                               err(1, "[deliver] semaphore decrement failed");
+                       }
+                       /* release child processes */
+                       release_children();
+               }
+               exit(0);
+
+               /* if there are remaining items, set up retry timer */
+       } else {
+
+               /*
+                * if there was an error, do not touch queues[0]!
+                * and try to deliver all items again
+                */
+
+               if (!error) {
+                       /* wipe out old queue */
+                       if (!LIST_EMPTY(&queues[0]->queue)) {
+                               LIST_FOREACH(tit, &queues[0]->queue, next) {
+                                       unlink(tit->queuefn);
+                                       LIST_REMOVE(tit, next);
+                               }
+                               LIST_INIT(&queues[0]->queue);
+                       }
+                       /* link temporary error queue to queues[0] */
+                       queues[0] = &tempfailqueue;
+                       /* and link queues[2] to wiped out queue */
+                       queues[2] = queue;
+               }
+
+               if (gettimeofday(&now, NULL) == 0 &&
+                   (now.tv_sec - st.st_mtimespec.tv_sec > MAX_TIMEOUT)) {
+                       char *msg;
+
+                       if (asprintf(&msg,
+                               "Could not deliver for the last %d seconds. Giving up.",
+                               MAX_TIMEOUT) > 0) {
+                               errmsg = msg;
+                       }
+                       /* bounce remaining items which have temporary errors */
+                       bouncequeue = queues[2];
+                       goto bounce;
+               }
+               sleep(backoff);
+               backoff *= 2;
+               if (backoff > MAX_RETRY)
+                       backoff = MAX_RETRY;
+               goto retry;
+       }
+
+bounce:
+       LIST_FOREACH(tit, &bouncequeue->queue, next) {
+               struct sigaction sa;
+               pid_t pid;
+               bzero(&sa, sizeof(sa));
+               sa.sa_flags = SA_NOCLDWAIT;
+               sa.sa_handler = SIG_IGN;
+               sigaction(SIGCHLD, &sa, NULL);
+
+               /* fork is needed, because bounce() does not return */
+               pid = fork();
+               switch (pid) {
+                       case -1:
+                               syslog(LOG_ERR, "can not fork: %m");
+                               exit(1);
+                               break;
+
+                       case 0:
+                               /*
+                                * Child:
+                                *
+                                * bounce mail
+                                */
+
+                               LIST_REMOVE(tit, next);
+                               if (LIST_NEXT(tit, next) == NULL) {
+                                       /*
+                                        * For the last bounce, do not increment
+                                        * the semaphore when delivering the
+                                        * bounce.
+                                        */
+                                       bounce(tit, errmsg, 1);
+                               } else {
+                                       bounce(tit, errmsg, 0);
+                               }
+                               /* NOTREACHED */
+
+                       default:
+                               /*
+                                * Parent:
+                                */
+                               break;
+               }
+
+       }
+       /* last parent shall exit, too */
+       _exit(0);
+       /* NOTREACHED */
+}
+
 static void
 load_queue(struct queue *queue)
 {
@@ -789,8 +1478,7 @@ run_queue(struct queue *queue)
        if (LIST_EMPTY(&queue->queue))
                return;
 
-       it = go_background(queue);
-       deliver(it);
+       go_background(queue, 0);
        /* NOTREACHED */
 }
 
@@ -820,8 +1508,8 @@ To\t: %s\n--\n", it->queueid, it->sender, it->addr);
  * - proper sysexit codes
  */
 
-int
-main(int argc, char **argv)
+static int
+parseandexecute(int argc, char **argv)
 {
        char *sender = NULL;
        char tag[255];
@@ -830,6 +1518,7 @@ main(int argc, char **argv)
        struct queue lqueue;
        int i, ch;
        int nodot = 0, doqueue = 0, showq = 0;
+       uint8_t null = 0, recipient_add_success = 0;
 
        atexit(deltmp);
        LIST_INIT(&queue.queue);
@@ -879,6 +1568,7 @@ main(int argc, char **argv)
                        break;
 
                default:
+                       release_children();
                        exit(1);
                }
        }
@@ -895,16 +1585,21 @@ main(int argc, char **argv)
        memset(config, 0, sizeof(struct config));
        if (parse_conf(CONF_PATH, config) < 0) {
                free(config);
+               release_children();
                errx(1, "reading config file");
        }
 
        if (config->features & VIRTUAL)
-               if (parse_virtuser(config->virtualpath) < 0)
+               if (parse_virtuser(config->virtualpath) < 0) {
+                       release_children();
                        errx(1, "error reading virtual user file: %s",
                                config->virtualpath);
+               }
 
-       if (parse_authfile(config->authpath) < 0)
+       if (parse_authfile(config->authpath) < 0) {
+               release_children();
                err(1, "reading SMTP authentication file");
+       }
 
        if (showq) {
                if (argc != 0)
@@ -912,7 +1607,7 @@ main(int argc, char **argv)
                                " mutually exclusive");
                load_queue(&lqueue);
                show_queue(&lqueue);
-               return (0);
+               return(0);
        }
 
        if (doqueue) {
@@ -920,44 +1615,505 @@ main(int argc, char **argv)
                        errx(1, "sending mail and queue pickup is mutually exclusive");
                load_queue(&lqueue);
                run_queue(&lqueue);
-               return (0);
+               return(0);
        }
 
-       if (read_aliases() != 0)
+       if (read_aliases() != 0) {
+               release_children();
                err(1, "reading aliases");
+       }
 
-       if ((sender = set_from(sender)) == NULL)
+       if ((sender = set_from(sender)) == NULL) {
+               release_children();
                err(1, "setting from address");
+       }
+
+       if (gentempf(&queue) != 0) {
+               release_children();
+               err(1, "create temp file");
+       }
 
        for (i = 0; i < argc; i++) {
-               if (add_recp(&queue, argv[i], sender, 1) != 0)
+               if (add_recp(&queue, argv[i], sender, 1) != 0) {
+                       release_children();
                        errx(1, "invalid recipient `%s'\n", argv[i]);
+               }
        }
 
-       if (LIST_EMPTY(&queue.queue))
+       if (LIST_EMPTY(&queue.queue)) {
+               release_children();
                errx(1, "no recipients");
+       }
 
-       if (gentempf(&queue) != 0)
-               err(1, "create temp file");
-
-       if (preparespool(&queue, sender) != 0)
+       if (preparespool(&queue, sender) != 0) {
+               release_children();
                err(1, "creating spools (1)");
+       }
 
-       if (readmail(&queue, sender, nodot) != 0)
+       if (readmail(&queue, sender, nodot) != 0) {
+               release_children();
                err(1, "reading mail");
+       }
 
-       if (linkspool(&queue) != 0)
+       if (linkspool(&queue) != 0) {
+               release_children();
                err(1, "creating spools (2)");
+       }
 
        /* From here on the mail is safe. */
 
        if (config->features & DEFER)
-               return (0);
+               return(0);
 
-       it = go_background(&queue);
-       deliver(it);
+       go_background(&queue, 0);
 
        /* NOTREACHED */
 
-       return (0);
+       return(0);
+}
+
+/*
+ * dotforwardhandler() waits for incoming username
+ * for each username, the .forward file is read and parsed
+ * earch entry is given back to add_recp which communicates
+ * with dotforwardhandler()
+ */
+static int
+dotforwardhandler()
+{
+       pid_t pid;
+       fd_set rfds;
+       int ret;
+       uint8_t stmt, namelength;
+
+       FD_ZERO(&rfds);
+       FD_SET(clientsocket_df, &rfds);
+
+       /* wait for incoming usernames */
+       ret = select(clientsocket_df + 1, &rfds, NULL, NULL, NULL);
+       if (ret == -1) {
+               return(-1);
+       }
+       while (read(clientsocket_df, &namelength, sizeof(namelength))) {
+               char *username;
+               struct passwd *userentry;
+               if (namelength == 0) {
+                       /* there will be no more usernames, we can terminate */
+                       break;
+               }
+               /* read username and get homedir */
+               username = calloc(1, namelength + 1);
+               read(clientsocket_df, username, namelength);
+               userentry = getpwnam(username);
+               endpwent();
+
+               pid = fork();
+               if (pid == 0) { /* child */
+                       FILE *forward;
+                       char *dotforward;
+                       /* drop privileges to user */
+                       if (chdir("/"))
+                               return(-1);
+                       if (initgroups(username, userentry->pw_gid))
+                               return(-1);
+                       if (setgid(userentry->pw_gid))
+                               return(-1);
+                       if (setuid(userentry->pw_uid))
+                               return(-1);
+
+                       /* read ~/.forward */
+                       dotforward = strdup(userentry->pw_dir);
+                       forward = fopen(strcat(dotforward, "/.forward"), "r");
+                       if (forward == NULL) { /* no dotforward */
+                               stmt = ENDOFDOTFORWARD;
+                               write(clientsocket_df, &stmt, 1);
+                               continue;
+                       }
+
+
+                       /* parse ~/.forward */
+                       while (!feof(forward)) { /* each line in ~/.forward */
+                               char *target = NULL;
+                               /* 255 Bytes should be enough for a pipe and a emailaddress */
+                               uint8_t len;
+                               char line[2048];
+                               memset(line, 0, 2048);
+                               fgets(line, sizeof(line), forward);
+                               /* FIXME allow comments? */
+                               if ((target = strtok(line, "\t\n")) != NULL)
+                               if (strncmp(target, "|", 1) == 0) {
+                                       /* if first char is a '|', the line is a pipe */
+                                       stmt = ISPIPE;
+                                       write(clientsocket_df, &stmt, 1);
+                                       len = strlen(target);
+                                       /* remove the '|' */
+                                       len--;
+                                       /* send result back to add_recp */
+                                       write(clientsocket_df, &len, sizeof(len));
+                                       write(clientsocket_df, target + 1, len);
+                               } else {
+                                       /* if first char is not a '|', the line is a mailbox */
+                                       stmt = ISMAILBOX;
+                                       write(clientsocket_df, &stmt, 1);
+                                       len = strlen(target);
+                                       /* send result back to add_recp */
+                                       write(clientsocket_df, &len, sizeof(len));
+                                       write(clientsocket_df, target, len);
+                               }
+                       }
+                       stmt = ENDOFDOTFORWARD;
+                       /* send end of .forward to add_recp */
+                       write(clientsocket_df, &stmt, 1);
+                       _exit(0);
+               } else if (pid < 0) { /* fork failed */
+                       return(1);
+               } else { /* parent */
+                       /* parent waits while child is processing .forward */
+                       waitpid(-1, NULL, 0);
+               }
+       }
+}
+
+/*
+ * write_to_local_user() writes to a mailbox or
+ * to a pipe in a user context and communicates with deliver_local()
+ */
+static int
+write_to_local_user() {
+       pid_t pid;
+       int length;
+       size_t linelen;
+
+       /* wait for incoming targets */
+       while (read(clientsocket_wl, &length, sizeof(length))) {
+               char *target;
+               uint8_t mode, fail = 0;
+               char fn[PATH_MAX+1];
+               char line[1000];
+               int mbox;
+               off_t mboxlen;
+               FILE *pipe;
+               int error;
+               pid_t pid;
+               struct passwd *userentry;
+
+               target = calloc(1, length + 1);
+               if (length == 0) {
+                       struct sembuf sema;
+                       int retval;
+                       /* check if semaphore is '0' */
+                       sema.sem_num = SEM_SIGHUP;
+                       sema.sem_op = 0;
+                       sema.sem_flg = IPC_NOWAIT;
+                       retval = semop(semkey, &sema, 1);
+                       if (retval == 0 || errno == EINVAL) {
+                               /*
+                                * if semaphore is '0' then the last mail is sent
+                                * and there is no need for a write_to_local_user()
+                                * so we can exit
+                                *
+                                * if errno is EINVAL, then someone has removed the semaphore, so we shall exit, too
+                                */
+                               break;
+                       } else {
+                               continue;
+                       }
+               }
+               /* read username and get uid/gid */
+               read(clientsocket_wl, target, length);
+
+               userentry = getpwnam(target);
+               endpwent();
+
+               pid = fork();
+               if (pid == 0) { /* child */
+                       /* drop privileges to user and tell if there is something wrong */
+                       if (chdir("/")) {
+                               fail = errno;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               fail = 0;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               free(target);
+                               _exit(1);
+                       }
+                       if (initgroups(target, userentry->pw_gid)) {
+                               fail = errno;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               fail = 0;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               free(target);
+                               _exit(1);
+                       }
+                       if (setgid(userentry->pw_gid)) {
+                               fail = errno;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               fail = 0;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               free(target);
+                               _exit(1);
+                       }
+                       if (setuid(userentry->pw_uid)) {
+                               fail = errno;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               fail = 0;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               free(target);
+                               _exit(1);
+                       }
+                       /* and go on with execution outside of if () */
+               } else if (pid < 0) { /* fork failed */
+                       fail = errno;
+                       write(clientsocket_wl, &fail, sizeof(fail));
+                       fail = 0;
+                       write(clientsocket_wl, &fail, sizeof(fail));
+                       free(target);
+                       _exit(1);
+               } else { /* parent */
+                       struct sembuf sema;
+                       int retval;
+                       /* wait for child to finish and continue loop */
+                       waitpid(-1, NULL, 0);
+                       /* check if semaphore is '0' */
+                       sema.sem_num = SEM_SIGHUP;
+                       sema.sem_op = 0;
+                       sema.sem_flg = IPC_NOWAIT;
+                       retval = semop(semkey, &sema, 1);
+                       if (retval == 0 || errno == EINVAL) {
+                               /*
+                                * if semaphore is '0' then the last mail is sent
+                                * and there is no need for a write_to_local_user()
+                                * so we can exit
+                                *
+                                * if errno is EINVAL, then someone has removed the semaphore, so we shall exit, too
+                                */
+                               break;
+                       } else if (errno != EAGAIN) {
+                               err(1, "[write_to_local_user] semop_op = 0 failed");
+                       }
+                       continue;
+               }
+               /* child code again here */
+               /* send ack, we are ready to go on with mode and target */
+               write(clientsocket_wl, &fail, sizeof(fail));
+
+               read(clientsocket_wl, &length, sizeof(length));
+               target = realloc(target, length + 1);
+               memset(target, 0, length + 1);
+               read(clientsocket_wl, target, length);
+               read(clientsocket_wl, &mode, sizeof(mode));
+               if (mode & ISMAILBOX) {
+                       /* if mode is mailbox, open mailbox */
+                       /* mailx removes users mailspool file if empty, so open with O_CREAT */
+                       mbox = open(target, O_WRONLY | O_EXLOCK | O_APPEND | O_CREAT, S_IRUSR | S_IWUSR);
+                       if (mbox < 0) {
+                               fail = errno;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               fail = 0;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               _exit(1);
+                       }
+                       mboxlen = lseek(mbox, 0, SEEK_CUR);
+               } else if (mode & ISPIPE) {
+                       /* if mode is mailbox, popen pipe */
+                       fflush(NULL);
+                       if ((pipe = popen(target, "w")) == NULL) {
+                               fail = errno;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               fail = 0;
+                               write(clientsocket_wl, &fail, sizeof(fail));
+                               _exit(1);
+                       }
+               }
+               /* send ack, we are ready to receive mail contents */
+               write(clientsocket_wl, &fail, sizeof(fail));
+               
+               /* write to file/pipe loop */
+               while (read(clientsocket_wl, &linelen, sizeof(linelen))) {
+                       if (linelen == 0) {
+                               read(clientsocket_wl, &linelen, sizeof(linelen)); 
+                               if (linelen == 0) {
+                                       break;
+                               } else {
+                                       /* if linelen != 0, then there is a error on sender side */
+                                       goto chop;
+                               }
+                       }
+                       /* receive line */
+                       read(clientsocket_wl, line, linelen);
+
+                       /* write line to target */
+                       if (mode & ISMAILBOX) { /* mailbox delivery */
+                               if ((size_t)write(mbox, line, linelen) != linelen) {
+                                       goto failure;
+                               }
+                       } else if (mode & ISPIPE) { /* pipe delivery */
+                               if (fwrite(line, 1, linelen, pipe) != linelen) {
+                                       goto failure;
+                               }
+                       }
+                       /* send ack */
+                       write(clientsocket_wl, &fail, sizeof(fail));
+               }
+
+               /* close target after succesfully written last line */
+               if (mode & ISMAILBOX) { /* mailbox delivery */
+                       close(mbox);
+               } else if (mode & ISPIPE) { /* pipe delivery */
+                       pclose(pipe);
+               }
+               /* send ack and exit */
+               write(clientsocket_wl, &fail, sizeof(fail));
+               _exit(0);
+failure:
+               fail = errno;
+               write(clientsocket_wl, &fail, sizeof(fail));
+chop:
+               fail = 0;
+               /* reset mailbox if there was something wrong */
+               if (mode & ISMAILBOX && ftruncate(mbox, mboxlen) != 0) {
+                       fail = 2;
+               }
+               write(clientsocket_wl, &fail, sizeof(fail));
+               if (mode & ISMAILBOX) { /* mailbox delivery */
+                       close(mbox);
+               } else if (mode & ISPIPE) { /* pipe delivery */
+                       pclose(pipe);
+               }
+               _exit(1);
+       }
+       uint8_t null = 0;
+       /* release dotforwardhandler out of loop */
+       write(controlsocket_df, &null, sizeof(null));
+       /* we do not need the semaphores any more */
+       semctl(semkey, 0, IPC_RMID, 0);
+       _exit(0);
+}
+
+int
+main(int argc, char **argv)
+{
+       pid_t pid;
+       int sockets1[2], sockets2[2];
+       struct sembuf sema;
+       struct ipc_perm semperm;
+
+       if (geteuid() != 0) {
+               fprintf(stderr, "This executable must be set setuid root!\n");
+               return(-1);
+       }
+
+       /* create socketpair for dotforwardhandler() communication */
+       if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockets1) != 0) {
+               err(1,"Socketpair1 creation failed!\n");
+       }
+       /* df is short for DotForwardhandler */
+       controlsocket_df = sockets1[0];
+       clientsocket_df = sockets1[1];
+
+       /* create socketpair for write_to_local_user() communication */
+       if (socketpair(PF_UNIX, SOCK_STREAM, 0, sockets2) != 0) {
+               err(1,"Socketpair2 creation failed!\n");
+       }
+       /* wl is short for Write_to_Local_user */
+       controlsocket_wl = sockets2[0];
+       clientsocket_wl = sockets2[1];
+
+       /*
+        * create semaphores: 
+        *      -one for exclusive dotforwardhandler communication
+        *      -one for exclusive write_to_local_user communication
+        *      -another for signaling that the queue is completely processed
+        */
+       semkey = semget(IPC_PRIVATE, 3, IPC_CREAT | IPC_EXCL | 0660);
+       if (semkey == -1) {
+               err(1,"[main] Creating semaphores failed");
+       }
+
+       /* adjust privileges of semaphores */
+       struct passwd *pw;
+       if ((pw = getpwnam("nobody")) == NULL)
+               err(1, "Can't get uid of user 'nobody'");
+       endpwent();
+
+       struct group *grp;
+       if ((grp = getgrnam("mail")) == NULL)
+               err(1, "Can't get gid of group 'mail'");
+       endgrent();
+
+       semperm.uid = pw->pw_uid;
+       semperm.gid = grp->gr_gid;
+       semperm.mode = 0660;
+       if (semctl(semkey, SEM_DF, IPC_SET, &semperm) == -1) {
+               err(1, "[main] semctl(SEM_DF)");
+       }
+       if (semctl(semkey, SEM_WL, IPC_SET, &semperm) == -1) {
+               err(1, "[main] semctl(SEM_WL)");
+       }
+       if (semctl(semkey, SEM_SIGHUP, IPC_SET, &semperm) == -1) {
+               err(1, "[main] semctl(SEM_SIGHUP)");
+       }
+
+       sema.sem_num = SEM_DF;
+       sema.sem_op = 1;
+       sema.sem_flg = 0;
+       if (semop(semkey, &sema, 1) == -1) {
+               err(1, "[main] increment semaphore SEM_DF");
+       }
+
+       sema.sem_num = SEM_WL;
+       sema.sem_op = 1;
+       sema.sem_flg = 0;
+       if (semop(semkey, &sema, 1) == -1) {
+               err(1, "[main] increment semaphore SEM_WL");
+       }
+
+       pid = fork();
+       if (pid == 0) { /* part _WITH_ root privileges */
+               /* fork another process which goes into background */
+               if (daemonize && daemon(0, 0) != 0) {
+                       syslog(LOG_ERR, "[main] can not daemonize: %m");
+                       exit(1);
+               }
+               pid = fork();
+               /* both processes are running simultaneousily */
+               if (pid == 0) { /* child */
+                       /* this process handles .forward read requests */
+                       dotforwardhandler();
+                       _exit(0);
+               } else if (pid < 0) {
+                       err(1, "[main] Fork failed!\n");
+                       return(-1);
+               } else { /* parent */
+                       /* this process writes to mailboxes if needed */
+                       write_to_local_user();
+                       _exit(0);
+               }
+       } else if (pid < 0) {
+               err(1, "Fork failed!\n");
+               return(-1);
+       } else { /* part _WITHOUT_ root privileges */
+               /* drop privileges */
+               /* FIXME to user mail? */
+               chdir("/");
+               if (initgroups("nobody", pw->pw_gid) != 0)
+                       err(1, "initgroups");
+#if 0          
+               if (setgid(grp->gr_gid) != 0) /* set to group 'mail' */
+#else
+               /* FIXME */
+               if (setgid(6) != 0) /* set to group 'mail' */
+#endif
+                       err(1, "setgid");
+               if (setuid(pw->pw_uid) != 0) /* set to user 'nobody' */
+                       err(1, "setuid");
+
+               /* parse command line and execute main mua code */
+               parseandexecute(argc, argv);
+
+               /* release child processes */
+               release_children();
+       }
+
+       return(0);
 }
+
index b8bc21b..273bf8e 100644 (file)
@@ -32,7 +32,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * $DragonFly: src/libexec/dma/dma.h,v 1.6 2008/09/02 15:11:49 matthias Exp $
+ * $DragonFly: src/libexec/dma/dma.h,v 1.7 2008/09/16 17:57:23 matthias Exp $
  */
 
 #ifndef DMA_H
 #define DEFER          0x010           /* Defer mails */
 #define INSECURE       0x020           /* Allow plain login w/o encryption */
 
+#define        ENDOFDOTFORWARD 0x01            /* no ~/.forward for this user */
+#define        ISPIPE          0x02            /* there is a pipe line in the .forward */
+#define        ISMAILBOX       0x04            /* there is a mailbox line in the .forward */
+
+#define ENDOFMAIL      0x01            /* on deliver_local() side everythings ok */
+#define GOTOCHOP       0x02            /* there was an problem with the queue-file, reset file seek */
+
+#define SEM_DF         0               /* semaphore for exclusive dotforwardhandler communication */
+#define SEM_WL         1               /* semaphore for exclusive write_to_local_user communication */
+#define SEM_SIGHUP     2               /* semaphore for signalling that the processes can terminate */
+
 #define CONF_PATH      "/etc/dma/dma.conf"     /* Default path to dma.conf */
 
 struct stritem {
@@ -83,11 +94,12 @@ struct qitem {
        LIST_ENTRY(qitem) next;
        const char *sender;
        char *addr;
+       char *pipeuser;
        char *queuefn;
        char *queueid;
        FILE *queuef;
        off_t hdrlen;
-       int remote;
+       int local;
 };
 LIST_HEAD(queueh, qitem);
 
@@ -148,7 +160,7 @@ extern int smtp_init_crypto(struct qitem *, int, int);
 /* net.c */
 extern int read_remote(int, int, char *);
 extern ssize_t send_remote_command(int, const char*, ...);
-extern int deliver_remote(struct qitem *, const char **);
+extern int deliver_remote(struct qitem *, const char **, struct queue **);
 
 /* base64.c */
 extern int base64_encode(const void *, int, char **);
index fbde18e..394dff0 100644 (file)
@@ -32,7 +32,7 @@
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- * $DragonFly: src/libexec/dma/net.c,v 1.7 2008/09/02 15:11:49 matthias Exp $
+ * $DragonFly: src/libexec/dma/net.c,v 1.8 2008/09/16 17:57:23 matthias Exp $
  */
 
 #include <sys/param.h>
@@ -284,8 +284,20 @@ open_connection(struct qitem *it, const char *host)
        return (fd);
 }
 
+static void
+copy_qitem(struct qitem *target, struct qitem *it) {
+       target->sender = it->sender;
+       target->addr = it->addr;
+       target->pipeuser = it->pipeuser;
+       target->queuefn = it->queuefn;
+       target->queueid = it->queueid;
+       target->queuef = it->queuef;
+       target->hdrlen = it->hdrlen;
+       target->local = 0;
+}
+
 int
-deliver_remote(struct qitem *it, const char **errmsg)
+deliver_remote(struct qitem *it, const char **errmsg, struct queue **queue)
 {
        struct authuser *a;
        char *host, line[1000];
@@ -309,7 +321,7 @@ deliver_remote(struct qitem *it, const char **errmsg)
 
        fd = open_connection(it, host);
        if (fd < 0)
-               return (1);
+               return(1);
 
        /* Check first reply from remote host */
        config->features |= NOSSL;
@@ -376,7 +388,7 @@ deliver_remote(struct qitem *it, const char **errmsg)
                if (error < 0) {
                        syslog(LOG_ERR, "%s: remote delivery failed:"
                                        " SMTP login failed: %m", it->queueid);
-                       return (-1);
+                       return(-1);
                }
                /* SMTP login is not available, so try without */
                else if (error > 0)
@@ -388,15 +400,75 @@ deliver_remote(struct qitem *it, const char **errmsg)
        if (read_remote(fd, 0, NULL) != 2) {
                syslog(LOG_ERR, "%s: remote delivery deferred:"
                       " MAIL FROM failed: %m", it->queueid);
-               return (1);
+               return(1);
        }
 
-       send_remote_command(fd, "RCPT TO:<%s>", it->addr);
-       if (read_remote(fd, 0, NULL) != 2) {
-               syslog(LOG_ERR, "%s: remote delivery deferred:"
-                               " RCPT TO failed: %m", it->queueid);
-               return (1);
-       }
+       if (queue == NULL) {
+       /* without given queue send only to one receipient */
+               send_remote_command(fd, "RCPT TO:<%s>", it->addr);
+               if (read_remote(fd, 0, NULL) != 2) {
+                       syslog(LOG_ERR, "%s: remote delivery deferred:"
+                                       " RCPT TO failed: %m", it->queueid);
+                       return(1);
+               }
+       } else {
+       /* Iterate over all recepients and open only one connection */
+/*
+ * we need 3 queues:
+ *     -delivered addresses
+ *     -temporary errors
+ *     -permanent errors
+ * these 3 queues are given in **queues (+the first queue with all the qitems to send)
+ *
+ */
+
+               struct qitem *tit;
+               int rcpt_success = 0;
+               struct stat st;
+               LIST_FOREACH(tit, &queue[0]->queue, next) {
+                       struct qitem *qit;
+                       qit = calloc(1, sizeof(struct qitem));
+                       copy_qitem(qit, tit);
+
+                       if (stat(tit->queuefn, &st) != 0) {
+                               syslog(LOG_ERR, "%s: lost queue file `%s'",
+                                               tit->queueid, tit->queuefn);
+                               /* drop qitem and mark it as successfully sent */
+                               LIST_INSERT_HEAD(&queue[1]->queue, qit, next);
+                               continue;
+                       }
+
+                       send_remote_command(fd, "RCPT TO:<%s>", tit->addr);
+                       switch (read_remote(fd, 0, NULL)) {
+                       case 2: /* everythings fine, receipient accepted */
+                               /* add item to temporary queue, these items will be deleted in deliver_smarthost */
+                               rcpt_success = 1;
+                               LIST_INSERT_HEAD(&queue[1]->queue, qit, next);
+                               syslog(LOG_INFO, "%s: mail from=<%s> to=<%s>",
+                                               tit->queueid, tit->sender, tit->addr);
+                               break;
+                       case 4: /* temporary error, try again later */
+                               /* add item to a temporary queue, these items will be tried again */
+                               LIST_INSERT_HEAD(&queue[2]->queue, qit, next);
+                               syslog(LOG_INFO, "%s: mail from=<%s> to=<%s>",
+                                               tit->queueid, tit->sender, tit->addr);
+                               syslog(LOG_ERR, "%s: remote delivery deferred:"
+                                               " RCPT TO failed: %m", tit->queueid);
+                               break;
+                       case 5: /* permanent error, bounce */
+                               /* add item to a queue, which will be returned to deliver_smarthost */
+                               LIST_INSERT_HEAD(&queue[3]->queue, qit, next);
+                               syslog(LOG_INFO, "%s: mail from=<%s> to=<%s>",
+                                               tit->queueid, tit->sender, tit->addr);
+                               syslog(LOG_ERR, "%s: remote delivery failed:"
+                                               " RCPT TO failed: %m", tit->queueid);
+                               break;
+                       }
+               }
+               /* if there was no successful RCPT TO, return _WITHOUT_ error */
+               if (!rcpt_success)
+                       goto out;
+       } 
 
        send_remote_command(fd, "DATA");
        if (read_remote(fd, 0, NULL) != 3) {