Removed mts.conf; the maildelivery option went into slocal directly.
[mmh] / uip / slocal.c
1 /*
2 ** slocal.c -- asynchronously filter and deliver new mail
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 /*
10 **  Under sendmail, users should add the line
11 **
12 **      "| /usr/local/nmh/lib/slocal"
13 **
14 **  to their $HOME/.forward file.
15 **
16 */
17
18 /* Changed to use getutent() and friends.  Assumes that when getutent() exists,
19 ** a number of other things also exist.  Please check.
20 ** Ruud de Rooij <ruud@ruud.org>  Sun, 28 May 2000 17:28:55 +0200
21 */
22
23 #include <h/mh.h>
24 #include <h/dropsbr.h>
25 #include <h/rcvmail.h>
26 #include <h/signals.h>
27 #include <h/tws.h>
28 #include <h/mts.h>
29 #include <h/utils.h>
30
31 #include <pwd.h>
32 #include <signal.h>
33 #include <sys/ioctl.h>
34 #include <fcntl.h>
35
36 #ifdef INITGROUPS_HEADER
37 #include INITGROUPS_HEADER
38 #else
39 /*
40 ** On AIX 4.1, initgroups() is defined and even documented (giving the
41 ** parameter types as char* and int), but doesn't have a prototype in any
42 ** of the system header files.  AIX 4.3, SunOS 4.1.3, and ULTRIX 4.2A have
43 ** the same problem.
44 */
45 extern int  initgroups(char*, int);
46 #endif
47
48 /*
49 ** This define is needed for Berkeley db v2 and above to
50 ** make the header file expose the 'historical' ndbm APIs.
51 ** We define it unconditionally because this is simple and
52 ** harmless.
53 */
54 #define DB_DBM_HSEARCH 1
55 #ifdef NDBM_HEADER
56 #include NDBM_HEADER
57 #endif
58
59 #include <utmp.h>
60
61 #ifndef HAVE_GETUTENT
62 # ifndef UTMP_FILE
63 #  ifdef _PATH_UTMP
64 #   define UTMP_FILE _PATH_UTMP
65 #  else
66 #   define UTMP_FILE "/etc/utmp"
67 #  endif
68 # endif
69 #endif
70
71 static struct swit switches[] = {
72 #define ADDRSW  0
73         { "addr address", 0 },
74 #define USERSW  1
75         { "user name", 0 },
76 #define FILESW  2
77         { "file file", 0 },
78 #define SENDERSW  3
79         { "sender address", 0 },
80 #define MAILBOXSW  4
81         { "mailbox file", 0 },
82 #define HOMESW  5
83         { "home directory", -4 },
84 #define INFOSW  6
85         { "info data", 0 },
86 #define MAILSW  7
87         { "maildelivery file", 0 },
88 #define VERBSW  8
89         { "verbose", 0 },
90 #define NVERBSW  9
91         { "noverbose", 0 },
92 #define SUPPRESSDUP   10
93         { "suppressdup", 0 },
94 #define NSUPPRESSDUP 11
95         { "nosuppressdup", 0 },
96 #define DEBUGSW  12
97         { "debug", 0 },
98 #define VERSIONSW  13
99         { "version", 0 },
100 #define HELPSW  14
101         { "help", 0 },
102         { NULL, 0 }
103 };
104
105
106 /* global maildelivery file */
107 char *maildelivery = NMHETCDIR"/maildelivery";
108
109
110 static int globbed = 0;  /* have we built "vars" table yet? */
111 static int parsed = 0;  /* have we built header field table yet   */
112 static int utmped = 0;  /* have we scanned umtp(x) file yet */
113 static int suppressdup = 0;  /* are we suppressing duplicate messages? */
114
115 static int verbose = 0;
116 static int debug = 0;
117
118 static char *addr = NULL;
119 static char *user = NULL;
120 static char *info = NULL;
121 static char *file = NULL;
122 static char *sender = NULL;
123 static char *envelope = NULL;  /* envelope information ("From " line)  */
124 static char *mbox = NULL;
125 static char *home = NULL;
126
127 static struct passwd *pw;  /* passwd file entry */
128
129 static char ddate[BUFSIZ];  /* record the delivery date */
130 struct tws *now;
131
132 static jmp_buf myctx;
133
134 /* flags for pair->p_flags */
135 #define P_NIL  0x00
136 #define P_ADR  0x01  /* field is address */
137 #define P_HID  0x02  /* special (fake) field */
138 #define P_CHK  0x04
139
140 struct pair {
141         char *p_name;
142         char *p_value;
143         char  p_flags;
144 };
145
146 #define NVEC 100
147
148 /*
149 ** Lookup table for matching fields and patterns
150 ** in messages.  The rest of the table is added
151 ** when the message is parsed.
152 */
153 static struct pair hdrs[NVEC + 1] = {
154         { "source",  NULL, P_HID },
155         { "addr",  NULL, P_HID },
156         { "Return-Path",  NULL, P_ADR },
157         { "Reply-To",  NULL, P_ADR },
158         { "From",  NULL, P_ADR },
159         { "Sender",  NULL, P_ADR },
160         { "To",  NULL, P_ADR },
161         { "cc",  NULL, P_ADR },
162         { "Resent-Reply-To", NULL, P_ADR },
163         { "Resent-From",  NULL, P_ADR },
164         { "Resent-Sender",   NULL, P_ADR },
165         { "Resent-To",  NULL, P_ADR },
166         { "Resent-Cc",  NULL, P_ADR },
167         { NULL, NULL, 0 }
168 };
169
170 /*
171 ** The list of builtin variables to expand in a string
172 ** before it is executed by the "pipe" or "qpipe" action.
173 */
174 static struct pair vars[] = {
175         { "sender",   NULL, P_NIL },
176         { "address",  NULL, P_NIL },
177         { "size",  NULL, P_NIL },
178         { "reply-to", NULL, P_CHK },
179         { "info",  NULL, P_NIL },
180         { NULL, NULL, 0 }
181 };
182
183 extern char **environ;
184
185 /*
186 ** static prototypes
187 */
188 static int localmail(int, char *);
189 static int usr_delivery(int, char *, int);
190 static int split(char *, char **);
191 static int parse(int);
192 static void expand(char *, char *, int);
193 static void glob(int);
194 static struct pair *lookup(struct pair *, char *);
195 static int logged_in(void);
196 static int timely(char *, char *);
197 static int usr_file(int, char *);
198 static int usr_pipe(int, char *, char *, char **, int);
199 static int usr_folder(int, char *);
200 static RETSIGTYPE alrmser(int);
201 static void get_sender(char *, char **);
202 static int copy_message(int, char *, int);
203 static void verbose_printf(char *fmt, ...);
204 static void adorn(char *, char *, ...);
205 static void debug_printf(char *fmt, ...);
206 static int suppress_duplicates(int, char *);
207 static char *trim(char *);
208
209
210 int
211 main(int argc, char **argv)
212 {
213         int fd, status;
214         FILE *fp = stdin;
215         char *cp, *mdlvr = NULL, buf[BUFSIZ];
216         char mailbox[BUFSIZ], tmpfil[BUFSIZ];
217         char **argp, **arguments;
218
219 #ifdef LOCALE
220         setlocale(LC_ALL, "");
221 #endif
222         invo_name = mhbasename(*argv);
223
224         /* foil search of user profile/context */
225         if (context_foil(NULL) == -1)
226                 done(1);
227
228         arguments = getarguments(invo_name, argc, argv, 0);
229         argp = arguments;
230
231         /* Parse arguments */
232         while ((cp = *argp++)) {
233                 if (*cp == '-') {
234                         switch (smatch(++cp, switches)) {
235                         case AMBIGSW:
236                                 ambigsw(cp, switches);
237                                 done(1);
238                         case UNKWNSW:
239                                 adios(NULL, "-%s unknown", cp);
240
241                         case HELPSW:
242                                 snprintf(buf, sizeof(buf), "%s [switches] [address info sender]", invo_name);
243                                 print_help(buf, switches, 0);
244                                 done(1);
245                         case VERSIONSW:
246                                 print_version(invo_name);
247                                 done(1);
248
249                         case ADDRSW:
250                                 if (!(addr = *argp++))
251                                         /* allow -xyz arguments */
252                                         adios(NULL, "missing argument to %s",
253                                                         argp[-2]);
254                                 continue;
255                         case INFOSW:
256                                 if (!(info = *argp++))
257                                         /* allow -xyz arguments */
258                                         adios(NULL, "missing argument to %s",
259                                                         argp[-2]);
260                                 continue;
261                         case USERSW:
262                                 if (!(user = *argp++))
263                                         /* allow -xyz arguments */
264                                         adios(NULL, "missing argument to %s",
265                                                         argp[-2]);
266                                 continue;
267                         case FILESW:
268                                 if (!(file = *argp++) || *file == '-')
269                                         adios(NULL, "missing argument to %s",
270                                                         argp[-2]);
271                                 continue;
272                         case SENDERSW:
273                                 if (!(sender = *argp++))
274                                         /* allow -xyz arguments */
275                                         adios(NULL, "missing argument to %s",
276                                                         argp[-2]);
277                                 continue;
278                         case MAILBOXSW:
279                                 if (!(mbox = *argp++) || *mbox == '-')
280                                         adios(NULL, "missing argument to %s",
281                                                         argp[-2]);
282                                 continue;
283                         case HOMESW:
284                                 if (!(home = *argp++) || *home == '-')
285                                         adios(NULL, "missing argument to %s",
286                                                         argp[-2]);
287                                 continue;
288
289                         case MAILSW:
290                                 if (!(cp = *argp++) || *cp == '-')
291                                         adios(NULL, "missing argument to %s",
292                                                         argp[-2]);
293                                 if (mdlvr)
294                                         adios(NULL, "only one maildelivery file at a time!");
295                                 mdlvr = cp;
296                                 continue;
297
298                         case VERBSW:
299                                 verbose++;
300                                 continue;
301                         case NVERBSW:
302                                 verbose = 0;
303                                 continue;
304
305                         case SUPPRESSDUP:
306                                 suppressdup++;
307                                 continue;
308                         case NSUPPRESSDUP:
309                                 suppressdup = 0;
310                                 continue;
311                         case DEBUGSW:
312                                 debug++;
313                                 continue;
314                         }
315                 }
316
317                 switch (argp - (argv + 1)) {
318                 case 1:
319                         addr = cp;
320                         break;
321                 case 2:
322                         info = cp;
323                         break;
324                 case 3:
325                         sender = cp;
326                         break;
327                 }
328         }
329
330         if (addr == NULL)
331                 addr = getusername();
332         if (user == NULL)
333                 user = (cp = strchr(addr, '.')) ? ++cp : addr;
334         if ((pw = getpwnam(user)) == NULL)
335                 adios(NULL, "no such local user as %s", user);
336
337         if (chdir(pw->pw_dir) == -1)
338                 chdir("/");
339         umask(0077);
340
341         if (geteuid() == 0) {
342                 setgid(pw->pw_gid);
343                 initgroups(pw->pw_name, pw->pw_gid);
344                 setuid(pw->pw_uid);
345         }
346
347         if (info == NULL)
348                 info = "";
349
350         setbuf(stdin, NULL);
351
352         /* Record the delivery time */
353         if ((now = dlocaltimenow()) == NULL)
354                 adios(NULL, "unable to ascertain local time");
355         snprintf(ddate, sizeof(ddate), "Delivery-Date: %s\n", dtimenow(0));
356
357         /*
358         ** Copy the message to a temporary file
359         */
360         if (file) {
361                 int tempfd;
362
363                 /* getting message from file */
364                 if ((tempfd = open(file, O_RDONLY)) == -1)
365                         adios(file, "unable to open");
366                 if (debug)
367                         debug_printf("retrieving message from file \"%s\"\n",
368                                         file);
369                 if ((fd = copy_message(tempfd, tmpfil, 1)) == -1)
370                         adios(NULL, "unable to create temporary file");
371                 close(tempfd);
372         } else {
373                 /* getting message from stdin */
374                 if (debug)
375                         debug_printf("retrieving message from stdin\n");
376                 if ((fd = copy_message(fileno(stdin), tmpfil, 1)) == -1)
377                         adios(NULL, "unable to create temporary file");
378         }
379
380         if (debug)
381                 debug_printf("temporary file=\"%s\"\n", tmpfil);
382
383         /*
384         ** Delete the temp file now or a copy of every single message
385         ** passed through slocal will be left in the /tmp directory until
386         ** deleted manually!  This unlink() used to be under an 'else'
387         ** of the 'if (debug)' above, but since some people like to
388         ** always run slocal with -debug and log the results, the /tmp
389         ** directory would get choked over time.  Of course, now that
390         ** we always delete the temp file, the "temporary file=" message
391         ** above is somewhat pointless -- someone watching debug output
392         ** wouldn't have a chance to 'tail -f' or 'ln' the temp file
393         ** before it's unlinked.  The best thing would be to delay this
394         ** unlink() until later if debug == 1, but I'll leave that for
395         ** someone who cares about the temp-file-accessing functionality
396         ** (they'll have to watch out for cases where we adios()).
397         */
398         unlink(tmpfil);
399
400         if (!(fp = fdopen(fd, "r+")))
401                 adios(NULL, "unable to access temporary file");
402
403         /*
404         ** If no sender given, extract it
405         ** from envelope information.
406         */
407         if (sender == NULL)
408                 get_sender(envelope, &sender);
409
410         if (mbox == NULL) {
411                 snprintf(mailbox, sizeof(mailbox), "%s/%s",
412                                 mailspool, pw->pw_name);
413                 mbox = mailbox;
414         }
415         if (home == NULL)
416                 home = pw->pw_dir;
417
418         if (debug) {
419                 debug_printf("addr=\"%s\"\n", trim(addr));
420                 debug_printf("user=\"%s\"\n", trim(user));
421                 debug_printf("info=\"%s\"\n", trim(info));
422                 debug_printf("sender=\"%s\"\n", trim(sender));
423                 debug_printf("envelope=\"%s\"\n",
424                                 envelope ? trim(envelope) : "");
425                 debug_printf("mbox=\"%s\"\n", trim(mbox));
426                 debug_printf("home=\"%s\"\n", trim(home));
427                 debug_printf("ddate=\"%s\"\n", trim(ddate));
428                 debug_printf("now=%02d:%02d\n\n", now->tw_hour, now->tw_min);
429         }
430
431         /* deliver the message */
432         status = localmail(fd, mdlvr);
433
434         done(status != -1 ? RCV_MOK : RCV_MBX);
435         return 1;
436 }
437
438
439 /*
440 ** Main routine for delivering message.
441 */
442
443 static int
444 localmail(int fd, char *mdlvr)
445 {
446         /* check if this message is a duplicate */
447         if (suppressdup && suppress_duplicates(fd, mdlvr ?
448                         mdlvr : ".maildelivery") == DONE)
449                 return 0;
450
451         /* delivery according to personal Maildelivery file */
452         if (usr_delivery(fd, mdlvr ? mdlvr : ".maildelivery", 0) != -1)
453                 return 0;
454
455         /* delivery according to global Maildelivery file */
456         if (usr_delivery(fd, maildelivery, 1) != -1)
457                 return 0;
458
459         if (verbose)
460                 verbose_printf("(delivering to standard mail spool)\n");
461
462         /* last resort - deliver to standard mail spool */
463         return usr_file(fd, mbox);
464 }
465
466
467 #define matches(a,b) (stringdex(b, a) >= 0)
468
469 /*
470 ** Parse the delivery file, and process incoming message.
471 */
472
473 static int
474 usr_delivery(int fd, char *delivery, int su)
475 {
476         int i, accept, status=1, won, vecp, next;
477         char *field, *pattern, *action, *result, *string;
478         char buffer[BUFSIZ], tmpbuf[BUFSIZ];
479         char *cp, *vec[NVEC];
480         struct stat st;
481         struct pair *p;
482         FILE *fp;
483
484         /* open the delivery file */
485         if ((fp = fopen(delivery, "r")) == NULL)
486                 return -1;
487
488         /* check if delivery file has bad ownership or permissions */
489         if (fstat(fileno(fp), &st) == -1 ||
490                         (st.st_uid != 0 && (su || st.st_uid != pw->pw_uid)) ||
491                         st.st_mode & (S_IWGRP|S_IWOTH)) {
492                 if (verbose) {
493                         verbose_printf("WARNING: %s has bad ownership/modes (su=%d,uid=%d,owner=%d,mode=0%o)\n", delivery, su, (int) pw->pw_uid, (int) st.st_uid, (int) st.st_mode);
494                 }
495                 return -1;
496         }
497
498         won = 0;
499         next = 1;
500
501         /* read and process delivery file */
502         while (fgets(buffer, sizeof(buffer), fp)) {
503                 /* skip comments and empty lines */
504                 if (*buffer == '#' || *buffer == '\n')
505                         continue;
506
507                 /* zap trailing newline */
508                 if ((cp = strchr(buffer, '\n')))
509                         *cp = 0;
510
511                 /* split buffer into fields */
512                 vecp = split(buffer, vec);
513
514                 /* check for too few fields */
515                 if (vecp < 5) {
516                         if (debug)
517                                 debug_printf("WARNING: entry with only %d fields, skipping.\n", vecp);
518                         continue;
519                 }
520
521                 if (debug) {
522                         for (i = 0; vec[i]; i++)
523                                 debug_printf("vec[%d]: \"%s\"\n",
524                                                 i, trim(vec[i]));
525                 }
526
527                 field   = vec[0];
528                 pattern = vec[1];
529                 action  = vec[2];
530                 result  = vec[3];
531                 string  = vec[4];
532
533                 /* find out how to perform the action */
534                 switch (result[0]) {
535                 case 'N':
536                 case 'n':
537                         /*
538                         ** If previous condition failed, don't
539                         ** do this - else fall through
540                         */
541                          if (!next)
542                                 continue;  /* else fall */
543
544                 case '?':
545                         /*
546                         ** If already delivered, skip this action.
547                         ** Else consider delivered if action is
548                         ** successful.
549                         */
550                         if (won)
551                                 continue;  /* else fall */
552
553                 case 'A':
554                 case 'a':
555                         /*
556                         ** Take action, and consider delivered if
557                         ** action is successful.
558                         */
559                         accept = 1;
560                         break;
561
562                 case 'R':
563                 case 'r':
564                 default:
565                         /*
566                         ** Take action, but don't consider delivered,
567                         ** even if action is successful
568                         */
569                         accept = 0;
570                         break;
571                 }
572
573                 if (vecp > 5) {
574                         if (!mh_strcasecmp(vec[5], "select")) {
575                                 if (logged_in() != -1)
576                                         continue;
577                                 if (vecp > 7 && timely(vec[6], vec[7]) == -1)
578                                         continue;
579                         }
580                 }
581
582                 /* check if the field matches */
583                 switch (*field) {
584                 case '*':
585                 /* always matches */
586                         break;
587
588                 case 'd':
589                 /*
590                 ** "default" matches only if the message hasn't
591                 ** been delivered yet.
592                 */
593                         if (!mh_strcasecmp(field, "default")) {
594                                 if (won)
595                                         continue;
596                                 break;
597                         }  /* else fall */
598
599                 default:
600                         /* parse message and build lookup table */
601                         if (!parsed && parse(fd) == -1) {
602                                 fclose(fp);
603                                 return -1;
604                         }
605                         /*
606                         ** find header field in lookup table, and
607                         ** see if the pattern matches.
608                         */
609                         if ((p = lookup(hdrs, field)) && (p->p_value != NULL)
610                                         && matches(p->p_value, pattern)) {
611                                 next = 1;
612                         } else {
613                                 next = 0;
614                                 continue;
615                         }
616                         break;
617                 }
618
619                 /* find out the action to perform */
620                 switch (*action) {
621                 case 'q':
622                         /* deliver to quoted pipe */
623                         if (mh_strcasecmp(action, "qpipe"))
624                                 continue;  /* else fall */
625                 case '^':
626                         expand(tmpbuf, string, fd);
627                         if (split(tmpbuf, vec) < 1)
628                                 continue;
629                         status = usr_pipe(fd, tmpbuf, vec[0], vec, 0);
630                         break;
631
632                 case 'p':
633                         /* deliver to pipe */
634                         if (mh_strcasecmp(action, "pipe"))
635                                 continue;  /* else fall */
636                 case '|':
637                         vec[2] = "sh";
638                         vec[3] = "-c";
639                         expand(tmpbuf, string, fd);
640                         vec[4] = tmpbuf;
641                         vec[5] = NULL;
642                         status = usr_pipe(fd, tmpbuf, "/bin/sh",
643                                         vec + 2, 0);
644                         break;
645
646                 case 'f':
647                         /* mbox format */
648                         if (!mh_strcasecmp(action, "file")) {
649                                 status = usr_file(fd, string);
650                                 break;
651                         }
652                         /* deliver to nmh folder */
653                         else if (mh_strcasecmp(action, "folder"))
654                                 continue;  /* else fall */
655                 case '+':
656                         status = usr_folder(fd, string);
657                         break;
658
659                 case 'm':
660                         /* mbox format */
661                         if (mh_strcasecmp(action, "mbox"))
662                                 continue;  /* else fall */
663
664                 case '>':
665                         /* mbox format */
666                         status = usr_file(fd, string);
667                         break;
668
669                 case 'd':
670                         /* ignore message */
671                         if (mh_strcasecmp(action, "destroy"))
672                                 continue;
673                         status = 0;
674                         break;
675                 }
676
677                 if (status)
678                         next = 0;  /* action failed, mark for 'N' result */
679
680                 if (accept && status == 0)
681                         won++;
682         }
683
684         fclose(fp);
685         return (won ? 0 : -1);
686 }
687
688
689 #define QUOTE  '\\'
690
691 /*
692 ** Split buffer into fields (delimited by whitespace or
693 ** comma's).  Return the number of fields found.
694 */
695
696 static int
697 split(char *cp, char **vec)
698 {
699         int i;
700         unsigned char *s;
701
702         s = cp;
703
704         /* split into a maximum of NVEC fields */
705         for (i = 0; i <= NVEC;) {
706                 vec[i] = NULL;
707
708                 /* zap any whitespace and comma's */
709                 while (isspace(*s) || *s == ',')
710                         *s++ = 0;
711
712                 /* end of buffer, time to leave */
713                 if (*s == 0)
714                         break;
715
716                 /* get double quote text as a single field */
717                 if (*s == '"') {
718                         for (vec[i++] = ++s; *s && *s != '"'; s++) {
719                                 /*
720                                 ** Check for escaped double quote.  We need
721                                 ** to shift the string to remove slash.
722                                 */
723                                 if (*s == QUOTE) {
724                                         if (*++s == '"')
725                                                 strcpy(s - 1, s);
726                                         s--;
727                                 }
728                         }
729                         if (*s == '"')  /* zap trailing double quote */
730                                 *s++ = 0;
731                         continue;
732                 }
733
734                 if (*s == QUOTE && *++s != '"')
735                         s--;
736                 vec[i++] = s++;
737
738                 /* move forward to next field delimiter */
739                 while (*s && !isspace(*s) && *s != ',')
740                         s++;
741         }
742         vec[i] = NULL;
743
744         return i;
745 }
746
747
748 /*
749 ** Parse the headers of a message, and build the
750 ** lookup table for matching fields and patterns.
751 */
752
753 static int
754 parse(int fd)
755 {
756         int i, state;
757         int fd1;
758         char *cp, *dp, *lp;
759         char name[NAMESZ], field[BUFSIZ];
760         struct pair *p, *q;
761         FILE  *in;
762
763         if (parsed++)
764                 return 0;
765
766         /* get a new FILE pointer to message */
767         if ((fd1 = dup(fd)) == -1)
768                 return -1;
769         if ((in = fdopen(fd1, "r")) == NULL) {
770                 close(fd1);
771                 return -1;
772         }
773         rewind(in);
774
775         /* add special entries to lookup table */
776         if ((p = lookup(hdrs, "source")))
777                 p->p_value = getcpy(sender);
778         if ((p = lookup(hdrs, "addr")))
779                 p->p_value = getcpy(addr);
780
781         /*
782          * Scan the headers of the message and build
783          * a lookup table.
784          */
785         for (i = 0, state = FLD;;) {
786                 switch (state = m_getfld(state, name, field, sizeof(field),
787                                 in)) {
788                 case FLD:
789                 case FLDEOF:
790                 case FLDPLUS:
791                         lp = getcpy(field);
792                         while (state == FLDPLUS) {
793                                 state = m_getfld(state, name, field,
794                                                 sizeof(field), in);
795                                 lp = add(field, lp);
796                         }
797                         for (p = hdrs; p->p_name; p++) {
798                                 if (!mh_strcasecmp(p->p_name, name)) {
799                                         if (!(p->p_flags & P_HID)) {
800                                                 if ((cp = p->p_value)) {
801                                                         if (p->p_flags & P_ADR) {
802                                                                 dp = cp + strlen(cp) - 1;
803                                                                 if (*dp == '\n')
804                                                                         *dp = 0;
805                                                                 cp = add(",\n\t", cp);
806                                                         } else {
807                                                                 cp = add("\t", cp);
808                                                         }
809                                                 }
810                                                 p->p_value = add(lp, cp);
811                                         }
812                                         free(lp);
813                                         break;
814                                 }
815                         }
816                         if (p->p_name == NULL && i < NVEC) {
817                                 p->p_name = getcpy(name);
818                                 p->p_value = lp;
819                                 p->p_flags = P_NIL;
820                                 p++, i++;
821                                 p->p_name = NULL;
822                         }
823                         if (state != FLDEOF)
824                                 continue;
825                         break;
826
827                 case BODY:
828                 case BODYEOF:
829                 case FILEEOF:
830                         break;
831
832                 case LENERR:
833                 case FMTERR:
834                         advise(NULL, "format error in message");
835                         break;
836
837                 default:
838                         advise(NULL, "internal error in m_getfld");
839                         fclose(in);
840                         return -1;
841                 }
842                 break;
843         }
844         fclose(in);
845
846         if ((p = lookup(vars, "reply-to"))) {
847                 if ((q = lookup(hdrs, "reply-to")) == NULL ||
848                                 q->p_value == NULL)
849                         q = lookup(hdrs, "from");
850                 p->p_value = getcpy(q ? q->p_value : "");
851                 p->p_flags &= ~P_CHK;
852                 if (debug)
853                         debug_printf("vars[%d]: name=\"%s\" value=\"%s\"\n",
854                                         p - vars, p->p_name, trim(p->p_value));
855         }
856         if (debug) {
857                 for (p = hdrs; p->p_name; p++)
858                         debug_printf("hdrs[%d]: name=\"%s\" value=\"%s\"\n",
859                                 p - hdrs, p->p_name,
860                                 p->p_value ? trim(p->p_value) : "");
861         }
862
863         return 0;
864 }
865
866
867 #define LPAREN '('
868 #define RPAREN ')'
869
870 /*
871 ** Expand any builtin variables such as $(sender),
872 ** $(address), etc., in a string.
873 */
874
875 static void
876 expand(char *s1, char *s2, int fd)
877 {
878         char c, *cp;
879         struct pair *p;
880
881         if (!globbed)
882                 glob(fd);
883
884         while ((c = *s2++)) {
885                 if (c != '$' || *s2 != LPAREN) {
886                         *s1++ = c;
887                 } else {
888                         for (cp = ++s2; *s2 && *s2 != RPAREN; s2++)
889                                 continue;
890                         if (*s2 != RPAREN) {
891                                 s2 = --cp;
892                                 continue;
893                         }
894                         *s2++ = 0;
895                         if ((p = lookup(vars, cp))) {
896                                 if (!parsed && (p->p_flags & P_CHK))
897                                         parse(fd);
898
899                                 strcpy(s1, p->p_value);
900                                 s1 += strlen(s1);
901                         }
902                 }
903         }
904         *s1 = 0;
905 }
906
907
908 /*
909 ** Fill in the information missing from the "vars"
910 ** table, which is necessary to expand any builtin
911 ** variables in the string for a "pipe" or "qpipe"
912 ** action.
913 */
914
915 static void
916 glob(int fd)
917 {
918         char buffer[BUFSIZ];
919         struct stat st;
920         struct pair *p;
921
922         if (globbed++)
923                 return;
924
925         if ((p = lookup(vars, "sender")))
926                 p->p_value = getcpy(sender);
927         if ((p = lookup(vars, "address")))
928                 p->p_value = getcpy(addr);
929         if ((p = lookup(vars, "size"))) {
930                 snprintf(buffer, sizeof(buffer), "%d",
931                                 fstat(fd, &st) != -1 ? (int) st.st_size : 0);
932                 p->p_value = getcpy(buffer);
933         }
934         if ((p = lookup(vars, "info")))
935                 p->p_value = getcpy(info);
936
937         if (debug) {
938                 for (p = vars; p->p_name; p++)
939                         debug_printf("vars[%d]: name=\"%s\" value=\"%s\"\n",
940                                         p - vars, p->p_name, trim(p->p_value));
941         }
942 }
943
944
945 /*
946 ** Find a matching name in a lookup table.  If found,
947 ** return the "pairs" entry, else return NULL.
948 */
949
950 static struct pair *
951 lookup(struct pair *pairs, char *key)
952 {
953         for (; pairs->p_name; pairs++)
954                 if (!mh_strcasecmp(pairs->p_name, key))
955                         return pairs;
956
957         return NULL;
958 }
959
960
961 /*
962 ** Check utmp(x) file to see if user is currently
963 ** logged in.
964 */
965
966 #ifdef HAVE_GETUTENT
967 static int
968 logged_in(void)
969 {
970         struct utmp * utp;
971
972         if (utmped)
973                 return utmped;
974
975         setutent();
976
977         while ((utp = getutent()) != NULL) {
978                 if (
979 #ifdef HAVE_STRUCT_UTMP_UT_TYPE
980                                 utp->ut_type == USER_PROCESS &&
981 #endif
982                                 utp->ut_name[0] != 0 &&
983                                 strncmp(user, utp->ut_name,
984                                 sizeof(utp->ut_name)) == 0) {
985                         if (debug)
986                                 continue;
987                         endutent();
988                         return (utmped = DONE);
989                 }
990         }
991
992         endutent();
993         return (utmped = NOTOK);
994 }
995 #else
996 static int
997 logged_in(void)
998 {
999         struct utmp ut;
1000         FILE *uf;
1001
1002         if (utmped)
1003                 return utmped;
1004
1005         if ((uf = fopen(UTMP_FILE, "r")) == NULL)
1006                 return NOTOK;
1007
1008         while (fread((char *) &ut, sizeof(ut), 1, uf) == 1) {
1009                 if (ut.ut_name[0] != 0 &&
1010                                 strncmp(user, ut.ut_name, sizeof(ut.ut_name))
1011                                 == 0) {
1012                         if (debug)
1013                                 continue;
1014                         fclose(uf);
1015                         return (utmped = DONE);
1016                 }
1017         }
1018
1019         fclose(uf);
1020         return (utmped = NOTOK);
1021 }
1022 #endif
1023
1024 #define check(t,a,b)  if (t < a || t > b) return -1
1025 #define cmpar(h1,m1,h2,m2)  if (h1 < h2 || (h1 == h2 && m1 < m2)) return 0
1026
1027 static int
1028 timely(char *t1, char *t2)
1029 {
1030         int t1hours, t1mins, t2hours, t2mins;
1031
1032         if (sscanf(t1, "%d:%d", &t1hours, &t1mins) != 2)
1033                 return -1;
1034         check(t1hours, 0, 23);
1035         check(t1mins, 0, 59);
1036
1037         if (sscanf(t2, "%d:%d", &t2hours, &t2mins) != 2)
1038                 return -1;
1039         check(t2hours, 0, 23);
1040         check(t2mins, 0, 59);
1041
1042         cmpar(now->tw_hour, now->tw_min, t1hours, t1mins);
1043         cmpar(t2hours, t2mins, now->tw_hour, now->tw_min);
1044
1045         return -1;
1046 }
1047
1048
1049 /*
1050 ** Deliver message by appending to a file.
1051 */
1052
1053 static int
1054 usr_file(int fd, char *mailbox)
1055 {
1056         int md;
1057
1058         if (verbose)
1059                 verbose_printf("delivering to file \"%s\"", mailbox);
1060
1061         if (verbose)
1062                 verbose_printf(" (mbox style)");
1063
1064         /* open and lock the file */
1065         if ((md = mbx_open(mailbox, pw->pw_uid, pw->pw_gid,
1066                         m_gmprot())) == -1) {
1067                 if (verbose)
1068                         adorn("", "unable to open:");
1069                 return -1;
1070         }
1071
1072         lseek(fd, (off_t) 0, SEEK_SET);
1073
1074         /* append message to file */
1075         if (mbx_copy(mailbox, md, fd, verbose) == -1) {
1076                 if (verbose)
1077                         adorn("", "error writing to:");
1078                 return -1;
1079         }
1080
1081         /* close and unlock file */
1082         if (mbx_close(mailbox, md) == NOTOK) {
1083                 if (verbose)
1084                         adorn("", "error closing:");
1085                 return -1;
1086         }
1087
1088         if (verbose)
1089                 verbose_printf(", success.\n");
1090         return 0;
1091 }
1092
1093
1094 /*
1095 ** Deliver message to a nmh folder.
1096 */
1097
1098 static int
1099 usr_folder(int fd, char *string)
1100 {
1101         int status;
1102         char folder[BUFSIZ], *vec[3];
1103
1104         /* get folder name ready */
1105         if (*string == '+')
1106                 strncpy(folder, string, sizeof(folder));
1107         else
1108                 snprintf(folder, sizeof(folder), "+%s", string);
1109
1110         if (verbose)
1111                 verbose_printf("delivering to folder \"%s\"", folder + 1);
1112
1113         vec[0] = "rcvstore";
1114         vec[1] = folder;
1115         vec[2] = NULL;
1116
1117         /* use rcvstore to put message in folder */
1118         status = usr_pipe(fd, "rcvstore", rcvstoreproc, vec, 1);
1119
1120 #if 0
1121         /*
1122         ** Currently, verbose status messages are handled by usr_pipe().
1123         */
1124         if (verbose) {
1125                 if (status == 0)
1126                         verbose_printf(", success.\n");
1127                 else
1128                         verbose_printf(", failed.\n");
1129         }
1130 #endif
1131
1132         return status;
1133 }
1134
1135 /*
1136 ** Deliver message to a process.
1137 */
1138
1139 static int
1140 usr_pipe(int fd, char *cmd, char *pgm, char **vec, int suppress)
1141 {
1142         pid_t child_id;
1143         int i, bytes, seconds, status;
1144         struct stat st;
1145
1146         if (verbose && !suppress)
1147                 verbose_printf("delivering to pipe \"%s\"", cmd);
1148
1149         lseek(fd, (off_t) 0, SEEK_SET);
1150
1151         for (i = 0; (child_id = fork()) == -1 && i < 5; i++)
1152                 sleep(5);
1153
1154         switch (child_id) {
1155         case -1:
1156                 /* fork error */
1157                 if (verbose)
1158                         adorn("fork", "unable to");
1159                 return -1;
1160
1161         case 0:
1162                 /* child process */
1163                 if (fd != 0)
1164                         dup2(fd, 0);
1165                 freopen("/dev/null", "w", stdout);
1166                 freopen("/dev/null", "w", stderr);
1167                 if (fd != 3)
1168                         dup2(fd, 3);
1169                 closefds(4);
1170
1171 #ifdef TIOCNOTTY
1172                 if ((fd = open("/dev/tty", O_RDWR)) != -1) {
1173                         ioctl(fd, TIOCNOTTY, NULL);
1174                         close(fd);
1175                 }
1176 #endif /* TIOCNOTTY */
1177
1178                 /* put in own process group */
1179                 setpgid((pid_t) 0, getpid());
1180
1181                 *environ = NULL;
1182                 m_putenv("USER", pw->pw_name);
1183                 m_putenv("HOME", pw->pw_dir);
1184                 m_putenv("SHELL", pw->pw_shell);
1185
1186                 execvp(pgm, vec);
1187                 _exit(-1);
1188
1189         default:
1190                 /* parent process */
1191                 if (!setjmp(myctx)) {
1192                         SIGNAL(SIGALRM, alrmser);
1193                         bytes = fstat(fd, &st) != -1 ?
1194                                         (int) st.st_size : 100;
1195
1196                         /*
1197                         ** amount of time to wait depends on
1198                         ** message size
1199                         */
1200                         if (bytes <= 100) {
1201                                 /* give at least 5 minutes */
1202                                 seconds = 300;
1203                         } else if (bytes >= 90000) {
1204                                 /* a half hour is long enough */
1205                                 seconds = 1800;
1206                         } else {
1207                                 seconds = (bytes / 60) + 300;
1208                         }
1209                         alarm((unsigned int) seconds);
1210                         status = pidwait(child_id, 0);
1211                         alarm(0);
1212
1213                         if (verbose) {
1214                                 if (status == 0)
1215                                         verbose_printf(", success.\n");
1216                                 else
1217                                         if ((status & 0xff00) == 0xff00)
1218                                                 verbose_printf(", system error\n");
1219                                         else
1220                                                 pidstatus(status, stdout, ", failed");
1221                         }
1222                         return (status == 0 ? 0 : -1);
1223                 } else {
1224                         /*
1225                         ** Ruthlessly kill the child and anything
1226                         ** else in its process group.
1227                         */
1228                         KILLPG(child_id, SIGKILL);
1229                         if (verbose)
1230                                 verbose_printf(", timed-out; terminated\n");
1231                         return -1;
1232                 }
1233         }
1234 }
1235
1236
1237 static RETSIGTYPE
1238 alrmser(int i)
1239 {
1240 #ifndef RELIABLE_SIGNALS
1241         SIGNAL(SIGALRM, alrmser);
1242 #endif
1243
1244         longjmp(myctx, DONE);
1245 }
1246
1247
1248 /*
1249 ** Get the `sender' from the envelope
1250 ** information ("From " line).
1251 */
1252
1253 static void
1254 get_sender(char *envelope, char **sender)
1255 {
1256         int i;
1257         unsigned char *cp;
1258         unsigned char buffer[BUFSIZ];
1259
1260         if (envelope == NULL) {
1261                 *sender = getcpy("");
1262                 return;
1263         }
1264
1265         i = strlen("From ");
1266         strncpy(buffer, envelope + i, sizeof(buffer));
1267         if ((cp = strchr(buffer, '\n'))) {
1268                 *cp = 0;
1269                 cp -= 24;
1270                 if (cp < buffer)
1271                         cp = buffer;
1272         } else {
1273                 cp = buffer;
1274         }
1275         *cp = 0;
1276
1277         for (cp = buffer + strlen(buffer) - 1; cp >= buffer; cp--)
1278                 if (isspace(*cp))
1279                         *cp = 0;
1280                 else
1281                         break;
1282         *sender = getcpy(buffer);
1283 }
1284
1285
1286 /*
1287 ** Copy message into a temporary file.
1288 ** While copying, it will do some header processing
1289 ** including the extraction of the envelope information.
1290 */
1291
1292 static int
1293 copy_message(int qd, char *tmpfil, int fold)
1294 {
1295         int i, first = 1, fd1, fd2;
1296         char buffer[BUFSIZ];
1297         FILE *qfp, *ffp;
1298         char *tfile = NULL;
1299
1300         tfile = m_mktemp2(NULL, invo_name, &fd1, NULL);
1301         if (tfile == NULL) return -1;
1302         fchmod(fd1, 0600);
1303         strncpy(tmpfil, tfile, BUFSIZ);
1304
1305         if (!fold) {
1306                 while ((i = read(qd, buffer, sizeof(buffer))) > 0)
1307                         if (write(fd1, buffer, i) != i) {
1308 you_lose:
1309                                 close(fd1);
1310                                 unlink(tmpfil);
1311                                 return -1;
1312                         }
1313                 if (i == -1)
1314                         goto you_lose;
1315                 lseek(fd1, (off_t) 0, SEEK_SET);
1316                 return fd1;
1317         }
1318
1319         /* dup the fd for incoming message */
1320         if ((fd2 = dup(qd)) == -1) {
1321                 close(fd1);
1322                 return -1;
1323         }
1324
1325         /* now create a FILE pointer for it */
1326         if ((qfp = fdopen(fd2, "r")) == NULL) {
1327                 close(fd1);
1328                 close(fd2);
1329                 return -1;
1330         }
1331
1332         /* dup the fd for temporary file */
1333         if ((fd2 = dup(fd1)) == -1) {
1334                 close(fd1);
1335                 fclose(qfp);
1336                 return -1;
1337         }
1338
1339         /* now create a FILE pointer for it */
1340         if ((ffp = fdopen(fd2, "r+")) == NULL) {
1341                 close(fd1);
1342                 close(fd2);
1343                 fclose(qfp);
1344                 return -1;
1345         }
1346
1347         /*
1348         ** copy message into temporary file
1349         ** and massage the headers.  Save
1350         ** a copy of the "From " line for later.
1351         */
1352         i = strlen("From ");
1353         while (fgets(buffer, sizeof(buffer), qfp)) {
1354                 if (first) {
1355                         first = 0;
1356                         if (strncmp(buffer, "From ", i)==0) {
1357 #ifdef RPATHS
1358                                 char *fp, *cp, *hp, *ep;
1359 #endif
1360                                 /*
1361                                 ** get copy of envelope information
1362                                 ** ("From " line)
1363                                 */
1364                                 envelope = getcpy(buffer);
1365
1366 #if 0
1367                                 /*
1368                                 ** First go ahead and put "From " line
1369                                 ** in message
1370                                 */
1371                                 fputs(buffer, ffp);
1372                                 if (ferror(ffp))
1373                                         goto fputs_error;
1374 #endif
1375
1376 #ifdef RPATHS
1377                                 /*
1378                                 ** Now create a "Return-Path:" line
1379                                 ** from the "From " line.
1380                                 */
1381                                 hp = cp = strchr(fp = envelope + i, ' ');
1382                                 while ((hp = strchr(++hp, 'r')))
1383                                         if (uprf(hp, "remote from")) {
1384                                                 hp = strrchr(hp, ' ');
1385                                                 break;
1386                                         }
1387                                 if (hp) {
1388                                         /*
1389                                         ** return path for UUCP style
1390                                         ** addressing
1391                                         */
1392                                         ep = strchr(++hp, '\n');
1393                                         snprintf(buffer, sizeof(buffer), "Return-Path: %.*s!%.*s\n", (int)(ep - hp), hp, (int)(cp - fp), fp);
1394                                 } else {
1395                                         /*
1396                                         ** return path for standard domain
1397                                         ** addressing
1398                                         */
1399                                         snprintf(buffer, sizeof(buffer), "Return-Path: %.*s\n", (int)(cp - fp), fp);
1400                                 }
1401
1402                                 /* Add Return-Path header to message */
1403                                 fputs(buffer, ffp);
1404                                 if (ferror(ffp))
1405                                         goto fputs_error;
1406 #endif
1407                                 /* Put the delivery date in message */
1408                                 fputs(ddate, ffp);
1409                                 if (ferror(ffp))
1410                                         goto fputs_error;
1411
1412                                 continue;
1413                         }
1414                 }
1415
1416                 fputs(buffer, ffp);
1417                 if (ferror(ffp))
1418                         goto fputs_error;
1419         }
1420
1421         fclose(ffp);
1422         if (ferror(qfp)) {
1423                 close(fd1);
1424                 fclose(qfp);
1425                 return -1;
1426         }
1427         fclose(qfp);
1428         lseek(fd1, (off_t) 0, SEEK_SET);
1429         return fd1;
1430
1431
1432 fputs_error:
1433         close(fd1);
1434         fclose(ffp);
1435         fclose(qfp);
1436         return -1;
1437 }
1438
1439 /*
1440 ** Trim strings for pretty printing of debugging output
1441 */
1442
1443 static char *
1444 trim(char *cp)
1445 {
1446         char buffer[BUFSIZ*4];
1447         unsigned char *bp, *sp;
1448
1449         if (cp == NULL)
1450                 return NULL;
1451
1452         /* copy string into temp buffer */
1453         strncpy(buffer, cp, sizeof(buffer));
1454         bp = buffer;
1455
1456         /* skip over leading whitespace */
1457         while (isspace(*bp))
1458                 bp++;
1459
1460         /* start at the end and zap trailing whitespace */
1461         for (sp = bp + strlen(bp) - 1; sp >= bp; sp--) {
1462                 if (isspace(*sp))
1463                         *sp = 0;
1464                 else
1465                         break;
1466         }
1467
1468         /* replace remaining whitespace with spaces */
1469         for (sp = bp; *sp; sp++)
1470                 if (isspace(*sp))
1471                         *sp = ' ';
1472
1473         /* now return a copy */
1474         return getcpy(bp);
1475 }
1476
1477 /*
1478 ** Function for printing `verbose' messages.
1479 */
1480
1481 static void
1482 verbose_printf(char *fmt, ...)
1483 {
1484         va_list ap;
1485
1486         va_start(ap, fmt);
1487         vfprintf(stdout, fmt, ap);
1488         va_end(ap);
1489
1490         fflush(stdout);  /* now flush output */
1491 }
1492
1493
1494 /*
1495 ** Function for printing `verbose' delivery
1496 ** error messages.
1497 */
1498
1499 static void
1500 adorn(char *what, char *fmt, ...)
1501 {
1502         va_list ap;
1503         int eindex;
1504         char *s;
1505
1506         eindex = errno;  /* save the errno */
1507         fprintf(stdout, ", ");
1508
1509         va_start(ap, fmt);
1510         vfprintf(stdout, fmt, ap);
1511         va_end(ap);
1512
1513         if (what) {
1514                 if (*what)
1515                         fprintf(stdout, " %s: ", what);
1516                 if ((s = strerror(eindex)))
1517                         fprintf(stdout, "%s", s);
1518                 else
1519                         fprintf(stdout, "Error %d", eindex);
1520         }
1521
1522         fputc('\n', stdout);
1523         fflush(stdout);
1524 }
1525
1526
1527 /*
1528 ** Function for printing `debug' messages.
1529 */
1530
1531 static void
1532 debug_printf(char *fmt, ...)
1533 {
1534         va_list ap;
1535
1536         va_start(ap, fmt);
1537         vfprintf(stderr, fmt, ap);
1538         va_end(ap);
1539 }
1540
1541
1542 /*
1543 ** Check ndbm/db file(s) to see if the Message-Id of this
1544 ** message matches the Message-Id of a previous message,
1545 ** so we can discard it.  If it doesn't match, we add the
1546 ** Message-Id of this message to the ndbm/db file.
1547 */
1548 static int
1549 suppress_duplicates(int fd, char *file)
1550 {
1551         int fd1, lockfd, state, result;
1552         char *cp, buf[BUFSIZ], name[NAMESZ];
1553         datum key, value;
1554         DBM *db;
1555         FILE *in;
1556
1557         if ((fd1 = dup(fd)) == -1)
1558                 return -1;
1559         if (!(in = fdopen(fd1, "r"))) {
1560                 close(fd1);
1561                 return -1;
1562         }
1563         rewind(in);
1564
1565         for (state = FLD;;) {
1566                 state = m_getfld(state, name, buf, sizeof(buf), in);
1567                 switch (state) {
1568                 case FLD:
1569                 case FLDPLUS:
1570                 case FLDEOF:
1571                         /* Search for the message ID */
1572                         if (mh_strcasecmp(name, "Message-ID")) {
1573                                 while (state == FLDPLUS)
1574                                         state = m_getfld(state, name, buf,
1575                                                         sizeof(buf), in);
1576                                 continue;
1577                         }
1578
1579                         cp = getcpy(buf);
1580                         while (state == FLDPLUS) {
1581                                 state = m_getfld(state, name, buf,
1582                                                 sizeof(buf), in);
1583                                 cp = add(buf, cp);
1584                         }
1585                         key.dptr = trimcpy(cp);
1586                         key.dsize = strlen(key.dptr) + 1;
1587                         free(cp);
1588                         cp = key.dptr;
1589
1590                         if (!(db = dbm_open(file, O_RDWR | O_CREAT, 0600))) {
1591                                 advise(file, "unable to perform dbm_open on");
1592                                 free(cp);
1593                                 fclose(in);
1594                                 return -1;
1595                         }
1596                         /*
1597                         ** Since it is difficult to portable lock a ndbm
1598                         ** file, we will open and lock the Maildelivery
1599                         ** file instead.  This will fail if your Maildelivery
1600                         ** file doesn't exist.
1601                         */
1602                         if ((lockfd = lkopen(file, O_RDWR, 0)) == -1) {
1603                                 advise(file, "unable to perform file locking on");
1604                                 free(cp);
1605                                 fclose(in);
1606                                 return -1;
1607                         }
1608                         value = dbm_fetch(db, key);
1609                         if (value.dptr) {
1610                                 if (verbose)
1611                                         verbose_printf("Message-ID: %s\n            already received on %s", cp, value.dptr);
1612                                 result = DONE;
1613                         } else {
1614                                 value.dptr  = ddate + sizeof("Delivery-Date:");
1615                                 value.dsize = strlen(value.dptr) + 1;
1616                                 if (dbm_store(db, key, value, DBM_INSERT))
1617                                         advise(file, "possibly corrupt file");
1618                                 result = 0;
1619                         }
1620
1621                         dbm_close(db);
1622                         lkclose(lockfd, file);
1623                         free(cp);
1624                         fclose(in);
1625                         return result;
1626                         break;
1627
1628                 case BODY:
1629                 case BODYEOF:
1630                 case FILEEOF:
1631                         break;
1632
1633                 case LENERR:
1634                 case FMTERR:
1635                 default:
1636                         break;
1637                 }
1638
1639                 break;
1640         }
1641
1642         fclose(in);
1643         return 0;
1644 }