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