=== modified file 'dma.c' --- dma.c 2008-08-28 11:37:08 +0000 +++ dma.c 2008-08-28 13:30:11 +0000 @@ -34,8 +34,11 @@ * $DragonFly: src/libexec/dma/dma.c,v 1.3 2008/02/04 08:58:54 matthias Exp $ */ +#include #include #include +#include +#include #include #include @@ -47,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -63,8 +67,8 @@ -static void deliver(struct qitem *); -static void deliver_smarthost(struct queue *); +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); @@ -73,6 +77,36 @@ 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) @@ -105,19 +139,19 @@ 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 @@ -125,11 +159,11 @@ { 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 @@ -144,10 +178,10 @@ 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, '@'); @@ -161,7 +195,7 @@ if (strcmp(tit->addr, it->addr) == 0) { free(it->addr); free(it); - return (0); + return(0); } } LIST_INSERT_HEAD(&queue->queue, it, next); @@ -177,30 +211,123 @@ continue; SLIST_FOREACH(sit, &al->dests, next) { if (add_recp(queue, sit->str, sender, 1) != 0) - return (-1); + 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 to another 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 no further expansion 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->local = 0; } - return (0); + return(0); out: free(it->addr); free(it); - return (-1); + return(-1); } static void @@ -221,22 +348,23 @@ 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); } /* @@ -265,53 +393,52 @@ 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); + return(-1); /* File may already exist*/ if (stat(it->queuefn, &st) == 0) { warn("Spoolfile already exists: %s", it->queuefn); - return (-1); + 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 * @@ -326,7 +453,7 @@ localtime(&now)); if (error == 0) strcpy(str, "(date fail)"); - return (str); + return(str); } static int @@ -348,9 +475,9 @@ 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) @@ -358,16 +485,16 @@ 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 @@ -375,21 +502,37 @@ { 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; } - 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; @@ -397,16 +540,16 @@ int seen_remote_address = 0; if (daemonize && daemon(0, 0) != 0) { - syslog(LOG_ERR, "[go background] 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) { /* * if smarthost is enabled, @@ -429,7 +572,6 @@ /* 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: @@ -447,9 +589,9 @@ 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); + deliver(it, leavesemaphore); else - deliver(it); + deliver(it, 0); else _exit(0); @@ -465,12 +607,11 @@ */ if (LIST_NEXT(it, next) == NULL) { if (seen_remote_address) { - deliver_smarthost(queue); + deliver_smarthost(queue, leavesemaphore); } else { _exit(0); } } - break; } } @@ -480,17 +621,33 @@ } 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) { + /* decrement 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); } @@ -560,10 +717,25 @@ unlink(it->queuefn); fclose(it->queuef); - go_background(&bounceq); + go_background(&bounceq, leavesemaphore); /* NOTREACHED */ fail: + /* + * if we are the last bounce, then decrement semaphore + * and release children + */ + if (leavesemaphore) { + /* decrement 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); @@ -572,44 +744,114 @@ static int deliver_local(struct qitem *it, const char **errmsg) { + char line[1000]; char fn[PATH_MAX+1]; - char line[1000]; + int len; + uint8_t mode = 0, fail = 0; size_t linelen; - int mbox; - int error; - off_t mboxlen; time_t now = time(NULL); - - 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); - } - - /* 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); - } - mboxlen = lseek(mbox, 0, SEEK_CUR); + char *username; + struct sembuf sema; + + + /* 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); + } + + + /* tell write_to_local_user() the delivery mode (write to mailbox or to 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); + } + 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 transferring 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 transer it to write_to_local_user() */ while (!feof(it->queuef)) { if (fgets(line, sizeof(line), it->queuef) == NULL) @@ -619,54 +861,123 @@ 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) - goto wrerror; - close(mbox); - return (0); + linelen = 1; + write(controlsocket_wl, &linelen, sizeof(linelen)); + write(controlsocket_wl, line, linelen); + read(controlsocket_wl, &fail, sizeof(fail)); + if (fail) { + goto wrerror; + } + + + /* 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 on this side */ + 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->local) + /* + * 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); @@ -674,13 +985,48 @@ switch (error) { case 0: - unlink(it->queuefn); + /* decrement 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(); + /* with pipe delivery, there is no spoolfile which can be deleted */ + 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 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: delivery to pipe `%s' failed, giving up", + it->queueid, it->addr); + exit(1); + } if (stat(it->queuefn, &st) != 0) { + /* decrement 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); @@ -707,7 +1053,7 @@ } bounce: - bounce(it, errmsg); + bounce(it, errmsg, 1); /* NOTREACHED */ } @@ -728,16 +1074,36 @@ */ static void -deliver_smarthost(struct queue *queue) +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; @@ -758,6 +1124,15 @@ if (stat(it->queuefn, &st) != 0) { syslog(LOG_ERR, "%s: lost queue file `%s'", it->queueid, it->queuefn); + /* decrement 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); @@ -812,6 +1187,17 @@ /* 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) { + /* decrement 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 */ @@ -883,7 +1269,13 @@ LIST_REMOVE(tit, next); if (LIST_NEXT(tit, next) == NULL) { - bounce(tit, errmsg); + /* + * for the last bounce, do not increment + * the semaphore when delivering the bounce + */ + bounce(tit, errmsg, 1); + } else { + bounce(tit, errmsg, 0); } /* NOTREACHED */ @@ -1026,7 +1418,7 @@ if (LIST_EMPTY(&queue->queue)) return; - go_background(queue); + go_background(queue, 0); /* NOTREACHED */ } @@ -1056,8 +1448,8 @@ * - proper sysexit codes */ -int -main(int argc, char **argv) +static int +parseandexecute(int argc, char **argv) { char *sender = NULL; char tag[255]; @@ -1066,6 +1458,7 @@ 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); @@ -1115,6 +1508,7 @@ break; default: + release_children(); exit(1); } } @@ -1131,16 +1525,21 @@ 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) @@ -1148,7 +1547,7 @@ " mutually exclusive"); load_queue(&lqueue); show_queue(&lqueue); - return (0); + return(0); } if (doqueue) { @@ -1156,43 +1555,505 @@ 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); - go_background(queue); + 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); +} + === modified file 'dma.h' --- dma.h 2008-08-28 11:37:08 +0000 +++ dma.h 2008-08-28 13:25:40 +0000 @@ -64,6 +64,17 @@ #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,6 +94,7 @@ LIST_ENTRY(qitem) next; const char *sender; char *addr; + char *pipeuser; char *queuefn; char *queueid; FILE *queuef;