dma: pass over the code and improve error handling
[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 static int add_recp(struct queue *, const char *, const char *, int);
65
66 struct aliases aliases = LIST_HEAD_INITIALIZER(aliases);
67 static struct strlist tmpfs = SLIST_HEAD_INITIALIZER(tmpfs);
68 struct virtusers virtusers = LIST_HEAD_INITIALIZER(virtusers);
69 struct authusers authusers = LIST_HEAD_INITIALIZER(authusers);
70 static int daemonize = 1;
71 struct config *config;
72 static const char *username;
73 static uid_t uid;
74 static struct strlist seenmsg[16][16];
75
76
77 char *
78 hostname(void)
79 {
80         static char name[MAXHOSTNAMELEN+1];
81         int initialized = 0;
82         FILE *fp;
83         size_t len;
84
85         if (initialized)
86                 return (name);
87
88         if (config->mailname != NULL && config->mailname[0] != '\0') {
89                 snprintf(name, sizeof(name), "%s", config->mailname);
90                 initialized = 1;
91                 return (name);
92         }
93         if (config->mailnamefile != NULL && config->mailnamefile[0] != '\0') {
94                 fp = fopen(config->mailnamefile, "r");
95                 if (fp != NULL) {
96                         if (fgets(name, sizeof(name), fp) != NULL) {
97                                 len = strlen(name);
98                                 while (len > 0 &&
99                                     (name[len - 1] == '\r' ||
100                                      name[len - 1] == '\n'))
101                                         name[--len] = '\0';
102                                 if (name[0] != '\0') {
103                                         initialized = 1;
104                                         return (name);
105                                 }
106                         }
107                         fclose(fp);
108                 }
109         }
110         if (gethostname(name, sizeof(name)) != 0)
111                 strcpy(name, "(unknown hostname)");
112         initialized = 1;
113         return name;
114 }
115
116 static const char *
117 check_username(const char *name, uid_t ckuid)
118 {
119         struct passwd *pwd;
120
121         if (name == NULL)
122                 return (NULL);
123         pwd = getpwnam(name);
124         if (pwd == NULL || pwd->pw_uid != ckuid)
125                 return (NULL);
126         return (name);
127 }
128
129 static void
130 set_username(void)
131 {
132         struct passwd *pwd;
133         char *u = NULL;
134
135         uid = getuid();
136         username = check_username(getlogin(), uid);
137         if (username != NULL)
138                 return;
139         username = check_username(getenv("LOGNAME"), uid);
140         if (username != NULL)
141                 return;
142         username = check_username(getenv("USER"), uid);
143         if (username != NULL)
144                 return;
145         pwd = getpwuid(uid);
146         if (pwd != NULL && pwd->pw_name != NULL && pwd->pw_name[0] != '\0' &&
147             (u = strdup(pwd->pw_name)) != NULL) {
148                 username = check_username(u, uid);
149                 if (username != NULL)
150                         return;
151                 else
152                         free(u);
153         }
154         asprintf(__DECONST(void *, &username), "%ld", (long)uid);
155         if (username != NULL)
156                 return;
157         username = "unknown-or-invalid-username";
158 }
159
160 static char *
161 set_from(const char *osender)
162 {
163         struct virtuser *v;
164         char *sender;
165
166         if ((config->features & VIRTUAL) != 0) {
167                 SLIST_FOREACH(v, &virtusers, next) {
168                         if (strcmp(v->login, username) == 0) {
169                                 sender = strdup(v->address);
170                                 if (sender == NULL)
171                                         return(NULL);
172                                 goto out;
173                         }
174                 }
175         }
176
177         if (osender) {
178                 sender = strdup(osender);
179                 if (sender == NULL)
180                         return (NULL);
181         } else {
182                 if (asprintf(&sender, "%s@%s", username, hostname()) <= 0)
183                         return (NULL);
184         }
185
186         if (strchr(sender, '\n') != NULL) {
187                 errno = EINVAL;
188                 return (NULL);
189         }
190
191 out:
192         return (sender);
193 }
194
195 static int
196 read_aliases(void)
197 {
198         yyin = fopen(config->aliases, "r");
199         if (yyin == NULL)
200                 return (0);     /* not fatal */
201         if (yyparse())
202                 return (-1);    /* fatal error, probably malloc() */
203         fclose(yyin);
204         return (0);
205 }
206
207 static int
208 add_recp(struct queue *queue, const char *str, const char *sender, int expand)
209 {
210         struct qitem *it, *tit;
211         struct stritem *sit;
212         struct alias *al;
213         struct passwd *pw;
214         char *host;
215         int aliased = 0;
216
217         it = calloc(1, sizeof(*it));
218         if (it == NULL)
219                 return (-1);
220         it->addr = strdup(str);
221         if (it->addr == NULL)
222                 return (-1);
223
224         it->sender = sender;
225         host = strrchr(it->addr, '@');
226         if (host != NULL &&
227             (strcmp(host + 1, hostname()) == 0 ||
228              strcmp(host + 1, "localhost") == 0)) {
229                 *host = 0;
230         }
231         LIST_FOREACH(tit, &queue->queue, next) {
232                 /* weed out duplicate dests */
233                 if (strcmp(tit->addr, it->addr) == 0) {
234                         free(it->addr);
235                         free(it);
236                         return (0);
237                 }
238         }
239         LIST_INSERT_HEAD(&queue->queue, it, next);
240         if (strrchr(it->addr, '@') == NULL) {
241                 it->remote = 0;
242                 if (expand) {
243                         LIST_FOREACH(al, &aliases, next) {
244                                 if (strcmp(al->alias, it->addr) != 0)
245                                         continue;
246                                 SLIST_FOREACH(sit, &al->dests, next) {
247                                         if (add_recp(queue, sit->str, sender, 1) != 0)
248                                                 return (-1);
249                                 }
250                                 aliased = 1;
251                         }
252                         if (aliased) {
253                                 LIST_REMOVE(it, next);
254                         } else {
255                                 /* Local destination, check */
256                                 pw = getpwnam(it->addr);
257                                 if (pw == NULL)
258                                         goto out;
259                                 endpwent();
260                         }
261                 }
262         } else {
263                 it->remote = 1;
264         }
265
266         return (0);
267
268 out:
269         free(it->addr);
270         free(it);
271         return (-1);
272 }
273
274 static void
275 deltmp(void)
276 {
277         struct stritem *t;
278
279         SLIST_FOREACH(t, &tmpfs, next) {
280                 unlink(t->str);
281         }
282 }
283
284 static int
285 gentempf(struct queue *queue)
286 {
287         char fn[PATH_MAX+1];
288         struct stritem *t;
289         int fd;
290
291         if (snprintf(fn, sizeof(fn), "%s/%s", config->spooldir, "tmp_XXXXXXXXXX") <= 0)
292                 return (-1);
293         fd = mkstemp(fn);
294         if (fd < 0)
295                 return (-1);
296         if (flock(fd, LOCK_EX) == -1)
297                 return (-1);
298         queue->mailfd = fd;
299         queue->tmpf = strdup(fn);
300         if (queue->tmpf == NULL) {
301                 unlink(fn);
302                 return (-1);
303         }
304         t = malloc(sizeof(*t));
305         if (t != NULL) {
306                 t->str = queue->tmpf;
307                 SLIST_INSERT_HEAD(&tmpfs, t, next);
308         }
309         return (0);
310 }
311
312 static int
313 open_locked(const char *fname, int flags)
314 {
315 #ifndef O_EXLOCK
316         int fd, save_errno;
317
318         fd = open(fname, flags, 0);
319         if (fd < 0)
320                 return(fd);
321         if (flock(fd, LOCK_EX|((flags & O_NONBLOCK)? LOCK_NB: 0)) < 0) {
322                 save_errno = errno;
323                 close(fd);
324                 errno = save_errno;
325                 return(-1);
326         }
327         return(fd);
328 #else
329         return(open(fname, flags|O_EXLOCK));
330 #endif
331 }
332
333 /*
334  * spool file format:
335  *
336  * envelope-from
337  * queue-id1 envelope-to1
338  * queue-id2 envelope-to2
339  * ...
340  * <empty line>
341  * mail data
342  *
343  * queue ids are unique, formed from the inode of the spool file
344  * and a unique identifier.
345  */
346 static int
347 preparespool(struct queue *queue, const char *sender)
348 {
349         char line[1000];        /* by RFC2822 */
350         struct stat st;
351         int error;
352         struct qitem *it;
353         FILE *queuef;
354         off_t hdrlen;
355
356         error = snprintf(line, sizeof(line), "%s\n", sender);
357         if (error < 0 || (size_t)error >= sizeof(line)) {
358                 errno = E2BIG;
359                 return (-1);
360         }
361         if (write(queue->mailfd, line, error) != error)
362                 return (-1);
363
364         queuef = fdopen(queue->mailfd, "r+");
365         if (queuef == NULL)
366                 return (-1);
367
368         /*
369          * Assign queue id to each dest.
370          */
371         if (fstat(queue->mailfd, &st) != 0)
372                 return (-1);
373         queue->id = st.st_ino;
374
375         syslog(LOG_INFO, "%"PRIxMAX": new mail from user=%s uid=%d envelope_from=<%s>",
376                queue->id, username, uid, sender);
377
378         LIST_FOREACH(it, &queue->queue, next) {
379                 if (asprintf(&it->queueid, "%"PRIxMAX".%"PRIxPTR,
380                              queue->id, (uintptr_t)it) <= 0)
381                         return (-1);
382                 if (asprintf(&it->queuefn, "%s/%s",
383                              config->spooldir, it->queueid) <= 0)
384                         return (-1);
385                 /* File may not exist yet */
386                 if (stat(it->queuefn, &st) == 0)
387                         return (-1);
388                 it->queuef = queuef;
389                 error = snprintf(line, sizeof(line), "%s %s\n",
390                                it->queueid, it->addr);
391                 if (error < 0 || (size_t)error >= sizeof(line))
392                         return (-1);
393                 if (write(queue->mailfd, line, error) != error)
394                         return (-1);
395
396                 syslog(LOG_INFO, "%"PRIxMAX": mail to=<%s> queued as %s",
397                        queue->id, it->addr, it->queueid);
398         }
399         line[0] = '\n';
400         if (write(queue->mailfd, line, 1) != 1)
401                 return (-1);
402
403         hdrlen = lseek(queue->mailfd, 0, SEEK_CUR);
404         LIST_FOREACH(it, &queue->queue, next) {
405                 it->hdrlen = hdrlen;
406         }
407         return (0);
408 }
409
410 static char *
411 rfc822date(void)
412 {
413         static char str[50];
414         size_t error;
415         time_t now;
416
417         now = time(NULL);
418         error = strftime(str, sizeof(str), "%a, %d %b %Y %T %z",
419                        localtime(&now));
420         if (error == 0)
421                 strcpy(str, "(date fail)");
422         return (str);
423 }
424
425 static int
426 readmail(struct queue *queue, const char *sender, int nodot)
427 {
428         char line[1000];        /* by RFC2822 */
429         size_t linelen;
430         int error;
431
432         error = snprintf(line, sizeof(line), "\
433 Received: from %s (uid %d)\n\
434 \t(envelope-from %s)\n\
435 \tid %"PRIxMAX"\n\
436 \tby %s (%s)\n\
437 \t%s\n",
438                 username, uid,
439                 sender,
440                 queue->id,
441                 hostname(), VERSION,
442                 rfc822date());
443         if (error < 0 || (size_t)error >= sizeof(line))
444                 return (-1);
445         if (write(queue->mailfd, line, error) != error)
446                 return (-1);
447
448         while (!feof(stdin)) {
449                 if (fgets(line, sizeof(line), stdin) == NULL)
450                         break;
451                 linelen = strlen(line);
452                 if (linelen == 0 || line[linelen - 1] != '\n') {
453                         errno = EINVAL;         /* XXX mark permanent errors */
454                         return (-1);
455                 }
456                 if (!nodot && linelen == 2 && line[0] == '.')
457                         break;
458                 if ((size_t)write(queue->mailfd, line, linelen) != linelen)
459                         return (-1);
460         }
461         if (fsync(queue->mailfd) != 0)
462                 return (-1);
463         return (0);
464 }
465
466 static int
467 linkspool(struct queue *queue)
468 {
469         struct qitem *it;
470
471         LIST_FOREACH(it, &queue->queue, next) {
472                 if (link(queue->tmpf, it->queuefn) != 0)
473                         goto delfiles;
474         }
475         unlink(queue->tmpf);
476         return (0);
477
478 delfiles:
479         LIST_FOREACH(it, &queue->queue, next) {
480                 unlink(it->queuefn);
481         }
482         return (-1);
483 }
484
485 static struct qitem *
486 go_background(struct queue *queue)
487 {
488         struct sigaction sa;
489         struct qitem *it;
490         FILE *newqf;
491         pid_t pid;
492
493         if (daemonize && daemon(0, 0) != 0) {
494                 syslog(LOG_ERR, "can not daemonize: %m");
495                 exit(1);
496         }
497         daemonize = 0;
498
499         bzero(&sa, sizeof(sa));
500         sa.sa_flags = SA_NOCLDWAIT;
501         sa.sa_handler = SIG_IGN;
502         sigaction(SIGCHLD, &sa, NULL);
503
504         LIST_FOREACH(it, &queue->queue, next) {
505                 /* No need to fork for the last dest */
506                 if (LIST_NEXT(it, next) == NULL)
507                         return (it);
508
509                 pid = fork();
510                 switch (pid) {
511                 case -1:
512                         syslog(LOG_ERR, "can not fork: %m");
513                         exit(1);
514                         break;
515
516                 case 0:
517                         /*
518                          * Child:
519                          *
520                          * return and deliver mail
521                          */
522                         /*
523                          * We have to prevent sharing of fds between children, so
524                          * we have to re-open the queue file.
525                          */
526                         newqf = fopen(it->queuefn, "r");
527                         if (newqf == NULL) {
528                                 syslog(LOG_ERR, "can not re-open queue file `%s': %m",
529                                        it->queuefn);
530                                 exit(1);
531                         }
532                         fclose(it->queuef);
533                         it->queuef = newqf;
534                         return (it);
535
536                 default:
537                         /*
538                          * Parent:
539                          *
540                          * fork next child
541                          */
542                         break;
543                 }
544         }
545
546         syslog(LOG_CRIT, "reached dead code");
547         exit(1);
548 }
549
550 static void
551 bounce(struct qitem *it, const char *reason)
552 {
553         struct queue bounceq;
554         struct qitem *bit;
555         char line[1000];
556         size_t pos;
557         int error;
558
559         /* Don't bounce bounced mails */
560         if (it->sender[0] == 0) {
561                 syslog(LOG_INFO, "%s: can not bounce a bounce message, discarding",
562                        it->queueid);
563                 exit(1);
564         }
565
566         syslog(LOG_ERR, "%s: delivery failed, bouncing",
567                it->queueid);
568
569         LIST_INIT(&bounceq.queue);
570         if (add_recp(&bounceq, it->sender, "", 1) != 0)
571                 goto fail;
572         if (gentempf(&bounceq) != 0)
573                 goto fail;
574         if (preparespool(&bounceq, "") != 0)
575                 goto fail;
576
577         bit = LIST_FIRST(&bounceq.queue);
578         error = fprintf(bit->queuef, "\
579 Received: from MAILER-DAEMON\n\
580 \tid %"PRIxMAX"\n\
581 \tby %s (%s)\n\
582 \t%s\n\
583 X-Original-To: <%s>\n\
584 From: MAILER-DAEMON <>\n\
585 To: %s\n\
586 Subject: Mail delivery failed\n\
587 Message-Id: <%"PRIxMAX"@%s>\n\
588 Date: %s\n\
589 \n\
590 This is the %s at %s.\n\
591 \n\
592 There was an error delivering your mail to <%s>.\n\
593 \n\
594 %s\n\
595 \n\
596 %s\n\
597 \n\
598 ",
599                 bounceq.id,
600                 hostname(), VERSION,
601                 rfc822date(),
602                 it->addr,
603                 it->sender,
604                 bounceq.id, hostname(),
605                 rfc822date(),
606                 VERSION, hostname(),
607                 it->addr,
608                 reason,
609                 config->features & FULLBOUNCE? "Original message follows.":
610                 "Message headers follow.");
611         if (error < 0)
612                 goto fail;
613         if (fflush(bit->queuef) != 0)
614                 goto fail;
615
616         if (fseek(it->queuef, it->hdrlen, SEEK_SET) != 0)
617                 goto fail;
618         if (config->features & FULLBOUNCE) {
619                 while ((pos = fread(line, 1, sizeof(line), it->queuef)) > 0) {
620                         if ((size_t)write(bounceq.mailfd, line, pos) != pos)
621                                 goto fail;
622                 }
623         } else {
624                 while (!feof(it->queuef)) {
625                         if (fgets(line, sizeof(line), it->queuef) == NULL)
626                                 break;
627                         if (line[0] == '\n')
628                                 break;
629                         if ((size_t)write(bounceq.mailfd, line, strlen(line)) != strlen(line))
630                                 goto fail;
631                 }
632         }
633         if (fsync(bounceq.mailfd) != 0)
634                 goto fail;
635         if (linkspool(&bounceq) != 0)
636                 goto fail;
637         /* bounce is safe */
638
639         unlink(it->queuefn);
640         fclose(it->queuef);
641
642         bit = go_background(&bounceq);
643         deliver(bit);
644         /* NOTREACHED */
645
646 fail:
647         syslog(LOG_CRIT, "%s: error creating bounce: %m", it->queueid);
648         unlink(it->queuefn);
649         exit(1);
650 }
651
652 static int
653 deliver_local(struct qitem *it, const char **errmsg)
654 {
655         char fn[PATH_MAX+1];
656         char line[1000];
657         size_t linelen;
658         int mbox;
659         int error;
660         off_t mboxlen;
661         time_t now = time(NULL);
662
663         error = snprintf(fn, sizeof(fn), "%s/%s", _PATH_MAILDIR, it->addr);
664         if (error < 0 || (size_t)error >= sizeof(fn)) {
665                 syslog(LOG_NOTICE, "%s: local delivery deferred: %m",
666                        it->queueid);
667                 return (1);
668         }
669
670         /* mailx removes users mailspool file if empty, so open with O_CREAT */
671         mbox = open_locked(fn, O_WRONLY | O_APPEND | O_CREAT);
672         if (mbox < 0) {
673                 syslog(LOG_NOTICE, "%s: local delivery deferred: can not open `%s': %m",
674                        it->queueid, fn);
675                 return (1);
676         }
677         mboxlen = lseek(mbox, 0, SEEK_CUR);
678
679         if (fseek(it->queuef, it->hdrlen, SEEK_SET) != 0) {
680                 syslog(LOG_NOTICE, "%s: local delivery deferred: can not seek: %m",
681                        it->queueid);
682                 return (1);
683         }
684
685         error = snprintf(line, sizeof(line), "From %s\t%s", it->sender, ctime(&now));
686         if (error < 0 || (size_t)error >= sizeof(line)) {
687                 syslog(LOG_NOTICE, "%s: local delivery deferred: can not write header: %m",
688                        it->queueid);
689                 return (1);
690         }
691         if (write(mbox, line, error) != error)
692                 goto wrerror;
693
694         while (!feof(it->queuef)) {
695                 if (fgets(line, sizeof(line), it->queuef) == NULL)
696                         break;
697                 linelen = strlen(line);
698                 if (linelen == 0 || line[linelen - 1] != '\n') {
699                         syslog(LOG_CRIT, "%s: local delivery failed: corrupted queue file",
700                                it->queueid);
701                         *errmsg = "corrupted queue file";
702                         error = -1;
703                         goto chop;
704                 }
705
706                 if (strncmp(line, "From ", 5) == 0) {
707                         const char *gt = ">";
708
709                         if (write(mbox, gt, 1) != 1)
710                                 goto wrerror;
711                 }
712                 if ((size_t)write(mbox, line, linelen) != linelen)
713                         goto wrerror;
714         }
715         line[0] = '\n';
716         if (write(mbox, line, 1) != 1)
717                 goto wrerror;
718         close(mbox);
719         return (0);
720
721 wrerror:
722         syslog(LOG_ERR, "%s: local delivery failed: write error: %m",
723                it->queueid);
724         error = 1;
725 chop:
726         if (ftruncate(mbox, mboxlen) != 0)
727                 syslog(LOG_WARNING, "%s: error recovering mbox `%s': %m",
728                        it->queueid, fn);
729         close(mbox);
730         return (error);
731 }
732
733 static void
734 deliver(struct qitem *it)
735 {
736         int error;
737         unsigned int backoff = MIN_RETRY;
738         const char *errmsg = "unknown bounce reason";
739         struct timeval now;
740         struct stat st;
741
742         syslog(LOG_INFO, "%s: mail from=<%s> to=<%s>",
743                it->queueid, it->sender, it->addr);
744
745 retry:
746         syslog(LOG_INFO, "%s: trying delivery",
747                it->queueid);
748
749         if (it->remote)
750                 error = deliver_remote(it, &errmsg);
751         else
752                 error = deliver_local(it, &errmsg);
753
754         switch (error) {
755         case 0:
756                 unlink(it->queuefn);
757                 syslog(LOG_INFO, "%s: delivery successful",
758                        it->queueid);
759                 exit(0);
760
761         case 1:
762                 if (stat(it->queuefn, &st) != 0) {
763                         syslog(LOG_ERR, "%s: lost queue file `%s'",
764                                it->queueid, it->queuefn);
765                         exit(1);
766                 }
767                 if (gettimeofday(&now, NULL) == 0 &&
768                     (now.tv_sec - st.st_mtimespec.tv_sec > MAX_TIMEOUT)) {
769                         asprintf(__DECONST(void *, &errmsg),
770                                  "Could not deliver for the last %d seconds. Giving up.",
771                                  MAX_TIMEOUT);
772                         goto bounce;
773                 }
774                 sleep(backoff);
775                 backoff *= 2;
776                 if (backoff > MAX_RETRY)
777                         backoff = MAX_RETRY;
778                 goto retry;
779
780         case -1:
781         default:
782                 break;
783         }
784
785 bounce:
786         bounce(it, errmsg);
787         /* NOTREACHED */
788 }
789
790 static int
791 c2x(char c)
792 {
793         if (c <= '9')
794                 return (c - '0');
795         else if (c <= 'F')
796                 return (c - 'A' + 10);
797         else
798                 return (c - 'a' + 10);
799 }
800
801 static void
802 seen_init(void)
803 {
804         int i, j;
805
806         for (i = 0; i < 16; i++)
807                 for (j = 0; j < 16; j++)
808                         SLIST_INIT(&seenmsg[i][j]);
809 }
810
811 static int
812 seen(const char *msgid)
813 {
814         const char *p;
815         size_t len;
816         int i, j;
817         struct stritem *t;
818
819         p = strchr(msgid, '.');
820         if (p == NULL)
821                 return (0);
822         len = p - msgid;
823         if (len >= 2) {
824                 i = c2x(msgid[len - 2]);
825                 j = c2x(msgid[len - 1]);
826         } else if (len == 1) {
827                 i = c2x(msgid[0]);
828                 j = 0;
829         } else {
830                 i = j = 0;
831         }
832         if (i < 0 || i >= 16 || j < 0 || j >= 16)
833                 errx(1, "INTERNAL ERROR: bad seen code for msgid %s", msgid);
834         SLIST_FOREACH(t, &seenmsg[i][j], next)
835                 if (!strncmp(t->str, msgid, len))
836                         return (1);
837         t = malloc(sizeof(*t));
838         if (t == NULL)
839                 errx(1, "Could not allocate %lu bytes",
840                     (unsigned long)(sizeof(*t)));
841         t->str = strdup(msgid);
842         if (t->str == NULL)
843                 errx(1, "Could not duplicate msgid %s", msgid);
844         SLIST_INSERT_HEAD(&seenmsg[i][j], t, next);
845         return (0);
846 }
847
848 static void
849 load_queue(struct queue *queue, int ignorelock)
850 {
851         struct stat st;
852         struct qitem *it;
853         //struct queue queue, itmqueue;
854         struct queue itmqueue;
855         DIR *spooldir;
856         struct dirent *de;
857         char line[1000];
858         char *fn;
859         FILE *queuef;
860         char *sender;
861         char *addr;
862         char *queueid;
863         char *queuefn;
864         off_t hdrlen;
865         int fd, locked, seenit;
866
867         LIST_INIT(&queue->queue);
868
869         spooldir = opendir(config->spooldir);
870         if (spooldir == NULL)
871                 err(1, "reading queue");
872
873         seen_init();
874         while ((de = readdir(spooldir)) != NULL) {
875                 sender = NULL;
876                 queuef = NULL;
877                 queueid = NULL;
878                 queuefn = NULL;
879                 fn = NULL;
880                 LIST_INIT(&itmqueue.queue);
881
882                 /* ignore temp files */
883                 if (strncmp(de->d_name, "tmp_", 4) == 0 ||
884                     de->d_type != DT_REG)
885                         continue;
886                 if (asprintf(&queuefn, "%s/%s", config->spooldir, de->d_name) < 0)
887                         goto fail;
888                 seenit = seen(de->d_name);
889                 locked = 0;
890                 fd = open_locked(queuefn, O_RDONLY|O_NONBLOCK);
891                 if (fd < 0) {
892                         /* Ignore locked files */
893                         if (errno != EWOULDBLOCK)
894                                 goto skip_item;
895                         if (!ignorelock || seenit)
896                                 continue;
897                         fd = open(queuefn, O_RDONLY);
898                         if (fd < 0)
899                                 goto skip_item;
900                         locked = 1;
901                 }
902
903                 queuef = fdopen(fd, "r");
904                 if (queuef == NULL)
905                         goto skip_item;
906                 if (fgets(line, sizeof(line), queuef) == NULL ||
907                     line[0] == 0)
908                         goto skip_item;
909                 line[strlen(line) - 1] = 0;     /* chop newline */
910                 sender = strdup(line);
911                 if (sender == NULL)
912                         goto skip_item;
913
914                 for (;;) {
915                         if (fgets(line, sizeof(line), queuef) == NULL ||
916                             line[0] == 0)
917                                 goto skip_item;
918                         if (line[0] == '\n')
919                                 break;
920                         line[strlen(line) - 1] = 0;
921                         queueid = strdup(line);
922                         if (queueid == NULL)
923                                 goto skip_item;
924                         addr = strchr(queueid, ' ');
925                         if (addr == NULL)
926                                 goto skip_item;
927                         *addr++ = 0;
928                         if (fn != NULL)
929                                 free(fn);
930                         if (asprintf(&fn, "%s/%s", config->spooldir, queueid) < 0)
931                                 goto skip_item;
932                         /* Item has already been delivered? */
933                         if (stat(fn, &st) != 0)
934                                 continue;
935                         if (add_recp(&itmqueue, addr, sender, 0) != 0)
936                                 goto skip_item;
937                         it = LIST_FIRST(&itmqueue.queue);
938                         it->queuef = queuef;
939                         it->queueid = queueid;
940                         it->queuefn = fn;
941                         it->locked = locked;
942                         fn = NULL;
943                 }
944                 if (LIST_EMPTY(&itmqueue.queue)) {
945                         warnx("queue file without items: `%s'", queuefn);
946                         goto skip_item2;
947                 }
948                 hdrlen = ftell(queuef);
949                 while ((it = LIST_FIRST(&itmqueue.queue)) != NULL) {
950                         it->hdrlen = hdrlen;
951                         LIST_REMOVE(it, next);
952                         LIST_INSERT_HEAD(&queue->queue, it, next);
953                 }
954                 continue;
955
956 skip_item:
957                 warn("reading queue: `%s'", queuefn);
958 skip_item2:
959                 if (sender != NULL)
960                         free(sender);
961                 if (queuefn != NULL)
962                         free(queuefn);
963                 if (fn != NULL)
964                         free(fn);
965                 if (queueid != NULL)
966                         free(queueid);
967                 close(fd);
968         }
969         closedir(spooldir);
970         return;
971
972 fail:
973         err(1, "reading queue");
974 }
975
976 static void
977 run_queue(struct queue *queue)
978 {
979         struct qitem *it;
980
981         if (LIST_EMPTY(&queue->queue))
982                 return;
983
984         it = go_background(queue);
985         deliver(it);
986         /* NOTREACHED */
987 }
988
989 static void
990 show_queue(struct queue *queue)
991 {
992         struct qitem *it;
993
994         if (LIST_EMPTY(&queue->queue)) {
995                 printf("Mail queue is empty\n");
996                 return;
997         }
998
999         LIST_FOREACH(it, &queue->queue, next) {
1000                 printf("\
1001 ID\t: %s%s\n\
1002 From\t: %s\n\
1003 To\t: %s\n--\n", it->queueid, it->locked? "*": "", it->sender, it->addr);
1004         }
1005 }
1006
1007 /*
1008  * TODO:
1009  *
1010  * - alias processing
1011  * - use group permissions
1012  * - proper sysexit codes
1013  */
1014
1015 int
1016 main(int argc, char **argv)
1017 {
1018         char *sender = NULL;
1019         char tag[255];
1020         struct qitem *it;
1021         struct queue queue;
1022         struct queue lqueue;
1023         int i, ch;
1024         int nodot = 0, doqueue = 0, showq = 0;
1025
1026         atexit(deltmp);
1027         LIST_INIT(&queue.queue);
1028         snprintf(tag, 254, "dma");
1029
1030         if (strcmp(argv[0], "mailq") == 0) {
1031                 argv++; argc--;
1032                 showq = 1;
1033                 if (argc != 0)
1034                         errx(1, "invalid arguments");
1035                 goto skipopts;
1036         }
1037
1038         opterr = 0;
1039         while ((ch = getopt(argc, argv, "A:b:B:C:d:Df:F:h:iL:N:no:O:q:r:R:UV:vX:")) != -1) {
1040                 switch (ch) {
1041                 case 'A':
1042                         /* -AX is being ignored, except for -A{c,m} */
1043                         if (optarg[0] == 'c' || optarg[0] == 'm') {
1044                                 break;
1045                         }
1046                         /* else FALLTRHOUGH */
1047                 case 'b':
1048                         /* -bX is being ignored, except for -bp */
1049                         if (optarg[0] == 'p') {
1050                                 showq = 1;
1051                                 break;
1052                         }
1053                         /* else FALLTRHOUGH */
1054                 case 'D':
1055                         daemonize = 0;
1056                         break;
1057                 case 'L':
1058                         if (optarg != NULL)
1059                                 snprintf(tag, 254, "%s", optarg);
1060                         break;
1061                 case 'f':
1062                 case 'r':
1063                         sender = optarg;
1064                         break;
1065
1066                 case 'o':
1067                         /* -oX is being ignored, except for -oi */
1068                         if (optarg[0] != 'i')
1069                                 break;
1070                         /* else FALLTRHOUGH */
1071                 case 'O':
1072                         break;
1073                 case 'i':
1074                         nodot = 1;
1075                         break;
1076
1077                 case 'q':
1078                         doqueue = 1;
1079                         break;
1080
1081                 /* Ignored options */
1082                 case 'B':
1083                 case 'C':
1084                 case 'd':
1085                 case 'F':
1086                 case 'h':
1087                 case 'N':
1088                 case 'n':
1089                 case 'R':
1090                 case 'U':
1091                 case 'V':
1092                 case 'v':
1093                 case 'X':
1094                         break;
1095
1096                 default:
1097                         exit(1);
1098                 }
1099         }
1100         argc -= optind;
1101         argv += optind;
1102         opterr = 1;
1103
1104 skipopts:
1105         openlog(tag, LOG_PID, LOG_MAIL);
1106         set_username();
1107
1108         config = malloc(sizeof(struct config));
1109         if (config == NULL)
1110                 err(1, NULL);
1111
1112         memset(config, 0, sizeof(struct config));
1113         if (parse_conf(CONF_PATH, config) < 0) {
1114                 free(config);
1115                 err(1, "can not read config file");
1116         }
1117
1118         if (config->features & VIRTUAL)
1119                 if (parse_virtuser(config->virtualpath) < 0)
1120                         err(1, "can not read virtual user file `%s'",
1121                                 config->virtualpath);
1122
1123         if (parse_authfile(config->authpath) < 0)
1124                 err(1, "can not read SMTP authentication file");
1125
1126         if (showq) {
1127                 if (argc != 0)
1128                         errx(1, "sending mail and displaying queue is"
1129                                 " mutually exclusive");
1130                 load_queue(&lqueue, 1);
1131                 show_queue(&lqueue);
1132                 return (0);
1133         }
1134
1135         if (doqueue) {
1136                 if (argc != 0)
1137                         errx(1, "sending mail and queue pickup is mutually exclusive");
1138                 load_queue(&lqueue, 0);
1139                 run_queue(&lqueue);
1140                 return (0);
1141         }
1142
1143         if (read_aliases() != 0)
1144                 err(1, "can not read aliases file `%s'", config->aliases);
1145
1146         if ((sender = set_from(sender)) == NULL)
1147                 err(1, NULL);
1148
1149         for (i = 0; i < argc; i++) {
1150                 if (add_recp(&queue, argv[i], sender, 1) != 0)
1151                         errx(1, "invalid recipient `%s'", argv[i]);
1152         }
1153
1154         if (LIST_EMPTY(&queue.queue))
1155                 errx(1, "no recipients");
1156
1157         if (gentempf(&queue) != 0)
1158                 err(1, "can not create temp file");
1159
1160         if (preparespool(&queue, sender) != 0)
1161                 err(1, "can not create spools (1)");
1162
1163         if (readmail(&queue, sender, nodot) != 0)
1164                 err(1, "can not read mail");
1165
1166         if (linkspool(&queue) != 0)
1167                 err(1, "can not create spools (2)");
1168
1169         /* From here on the mail is safe. */
1170
1171         if (config->features & DEFER)
1172                 return (0);
1173
1174         it = go_background(&queue);
1175         deliver(it);
1176
1177         /* NOTREACHED */
1178         return (0);
1179 }