b4ced55fc3f05bd75653dcf8d67f6b19da8126fb
[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 static int daemonize = 1;
70 struct config *config;
71 static const char *username;
72 static uid_t uid;
73
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 const char *
115 check_username(const char *name, uid_t ckuid)
116 {
117         struct passwd *pwd;
118
119         if (name == NULL)
120                 return (NULL);
121         pwd = getpwnam(name);
122         if (pwd == NULL || pwd->pw_uid != ckuid)
123                 return (NULL);
124         return (name);
125 }
126
127 static void
128 set_username(void)
129 {
130         struct passwd *pwd;
131         char *u = NULL;
132
133         uid = getuid();
134         username = check_username(getlogin(), uid);
135         if (username != NULL)
136                 return;
137         username = check_username(getenv("LOGNAME"), uid);
138         if (username != NULL)
139                 return;
140         username = check_username(getenv("USER"), uid);
141         if (username != NULL)
142                 return;
143         pwd = getpwuid(uid);
144         if (pwd != NULL && pwd->pw_name != NULL && pwd->pw_name[0] != '\0' &&
145             (u = strdup(pwd->pw_name)) != NULL) {
146                 username = check_username(u, uid);
147                 if (username != NULL)
148                         return;
149                 else
150                         free(u);
151         }
152         asprintf(__DECONST(void *, &username), "%ld", (long)uid);
153         if (username != NULL)
154                 return;
155         username = "unknown-or-invalid-username";
156 }
157
158 static char *
159 set_from(const char *osender)
160 {
161         struct virtuser *v;
162         char *sender;
163
164         if ((config->features & VIRTUAL) != 0) {
165                 SLIST_FOREACH(v, &virtusers, next) {
166                         if (strcmp(v->login, username) == 0) {
167                                 sender = strdup(v->address);
168                                 if (sender == NULL)
169                                         return(NULL);
170                                 goto out;
171                         }
172                 }
173         }
174
175         if (osender) {
176                 sender = strdup(osender);
177                 if (sender == NULL)
178                         return (NULL);
179         } else {
180                 if (asprintf(&sender, "%s@%s", username, hostname()) <= 0)
181                         return (NULL);
182         }
183
184         if (strchr(sender, '\n') != NULL) {
185                 errno = EINVAL;
186                 return (NULL);
187         }
188
189 out:
190         return (sender);
191 }
192
193 static int
194 read_aliases(void)
195 {
196         yyin = fopen(config->aliases, "r");
197         if (yyin == NULL)
198                 return (0);     /* not fatal */
199         if (yyparse())
200                 return (-1);    /* fatal error, probably malloc() */
201         fclose(yyin);
202         return (0);
203 }
204
205 int
206 add_recp(struct queue *queue, const char *str, const char *sender, int expand)
207 {
208         struct qitem *it, *tit;
209         struct stritem *sit;
210         struct alias *al;
211         struct passwd *pw;
212         char *host;
213         int aliased = 0;
214
215         it = calloc(1, sizeof(*it));
216         if (it == NULL)
217                 return (-1);
218         it->addr = strdup(str);
219         if (it->addr == NULL)
220                 return (-1);
221
222         it->sender = sender;
223         host = strrchr(it->addr, '@');
224         if (host != NULL &&
225             (strcmp(host + 1, hostname()) == 0 ||
226              strcmp(host + 1, "localhost") == 0)) {
227                 *host = 0;
228         }
229         LIST_FOREACH(tit, &queue->queue, next) {
230                 /* weed out duplicate dests */
231                 if (strcmp(tit->addr, it->addr) == 0) {
232                         free(it->addr);
233                         free(it);
234                         return (0);
235                 }
236         }
237         LIST_INSERT_HEAD(&queue->queue, it, next);
238         if (strrchr(it->addr, '@') == NULL) {
239                 it->remote = 0;
240                 if (expand) {
241                         LIST_FOREACH(al, &aliases, next) {
242                                 if (strcmp(al->alias, it->addr) != 0)
243                                         continue;
244                                 SLIST_FOREACH(sit, &al->dests, next) {
245                                         if (add_recp(queue, sit->str, sender, 1) != 0)
246                                                 return (-1);
247                                 }
248                                 aliased = 1;
249                         }
250                         if (aliased) {
251                                 LIST_REMOVE(it, next);
252                         } else {
253                                 /* Local destination, check */
254                                 pw = getpwnam(it->addr);
255                                 if (pw == NULL)
256                                         goto out;
257                                 /* XXX read .forward */
258                                 endpwent();
259                         }
260                 }
261         } else {
262                 it->remote = 1;
263         }
264
265         return (0);
266
267 out:
268         free(it->addr);
269         free(it);
270         return (-1);
271 }
272
273 static void
274 deltmp(void)
275 {
276         struct stritem *t;
277
278         SLIST_FOREACH(t, &tmpfs, next) {
279                 unlink(t->str);
280         }
281 }
282
283 int
284 open_locked(const char *fname, int flags, ...)
285 {
286         int mode = 0;
287
288         if (flags & O_CREAT) {
289                 va_list ap;
290                 va_start(ap, flags);
291                 mode = va_arg(ap, int);
292                 va_end(ap);
293         }
294
295 #ifndef O_EXLOCK
296         int fd, save_errno;
297
298         fd = open(fname, flags, mode);
299         if (fd < 0)
300                 return(fd);
301         if (flock(fd, LOCK_EX|((flags & O_NONBLOCK)? LOCK_NB: 0)) < 0) {
302                 save_errno = errno;
303                 close(fd);
304                 errno = save_errno;
305                 return(-1);
306         }
307         return(fd);
308 #else
309         return(open(fname, flags|O_EXLOCK, mode));
310 #endif
311 }
312
313 static char *
314 rfc822date(void)
315 {
316         static char str[50];
317         size_t error;
318         time_t now;
319
320         now = time(NULL);
321         error = strftime(str, sizeof(str), "%a, %d %b %Y %T %z",
322                        localtime(&now));
323         if (error == 0)
324                 strcpy(str, "(date fail)");
325         return (str);
326 }
327
328 static int
329 strprefixcmp(const char *str, const char *prefix)
330 {
331         return (strncasecmp(str, prefix, strlen(prefix)));
332 }
333
334 static int
335 readmail(struct queue *queue, const char *sender, int nodot)
336 {
337         char line[1000];        /* by RFC2822 */
338         size_t linelen;
339         int error;
340         int had_headers = 0;
341         int had_from = 0;
342         int had_messagid = 0;
343         int had_date = 0;
344
345         error = snprintf(line, sizeof(line),
346                 "Received: from %s (uid %d)\n"
347                 "\t(envelope-from %s)\n"
348                 "\tid %"PRIxMAX"\n"
349                 "\tby %s (%s)\n"
350                 "\t%s\n",
351                 username, uid,
352                 sender,
353                 queue->id,
354                 hostname(), VERSION,
355                 rfc822date());
356         if (error < 0 || (size_t)error >= sizeof(line))
357                 return (-1);
358         if (write(queue->mailfd, line, error) != error)
359                 return (-1);
360
361         while (!feof(stdin)) {
362                 if (fgets(line, sizeof(line), stdin) == NULL)
363                         break;
364                 linelen = strlen(line);
365                 if (linelen == 0 || line[linelen - 1] != '\n') {
366                         errno = EINVAL;         /* XXX mark permanent errors */
367                         return (-1);
368                 }
369                 if (!had_headers) {
370                         if (strprefixcmp(line, "Date:") == 0)
371                                 had_date = 1;
372                         else if (strprefixcmp(line, "Message-Id:") == 0)
373                                 had_messagid = 1;
374                         else if (strprefixcmp(line, "From:") == 0)
375                                 had_from = 1;
376                 }
377                 if (strcmp(line, "\n") == 0 && !had_headers) {
378                         had_headers = 1;
379                         while (!had_date || !had_messagid || !had_from) {
380                                 if (!had_date) {
381                                         had_date = 1;
382                                         snprintf(line, sizeof(line), "Date: %s\n", rfc822date());
383                                 } else if (!had_messagid) {
384                                         /* XXX better msgid, assign earlier and log? */
385                                         had_messagid = 1;
386                                         snprintf(line, sizeof(line), "Message-Id: <%"PRIxMAX"@%s>\n",
387                                                  queue->id, hostname());
388                                 } else if (!had_from) {
389                                         had_from = 1;
390                                         snprintf(line, sizeof(line), "From: <%s>\n", sender);
391                                 }
392                                 if ((size_t)write(queue->mailfd, line, strlen(line)) != strlen(line))
393                                         return (-1);
394                         }
395                         strcpy(line, "\n");
396                 }
397                 if (!nodot && linelen == 2 && line[0] == '.')
398                         break;
399                 if ((size_t)write(queue->mailfd, line, linelen) != linelen)
400                         return (-1);
401         }
402         if (fsync(queue->mailfd) != 0)
403                 return (-1);
404         return (0);
405 }
406
407 static struct qitem *
408 go_background(struct queue *queue)
409 {
410         struct sigaction sa;
411         struct qitem *it;
412         pid_t pid;
413
414         if (daemonize && daemon(0, 0) != 0) {
415                 syslog(LOG_ERR, "can not daemonize: %m");
416                 exit(1);
417         }
418         daemonize = 0;
419
420         bzero(&sa, sizeof(sa));
421         sa.sa_flags = SA_NOCLDWAIT;
422         sa.sa_handler = SIG_IGN;
423         sigaction(SIGCHLD, &sa, NULL);
424
425         LIST_FOREACH(it, &queue->queue, next) {
426                 /* No need to fork for the last dest */
427                 if (LIST_NEXT(it, next) == NULL)
428                         return (it);
429
430                 pid = fork();
431                 switch (pid) {
432                 case -1:
433                         syslog(LOG_ERR, "can not fork: %m");
434                         exit(1);
435                         break;
436
437                 case 0:
438                         /*
439                          * Child:
440                          *
441                          * return and deliver mail
442                          */
443                         return (it);
444
445                 default:
446                         /*
447                          * Parent:
448                          *
449                          * fork next child
450                          */
451                         break;
452                 }
453         }
454
455         syslog(LOG_CRIT, "reached dead code");
456         exit(1);
457 }
458
459 static void
460 bounce(struct qitem *it, const char *reason)
461 {
462         struct queue bounceq;
463         struct qitem *bit;
464         char line[1000];
465         size_t pos;
466         int error;
467
468         /* Don't bounce bounced mails */
469         if (it->sender[0] == 0) {
470                 syslog(LOG_INFO, "%s: can not bounce a bounce message, discarding",
471                        it->queueid);
472                 exit(1);
473         }
474
475         syslog(LOG_ERR, "%s: delivery failed, bouncing",
476                it->queueid);
477
478         LIST_INIT(&bounceq.queue);
479         if (add_recp(&bounceq, it->sender, "", 1) != 0)
480                 goto fail;
481         if (newspoolf(&bounceq, "") != 0)
482                 goto fail;
483         bit = LIST_FIRST(&bounceq.queue);
484         error = fprintf(bit->mailf,
485                 "Received: from MAILER-DAEMON\n"
486                 "\tid %"PRIxMAX"\n"
487                 "\tby %s (%s)\n"
488                 "\t%s\n"
489                 "X-Original-To: <%s>\n"
490                 "From: MAILER-DAEMON <>\n"
491                 "To: %s\n"
492                 "Subject: Mail delivery failed\n"
493                 "Message-Id: <%"PRIxMAX"@%s>\n"
494                 "Date: %s\n"
495                 "\n"
496                 "This is the %s at %s.\n"
497                 "\n"
498                 "There was an error delivering your mail to <%s>.\n"
499                 "\n"
500                 "%s\n"
501                 "\n"
502                 "%s\n"
503                 "\n",
504                 bounceq.id,
505                 hostname(), VERSION,
506                 rfc822date(),
507                 it->addr,
508                 it->sender,
509                 bounceq.id, hostname(),
510                 rfc822date(),
511                 VERSION, hostname(),
512                 it->addr,
513                 reason,
514                 config->features & FULLBOUNCE ?
515                     "Original message follows." :
516                     "Message headers follow.");
517         if (error < 0)
518                 goto fail;
519         if (fflush(bit->mailf) != 0)
520                 goto fail;
521
522         if (fseek(it->mailf, it->hdrlen, SEEK_SET) != 0)
523                 goto fail;
524         if (config->features & FULLBOUNCE) {
525                 while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) {
526                         if ((size_t)write(bounceq.mailfd, line, pos) != pos)
527                                 goto fail;
528                 }
529         } else {
530                 while (!feof(it->mailf)) {
531                         if (fgets(line, sizeof(line), it->mailf) == NULL)
532                                 break;
533                         if (line[0] == '\n')
534                                 break;
535                         if ((size_t)write(bounceq.mailfd, line, strlen(line)) != strlen(line))
536                                 goto fail;
537                 }
538         }
539         if (fsync(bounceq.mailfd) != 0)
540                 goto fail;
541         if (linkspool(&bounceq) != 0)
542                 goto fail;
543         /* bounce is safe */
544
545         delqueue(it);
546
547         bit = go_background(&bounceq);
548         deliver(bit);
549         /* NOTREACHED */
550
551 fail:
552         syslog(LOG_CRIT, "%s: error creating bounce: %m", it->queueid);
553         delqueue(it);
554         exit(1);
555 }
556
557 static void
558 deliver(struct qitem *it)
559 {
560         int error;
561         unsigned int backoff = MIN_RETRY;
562         const char *errmsg = "unknown bounce reason";
563         struct timeval now;
564         struct stat st;
565
566         syslog(LOG_INFO, "%s: mail from=<%s> to=<%s>",
567                it->queueid, it->sender, it->addr);
568
569 retry:
570         syslog(LOG_INFO, "%s: trying delivery",
571                it->queueid);
572
573         if (it->remote)
574                 error = deliver_remote(it, &errmsg);
575         else
576                 error = deliver_local(it, &errmsg);
577
578         switch (error) {
579         case 0:
580                 delqueue(it);
581                 syslog(LOG_INFO, "%s: delivery successful",
582                        it->queueid);
583                 exit(0);
584
585         case 1:
586                 if (stat(it->queuefn, &st) != 0) {
587                         syslog(LOG_ERR, "%s: lost queue file `%s'",
588                                it->queueid, it->queuefn);
589                         exit(1);
590                 }
591                 if (gettimeofday(&now, NULL) == 0 &&
592                     (now.tv_sec - st.st_mtimespec.tv_sec > MAX_TIMEOUT)) {
593                         asprintf(__DECONST(void *, &errmsg),
594                                  "Could not deliver for the last %d seconds. Giving up.",
595                                  MAX_TIMEOUT);
596                         goto bounce;
597                 }
598                 sleep(backoff);
599                 backoff *= 2;
600                 if (backoff > MAX_RETRY)
601                         backoff = MAX_RETRY;
602                 goto retry;
603
604         case -1:
605         default:
606                 break;
607         }
608
609 bounce:
610         bounce(it, errmsg);
611         /* NOTREACHED */
612 }
613
614 static void
615 run_queue(struct queue *queue)
616 {
617         struct qitem *it;
618
619         if (LIST_EMPTY(&queue->queue))
620                 return;
621
622         it = go_background(queue);
623         deliver(it);
624         /* NOTREACHED */
625 }
626
627 static void
628 show_queue(struct queue *queue)
629 {
630         struct qitem *it;
631
632         if (LIST_EMPTY(&queue->queue)) {
633                 printf("Mail queue is empty\n");
634                 return;
635         }
636
637         LIST_FOREACH(it, &queue->queue, next) {
638                 printf("ID\t: %s%s\n"
639                        "From\t: %s\n"
640                        "To\t: %s\n"
641                        "--\n",
642                        it->queueid,
643                        it->locked ? "*" : "",
644                        it->sender, it->addr);
645         }
646 }
647
648 /*
649  * TODO:
650  *
651  * - alias processing
652  * - use group permissions
653  * - proper sysexit codes
654  */
655
656 int
657 main(int argc, char **argv)
658 {
659         char *sender = NULL;
660         const char *tag = "dma";
661         struct qitem *it;
662         struct queue queue;
663         struct queue lqueue;
664         int i, ch;
665         int nodot = 0, doqueue = 0, showq = 0;
666
667         atexit(deltmp);
668         LIST_INIT(&queue.queue);
669
670         if (strcmp(argv[0], "mailq") == 0) {
671                 argv++; argc--;
672                 showq = 1;
673                 if (argc != 0)
674                         errx(1, "invalid arguments");
675                 goto skipopts;
676         }
677
678         opterr = 0;
679         while ((ch = getopt(argc, argv, "A:b:B:C:d:Df:F:h:iL:N:no:O:q:r:R:UV:vX:")) != -1) {
680                 switch (ch) {
681                 case 'A':
682                         /* -AX is being ignored, except for -A{c,m} */
683                         if (optarg[0] == 'c' || optarg[0] == 'm') {
684                                 break;
685                         }
686                         /* else FALLTRHOUGH */
687                 case 'b':
688                         /* -bX is being ignored, except for -bp */
689                         if (optarg[0] == 'p') {
690                                 showq = 1;
691                                 break;
692                         }
693                         /* else FALLTRHOUGH */
694                 case 'D':
695                         daemonize = 0;
696                         break;
697                 case 'L':
698                         tag = optarg;
699                         break;
700                 case 'f':
701                 case 'r':
702                         sender = optarg;
703                         break;
704
705                 case 'o':
706                         /* -oX is being ignored, except for -oi */
707                         if (optarg[0] != 'i')
708                                 break;
709                         /* else FALLTRHOUGH */
710                 case 'O':
711                         break;
712                 case 'i':
713                         nodot = 1;
714                         break;
715
716                 case 'q':
717                         doqueue = 1;
718                         break;
719
720                 /* Ignored options */
721                 case 'B':
722                 case 'C':
723                 case 'd':
724                 case 'F':
725                 case 'h':
726                 case 'N':
727                 case 'n':
728                 case 'R':
729                 case 'U':
730                 case 'V':
731                 case 'v':
732                 case 'X':
733                         break;
734
735                 default:
736                         exit(1);
737                 }
738         }
739         argc -= optind;
740         argv += optind;
741         opterr = 1;
742
743         if (argc != 0 && (showq || doqueue))
744                 errx(1, "sending mail and queue operations are mutually exclusive");
745
746         if (showq + doqueue > 1)
747                 errx(1, "conflicting queue operations");
748
749 skipopts:
750         openlog(tag, LOG_PID, LOG_MAIL);
751         set_username();
752
753         /* XXX fork root here */
754
755         config = calloc(1, sizeof(*config));
756         if (config == NULL)
757                 err(1, NULL);
758
759         if (parse_conf(CONF_PATH) < 0) {
760                 free(config);
761                 err(1, "can not read config file");
762         }
763
764         if (config->features & VIRTUAL)
765                 if (parse_virtuser(config->virtualpath) < 0)
766                         err(1, "can not read virtual user file `%s'",
767                                 config->virtualpath);
768
769         if (parse_authfile(config->authpath) < 0)
770                 err(1, "can not read SMTP authentication file");
771
772         if (showq) {
773                 load_queue(&lqueue, 1);
774                 show_queue(&lqueue);
775                 return (0);
776         }
777
778         if (doqueue) {
779                 load_queue(&lqueue, 0);
780                 run_queue(&lqueue);
781                 return (0);
782         }
783
784         if (read_aliases() != 0)
785                 err(1, "can not read aliases file `%s'", config->aliases);
786
787         if ((sender = set_from(sender)) == NULL)
788                 err(1, NULL);
789
790         for (i = 0; i < argc; i++) {
791                 if (add_recp(&queue, argv[i], sender, 1) != 0)
792                         errx(1, "invalid recipient `%s'", argv[i]);
793         }
794
795         if (LIST_EMPTY(&queue.queue))
796                 errx(1, "no recipients");
797
798         if (newspoolf(&queue, sender) != 0)
799                 err(1, "can not create temp file");
800
801         if (readmail(&queue, sender, nodot) != 0)
802                 err(1, "can not read mail");
803
804         if (linkspool(&queue) != 0)
805                 err(1, "can not create spools");
806
807         /* From here on the mail is safe. */
808
809         if (config->features & DEFER)
810                 return (0);
811
812         it = go_background(&queue);
813         deliver(it);
814
815         /* NOTREACHED */
816         return (0);
817 }