dma: rewrite file management
[dragonfly.git] / libexec / dma / dma.c
1 /*
2  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Simon 'corecode' Schubert <corecode@fs.ei.tum.de>.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $DragonFly: src/libexec/dma/dma.c,v 1.5 2008/09/30 17:47:21 swildner Exp $
35  */
36
37 #include <sys/param.h>
38 #include <sys/queue.h>
39 #include <sys/stat.h>
40 #include <sys/types.h>
41 #include <sys/wait.h>
42
43 #include <dirent.h>
44 #include <err.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <inttypes.h>
48 #include <netdb.h>
49 #include <paths.h>
50 #include <pwd.h>
51 #include <signal.h>
52 #include <stdarg.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <syslog.h>
57 #include <unistd.h>
58
59 #include "dma.h"
60
61
62
63 static void deliver(struct qitem *);
64
65 struct aliases aliases = LIST_HEAD_INITIALIZER(aliases);
66 struct strlist tmpfs = SLIST_HEAD_INITIALIZER(tmpfs);
67 struct virtusers virtusers = LIST_HEAD_INITIALIZER(virtusers);
68 struct authusers authusers = LIST_HEAD_INITIALIZER(authusers);
69 struct config *config;
70 const char *username;
71
72 static int daemonize = 1;
73 static const char *logident_base;
74
75 const char *
76 hostname(void)
77 {
78         static char name[MAXHOSTNAMELEN+1];
79         int initialized = 0;
80         FILE *fp;
81         size_t len;
82
83         if (initialized)
84                 return (name);
85
86         if (config->mailname != NULL && config->mailname[0] != '\0') {
87                 snprintf(name, sizeof(name), "%s", config->mailname);
88                 initialized = 1;
89                 return (name);
90         }
91         if (config->mailnamefile != NULL && config->mailnamefile[0] != '\0') {
92                 fp = fopen(config->mailnamefile, "r");
93                 if (fp != NULL) {
94                         if (fgets(name, sizeof(name), fp) != NULL) {
95                                 len = strlen(name);
96                                 while (len > 0 &&
97                                     (name[len - 1] == '\r' ||
98                                      name[len - 1] == '\n'))
99                                         name[--len] = '\0';
100                                 if (name[0] != '\0') {
101                                         initialized = 1;
102                                         return (name);
103                                 }
104                         }
105                         fclose(fp);
106                 }
107         }
108         if (gethostname(name, sizeof(name)) != 0)
109                 strcpy(name, "(unknown hostname)");
110         initialized = 1;
111         return name;
112 }
113
114 static void
115 setlogident(const char *fmt, ...)
116 {
117         char *tag = NULL;
118
119         if (fmt != NULL) {
120                 va_list ap;
121                 char *sufx;
122
123                 va_start(ap, fmt);
124                 vasprintf(&sufx, fmt, ap);
125                 if (sufx != NULL) {
126                         asprintf(&tag, "%s[%s]", logident_base, sufx);
127                         free(sufx);
128                 }
129                 va_end(ap);
130         }
131         closelog();
132         openlog(tag != NULL ? tag : logident_base, 0, LOG_MAIL);
133 }
134
135 static const char *
136 check_username(const char *name, uid_t ckuid)
137 {
138         struct passwd *pwd;
139
140         if (name == NULL)
141                 return (NULL);
142         pwd = getpwnam(name);
143         if (pwd == NULL || pwd->pw_uid != ckuid)
144                 return (NULL);
145         return (name);
146 }
147
148 static void
149 set_username(void)
150 {
151         struct passwd *pwd;
152         char *u = NULL;
153         uid_t uid;
154
155         uid = getuid();
156         username = check_username(getlogin(), uid);
157         if (username != NULL)
158                 return;
159         username = check_username(getenv("LOGNAME"), uid);
160         if (username != NULL)
161                 return;
162         username = check_username(getenv("USER"), uid);
163         if (username != NULL)
164                 return;
165         pwd = getpwuid(uid);
166         if (pwd != NULL && pwd->pw_name != NULL && pwd->pw_name[0] != '\0' &&
167             (u = strdup(pwd->pw_name)) != NULL) {
168                 username = check_username(u, uid);
169                 if (username != NULL)
170                         return;
171                 else
172                         free(u);
173         }
174         asprintf(__DECONST(void *, &username), "%ld", (long)uid);
175         if (username != NULL)
176                 return;
177         username = "unknown-or-invalid-username";
178 }
179
180 static char *
181 set_from(const char *osender)
182 {
183         struct virtuser *v;
184         char *sender;
185
186         if ((config->features & VIRTUAL) != 0) {
187                 SLIST_FOREACH(v, &virtusers, next) {
188                         if (strcmp(v->login, username) == 0) {
189                                 sender = strdup(v->address);
190                                 if (sender == NULL)
191                                         return(NULL);
192                                 goto out;
193                         }
194                 }
195         }
196
197         if (osender) {
198                 sender = strdup(osender);
199                 if (sender == NULL)
200                         return (NULL);
201         } else {
202                 if (asprintf(&sender, "%s@%s", username, hostname()) <= 0)
203                         return (NULL);
204         }
205
206         if (strchr(sender, '\n') != NULL) {
207                 errno = EINVAL;
208                 return (NULL);
209         }
210
211 out:
212         return (sender);
213 }
214
215 static int
216 read_aliases(void)
217 {
218         yyin = fopen(config->aliases, "r");
219         if (yyin == NULL)
220                 return (0);     /* not fatal */
221         if (yyparse())
222                 return (-1);    /* fatal error, probably malloc() */
223         fclose(yyin);
224         return (0);
225 }
226
227 int
228 add_recp(struct queue *queue, const char *str, const char *sender, int expand)
229 {
230         struct qitem *it, *tit;
231         struct stritem *sit;
232         struct alias *al;
233         struct passwd *pw;
234         char *host;
235         int aliased = 0;
236
237         it = calloc(1, sizeof(*it));
238         if (it == NULL)
239                 return (-1);
240         it->addr = strdup(str);
241         if (it->addr == NULL)
242                 return (-1);
243
244         it->sender = sender;
245         host = strrchr(it->addr, '@');
246         if (host != NULL &&
247             (strcmp(host + 1, hostname()) == 0 ||
248              strcmp(host + 1, "localhost") == 0)) {
249                 *host = 0;
250         }
251         LIST_FOREACH(tit, &queue->queue, next) {
252                 /* weed out duplicate dests */
253                 if (strcmp(tit->addr, it->addr) == 0) {
254                         free(it->addr);
255                         free(it);
256                         return (0);
257                 }
258         }
259         LIST_INSERT_HEAD(&queue->queue, it, next);
260         if (strrchr(it->addr, '@') == NULL) {
261                 it->remote = 0;
262                 if (expand) {
263                         LIST_FOREACH(al, &aliases, next) {
264                                 if (strcmp(al->alias, it->addr) != 0)
265                                         continue;
266                                 SLIST_FOREACH(sit, &al->dests, next) {
267                                         if (add_recp(queue, sit->str, sender, 1) != 0)
268                                                 return (-1);
269                                 }
270                                 aliased = 1;
271                         }
272                         if (aliased) {
273                                 LIST_REMOVE(it, next);
274                         } else {
275                                 /* Local destination, check */
276                                 pw = getpwnam(it->addr);
277                                 if (pw == NULL)
278                                         goto out;
279                                 /* XXX read .forward */
280                                 endpwent();
281                         }
282                 }
283         } else {
284                 it->remote = 1;
285         }
286
287         return (0);
288
289 out:
290         free(it->addr);
291         free(it);
292         return (-1);
293 }
294
295 static void
296 deltmp(void)
297 {
298         struct stritem *t;
299
300         SLIST_FOREACH(t, &tmpfs, next) {
301                 unlink(t->str);
302         }
303 }
304
305 int
306 open_locked(const char *fname, int flags, ...)
307 {
308         int mode = 0;
309
310         if (flags & O_CREAT) {
311                 va_list ap;
312                 va_start(ap, flags);
313                 mode = va_arg(ap, int);
314                 va_end(ap);
315         }
316
317 #ifndef O_EXLOCK
318         int fd, save_errno;
319
320         fd = open(fname, flags, mode);
321         if (fd < 0)
322                 return(fd);
323         if (flock(fd, LOCK_EX|((flags & O_NONBLOCK)? LOCK_NB: 0)) < 0) {
324                 save_errno = errno;
325                 close(fd);
326                 errno = save_errno;
327                 return(-1);
328         }
329         return(fd);
330 #else
331         return(open(fname, flags|O_EXLOCK, mode));
332 #endif
333 }
334
335 static char *
336 rfc822date(void)
337 {
338         static char str[50];
339         size_t error;
340         time_t now;
341
342         now = time(NULL);
343         error = strftime(str, sizeof(str), "%a, %d %b %Y %T %z",
344                        localtime(&now));
345         if (error == 0)
346                 strcpy(str, "(date fail)");
347         return (str);
348 }
349
350 static int
351 strprefixcmp(const char *str, const char *prefix)
352 {
353         return (strncasecmp(str, prefix, strlen(prefix)));
354 }
355
356 static int
357 readmail(struct queue *queue, const char *sender, int nodot)
358 {
359         char line[1000];        /* by RFC2822 */
360         size_t linelen;
361         size_t error;
362         int had_headers = 0;
363         int had_from = 0;
364         int had_messagid = 0;
365         int had_date = 0;
366
367         error = fprintf(queue->mailf,
368                 "Received: from %s (uid %d)\n"
369                 "\t(envelope-from %s)\n"
370                 "\tid %s\n"
371                 "\tby %s (%s)\n"
372                 "\t%s\n",
373                 username, getuid(),
374                 sender,
375                 queue->id,
376                 hostname(), VERSION,
377                 rfc822date());
378         if ((ssize_t)error < 0)
379                 return (-1);
380
381         while (!feof(stdin)) {
382                 if (fgets(line, sizeof(line), stdin) == NULL)
383                         break;
384                 linelen = strlen(line);
385                 if (linelen == 0 || line[linelen - 1] != '\n') {
386                         errno = EINVAL;         /* XXX mark permanent errors */
387                         return (-1);
388                 }
389                 if (!had_headers) {
390                         if (strprefixcmp(line, "Date:") == 0)
391                                 had_date = 1;
392                         else if (strprefixcmp(line, "Message-Id:") == 0)
393                                 had_messagid = 1;
394                         else if (strprefixcmp(line, "From:") == 0)
395                                 had_from = 1;
396                 }
397                 if (strcmp(line, "\n") == 0 && !had_headers) {
398                         had_headers = 1;
399                         while (!had_date || !had_messagid || !had_from) {
400                                 if (!had_date) {
401                                         had_date = 1;
402                                         snprintf(line, sizeof(line), "Date: %s\n", rfc822date());
403                                 } else if (!had_messagid) {
404                                         /* XXX better msgid, assign earlier and log? */
405                                         had_messagid = 1;
406                                         snprintf(line, sizeof(line), "Message-Id: <%s@%s>\n",
407                                                  queue->id, hostname());
408                                 } else if (!had_from) {
409                                         had_from = 1;
410                                         snprintf(line, sizeof(line), "From: <%s>\n", sender);
411                                 }
412                                 if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
413                                         return (-1);
414                         }
415                         strcpy(line, "\n");
416                 }
417                 if (!nodot && linelen == 2 && line[0] == '.')
418                         break;
419                 if (fwrite(line, strlen(line), 1, queue->mailf) != 1)
420                         return (-1);
421         }
422
423         return (0);
424 }
425
426 static struct qitem *
427 go_background(struct queue *queue)
428 {
429         struct sigaction sa;
430         struct qitem *it;
431         pid_t pid;
432
433         if (daemonize && daemon(0, 0) != 0) {
434                 syslog(LOG_ERR, "can not daemonize: %m");
435                 exit(1);
436         }
437         daemonize = 0;
438
439         bzero(&sa, sizeof(sa));
440         sa.sa_flags = SA_NOCLDWAIT;
441         sa.sa_handler = SIG_IGN;
442         sigaction(SIGCHLD, &sa, NULL);
443
444         LIST_FOREACH(it, &queue->queue, next) {
445                 /* No need to fork for the last dest */
446                 if (LIST_NEXT(it, next) == NULL)
447                         goto retit;
448
449                 pid = fork();
450                 switch (pid) {
451                 case -1:
452                         syslog(LOG_ERR, "can not fork: %m");
453                         exit(1);
454                         break;
455
456                 case 0:
457                         /*
458                          * Child:
459                          *
460                          * return and deliver mail
461                          */
462 retit:
463                         /*
464                          * If necessary, aquire the queue and * mail files.
465                          * If this fails, we probably were raced by another
466                          * process.
467                          */
468                         setlogident("%s", it->queueid);
469                         if (aquirespool(it) < 0)
470                                 exit(1);
471                         dropspool(queue, it);
472                         return (it);
473
474                 default:
475                         /*
476                          * Parent:
477                          *
478                          * fork next child
479                          */
480                         break;
481                 }
482         }
483
484         syslog(LOG_CRIT, "reached dead code");
485         exit(1);
486 }
487
488 static void
489 bounce(struct qitem *it, const char *reason)
490 {
491         struct queue bounceq;
492         struct qitem *bit;
493         char line[1000];
494         size_t pos;
495         int error;
496
497         /* Don't bounce bounced mails */
498         if (it->sender[0] == 0) {
499                 syslog(LOG_INFO, "can not bounce a bounce message, discarding");
500                 exit(1);
501         }
502
503         LIST_INIT(&bounceq.queue);
504         if (newspoolf(&bounceq, "") != 0)
505                 goto fail;
506
507         syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id);
508         setlogident("%s", bounceq.id);
509
510         error = fprintf(bounceq.mailf,
511                 "Received: from MAILER-DAEMON\n"
512                 "\tid %s\n"
513                 "\tby %s (%s)\n"
514                 "\t%s\n"
515                 "X-Original-To: <%s>\n"
516                 "From: MAILER-DAEMON <>\n"
517                 "To: %s\n"
518                 "Subject: Mail delivery failed\n"
519                 "Message-Id: <%s@%s>\n"
520                 "Date: %s\n"
521                 "\n"
522                 "This is the %s at %s.\n"
523                 "\n"
524                 "There was an error delivering your mail to <%s>.\n"
525                 "\n"
526                 "%s\n"
527                 "\n"
528                 "%s\n"
529                 "\n",
530                 bounceq.id,
531                 hostname(), VERSION,
532                 rfc822date(),
533                 it->addr,
534                 it->sender,
535                 bounceq.id, hostname(),
536                 rfc822date(),
537                 VERSION, hostname(),
538                 it->addr,
539                 reason,
540                 config->features & FULLBOUNCE ?
541                     "Original message follows." :
542                     "Message headers follow.");
543         if (error < 0)
544                 goto fail;
545
546         if (add_recp(&bounceq, it->sender, "", 1) != 0)
547                 goto fail;
548
549         if (fseek(it->mailf, it->hdrlen, SEEK_SET) != 0)
550                 goto fail;
551         if (config->features & FULLBOUNCE) {
552                 while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) {
553                         if (fwrite(line, 1, pos, bounceq.mailf) != pos)
554                                 goto fail;
555                 }
556         } else {
557                 while (!feof(it->mailf)) {
558                         if (fgets(line, sizeof(line), it->mailf) == NULL)
559                                 break;
560                         if (line[0] == '\n')
561                                 break;
562                         if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1)
563                                 goto fail;
564                 }
565         }
566
567         if (linkspool(&bounceq, "MAILER-DAEMON") != 0)
568                 goto fail;
569         /* bounce is safe */
570
571         delqueue(it);
572
573         bit = go_background(&bounceq);
574         deliver(bit);
575         /* NOTREACHED */
576
577 fail:
578         syslog(LOG_CRIT, "error creating bounce: %m");
579         delqueue(it);
580         exit(1);
581 }
582
583 static void
584 deliver(struct qitem *it)
585 {
586         int error;
587         unsigned int backoff = MIN_RETRY;
588         const char *errmsg = "unknown bounce reason";
589         struct timeval now;
590         struct stat st;
591
592 retry:
593         syslog(LOG_INFO, "trying delivery");
594
595         if (it->remote)
596                 error = deliver_remote(it, &errmsg);
597         else
598                 error = deliver_local(it, &errmsg);
599
600         switch (error) {
601         case 0:
602                 delqueue(it);
603                 syslog(LOG_INFO, "delivery successful");
604                 exit(0);
605
606         case 1:
607                 if (stat(it->queuefn, &st) != 0) {
608                         syslog(LOG_ERR, "lost queue file `%s'", it->queuefn);
609                         exit(1);
610                 }
611                 if (gettimeofday(&now, NULL) == 0 &&
612                     (now.tv_sec - st.st_mtimespec.tv_sec > MAX_TIMEOUT)) {
613                         asprintf(__DECONST(void *, &errmsg),
614                                  "Could not deliver for the last %d seconds. Giving up.",
615                                  MAX_TIMEOUT);
616                         goto bounce;
617                 }
618                 sleep(backoff);
619                 backoff *= 2;
620                 if (backoff > MAX_RETRY)
621                         backoff = MAX_RETRY;
622                 goto retry;
623
624         case -1:
625         default:
626                 break;
627         }
628
629 bounce:
630         bounce(it, errmsg);
631         /* NOTREACHED */
632 }
633
634 static void
635 run_queue(struct queue *queue)
636 {
637         struct qitem *it;
638
639         if (LIST_EMPTY(&queue->queue))
640                 return;
641
642         it = go_background(queue);
643         deliver(it);
644         /* NOTREACHED */
645 }
646
647 static void
648 show_queue(struct queue *queue)
649 {
650         struct qitem *it;
651         int locked = 0; /* XXX */
652
653         if (LIST_EMPTY(&queue->queue)) {
654                 printf("Mail queue is empty\n");
655                 return;
656         }
657
658         LIST_FOREACH(it, &queue->queue, next) {
659                 printf("ID\t: %s%s\n"
660                        "From\t: %s\n"
661                        "To\t: %s\n"
662                        "--\n",
663                        it->queueid,
664                        locked ? "*" : "",
665                        it->sender, it->addr);
666         }
667 }
668
669 /*
670  * TODO:
671  *
672  * - alias processing
673  * - use group permissions
674  * - proper sysexit codes
675  */
676
677 int
678 main(int argc, char **argv)
679 {
680         char *sender = NULL;
681         struct qitem *it;
682         struct queue queue;
683         struct queue lqueue;
684         int i, ch;
685         int nodot = 0, doqueue = 0, showq = 0;
686
687         atexit(deltmp);
688         LIST_INIT(&queue.queue);
689
690         if (strcmp(argv[0], "mailq") == 0) {
691                 argv++; argc--;
692                 showq = 1;
693                 if (argc != 0)
694                         errx(1, "invalid arguments");
695                 goto skipopts;
696         }
697
698         opterr = 0;
699         while ((ch = getopt(argc, argv, "A:b:B:C:d:Df:F:h:iL:N:no:O:q:r:R:UV:vX:")) != -1) {
700                 switch (ch) {
701                 case 'A':
702                         /* -AX is being ignored, except for -A{c,m} */
703                         if (optarg[0] == 'c' || optarg[0] == 'm') {
704                                 break;
705                         }
706                         /* else FALLTRHOUGH */
707                 case 'b':
708                         /* -bX is being ignored, except for -bp */
709                         if (optarg[0] == 'p') {
710                                 showq = 1;
711                                 break;
712                         }
713                         /* else FALLTRHOUGH */
714                 case 'D':
715                         daemonize = 0;
716                         break;
717                 case 'L':
718                         logident_base = optarg;
719                         break;
720                 case 'f':
721                 case 'r':
722                         sender = optarg;
723                         break;
724
725                 case 'o':
726                         /* -oX is being ignored, except for -oi */
727                         if (optarg[0] != 'i')
728                                 break;
729                         /* else FALLTRHOUGH */
730                 case 'O':
731                         break;
732                 case 'i':
733                         nodot = 1;
734                         break;
735
736                 case 'q':
737                         doqueue = 1;
738                         break;
739
740                 /* Ignored options */
741                 case 'B':
742                 case 'C':
743                 case 'd':
744                 case 'F':
745                 case 'h':
746                 case 'N':
747                 case 'n':
748                 case 'R':
749                 case 'U':
750                 case 'V':
751                 case 'v':
752                 case 'X':
753                         break;
754
755                 default:
756                         exit(1);
757                 }
758         }
759         argc -= optind;
760         argv += optind;
761         opterr = 1;
762
763         if (argc != 0 && (showq || doqueue))
764                 errx(1, "sending mail and queue operations are mutually exclusive");
765
766         if (showq + doqueue > 1)
767                 errx(1, "conflicting queue operations");
768
769 skipopts:
770         if (logident_base == NULL)
771                 logident_base = "dma";
772         setlogident(NULL);
773         set_username();
774
775         /* XXX fork root here */
776
777         config = calloc(1, sizeof(*config));
778         if (config == NULL)
779                 err(1, NULL);
780
781         if (parse_conf(CONF_PATH) < 0) {
782                 free(config);
783                 err(1, "can not read config file");
784         }
785
786         if (config->features & VIRTUAL)
787                 if (parse_virtuser(config->virtualpath) < 0)
788                         err(1, "can not read virtual user file `%s'",
789                                 config->virtualpath);
790
791         if (parse_authfile(config->authpath) < 0)
792                 err(1, "can not read SMTP authentication file");
793
794         if (showq) {
795                 load_queue(&lqueue);
796                 show_queue(&lqueue);
797                 return (0);
798         }
799
800         if (doqueue) {
801                 load_queue(&lqueue);
802                 run_queue(&lqueue);
803                 return (0);
804         }
805
806         if (read_aliases() != 0)
807                 err(1, "can not read aliases file `%s'", config->aliases);
808
809         if ((sender = set_from(sender)) == NULL)
810                 err(1, NULL);
811
812         for (i = 0; i < argc; i++) {
813                 if (add_recp(&queue, argv[i], sender, 1) != 0)
814                         errx(1, "invalid recipient `%s'", argv[i]);
815         }
816
817         if (LIST_EMPTY(&queue.queue))
818                 errx(1, "no recipients");
819
820         if (newspoolf(&queue, sender) != 0)
821                 err(1, "can not create temp file");
822
823         setlogident("%s", queue.id);
824
825         if (readmail(&queue, sender, nodot) != 0)
826                 err(1, "can not read mail");
827
828         if (linkspool(&queue, sender) != 0)
829                 err(1, "can not create spools");
830
831         /* From here on the mail is safe. */
832
833         if (config->features & DEFER)
834                 return (0);
835
836         it = go_background(&queue);
837         deliver(it);
838
839         /* NOTREACHED */
840         return (0);
841 }