Added argument to fmt_scan() to specify the buffer size.
[mmh] / uip / mhlsbr.c
1
2 /*
3  * mhlsbr.c -- main routines for nmh message lister
4  *
5  * This code is Copyright (c) 2002, by the authors of nmh.  See the
6  * COPYRIGHT file in the root directory of the nmh distribution for
7  * complete copyright information.
8  */
9
10 #include <h/mh.h>
11 #include <h/signals.h>
12 #include <h/addrsbr.h>
13 #include <h/fmt_scan.h>
14 #include <h/tws.h>
15 #include <h/utils.h>
16 #include <setjmp.h>
17 #include <signal.h>
18 #include <errno.h>
19 #include <sys/wait.h>
20 #include <sys/types.h>
21
22 /*
23  * MAJOR BUG:
24  * for a component containing addresses, ADDRFMT, if COMPRESS is also
25  * set, then addresses get split wrong (not at the spaces between commas).
26  * To fix this correctly, putstr() should know about "atomic" strings that
27  * must NOT be broken across lines.  That's too difficult for right now
28  * (it turns out that there are a number of degernate cases), so in
29  * oneline(), instead of
30  *
31  *       (*onelp == '\n' && !onelp[1])
32  *
33  * being a terminating condition,
34  *
35  *       (*onelp == '\n' && (!onelp[1] || (flags & ADDRFMT)))
36  *
37  * is used instead.  This cuts the line prematurely, and gives us a much
38  * better chance of getting things right.
39  */
40
41 #define ONECOMP  0
42 #define TWOCOMP  1
43 #define BODYCOMP 2
44
45 #define QUOTE   '\\'
46
47 static struct swit mhlswitches[] = {
48 #define BELLSW         0
49     { "bell", 0 },
50 #define NBELLSW        1
51     { "nobell", 0 },
52 #define CLRSW          2
53     { "clear", 0 },
54 #define NCLRSW         3
55     { "noclear", 0 },
56 #define FOLDSW         4
57     { "folder +folder", 0 },
58 #define FORMSW         5
59     { "form formfile", 0 },
60 #define PROGSW         6
61     { "moreproc program", 0 },
62 #define NPROGSW        7
63     { "nomoreproc", 0 },
64 #define LENSW          8
65     { "length lines", 0 },
66 #define WIDTHSW        9
67     { "width columns", 0 },
68 #define SLEEPSW       10
69     { "sleep seconds",  0 },
70 #define BITSTUFFSW    11
71     { "dashstuffing", -12 },    /* interface from forw */
72 #define NBITSTUFFSW   12
73     { "nodashstuffing", -14 },  /* interface from forw */
74 #define VERSIONSW     13
75     { "version", 0 },
76 #define HELPSW        14
77     { "help", 0 },
78 #define FORW1SW       15
79     { "forward", -7 },          /* interface from forw */
80 #define FORW2SW       16
81     { "forwall", -7 },          /* interface from forw */
82 #define DGSTSW        17
83     { "digest list", -6 },
84 #define VOLUMSW       18
85     { "volume number", -6 },
86 #define ISSUESW       19
87     { "issue number", -5 },
88 #define NBODYSW       20
89     { "nobody", -6 },
90 #define FMTPROCSW     21
91     { "fmtproc program", 0 },
92 #define NFMTPROCSW    22
93     { "nofmtproc", 0 },
94     { NULL, 0 }
95 };
96
97 #define NOCOMPONENT 0x000001    /* don't show component name         */
98 #define UPPERCASE   0x000002    /* display in all upper case         */
99 #define CENTER      0x000004    /* center line                       */
100 #define CLEARTEXT   0x000008    /* cleartext                         */
101 #define EXTRA       0x000010    /* an "extra" component              */
102 #define HDROUTPUT   0x000020    /* already output                    */
103 #define CLEARSCR    0x000040    /* clear screen                      */
104 #define LEFTADJUST  0x000080    /* left justify multiple lines       */
105 #define COMPRESS    0x000100    /* compress text                     */
106 #define ADDRFMT     0x000200    /* contains addresses                */
107 #define BELL        0x000400    /* sound bell at EOP                 */
108 #define DATEFMT     0x000800    /* contains dates                    */
109 #define FORMAT      0x001000    /* parse address/date/RFC-2047 field */
110 #define INIT        0x002000    /* initialize component              */
111 #define SPLIT       0x010000    /* split headers (don't concatenate) */
112 #define NONEWLINE   0x020000    /* don't write trailing newline      */
113 #define NOWRAP      0x040000    /* Don't wrap lines ever             */
114 #define FMTFILTER   0x080000    /* Filter through format filter      */
115 #define LBITS   "\020\01NOCOMPONENT\02UPPERCASE\03CENTER\04CLEARTEXT\05EXTRA\06HDROUTPUT\07CLEARSCR\010LEFTADJUST\011COMPRESS\012ADDRFMT\013BELL\014DATEFMT\015FORMAT\016INIT\021SPLIT\022NONEWLINE\023NOWRAP\024FMTFILTER"
116 #define GFLAGS  (NOCOMPONENT | UPPERCASE | CENTER | LEFTADJUST | COMPRESS | SPLIT | NOWRAP)
117
118 /*
119  * A list of format arguments
120  */
121
122 struct arglist {
123     struct format *a_fmt;
124     char *a_nfs;
125     struct arglist *a_next;
126 };
127
128 struct mcomp {
129     char *c_name;               /* component name          */
130     char *c_text;               /* component text          */
131     char *c_ovtxt;              /* text overflow indicator */
132     char *c_nfs;                /* iff FORMAT              */
133     struct format *c_fmt;       /*   ..                    */
134     int c_offset;               /* left margin indentation */
135     int c_ovoff;                /* overflow indentation    */
136     int c_width;                /* width of field          */
137     int c_cwidth;               /* width of component      */
138     int c_length;               /* length in lines         */
139     long c_flags;
140     struct arglist *c_f_args;   /* Argument list for filter*/
141     struct arglist *c_f_tail;   /* Pointer to tail of list */
142     int c_nargs;                /* Number of arguments     */
143     struct mcomp *c_next;
144 };
145
146 static struct mcomp *msghd = NULL;
147 static struct mcomp *msgtl = NULL;
148 static struct mcomp *fmthd = NULL;
149 static struct mcomp *fmttl = NULL;
150
151 static struct mcomp global = {
152     NULL, NULL, NULL, NULL, NULL, 0, -1, 80, -1, 40, BELL, NULL, NULL, 0,
153     NULL
154 };
155
156 static struct mcomp holder = {
157     NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0, NOCOMPONENT, NULL, NULL, 0,
158     NULL
159 };
160
161 struct pair {
162     char *p_name;
163     long p_flags;
164 };
165
166 static struct pair pairs[] = {
167     { "Date",            DATEFMT },
168     { "From",            ADDRFMT },
169     { "Sender",          ADDRFMT },
170     { "Reply-To",        ADDRFMT },
171     { "To",              ADDRFMT },
172     { "cc",              ADDRFMT },
173     { "Bcc",             ADDRFMT },
174     { "Resent-Date",     DATEFMT },
175     { "Resent-From",     ADDRFMT },
176     { "Resent-Sender",   ADDRFMT },
177     { "Resent-Reply-To", ADDRFMT },
178     { "Resent-To",       ADDRFMT },
179     { "Resent-cc",       ADDRFMT },
180     { "Resent-Bcc",      ADDRFMT },
181     { NULL,              0 }
182 };
183
184 struct triple {
185     char *t_name;
186     long t_on;
187     long t_off;
188 };
189
190 static struct triple triples[] = {
191     { "nocomponent",   NOCOMPONENT, 0 },
192     { "uppercase",     UPPERCASE,   0 },
193     { "nouppercase",   0,           UPPERCASE },
194     { "center",        CENTER,      0 },
195     { "nocenter",      0,           CENTER },
196     { "clearscreen",   CLEARSCR,    0 },
197     { "noclearscreen", 0,           CLEARSCR },
198     { "noclear",       0,           CLEARSCR },
199     { "leftadjust",    LEFTADJUST,  0 },
200     { "noleftadjust",  0,           LEFTADJUST },
201     { "compress",      COMPRESS,    0 },
202     { "nocompress",    0,           COMPRESS },
203     { "split",         SPLIT,       0 },
204     { "nosplit",       0,           SPLIT },
205     { "addrfield",     ADDRFMT,     DATEFMT },
206     { "bell",          BELL,        0 },
207     { "nobell",        0,           BELL },
208     { "datefield",     DATEFMT,     ADDRFMT },
209     { "newline",       0,           NONEWLINE },
210     { "nonewline",     NONEWLINE,   0 },
211     { "wrap",          0,           NOWRAP },
212     { "nowrap",        NOWRAP,      0 },
213     { "format",        FMTFILTER,   0 },
214     { "noformat",      0,           FMTFILTER },
215     { NULL,            0,           0 }
216 };
217
218 static char *addrcomps[] = {
219     "from",
220     "sender",
221     "reply-to",
222     "to",
223     "cc",
224     "bcc",
225     "resent-from",
226     "resent-sender",
227     "resent-reply-to",
228     "resent-to",
229     "resent-cc",
230     "resent-bcc",
231     NULL
232 };
233
234
235 static int bellflg   = 0;
236 static int clearflg  = 0;
237 static int dashstuff = 0;
238 static int dobody    = 1;
239 static int forwflg   = 0;
240 static int forwall   = 0;
241
242 static int sleepsw = NOTOK;
243
244 static char *digest = NULL;
245 static int volume = 0;
246 static int issue = 0;
247
248 static int exitstat = 0;
249 static int mhldebug = 0;
250
251 static int filesize = 0;
252
253 #define PITTY   (-1)
254 #define NOTTY   0
255 #define ISTTY   1
256 static int ontty = NOTTY;
257
258 static int row;
259 static unsigned int column;
260
261 static int lm;
262 static int llim;
263 static int ovoff;
264 static int term;
265 static unsigned int wid;
266
267 static char *ovtxt;
268
269 static unsigned char *onelp;
270
271 static char *parptr;
272
273 static int num_ignores = 0;
274 static char *ignores[MAXARGS];
275
276 static  jmp_buf env;
277 static  jmp_buf mhlenv;
278
279 static char delim3[] =          /* from forw.c */
280     "\n----------------------------------------------------------------------\n\n";
281 static char delim4[] = "\n------------------------------\n\n";
282
283 static FILE *(*mhl_action) () = (FILE *(*) ()) 0;
284
285 static struct comp *mhlcomp[128];
286
287 /*
288  * Redefine a couple of functions.
289  * These are undefined later in the code.
290  */
291 #define adios mhladios
292 #define done  mhldone
293
294 /*
295  * prototypes
296  */
297 static void mhl_format (char *, int, int);
298 static int evalvar (struct mcomp *);
299 static int ptoi (char *, int *);
300 static int ptos (char *, char **);
301 static char *parse (void);
302 static void process (char *, char *, int, int);
303 static void mhlfile (FILE *, char *, int, int);
304 static int mcomp_flags (char *);
305 static char *mcomp_add (long, char *, char *);
306 static void mcomp_format (struct mcomp *, struct mcomp *);
307 static struct mcomp *add_queue (struct mcomp **, struct mcomp **, char *, char *, int);
308 static void free_queue (struct mcomp **, struct mcomp **);
309 static void putcomp (struct mcomp *, struct mcomp *, int);
310 static char *oneline (char *, long);
311 static void putstr (char *, long);
312 static void putch (char, long);
313 static void intrser (int);
314 static void pipeser (int);
315 static void quitser (int);
316 static void mhladios (char *, char *, ...);
317 static void mhldone (int);
318 static void m_popen (char *);
319 static void filterbody (struct mcomp *, char *, int, int, FILE *);
320 static int compileargs (struct mcomp *, char *);
321 static int checkcomp (char *, char *);
322 static void addcomp (int, char *, char *);
323 static void freecomps (void);
324 static void freecomptext (void);
325
326
327 int
328 mhl (int argc, char **argv)
329 {
330     int length = 0, nomore = 0;
331     unsigned int i, vecp = 0;
332     int width = 0;
333     char *cp, *folder = NULL, *form = NULL;
334     char buf[BUFSIZ], *files[MAXARGS];
335     char **argp, **arguments;
336
337     invo_name = r1bindex (argv[0], '/');
338
339     /* read user profile/context */
340     context_read();
341
342     arguments = getarguments (invo_name, argc, argv, 1);
343     argp = arguments;
344
345     if ((cp = getenv ("MHLDEBUG")) && *cp)
346         mhldebug++;
347
348     while ((cp = *argp++)) {
349         if (*cp == '-') {
350             switch (smatch (++cp, mhlswitches)) {
351                 case AMBIGSW: 
352                     ambigsw (cp, mhlswitches);
353                     done (1);
354                 case UNKWNSW: 
355                     adios (NULL, "-%s unknown\n", cp);
356
357                 case HELPSW: 
358                     snprintf (buf, sizeof(buf), "%s [switches] [files ...]", invo_name);
359                     print_help (buf, mhlswitches, 1);
360                     done (0);
361                 case VERSIONSW:
362                     print_version(invo_name);
363                     done (0);
364
365                 case BELLSW: 
366                     bellflg = 1;
367                     continue;
368                 case NBELLSW: 
369                     bellflg = -1;
370                     continue;
371
372                 case CLRSW: 
373                     clearflg = 1;
374                     continue;
375                 case NCLRSW: 
376                     clearflg = -1;
377                     continue;
378
379                 case FOLDSW: 
380                     if (!(folder = *argp++) || *folder == '-')
381                         adios (NULL, "missing argument to %s", argp[-2]);
382                     continue;
383                 case FORMSW: 
384                     if (!(form = *argp++) || *form == '-')
385                         adios (NULL, "missing argument to %s", argp[-2]);
386                     continue;
387
388                 case SLEEPSW:
389                     if (!(cp = *argp++) || *cp == '-')
390                         adios (NULL, "missing argument to %s", argp[-2]);
391                     sleepsw = atoi (cp);/* ZERO ok! */
392                     continue;
393
394                 case PROGSW:
395                     if (!(moreproc = *argp++) || *moreproc == '-')
396                         adios (NULL, "missing argument to %s", argp[-2]);
397                     continue;
398                 case NPROGSW:
399                     nomore++;
400                     continue;
401
402                 case FMTPROCSW:
403                     if (!(formatproc = *argp++) || *formatproc == '-')
404                         adios (NULL, "missing argument to %s", argp[-2]);
405                     continue;
406                 case NFMTPROCSW:
407                     formatproc = NULL;
408                     continue;
409
410                 case LENSW: 
411                     if (!(cp = *argp++) || *cp == '-')
412                         adios (NULL, "missing argument to %s", argp[-2]);
413                     if ((length = atoi (cp)) < 1)
414                         adios (NULL, "bad argument %s %s", argp[-2], cp);
415                     continue;
416                 case WIDTHSW: 
417                     if (!(cp = *argp++) || *cp == '-')
418                         adios (NULL, "missing argument to %s", argp[-2]);
419                     if ((width = atoi (cp)) < 1)
420                         adios (NULL, "bad argument %s %s", argp[-2], cp);
421                     continue;
422
423                 case DGSTSW: 
424                     if (!(digest = *argp++) || *digest == '-')
425                         adios (NULL, "missing argument to %s", argp[-2]);
426                     continue;
427                 case ISSUESW:
428                     if (!(cp = *argp++) || *cp == '-')
429                         adios (NULL, "missing argument to %s", argp[-2]);
430                     if ((issue = atoi (cp)) < 1)
431                         adios (NULL, "bad argument %s %s", argp[-2], cp);
432                     continue;
433                 case VOLUMSW:
434                     if (!(cp = *argp++) || *cp == '-')
435                         adios (NULL, "missing argument to %s", argp[-2]);
436                     if ((volume = atoi (cp)) < 1)
437                         adios (NULL, "bad argument %s %s", argp[-2], cp);
438                     continue;
439
440                 case FORW2SW: 
441                     forwall++;  /* fall */
442                 case FORW1SW: 
443                     forwflg++;
444                     clearflg = -1;/* XXX */
445                     continue;
446
447                 case BITSTUFFSW: 
448                     dashstuff = 1;      /* trinary logic */
449                     continue;
450                 case NBITSTUFFSW: 
451                     dashstuff = -1;     /* trinary logic */
452                     continue;
453
454                 case NBODYSW: 
455                     dobody = 0;
456                     continue;
457             }
458         }
459         files[vecp++] = cp;
460     }
461
462     if (!folder)
463         folder = getenv ("mhfolder");
464
465     if (isatty (fileno (stdout))) {
466         if (!nomore && !sc_hardcopy() && moreproc && *moreproc != '\0') {
467             if (mhl_action) {
468                 SIGNAL (SIGINT, SIG_IGN);
469                 SIGNAL2 (SIGQUIT, quitser);
470             }
471             SIGNAL2 (SIGPIPE, pipeser);
472             m_popen (moreproc);
473             ontty = PITTY;
474         } else {
475             SIGNAL (SIGINT, SIG_IGN);
476             SIGNAL2 (SIGQUIT, quitser);
477             ontty = ISTTY;
478         }
479     } else {
480         ontty = NOTTY;
481     }
482
483     for (i = 0; i < sizeof(mhlcomp)/sizeof(mhlcomp[0]); i++)
484         mhlcomp[i] = NULL;
485
486     mhl_format (form ? form : mhlformat, length, width);
487
488     if (vecp == 0) {
489         process (folder, NULL, 1, vecp = 1);
490     } else {
491         for (i = 0; i < vecp; i++)
492             process (folder, files[i], i + 1, vecp);
493     }
494
495     freecomps();
496
497     if (forwall) {
498         if (digest) {
499             printf ("%s", delim4);
500             if (volume == 0) {
501                 snprintf (buf, sizeof(buf), "End of %s Digest\n", digest);
502             } else {
503                 snprintf (buf, sizeof(buf), "End of %s Digest [Volume %d Issue %d]\n",
504                         digest, volume, issue);
505             }
506             i = strlen (buf);
507             for (cp = buf + i; i > 1; i--)
508                 *cp++ = '*';
509             *cp++ = '\n';
510             *cp = 0;
511             printf ("%s", buf);
512         }
513         else
514             printf ("\n------- End of Forwarded Message%s\n",
515                     vecp > 1 ? "s" : "");
516     }
517
518     fflush(stdout);
519     if(ferror(stdout)){
520             adios("output", "error writing");
521     }
522     
523     if (clearflg > 0 && ontty == NOTTY)
524         clear_screen ();
525
526     if (ontty == PITTY)
527         m_pclose ();
528
529     return exitstat;
530 }
531
532
533 static void
534 mhl_format (char *file, int length, int width)
535 {
536     int i;
537     char *bp, *cp, **ip;
538     char *ap, buffer[BUFSIZ], name[NAMESZ];
539     struct mcomp *c1;
540     struct stat st;
541     FILE *fp;
542     static dev_t dev = 0;
543     static ino_t ino = 0;
544     static time_t mtime = 0;
545
546     if (fmthd != NULL) {
547         if (stat (etcpath (file), &st) != NOTOK
548                 && mtime == st.st_mtime
549                 && dev == st.st_dev
550                 && ino == st.st_ino)
551             goto out;
552         else
553             free_queue (&fmthd, &fmttl);
554     }
555
556     if ((fp = fopen (etcpath (file), "r")) == NULL)
557         adios (file, "unable to open format file");
558
559     if (fstat (fileno (fp), &st) != NOTOK) {
560         mtime = st.st_mtime;
561         dev = st.st_dev;
562         ino = st.st_ino;
563     }
564
565     global.c_ovtxt = global.c_nfs = NULL;
566     global.c_fmt = NULL;
567     global.c_offset = 0;
568     global.c_ovoff = -1;
569     if ((i = sc_width ()) > 5)
570         global.c_width = i;
571     global.c_cwidth = -1;
572     if ((i = sc_length ()) > 5)
573         global.c_length = i - 1;
574     global.c_flags = BELL;              /* BELL is default */
575     *(ip = ignores) = NULL;
576
577     while (vfgets (fp, &ap) == OK) {
578         bp = ap;
579         if (*bp == ';')
580             continue;
581
582         if ((cp = strchr(bp, '\n')))
583             *cp = 0;
584
585         if (*bp == ':') {
586             c1 = add_queue (&fmthd, &fmttl, NULL, bp + 1, CLEARTEXT);
587             continue;
588         }
589
590         parptr = bp;
591         strncpy (name, parse(), sizeof(name));
592         switch (*parptr) {
593             case '\0': 
594             case ',': 
595             case '=': 
596                 /*
597                  * Split this list of fields to ignore, and copy
598                  * it to the end of the current "ignores" list.
599                  */
600                 if (!mh_strcasecmp (name, "ignores")) {
601                     char **tmparray, **p;
602                     int n = 0;
603
604                     /* split the fields */
605                     tmparray = brkstring (getcpy (++parptr), ",", NULL);
606
607                     /* count number of fields split */
608                     p = tmparray;
609                     while (*p++)
610                         n++;
611
612                     /* copy pointers to split fields to ignores array */
613                     ip = copyip (tmparray, ip, MAXARGS - num_ignores);
614                     num_ignores += n;
615                     continue;
616                 }
617                 parptr = bp;
618                 while (*parptr) {
619                     if (evalvar (&global))
620                         adios (NULL, "format file syntax error: %s", bp);
621                     if (*parptr)
622                         parptr++;
623                 }
624                 continue;
625
626             case ':': 
627                 c1 = add_queue (&fmthd, &fmttl, name, NULL, INIT);
628                 while (*parptr == ':' || *parptr == ',') {
629                     parptr++;
630                     if (evalvar (c1))
631                         adios (NULL, "format file syntax error: %s", bp);
632                 }
633                 if (!c1->c_nfs && global.c_nfs) {
634                     if (c1->c_flags & DATEFMT) {
635                         if (global.c_flags & DATEFMT)
636                             c1->c_nfs = getcpy (global.c_nfs);
637                     }
638                     else
639                         if (c1->c_flags & ADDRFMT) {
640                             if (global.c_flags & ADDRFMT)
641                                 c1->c_nfs = getcpy (global.c_nfs);
642                         }
643                 }
644                 continue;
645
646             default: 
647                 adios (NULL, "format file syntax error: %s", bp);
648         }
649     }
650     fclose (fp);
651
652     if (mhldebug) {
653         for (c1 = fmthd; c1; c1 = c1->c_next) {
654             fprintf (stderr, "c1: name=\"%s\" text=\"%s\" ovtxt=\"%s\"\n",
655                     c1->c_name, c1->c_text, c1->c_ovtxt);
656             fprintf (stderr, "\tnfs=0x%x fmt=0x%x\n",
657                      (unsigned int)(unsigned long) c1->c_nfs,
658                      (unsigned int)(unsigned long) c1->c_fmt);
659             fprintf (stderr, "\toffset=%d ovoff=%d width=%d cwidth=%d length=%d\n",
660                     c1->c_offset, c1->c_ovoff, c1->c_width,
661                     c1->c_cwidth, c1->c_length);
662             fprintf (stderr, "\tflags=%s\n",
663                     snprintb (buffer, sizeof(buffer), (unsigned) c1->c_flags, LBITS));
664         }
665     }
666
667 out:
668     if (clearflg == 1) {
669         global.c_flags |= CLEARSCR;
670     } else {
671         if (clearflg == -1)
672             global.c_flags &= ~CLEARSCR;
673     }
674
675     switch (bellflg) {          /* command line may override format file */
676         case 1: 
677             global.c_flags |= BELL;
678             break;
679         case -1: 
680             global.c_flags &= ~BELL;
681             break;
682     }
683
684     if (length)
685         global.c_length = length;
686     if (width)
687         global.c_width = width;
688     if (global.c_length < 5)
689         global.c_length = 10000;
690     if (global.c_width < 5)
691         global.c_width = 10000;
692 }
693
694
695 static int
696 evalvar (struct mcomp *c1)
697 {
698     char *cp, name[NAMESZ];
699     struct triple *ap;
700
701     if (!*parptr)
702         return 0;
703     strncpy (name, parse(), sizeof(name));
704
705     if (!mh_strcasecmp (name, "component")) {
706         if (ptos (name, &c1->c_text))
707             return 1;
708         c1->c_flags &= ~NOCOMPONENT;
709         return 0;
710     }
711
712     if (!mh_strcasecmp (name, "overflowtext"))
713         return ptos (name, &c1->c_ovtxt);
714
715     if (!mh_strcasecmp (name, "formatfield")) {
716         char *nfs;
717
718         if (ptos (name, &cp))
719             return 1;
720         nfs = new_fs (NULL, NULL, cp);
721         c1->c_nfs = getcpy (nfs);
722         c1->c_flags |= FORMAT;
723         return 0;
724     }
725
726     if (!mh_strcasecmp (name, "decode")) {
727         char *nfs;
728
729         nfs = new_fs (NULL, NULL, "%(decode{text})");
730         c1->c_nfs = getcpy (nfs);
731         c1->c_flags |= FORMAT;
732         return 0;
733     }
734
735     if (!mh_strcasecmp (name, "offset"))
736         return ptoi (name, &c1->c_offset);
737     if (!mh_strcasecmp (name, "overflowoffset"))
738         return ptoi (name, &c1->c_ovoff);
739     if (!mh_strcasecmp (name, "width"))
740         return ptoi (name, &c1->c_width);
741     if (!mh_strcasecmp (name, "compwidth"))
742         return ptoi (name, &c1->c_cwidth);
743     if (!mh_strcasecmp (name, "length"))
744         return ptoi (name, &c1->c_length);
745     if (!mh_strcasecmp (name, "nodashstuffing"))
746         return (dashstuff = -1);
747
748     for (ap = triples; ap->t_name; ap++)
749         if (!mh_strcasecmp (ap->t_name, name)) {
750             c1->c_flags |= ap->t_on;
751             c1->c_flags &= ~ap->t_off;
752             return 0;
753         }
754
755    if (!mh_strcasecmp (name, "formatarg")) {
756         char *nfs;
757         int rc;
758
759         if (ptos (name, &cp))
760             return 1;
761         nfs = new_fs (NULL, NULL, cp);
762
763         rc = compileargs(c1, nfs);
764
765         return rc;
766     }
767
768
769     return 1;
770 }
771
772
773 static int
774 ptoi (char *name, int *i)
775 {
776     char *cp;
777
778     if (*parptr++ != '=' || !*(cp = parse ())) {
779         advise (NULL, "missing argument to variable %s", name);
780         return 1;
781     }
782
783     *i = atoi (cp);
784     return 0;
785 }
786
787
788 static int
789 ptos (char *name, char **s)
790 {
791     char c, *cp;
792
793     if (*parptr++ != '=') {
794         advise (NULL, "missing argument to variable %s", name);
795         return 1;
796     }
797
798     if (*parptr != '"') {
799         for (cp = parptr;
800                 *parptr && *parptr != ':' && *parptr != ',';
801                 parptr++)
802             continue;
803     } else {
804         for (cp = ++parptr; *parptr && *parptr != '"'; parptr++)
805             if (*parptr == QUOTE)
806                 if (!*++parptr)
807                     parptr--;
808     }
809     c = *parptr;
810     *parptr = 0;
811     *s = getcpy (cp);
812     if ((*parptr = c) == '"')
813         parptr++;
814     return 0;
815 }
816
817
818 static char *
819 parse (void)
820 {
821     int c;
822     char *cp;
823     static char result[NAMESZ];
824
825     for (cp = result; *parptr && (cp - result < NAMESZ); parptr++) {
826         c = *parptr;
827         if (isalnum (c)
828                 || c == '.'
829                 || c == '-'
830                 || c == '_'
831                 || c =='['
832                 || c == ']')
833             *cp++ = c;
834         else
835             break;
836     }
837     *cp = '\0';
838
839     return result;
840 }
841
842
843 static void
844 process (char *folder, char *fname, int ofilen, int ofilec)
845 {
846     char *cp = NULL;
847     FILE *fp = NULL;
848     struct mcomp *c1;
849     struct stat st;
850
851     switch (setjmp (env)) {
852         case OK: 
853             if (fname) {
854                 fp = mhl_action ? (*mhl_action) (fname) : fopen (fname, "r");
855                 if (fp == NULL) {
856                     advise (fname, "unable to open");
857                     exitstat++;
858                     return;
859                 }
860             } else {
861                 fname = "(stdin)";
862                 fp = stdin;
863             }
864             if (fstat(fileno(fp), &st) == 0) {
865                 filesize = st.st_size;
866             } else {
867                 filesize = 0;
868             }
869             cp = folder ? concat (folder, ":", fname, NULL) : getcpy (fname);
870             if (ontty != PITTY)
871                 SIGNAL (SIGINT, intrser);
872             mhlfile (fp, cp, ofilen, ofilec);  /* FALL THROUGH! */
873
874         default: 
875             if (ontty != PITTY)
876                 SIGNAL (SIGINT, SIG_IGN);
877             if (mhl_action == NULL && fp != stdin)
878                 fclose (fp);
879             free (cp);
880             if (holder.c_text) {
881                 free (holder.c_text);
882                 holder.c_text = NULL;
883             }
884             free_queue (&msghd, &msgtl);
885             for (c1 = fmthd; c1; c1 = c1->c_next)
886                 c1->c_flags &= ~HDROUTPUT;
887             break;
888     }
889
890     freecomptext();
891 }
892
893
894 static void
895 mhlfile (FILE *fp, char *mname, int ofilen, int ofilec)
896 {
897     int state, bucket;
898     struct mcomp *c1, *c2, *c3;
899     char **ip, name[NAMESZ], buf[BUFSIZ];
900
901     if (forwall) {
902         if (digest)
903             printf ("%s", ofilen == 1 ? delim3 : delim4);
904         else {
905             printf ("\n-------");
906             if (ofilen == 1)
907                 printf (" Forwarded Message%s", ofilec > 1 ? "s" : "");
908             else
909                 printf (" Message %d", ofilen);
910             printf ("\n\n");
911         }
912     } else {
913         switch (ontty) {
914             case PITTY: 
915                 if (ofilec > 1) {
916                     if (ofilen > 1) {
917                         if ((global.c_flags & CLEARSCR))
918                             clear_screen ();
919                         else
920                             printf ("\n\n\n");
921                     }
922                     printf (">>> %s\n\n", mname);
923                 }
924                 break;
925
926             case ISTTY: 
927                 strncpy (buf, "\n", sizeof(buf));
928                 if (ofilec > 1) {
929                     if (SOprintf ("Press <return> to list \"%s\"...", mname)) {
930                         if (ofilen > 1)
931                             printf ("\n\n\n");
932                         printf ("Press <return> to list \"%s\"...", mname);
933                     }
934                     fflush (stdout);
935                     buf[0] = 0;
936                     read (fileno (stdout), buf, sizeof(buf));
937                 }
938                 if (strchr(buf, '\n')) {
939                     if ((global.c_flags & CLEARSCR))
940                         clear_screen ();
941                 }
942                 else
943                     printf ("\n");
944                 break;
945
946             default: 
947                 if (ofilec > 1) {
948                     if (ofilen > 1) {
949                         printf ("\n\n\n");
950                         if (clearflg > 0)
951                             clear_screen ();
952                     }
953                     printf (">>> %s\n\n", mname);
954                 }
955                 break;
956         }
957     }
958
959     for (state = FLD;;) {
960         switch (state = m_getfld (state, name, buf, sizeof(buf), fp)) {
961             case FLD: 
962             case FLDPLUS: 
963                 bucket = checkcomp(name, buf);
964                 for (ip = ignores; *ip; ip++)
965                     if (!mh_strcasecmp (name, *ip)) {
966                         while (state == FLDPLUS) {
967                             state = m_getfld (state, name, buf, sizeof(buf), fp);
968                             addcomp(bucket, name, buf);
969                         }
970                         break;
971                     }
972                 if (*ip)
973                     continue;
974
975                 for (c2 = fmthd; c2; c2 = c2->c_next)
976                     if (!mh_strcasecmp (c2->c_name, name))
977                         break;
978                 c1 = NULL;
979                 if (!((c3 = c2 ? c2 : &global)->c_flags & SPLIT))
980                     for (c1 = msghd; c1; c1 = c1->c_next)
981                         if (!mh_strcasecmp (c1->c_name, c3->c_name)) {
982                             c1->c_text =
983                                 mcomp_add (c1->c_flags, buf, c1->c_text);
984                             break;
985                         }
986                 if (c1 == NULL)
987                     c1 = add_queue (&msghd, &msgtl, name, buf, 0);
988                 while (state == FLDPLUS) {
989                     state = m_getfld (state, name, buf, sizeof(buf), fp);
990                     c1->c_text = add (buf, c1->c_text);
991                     addcomp(bucket, name, buf);
992                 }
993                 if (c2 == NULL)
994                     c1->c_flags |= EXTRA;
995                 continue;
996
997             case BODY: 
998             case FILEEOF: 
999                 row = column = 0;
1000                 for (c1 = fmthd; c1; c1 = c1->c_next) {
1001                     if (c1->c_flags & CLEARTEXT) {
1002                         putcomp (c1, c1, ONECOMP);
1003                         continue;
1004                     }
1005                     if (!mh_strcasecmp (c1->c_name, "messagename")) {
1006                         holder.c_text = concat ("(Message ", mname, ")\n",
1007                                             NULL);
1008                         putcomp (c1, &holder, ONECOMP);
1009                         free (holder.c_text);
1010                         holder.c_text = NULL;
1011                         continue;
1012                     }
1013                     if (!mh_strcasecmp (c1->c_name, "extras")) {
1014                         for (c2 = msghd; c2; c2 = c2->c_next)
1015                             if (c2->c_flags & EXTRA)
1016                                 putcomp (c1, c2, TWOCOMP);
1017                         continue;
1018                     }
1019                     if (dobody && !mh_strcasecmp (c1->c_name, "body")) {
1020                         if (c1->c_flags & FMTFILTER && state == BODY &&
1021                                                         formatproc != NULL) {
1022                             filterbody(c1, buf, sizeof(buf), state, fp);
1023                         } else {
1024                             holder.c_text = mh_xmalloc (sizeof(buf));
1025                             strncpy (holder.c_text, buf, sizeof(buf));
1026                             while (state == BODY) {
1027                                 putcomp (c1, &holder, BODYCOMP);
1028                                 state = m_getfld (state, name, holder.c_text,
1029                                             sizeof(buf), fp);
1030                             }
1031                             free (holder.c_text);
1032                             holder.c_text = NULL;
1033                         }
1034                         continue;
1035                     }
1036                     for (c2 = msghd; c2; c2 = c2->c_next)
1037                         if (!mh_strcasecmp (c2->c_name, c1->c_name)) {
1038                             putcomp (c1, c2, ONECOMP);
1039                             if (!(c1->c_flags & SPLIT))
1040                                 break;
1041                         }
1042                 }
1043                 return;
1044
1045             case LENERR: 
1046             case FMTERR: 
1047                 advise (NULL, "format error in message %s", mname);
1048                 exitstat++;
1049                 return;
1050
1051             default: 
1052                 adios (NULL, "getfld() returned %d", state);
1053         }
1054     }
1055 }
1056
1057
1058 static int
1059 mcomp_flags (char *name)
1060 {
1061     struct pair *ap;
1062
1063     for (ap = pairs; ap->p_name; ap++)
1064         if (!mh_strcasecmp (ap->p_name, name))
1065             return (ap->p_flags);
1066
1067     return 0;
1068 }
1069
1070
1071 static char *
1072 mcomp_add (long flags, char *s1, char *s2)
1073 {
1074     char *dp;
1075
1076     if (!(flags & ADDRFMT))
1077         return add (s1, s2);
1078
1079     if (s2 && *(dp = s2 + strlen (s2) - 1) == '\n')
1080         *dp = 0;
1081
1082     return add (s1, add (",\n", s2));
1083 }
1084
1085
1086 struct pqpair {
1087     char *pq_text;
1088     char *pq_error;
1089     struct pqpair *pq_next;
1090 };
1091
1092
1093 static void
1094 mcomp_format (struct mcomp *c1, struct mcomp *c2)
1095 {
1096     int dat[5];
1097     char *ap, *cp;
1098     char buffer[BUFSIZ], error[BUFSIZ];
1099     struct comp *cptr;
1100     struct pqpair *p, *q;
1101     struct pqpair pq;
1102     struct mailname *mp;
1103
1104     ap = c2->c_text;
1105     c2->c_text = NULL;
1106     dat[0] = 0;
1107     dat[1] = 0;
1108     dat[2] = filesize;
1109     dat[3] = sizeof(buffer) - 1;
1110     dat[4] = 0;
1111     fmt_compile (c1->c_nfs, &c1->c_fmt);
1112
1113     if (!(c1->c_flags & ADDRFMT)) {
1114         FINDCOMP (cptr, "text");
1115         if (cptr)
1116             cptr->c_text = ap;
1117         if ((cp = strrchr(ap, '\n')))   /* drop ending newline */
1118             if (!cp[1])
1119                 *cp = 0;
1120
1121         fmt_scan (c1->c_fmt, buffer, sizeof buffer - 1, sizeof buffer - 1, dat);
1122         /* Don't need to append a newline, dctime() already did */
1123         c2->c_text = getcpy (buffer);
1124
1125         free (ap);
1126         return;
1127     }
1128
1129     (q = &pq)->pq_next = NULL;
1130     while ((cp = getname (ap))) {
1131         if ((p = (struct pqpair *) calloc ((size_t) 1, sizeof(*p))) == NULL)
1132             adios (NULL, "unable to allocate pqpair memory");
1133
1134         if ((mp = getm (cp, NULL, 0, AD_NAME, error)) == NULL) {
1135             p->pq_text = getcpy (cp);
1136             p->pq_error = getcpy (error);
1137         } else {
1138             p->pq_text = getcpy (mp->m_text);
1139             mnfree (mp);
1140         }
1141         q = (q->pq_next = p);
1142     }
1143
1144     for (p = pq.pq_next; p; p = q) {
1145         FINDCOMP (cptr, "text");
1146         if (cptr)
1147             cptr->c_text = p->pq_text;
1148         FINDCOMP (cptr, "error");
1149         if (cptr)
1150             cptr->c_text = p->pq_error;
1151
1152         fmt_scan (c1->c_fmt, buffer, sizeof buffer - 1, sizeof buffer - 1, dat);
1153         if (*buffer) {
1154             if (c2->c_text)
1155                 c2->c_text = add (",\n", c2->c_text);
1156             if (*(cp = buffer + strlen (buffer) - 1) == '\n')
1157                 *cp = 0;
1158             c2->c_text = add (buffer, c2->c_text);
1159         }
1160
1161         free (p->pq_text);
1162         if (p->pq_error)
1163             free (p->pq_error);
1164         q = p->pq_next;
1165         free ((char *) p);
1166     }
1167
1168     c2->c_text = add ("\n", c2->c_text);
1169     free (ap);
1170 }
1171
1172
1173 static struct mcomp *
1174 add_queue (struct mcomp **head, struct mcomp **tail, char *name, char *text, int flags)
1175 {
1176     struct mcomp *c1;
1177
1178     if ((c1 = (struct mcomp *) calloc ((size_t) 1, sizeof(*c1))) == NULL)
1179         adios (NULL, "unable to allocate comp memory");
1180
1181     c1->c_flags = flags & ~INIT;
1182     if ((c1->c_name = name ? getcpy (name) : NULL))
1183         c1->c_flags |= mcomp_flags (c1->c_name);
1184     c1->c_text = text ? getcpy (text) : NULL;
1185     if (flags & INIT) {
1186         if (global.c_ovtxt)
1187             c1->c_ovtxt = getcpy (global.c_ovtxt);
1188         c1->c_offset = global.c_offset;
1189         c1->c_ovoff = global. c_ovoff;
1190         c1->c_width = c1->c_length = 0;
1191         c1->c_cwidth = global.c_cwidth;
1192         c1->c_flags |= global.c_flags & GFLAGS;
1193     }
1194     if (*head == NULL)
1195         *head = c1;
1196     if (*tail != NULL)
1197         (*tail)->c_next = c1;
1198     *tail = c1;
1199
1200     return c1;
1201 }
1202
1203
1204 static void
1205 free_queue (struct mcomp **head, struct mcomp **tail)
1206 {
1207     struct mcomp *c1, *c2;
1208
1209     for (c1 = *head; c1; c1 = c2) {
1210         c2 = c1->c_next;
1211         if (c1->c_name)
1212             free (c1->c_name);
1213         if (c1->c_text)
1214             free (c1->c_text);
1215         if (c1->c_ovtxt)
1216             free (c1->c_ovtxt);
1217         if (c1->c_nfs)
1218             free (c1->c_nfs);
1219         if (c1->c_fmt)
1220             free ((char *) c1->c_fmt);
1221         if (c1->c_f_args) {
1222             struct arglist *a1, *a2;
1223             for (a1 = c1->c_f_args; a1; a1 = a2) {
1224                 a2 = a1->a_next;
1225                 if (a1->a_fmt)
1226                     free(a1->a_fmt);
1227                 if (a1->a_nfs)
1228                     free(a1->a_nfs);
1229             }
1230             free(a1);
1231         }
1232         free ((char *) c1);
1233     }
1234
1235     *head = *tail = NULL;
1236 }
1237
1238
1239 static void
1240 putcomp (struct mcomp *c1, struct mcomp *c2, int flag)
1241 {
1242     int count, cchdr;
1243     unsigned char *cp;
1244
1245     cchdr = 0;
1246     lm = 0;
1247     llim = c1->c_length ? c1->c_length : -1;
1248     wid = c1->c_width ? c1->c_width : global.c_width;
1249     ovoff = (c1->c_ovoff >= 0 ? c1->c_ovoff : global.c_ovoff)
1250         + c1->c_offset;
1251     if ((ovtxt = c1->c_ovtxt ? c1->c_ovtxt : global.c_ovtxt) == NULL)
1252         ovtxt = "";
1253     if (wid < ovoff + strlen (ovtxt) + 5)
1254         adios (NULL, "component: %s width(%d) too small for overflow(%d)",
1255                 c1->c_name, wid, ovoff + strlen (ovtxt) + 5);
1256     onelp = NULL;
1257
1258     if (c1->c_flags & CLEARTEXT) {
1259         putstr (c1->c_text, c1->c_flags);
1260         putstr ("\n", c1->c_flags);
1261         return;
1262     }
1263
1264     if (c1->c_nfs && (c1->c_flags & (ADDRFMT | DATEFMT | FORMAT)))
1265         mcomp_format (c1, c2);
1266
1267     if (c1->c_flags & CENTER) {
1268         count = (c1->c_width ? c1->c_width : global.c_width)
1269             - c1->c_offset - strlen (c2->c_text);
1270         if (!(c1->c_flags & HDROUTPUT) && !(c1->c_flags & NOCOMPONENT))
1271             count -= strlen (c1->c_text ? c1->c_text : c1->c_name) + 2;
1272         lm = c1->c_offset + (count / 2);
1273     } else {
1274         if (c1->c_offset)
1275             lm = c1->c_offset;
1276     }
1277
1278     if (!(c1->c_flags & HDROUTPUT) && !(c1->c_flags & NOCOMPONENT)) {
1279         if (c1->c_flags & UPPERCASE)            /* uppercase component also */
1280             for (cp = (c1->c_text ? c1->c_text : c1->c_name); *cp; cp++)
1281                 if (islower (*cp))
1282                     *cp = toupper (*cp);
1283         putstr (c1->c_text ? c1->c_text : c1->c_name, c1->c_flags);
1284         if (flag != BODYCOMP) {
1285             putstr (": ", c1->c_flags);
1286             if (!(c1->c_flags & SPLIT))
1287                 c1->c_flags |= HDROUTPUT;
1288
1289         cchdr++;
1290         if ((count = c1->c_cwidth -
1291                 strlen (c1->c_text ? c1->c_text : c1->c_name) - 2) > 0)
1292             while (count--)
1293                 putstr (" ", c1->c_flags);
1294         }
1295         else
1296             c1->c_flags |= HDROUTPUT;           /* for BODYCOMP */
1297     }
1298
1299     if (flag == TWOCOMP
1300             && !(c2->c_flags & HDROUTPUT)
1301             && !(c2->c_flags & NOCOMPONENT)) {
1302         if (c1->c_flags & UPPERCASE)
1303             for (cp = c2->c_name; *cp; cp++)
1304                 if (islower (*cp))
1305                     *cp = toupper (*cp);
1306         putstr (c2->c_name, c1->c_flags);
1307         putstr (": ", c1->c_flags);
1308         if (!(c1->c_flags & SPLIT))
1309             c2->c_flags |= HDROUTPUT;
1310
1311         cchdr++;
1312         if ((count = c1->c_cwidth - strlen (c2->c_name) - 2) > 0)
1313             while (count--)
1314                 putstr (" ", c1->c_flags);
1315     }
1316     if (c1->c_flags & UPPERCASE)
1317         for (cp = c2->c_text; *cp; cp++)
1318             if (islower (*cp))
1319                 *cp = toupper (*cp);
1320
1321     count = 0;
1322     if (cchdr) {
1323         if (flag == TWOCOMP)
1324             count = (c1->c_cwidth >= 0) ? c1->c_cwidth
1325                         : (int) strlen (c2->c_name) + 2;
1326         else
1327             count = (c1->c_cwidth >= 0) ? (size_t) c1->c_cwidth
1328                         : strlen (c1->c_text ? c1->c_text : c1->c_name) + 2;
1329     }
1330     count += c1->c_offset;
1331
1332     if ((cp = oneline (c2->c_text, c1->c_flags)))
1333        putstr(cp, c1->c_flags);
1334     if (term == '\n')
1335         putstr ("\n", c1->c_flags);
1336     while ((cp = oneline (c2->c_text, c1->c_flags))) {
1337         lm = count;
1338         if (flag == BODYCOMP
1339                 && !(c1->c_flags & NOCOMPONENT))
1340             putstr (c1->c_text ? c1->c_text : c1->c_name, c1->c_flags);
1341         if (*cp)
1342             putstr (cp, c1->c_flags);
1343         if (term == '\n')
1344             putstr ("\n", c1->c_flags);
1345     }
1346     if (flag == BODYCOMP && term == '\n')
1347         c1->c_flags &= ~HDROUTPUT;              /* Buffer ended on a newline */
1348 }
1349
1350
1351 static char *
1352 oneline (char *stuff, long flags)
1353 {
1354     int spc;
1355     char *cp, *ret;
1356
1357     if (onelp == NULL)
1358         onelp = stuff;
1359     if (*onelp == 0)
1360         return (onelp = NULL);
1361
1362     ret = onelp;
1363     term = 0;
1364     if (flags & COMPRESS) {
1365         for (spc = 1, cp = ret; *onelp; onelp++)
1366             if (isspace (*onelp)) {
1367                 if (*onelp == '\n' && (!onelp[1] || (flags & ADDRFMT))) {
1368                     term = '\n';
1369                     *onelp++ = 0;
1370                     break;
1371                 }
1372                 else
1373                     if (!spc) {
1374                         *cp++ = ' ';
1375                         spc++;
1376                     }
1377             }
1378             else {
1379                 *cp++ = *onelp;
1380                 spc = 0;
1381             }
1382
1383         *cp = 0;
1384     }
1385     else {
1386         while (*onelp && *onelp != '\n')
1387             onelp++;
1388         if (*onelp == '\n') {
1389             term = '\n';
1390             *onelp++ = 0;
1391         }
1392         if (flags & LEFTADJUST)
1393             while (*ret == ' ' || *ret == '\t')
1394                 ret++;
1395     }
1396     if (*onelp == 0 && term == '\n' && (flags & NONEWLINE))
1397         term = 0;
1398
1399     return ret;
1400 }
1401
1402
1403 static void
1404 putstr (char *string, long flags)
1405 {
1406     if (!column && lm > 0) {
1407         while (lm > 0)
1408             if (lm >= 8) {
1409                 putch ('\t', flags);
1410                 lm -= 8;
1411             }
1412             else {
1413                 putch (' ', flags);
1414                 lm--;
1415             }
1416     }
1417     lm = 0;
1418     while (*string)
1419         putch (*string++, flags);
1420 }
1421
1422
1423 static void
1424 putch (char ch, long flags)
1425 {
1426     char buf[BUFSIZ];
1427
1428     if (llim == 0)
1429         return;
1430
1431     switch (ch) {
1432         case '\n': 
1433             if (llim > 0)
1434                 llim--;
1435             column = 0;
1436             row++;
1437             if (ontty != ISTTY || row != global.c_length)
1438                 break;
1439             if (global.c_flags & BELL)
1440                 putchar ('\007');
1441             fflush (stdout);
1442             buf[0] = 0;
1443             read (fileno (stdout), buf, sizeof(buf));
1444             if (strchr(buf, '\n')) {
1445                 if (global.c_flags & CLEARSCR)
1446                     clear_screen ();
1447                 row = 0;
1448             } else {
1449                 putchar ('\n');
1450                 row = global.c_length / 3;
1451             }
1452             return;
1453
1454         case '\t': 
1455             column |= 07;
1456             column++;
1457             break;
1458
1459         case '\b': 
1460             column--;
1461             break;
1462
1463         case '\r': 
1464             column = 0;
1465             break;
1466
1467         default: 
1468             /*
1469              * If we are forwarding this message, and the first
1470              * column contains a dash, then add a dash and a space.
1471              */
1472             if (column == 0 && forwflg && (dashstuff >= 0) && ch == '-') {
1473                 putchar ('-');
1474                 putchar (' ');
1475             }
1476             if (ch >= ' ')
1477                 column++;
1478             break;
1479     }
1480
1481     if (column >= wid && (flags & NOWRAP) == 0) {
1482         putch ('\n', flags);
1483         if (ovoff > 0)
1484             lm = ovoff;
1485         putstr (ovtxt ? ovtxt : "", flags);
1486         putch (ch, flags);
1487         return;
1488     }
1489
1490     putchar (ch);
1491 }
1492
1493
1494 static void
1495 intrser (int i)
1496 {
1497     NMH_UNUSED (i);
1498
1499     discard (stdout);
1500     putchar ('\n');
1501     longjmp (env, DONE);
1502 }
1503
1504
1505 static void
1506 pipeser (int i)
1507 {
1508     NMH_UNUSED (i);
1509
1510     done (NOTOK);
1511 }
1512
1513
1514 static void
1515 quitser (int i)
1516 {
1517     NMH_UNUSED (i);
1518
1519     putchar ('\n');
1520     fflush (stdout);
1521     done (NOTOK);
1522 }
1523
1524
1525 int
1526 mhlsbr (int argc, char **argv, FILE *(*action)())
1527 {
1528     SIGNAL_HANDLER istat = NULL, pstat = NULL, qstat = NULL;
1529     char *cp = NULL;
1530     struct mcomp *c1;
1531
1532     switch (setjmp (mhlenv)) {
1533         case OK: 
1534             cp = invo_name;
1535             sleepsw = 0;        /* XXX */
1536             bellflg = clearflg = forwflg = forwall = exitstat = 0;
1537             digest = NULL;
1538             ontty = NOTTY;
1539             mhl_action = action;
1540
1541             /*
1542              * If signal is at default action, then start ignoring
1543              * it, else let it set to its current action.
1544              */
1545             if ((istat = SIGNAL (SIGINT, SIG_IGN)) != SIG_DFL)
1546                 SIGNAL (SIGINT, istat);
1547             if ((qstat = SIGNAL (SIGQUIT, SIG_IGN)) != SIG_DFL)
1548                 SIGNAL (SIGQUIT, qstat);
1549             pstat = SIGNAL (SIGPIPE, pipeser);
1550             mhl (argc, argv);                  /* FALL THROUGH! */
1551
1552         default: 
1553             SIGNAL (SIGINT, istat);
1554             SIGNAL (SIGQUIT, qstat);
1555             SIGNAL (SIGPIPE, SIG_IGN);/* should probably change to block instead */
1556             if (ontty == PITTY)
1557                 m_pclose ();
1558             SIGNAL (SIGPIPE, pstat);
1559             invo_name = cp;
1560             if (holder.c_text) {
1561                 free (holder.c_text);
1562                 holder.c_text = NULL;
1563             }
1564             free_queue (&msghd, &msgtl);
1565             for (c1 = fmthd; c1; c1 = c1->c_next)
1566                 c1->c_flags &= ~HDROUTPUT;
1567             return exitstat;
1568     }
1569 }
1570
1571 #undef adios
1572 #undef done
1573
1574 static void
1575 mhladios (char *what, char *fmt, ...)
1576 {
1577     va_list ap;
1578
1579     va_start(ap, fmt);
1580     advertise (what, NULL, fmt, ap);
1581     va_end(ap);
1582     mhldone (1);
1583 }
1584
1585
1586 static void
1587 mhldone (int status)
1588 {
1589     exitstat = status;
1590     if (mhl_action)
1591         longjmp (mhlenv, DONE);
1592     else
1593         done (exitstat);
1594 }
1595
1596
1597 static  int m_pid = NOTOK;
1598 static  int sd = NOTOK;
1599
1600 static void
1601 m_popen (char *name)
1602 {
1603     int pd[2];
1604
1605     if (mhl_action && (sd = dup (fileno (stdout))) == NOTOK)
1606         adios ("standard output", "unable to dup()");
1607
1608     if (pipe (pd) == NOTOK)
1609         adios ("pipe", "unable to");
1610
1611     switch (m_pid = vfork()) {
1612         case NOTOK: 
1613             adios ("fork", "unable to");
1614
1615         case OK: 
1616             SIGNAL (SIGINT, SIG_DFL);
1617             SIGNAL (SIGQUIT, SIG_DFL);
1618
1619             close (pd[1]);
1620             if (pd[0] != fileno (stdin)) {
1621                 dup2 (pd[0], fileno (stdin));
1622                 close (pd[0]);
1623             }
1624             execlp (name, r1bindex (name, '/'), NULL);
1625             fprintf (stderr, "unable to exec ");
1626             perror (name);
1627             _exit (-1);
1628
1629         default: 
1630             close (pd[0]);
1631             if (pd[1] != fileno (stdout)) {
1632                 dup2 (pd[1], fileno (stdout));
1633                 close (pd[1]);
1634             }
1635     }
1636 }
1637
1638
1639 void
1640 m_pclose (void)
1641 {
1642     if (m_pid == NOTOK)
1643         return;
1644
1645     if (sd != NOTOK) {
1646         fflush (stdout);
1647         if (dup2 (sd, fileno (stdout)) == NOTOK)
1648             adios ("standard output", "unable to dup2()");
1649
1650         clearerr (stdout);
1651         close (sd);
1652         sd = NOTOK;
1653     }
1654     else
1655         fclose (stdout);
1656
1657     pidwait (m_pid, OK);
1658     m_pid = NOTOK;
1659 }
1660
1661
1662 /*
1663  * Compile a format string and add it to the list of arguments used by
1664  * the formatproc.
1665  *
1666  * This deserves some explanation.  Here's the deal:
1667  *
1668  * We want to keep track of components used as arguments by formatproc,
1669  * but the hash table is reset every time fmt_compile is called.  So we
1670  * iterate through the function list looking for things that use components
1671  * and save the name.  And because we might get the same components used
1672  * by different arguments we need to keep track to every reference of
1673  * every component so we can add them when the message is processed.  So
1674  * we compile the argument string now (to get the components we use) and
1675  * save them for later.
1676  */
1677
1678 static int
1679 compileargs (struct mcomp *c1, char *nfs)
1680 {
1681     struct format *fmt;
1682     struct arglist *args;
1683     char **ap;
1684     struct comp *cptr;
1685     unsigned int i;
1686
1687     i = fmt_compile(nfs, &fmt);
1688
1689     /*
1690      * Search through and mark any components that are address components
1691      */
1692
1693     for (ap = addrcomps; *ap; ap++) {
1694         FINDCOMP (cptr, *ap);
1695         if (cptr)
1696             cptr->c_type |= CT_ADDR;
1697     }
1698
1699     args = (struct arglist *) mh_xmalloc(sizeof(struct arglist));
1700
1701     if (! args)
1702         adios (NULL, "Unable to allocate formatproc args storage");
1703
1704     args->a_fmt = fmt;
1705     args->a_nfs = format_string;
1706     args->a_next = NULL;
1707     c1->c_nargs++;
1708     format_string = NULL;
1709
1710     if (c1->c_f_tail)
1711         c1->c_f_tail->a_next = args;
1712
1713     c1->c_f_tail = args;
1714
1715     if (! c1->c_f_args)
1716         c1->c_f_args = args;
1717
1718     if (i == 0)
1719         return 0;
1720
1721     /*
1722      * If wantcomp ever changes size, we need to change the size
1723      * of mhlcomp as well
1724      */
1725
1726     for (i = 0; i < sizeof(wantcomp)/sizeof(wantcomp[0]); i++) {
1727         if (wantcomp[i]) {
1728             if (mhlcomp[i]) {
1729                 struct comp *c;
1730                 for (c = mhlcomp[i]; c->c_next != NULL; c = c->c_next)
1731                     ;
1732                 c->c_next = wantcomp[i];
1733             } else
1734                 mhlcomp[i] = wantcomp[i];
1735         }
1736     }
1737
1738     return 0;
1739 }
1740
1741 /*
1742  * Check to see if we are interested in a component.  If we are, save
1743  * the text.
1744  */
1745
1746 static int
1747 checkcomp(char *name, char *buf)
1748 {
1749     int found = 0, i;
1750     struct comp *c;
1751     int bucket = CHASH(name);
1752     char *cp;
1753  
1754     if ((c = mhlcomp[bucket])) {
1755         do {
1756             if (mh_strcasecmp(name, c->c_name) == 0) {
1757                 found++;
1758                 if (! c->c_text) {
1759                     c->c_text = strdup(buf);
1760                 } else {
1761                     i = strlen(cp = c->c_text) - 1;
1762                     if (cp[i] == '\n') {
1763                         if (c->c_type & CT_ADDR) {
1764                             cp[i] = '\0';
1765                             cp = add (",\n\t", cp);
1766                         } else {
1767                             cp = add ("\t", cp);
1768                         }
1769                     }
1770                     c->c_text = add (buf, cp);
1771                 }
1772             }
1773         } while ((c = c->c_next));
1774     }
1775
1776     return found ? bucket : -1;
1777 }
1778
1779 /*
1780  * Add text to an existing component
1781  */
1782
1783 static void
1784 addcomp(int bucket, char *name, char *buf)
1785 {
1786     struct comp *c;
1787
1788     if (bucket != -1) {
1789         c = mhlcomp[bucket];
1790         do {
1791             if (mh_strcasecmp(name, c->c_name) == 0)
1792                 c->c_text = add (buf, c->c_text);
1793         } while ((c = c->c_next));
1794     }
1795 }
1796
1797 /*
1798  * Free up saved component structures
1799  */
1800
1801 static void
1802 freecomps(void)
1803 {
1804     struct comp *c1, *c2;
1805     unsigned int i;
1806
1807     for (i = 0; i < sizeof(mhlcomp)/sizeof(mhlcomp[0]); i++) {
1808         if ((c1 = mhlcomp[i]))
1809             for (; c1; c1 = c2) {
1810                 c2 = c1->c_next;
1811                 if (c1->c_text)
1812                     free(c1->c_text);
1813                 free(c1);
1814             }
1815     }
1816 }
1817
1818 /*
1819  * Just free up the component text.
1820  */
1821
1822 static void
1823 freecomptext(void)
1824 {
1825     struct comp *c1;
1826     unsigned int i;
1827
1828     for (i = 0; i < sizeof(mhlcomp)/sizeof(mhlcomp[0]); i++) {
1829         if ((c1 = mhlcomp[i]))
1830             for (; c1; c1 = c1->c_next) {
1831                 if (c1->c_text) {
1832                     free(c1->c_text);
1833                     c1->c_text = NULL;
1834                 }
1835             }
1836     }
1837 }
1838
1839 /*
1840  * Filter the body of a message through a specified format program
1841  */
1842
1843 static void
1844 filterbody (struct mcomp *c1, char *buf, int bufsz, int state, FILE *fp)
1845 {
1846     struct mcomp holder;
1847     char name[NAMESZ];
1848     int fdinput[2], fdoutput[2], waitstat;
1849     ssize_t cc;
1850     pid_t writerpid, filterpid;
1851
1852     /*
1853      * Create pipes so we can communicate with our filter process.
1854      */
1855
1856     if (pipe(fdinput) < 0) {
1857         adios(NULL, "Unable to create input pipe");
1858     }
1859
1860     if (pipe(fdoutput) < 0) {
1861         adios(NULL, "Unable to create output pipe");
1862     }
1863
1864     /*
1865      * Here's what we're doing to do.
1866      *
1867      * - Fork ourselves and start writing data to the write side of the
1868      *   input pipe (fdinput[1]).
1869      *
1870      * - Fork and exec our filter program.  We set the standard input of
1871      *   our filter program to be the read side of our input pipe (fdinput[0]).
1872      *   Standard output is set to the write side of our output pipe
1873      *   (fdoutput[1]).
1874      *
1875      * - We read from the read side of the output pipe (fdoutput[0]).
1876      *
1877      * We're forking because that's the simplest way to prevent any deadlocks.
1878      * (without doing something like switching to non-blocking I/O and using
1879      * select or poll, and I'm not interested in doing that).
1880      */
1881
1882     switch (writerpid = fork()) {
1883     case 0:
1884         /*
1885          * Our child process - just write to the filter input (fdinput[1]).
1886          * Close all other descriptors that we don't need.
1887          */
1888
1889         close(fdinput[0]);
1890         close(fdoutput[0]);
1891         close(fdoutput[1]);
1892
1893         /*
1894          * Call m_getfld() until we're no longer in the BODY state
1895          */
1896
1897         while (state == BODY) {
1898             write(fdinput[1], buf, strlen(buf));
1899             state = m_getfld(state, name, buf, bufsz, fp);
1900         }
1901
1902         /*
1903          * We should be done; time to exit.
1904          */
1905
1906         close(fdinput[1]);
1907         /*
1908          * Make sure we call _exit(), otherwise we may flush out the stdio
1909          * buffers that we have duplicated from the parent.
1910          */
1911         _exit(0);
1912         break;
1913     case -1:
1914         adios(NULL, "Unable to fork for filter writer process");
1915         break;
1916     }
1917
1918     /*
1919      * Fork and exec() our filter program, after redirecting standard in
1920      * and standard out appropriately.
1921      */
1922
1923     switch (filterpid = fork()) {
1924         char **args;
1925         struct arglist *a;
1926         int i, dat[5], s;
1927
1928     case 0:
1929         /*
1930          * Allocate an argument array for us
1931          */
1932
1933         args = (char **) mh_xmalloc((c1->c_nargs + 2) * sizeof(char *));
1934         args[0] = formatproc;
1935         args[c1->c_nargs + 1] = NULL;
1936         dat[0] = 0;
1937         dat[1] = 0;
1938         dat[2] = 0;
1939         dat[3] = BUFSIZ;
1940         dat[4] = 0;
1941
1942         /*
1943          * Pull out each argument and scan them.
1944          */
1945
1946         for (a = c1->c_f_args, i = 1; a != NULL; a = a->a_next, i++) {
1947             args[i] = mh_xmalloc(BUFSIZ);
1948             fmt_scan(a->a_fmt, args[i], BUFSIZ - 1, BUFSIZ, dat);
1949             /*
1950              * fmt_scan likes to put a trailing newline at the end of the
1951              * format string.  If we have one, get rid of it.
1952              */
1953             s = strlen(args[i]);
1954             if (args[i][s - 1] == '\n')
1955                 args[i][s - 1] = '\0';
1956         }
1957
1958         if (dup2(fdinput[0], STDIN_FILENO) < 0) {
1959             adios("formatproc", "Unable to dup2() standard input");
1960         }
1961         if (dup2(fdoutput[1], STDOUT_FILENO) < 0) {
1962             adios("formatproc", "Unable to dup2() standard output");
1963         }
1964
1965         /*
1966          * Close everything (especially the old input and output
1967          * descriptors, since they've been dup'd to stdin and stdout),
1968          * and exec the formatproc.
1969          */
1970
1971         close(fdinput[0]);
1972         close(fdinput[1]);
1973         close(fdoutput[0]);
1974         close(fdoutput[1]);
1975
1976         execvp(formatproc, args);
1977
1978         adios(formatproc, "Unable to execute filter");
1979
1980         break;
1981
1982     case -1:
1983         adios(NULL, "Unable to fork format program");
1984     }
1985
1986     /*
1987      * Close everything except our reader (fdoutput[0]);
1988      */
1989
1990     close(fdinput[0]);
1991     close(fdinput[1]);
1992     close(fdoutput[1]);
1993
1994     /*
1995      * As we read in this data, send it to putcomp
1996      */
1997
1998     holder.c_text = buf;
1999
2000     while ((cc = read(fdoutput[0], buf, bufsz - 1)) > 0) {
2001         buf[cc] = '\0';
2002         putcomp(c1, &holder, BODYCOMP);
2003     }
2004
2005     if (cc < 0) {
2006         adios(NULL, "reading from formatproc");
2007     }
2008
2009     /*
2010      * See if we got any errors along the way.  I'm a little leery of calling
2011      * waitpid() without WNOHANG, but it seems to be the most correct solution.
2012      */
2013
2014     if (waitpid(filterpid, &waitstat, 0) < 0) {
2015         if (errno != ECHILD) {
2016             adios("filterproc", "Unable to determine status");
2017         }
2018     } else {
2019         if (! (WIFEXITED(waitstat) && WEXITSTATUS(waitstat) == 0)) {
2020             pidstatus(waitstat, stderr, "filterproc");
2021         }
2022     }
2023
2024     if (waitpid(writerpid, &waitstat, 0) < 0) {
2025         if (errno != ECHILD) {
2026             adios("writer process", "Unable to determine status");
2027             done(1);
2028         }
2029     } else {
2030         if (! (WIFEXITED(waitstat) && WEXITSTATUS(waitstat) == 0)) {
2031             pidstatus(waitstat, stderr, "writer process");
2032             done(1);
2033         }
2034     }
2035
2036     close(fdoutput[0]);
2037 }