Changed msg_style and msg_delim to be file static to m_getfld.c
[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     struct arglist *ap;
899
900     switch (setjmp (env)) {
901         case OK: 
902             if (fname) {
903                 fp = mhl_action ? (*mhl_action) (fname) : fopen (fname, "r");
904                 if (fp == NULL) {
905                     advise (fname, "unable to open");
906                     exitstat++;
907                     return;
908                 }
909             } else {
910                 fname = "(stdin)";
911                 fp = stdin;
912             }
913             if (fstat(fileno(fp), &st) == 0) {
914                 filesize = st.st_size;
915             } else {
916                 filesize = 0;
917             }
918             cp = folder ? concat (folder, ":", fname, NULL) : getcpy (fname);
919             if (ontty != PITTY)
920                 SIGNAL (SIGINT, intrser);
921             mhlfile (fp, cp, ofilen, ofilec);  /* FALL THROUGH! */
922
923             for (ap = arglist_head; ap; ap = ap->a_next) {
924                 fmt_free(ap->a_fmt, 0);
925                 ap->a_fmt = NULL;
926             }
927
928             if (arglist_head)
929                 fmt_free(NULL, 1);
930
931         default: 
932             if (ontty != PITTY)
933                 SIGNAL (SIGINT, SIG_IGN);
934             if (mhl_action == NULL && fp != stdin)
935                 fclose (fp);
936             free (cp);
937             if (holder.c_text) {
938                 free (holder.c_text);
939                 holder.c_text = NULL;
940             }
941             free_queue (&msghd, &msgtl);
942             for (c1 = fmthd; c1; c1 = c1->c_next)
943                 c1->c_flags &= ~HDROUTPUT;
944             break;
945     }
946
947 }
948
949
950 static void
951 mhlfile (FILE *fp, char *mname, int ofilen, int ofilec)
952 {
953     int state, bucket;
954     struct mcomp *c1, *c2, *c3;
955     char **ip, name[NAMESZ], buf[BUFSIZ];
956
957     compile_filterargs();
958
959     if (forwall) {
960         if (digest)
961             printf ("%s", ofilen == 1 ? delim3 : delim4);
962         else {
963             printf ("\n-------");
964             if (ofilen == 1)
965                 printf (" Forwarded Message%s", ofilec > 1 ? "s" : "");
966             else
967                 printf (" Message %d", ofilen);
968             printf ("\n\n");
969         }
970     } else {
971         switch (ontty) {
972             case PITTY: 
973                 if (ofilec > 1) {
974                     if (ofilen > 1) {
975                         if ((global.c_flags & CLEARSCR))
976                             clear_screen ();
977                         else
978                             printf ("\n\n\n");
979                     }
980                     printf (">>> %s\n\n", mname);
981                 }
982                 break;
983
984             case ISTTY: 
985                 strncpy (buf, "\n", sizeof(buf));
986                 if (ofilec > 1) {
987                     if (SOprintf ("Press <return> to list \"%s\"...", mname)) {
988                         if (ofilen > 1)
989                             printf ("\n\n\n");
990                         printf ("Press <return> to list \"%s\"...", mname);
991                     }
992                     fflush (stdout);
993                     buf[0] = 0;
994                     read (fileno (stdout), buf, sizeof(buf));
995                 }
996                 if (strchr(buf, '\n')) {
997                     if ((global.c_flags & CLEARSCR))
998                         clear_screen ();
999                 }
1000                 else
1001                     printf ("\n");
1002                 break;
1003
1004             default: 
1005                 if (ofilec > 1) {
1006                     if (ofilen > 1) {
1007                         printf ("\n\n\n");
1008                         if (clearflg > 0)
1009                             clear_screen ();
1010                     }
1011                     printf (">>> %s\n\n", mname);
1012                 }
1013                 break;
1014         }
1015     }
1016
1017     for (state = FLD;;) {
1018         switch (state = m_getfld (state, name, buf, sizeof(buf), fp)) {
1019             case FLD: 
1020             case FLDPLUS: 
1021                 bucket = fmt_addcomptext(name, buf);
1022                 for (ip = ignores; *ip; ip++)
1023                     if (!mh_strcasecmp (name, *ip)) {
1024                         while (state == FLDPLUS) {
1025                             state = m_getfld (state, name, buf, sizeof(buf), fp);
1026                             fmt_appendcomp(bucket, name, buf);
1027                         }
1028                         break;
1029                     }
1030                 if (*ip)
1031                     continue;
1032
1033                 for (c2 = fmthd; c2; c2 = c2->c_next)
1034                     if (!mh_strcasecmp (c2->c_name, name))
1035                         break;
1036                 c1 = NULL;
1037                 if (!((c3 = c2 ? c2 : &global)->c_flags & SPLIT))
1038                     for (c1 = msghd; c1; c1 = c1->c_next)
1039                         if (!mh_strcasecmp (c1->c_name, c3->c_name)) {
1040                             c1->c_text =
1041                                 mcomp_add (c1->c_flags, buf, c1->c_text);
1042                             break;
1043                         }
1044                 if (c1 == NULL)
1045                     c1 = add_queue (&msghd, &msgtl, name, buf, 0);
1046                 while (state == FLDPLUS) {
1047                     state = m_getfld (state, name, buf, sizeof(buf), fp);
1048                     c1->c_text = add (buf, c1->c_text);
1049                     fmt_appendcomp(bucket, name, buf);
1050                 }
1051                 if (c2 == NULL)
1052                     c1->c_flags |= EXTRA;
1053                 continue;
1054
1055             case BODY: 
1056             case FILEEOF: 
1057                 row = column = 0;
1058                 for (c1 = fmthd; c1; c1 = c1->c_next) {
1059                     if (c1->c_flags & CLEARTEXT) {
1060                         putcomp (c1, c1, ONECOMP);
1061                         continue;
1062                     }
1063                     if (!mh_strcasecmp (c1->c_name, "messagename")) {
1064                         holder.c_text = concat ("(Message ", mname, ")\n",
1065                                             NULL);
1066                         putcomp (c1, &holder, ONECOMP);
1067                         free (holder.c_text);
1068                         holder.c_text = NULL;
1069                         continue;
1070                     }
1071                     if (!mh_strcasecmp (c1->c_name, "extras")) {
1072                         for (c2 = msghd; c2; c2 = c2->c_next)
1073                             if (c2->c_flags & EXTRA)
1074                                 putcomp (c1, c2, TWOCOMP);
1075                         continue;
1076                     }
1077                     if (dobody && !mh_strcasecmp (c1->c_name, "body")) {
1078                         if (c1->c_flags & FMTFILTER && state == BODY &&
1079                                                         formatproc != NULL) {
1080                             filterbody(c1, buf, sizeof(buf), state, fp);
1081                         } else {
1082                             holder.c_text = mh_xmalloc (sizeof(buf));
1083                             strncpy (holder.c_text, buf, sizeof(buf));
1084                             while (state == BODY) {
1085                                 putcomp (c1, &holder, BODYCOMP);
1086                                 state = m_getfld (state, name, holder.c_text,
1087                                             sizeof(buf), fp);
1088                             }
1089                             free (holder.c_text);
1090                             holder.c_text = NULL;
1091                         }
1092                         continue;
1093                     }
1094                     for (c2 = msghd; c2; c2 = c2->c_next)
1095                         if (!mh_strcasecmp (c2->c_name, c1->c_name)) {
1096                             putcomp (c1, c2, ONECOMP);
1097                             if (!(c1->c_flags & SPLIT))
1098                                 break;
1099                         }
1100                 }
1101                 return;
1102
1103             case LENERR: 
1104             case FMTERR: 
1105                 advise (NULL, "format error in message %s", mname);
1106                 exitstat++;
1107                 return;
1108
1109             default: 
1110                 adios (NULL, "getfld() returned %d", state);
1111         }
1112     }
1113 }
1114
1115
1116 static int
1117 mcomp_flags (char *name)
1118 {
1119     struct pair *ap;
1120
1121     for (ap = pairs; ap->p_name; ap++)
1122         if (!mh_strcasecmp (ap->p_name, name))
1123             return (ap->p_flags);
1124
1125     return 0;
1126 }
1127
1128
1129 static char *
1130 mcomp_add (long flags, char *s1, char *s2)
1131 {
1132     char *dp;
1133
1134     if (!(flags & ADDRFMT))
1135         return add (s1, s2);
1136
1137     if (s2 && *(dp = s2 + strlen (s2) - 1) == '\n')
1138         *dp = 0;
1139
1140     return add (s1, add (",\n", s2));
1141 }
1142
1143
1144 struct pqpair {
1145     char *pq_text;
1146     char *pq_error;
1147     struct pqpair *pq_next;
1148 };
1149
1150
1151 static void
1152 mcomp_format (struct mcomp *c1, struct mcomp *c2)
1153 {
1154     int dat[5];
1155     char *ap, *cp;
1156     char buffer[BUFSIZ], error[BUFSIZ];
1157     struct pqpair *p, *q;
1158     struct pqpair pq;
1159     struct mailname *mp;
1160
1161     ap = c2->c_text;
1162     c2->c_text = NULL;
1163     dat[0] = 0;
1164     dat[1] = 0;
1165     dat[2] = filesize;
1166     dat[3] = sizeof(buffer) - 1;
1167     dat[4] = 0;
1168
1169     if (!(c1->c_flags & ADDRFMT)) {
1170         if (c1->c_c_text)
1171             c1->c_c_text->c_text = ap;
1172         if ((cp = strrchr(ap, '\n')))   /* drop ending newline */
1173             if (!cp[1])
1174                 *cp = 0;
1175
1176         fmt_scan (c1->c_fmt, buffer, sizeof buffer - 1, sizeof buffer - 1, dat);
1177         /* Don't need to append a newline, dctime() already did */
1178         c2->c_text = getcpy (buffer);
1179
1180         /* ap is now owned by the component struct, so do NOT free it here */
1181         return;
1182     }
1183
1184     (q = &pq)->pq_next = NULL;
1185     while ((cp = getname (ap))) {
1186         if ((p = (struct pqpair *) calloc ((size_t) 1, sizeof(*p))) == NULL)
1187             adios (NULL, "unable to allocate pqpair memory");
1188
1189         if ((mp = getm (cp, NULL, 0, AD_NAME, error)) == NULL) {
1190             p->pq_text = getcpy (cp);
1191             p->pq_error = getcpy (error);
1192         } else {
1193             p->pq_text = getcpy (mp->m_text);
1194             mnfree (mp);
1195         }
1196         q = (q->pq_next = p);
1197     }
1198
1199     for (p = pq.pq_next; p; p = q) {
1200         if (c1->c_c_text) {
1201             c1->c_c_text->c_text = p->pq_text;
1202             p->pq_text = NULL;
1203         }
1204         if (c1->c_c_error) {
1205             c1->c_c_error->c_text = p->pq_error;
1206             p->pq_error = NULL;
1207         }
1208
1209         fmt_scan (c1->c_fmt, buffer, sizeof buffer - 1, sizeof buffer - 1, dat);
1210         if (*buffer) {
1211             if (c2->c_text)
1212                 c2->c_text = add (",\n", c2->c_text);
1213             if (*(cp = buffer + strlen (buffer) - 1) == '\n')
1214                 *cp = 0;
1215             c2->c_text = add (buffer, c2->c_text);
1216         }
1217
1218         if (p->pq_text)
1219             free (p->pq_text);
1220         if (p->pq_error)
1221             free (p->pq_error);
1222         q = p->pq_next;
1223         free ((char *) p);
1224     }
1225
1226     c2->c_text = add ("\n", c2->c_text);
1227     free (ap);
1228 }
1229
1230
1231 static struct mcomp *
1232 add_queue (struct mcomp **head, struct mcomp **tail, char *name, char *text, int flags)
1233 {
1234     struct mcomp *c1;
1235
1236     if ((c1 = (struct mcomp *) calloc ((size_t) 1, sizeof(*c1))) == NULL)
1237         adios (NULL, "unable to allocate comp memory");
1238
1239     c1->c_flags = flags & ~INIT;
1240     if ((c1->c_name = name ? getcpy (name) : NULL))
1241         c1->c_flags |= mcomp_flags (c1->c_name);
1242     c1->c_text = text ? getcpy (text) : NULL;
1243     if (flags & INIT) {
1244         if (global.c_ovtxt)
1245             c1->c_ovtxt = getcpy (global.c_ovtxt);
1246         c1->c_offset = global.c_offset;
1247         c1->c_ovoff = global. c_ovoff;
1248         c1->c_width = c1->c_length = 0;
1249         c1->c_cwidth = global.c_cwidth;
1250         c1->c_flags |= global.c_flags & GFLAGS;
1251     }
1252     if (*head == NULL)
1253         *head = c1;
1254     if (*tail != NULL)
1255         (*tail)->c_next = c1;
1256     *tail = c1;
1257
1258     return c1;
1259 }
1260
1261
1262 static void
1263 free_queue (struct mcomp **head, struct mcomp **tail)
1264 {
1265     struct mcomp *c1, *c2;
1266
1267     for (c1 = *head; c1; c1 = c2) {
1268         c2 = c1->c_next;
1269         if (c1->c_name)
1270             free (c1->c_name);
1271         if (c1->c_text)
1272             free (c1->c_text);
1273         if (c1->c_ovtxt)
1274             free (c1->c_ovtxt);
1275         if (c1->c_nfs)
1276             free (c1->c_nfs);
1277         if (c1->c_fmt)
1278             fmt_free (c1->c_fmt, 0);
1279         free ((char *) c1);
1280     }
1281
1282     *head = *tail = NULL;
1283 }
1284
1285
1286 static void
1287 putcomp (struct mcomp *c1, struct mcomp *c2, int flag)
1288 {
1289     int count, cchdr;
1290     unsigned char *cp;
1291
1292     cchdr = 0;
1293     lm = 0;
1294     llim = c1->c_length ? c1->c_length : -1;
1295     wid = c1->c_width ? c1->c_width : global.c_width;
1296     ovoff = (c1->c_ovoff >= 0 ? c1->c_ovoff : global.c_ovoff)
1297         + c1->c_offset;
1298     if ((ovtxt = c1->c_ovtxt ? c1->c_ovtxt : global.c_ovtxt) == NULL)
1299         ovtxt = "";
1300     if (wid < ovoff + strlen (ovtxt) + 5)
1301         adios (NULL, "component: %s width(%d) too small for overflow(%d)",
1302                 c1->c_name, wid, ovoff + strlen (ovtxt) + 5);
1303     onelp = NULL;
1304
1305     if (c1->c_flags & CLEARTEXT) {
1306         putstr (c1->c_text, c1->c_flags);
1307         putstr ("\n", c1->c_flags);
1308         return;
1309     }
1310
1311     if (c1->c_nfs && (c1->c_flags & (ADDRFMT | DATEFMT | FORMAT)))
1312         mcomp_format (c1, c2);
1313
1314     if (c1->c_flags & CENTER) {
1315         count = (c1->c_width ? c1->c_width : global.c_width)
1316             - c1->c_offset - strlen (c2->c_text);
1317         if (!(c1->c_flags & HDROUTPUT) && !(c1->c_flags & NOCOMPONENT))
1318             count -= strlen (c1->c_text ? c1->c_text : c1->c_name) + 2;
1319         lm = c1->c_offset + (count / 2);
1320     } else {
1321         if (c1->c_offset)
1322             lm = c1->c_offset;
1323     }
1324
1325     if (!(c1->c_flags & HDROUTPUT) && !(c1->c_flags & NOCOMPONENT)) {
1326         if (c1->c_flags & UPPERCASE)            /* uppercase component also */
1327             for (cp = (c1->c_text ? c1->c_text : c1->c_name); *cp; cp++)
1328                 if (islower (*cp))
1329                     *cp = toupper (*cp);
1330         putstr (c1->c_text ? c1->c_text : c1->c_name, c1->c_flags);
1331         if (flag != BODYCOMP) {
1332             putstr (": ", c1->c_flags);
1333             if (!(c1->c_flags & SPLIT))
1334                 c1->c_flags |= HDROUTPUT;
1335
1336         cchdr++;
1337         if ((count = c1->c_cwidth -
1338                 strlen (c1->c_text ? c1->c_text : c1->c_name) - 2) > 0)
1339             while (count--)
1340                 putstr (" ", c1->c_flags);
1341         }
1342         else
1343             c1->c_flags |= HDROUTPUT;           /* for BODYCOMP */
1344     }
1345
1346     if (flag == TWOCOMP
1347             && !(c2->c_flags & HDROUTPUT)
1348             && !(c2->c_flags & NOCOMPONENT)) {
1349         if (c1->c_flags & UPPERCASE)
1350             for (cp = c2->c_name; *cp; cp++)
1351                 if (islower (*cp))
1352                     *cp = toupper (*cp);
1353         putstr (c2->c_name, c1->c_flags);
1354         putstr (": ", c1->c_flags);
1355         if (!(c1->c_flags & SPLIT))
1356             c2->c_flags |= HDROUTPUT;
1357
1358         cchdr++;
1359         if ((count = c1->c_cwidth - strlen (c2->c_name) - 2) > 0)
1360             while (count--)
1361                 putstr (" ", c1->c_flags);
1362     }
1363     if (c1->c_flags & UPPERCASE)
1364         for (cp = c2->c_text; *cp; cp++)
1365             if (islower (*cp))
1366                 *cp = toupper (*cp);
1367
1368     count = 0;
1369     if (cchdr) {
1370         if (flag == TWOCOMP)
1371             count = (c1->c_cwidth >= 0) ? c1->c_cwidth
1372                         : (int) strlen (c2->c_name) + 2;
1373         else
1374             count = (c1->c_cwidth >= 0) ? (size_t) c1->c_cwidth
1375                         : strlen (c1->c_text ? c1->c_text : c1->c_name) + 2;
1376     }
1377     count += c1->c_offset;
1378
1379     if ((cp = oneline (c2->c_text, c1->c_flags)))
1380        putstr(cp, c1->c_flags);
1381     if (term == '\n')
1382         putstr ("\n", c1->c_flags);
1383     while ((cp = oneline (c2->c_text, c1->c_flags))) {
1384         lm = count;
1385         if (flag == BODYCOMP
1386                 && !(c1->c_flags & NOCOMPONENT))
1387             putstr (c1->c_text ? c1->c_text : c1->c_name, c1->c_flags);
1388         if (*cp)
1389             putstr (cp, c1->c_flags);
1390         if (term == '\n')
1391             putstr ("\n", c1->c_flags);
1392     }
1393     if (flag == BODYCOMP && term == '\n')
1394         c1->c_flags &= ~HDROUTPUT;              /* Buffer ended on a newline */
1395 }
1396
1397
1398 static char *
1399 oneline (char *stuff, long flags)
1400 {
1401     int spc;
1402     char *cp, *ret;
1403
1404     if (onelp == NULL)
1405         onelp = stuff;
1406     if (*onelp == 0)
1407         return (onelp = NULL);
1408
1409     ret = onelp;
1410     term = 0;
1411     if (flags & COMPRESS) {
1412         for (spc = 1, cp = ret; *onelp; onelp++)
1413             if (isspace (*onelp)) {
1414                 if (*onelp == '\n' && (!onelp[1] || (flags & ADDRFMT))) {
1415                     term = '\n';
1416                     *onelp++ = 0;
1417                     break;
1418                 }
1419                 else
1420                     if (!spc) {
1421                         *cp++ = ' ';
1422                         spc++;
1423                     }
1424             }
1425             else {
1426                 *cp++ = *onelp;
1427                 spc = 0;
1428             }
1429
1430         *cp = 0;
1431     }
1432     else {
1433         while (*onelp && *onelp != '\n')
1434             onelp++;
1435         if (*onelp == '\n') {
1436             term = '\n';
1437             *onelp++ = 0;
1438         }
1439         if (flags & LEFTADJUST)
1440             while (*ret == ' ' || *ret == '\t')
1441                 ret++;
1442     }
1443     if (*onelp == 0 && term == '\n' && (flags & NONEWLINE))
1444         term = 0;
1445
1446     return ret;
1447 }
1448
1449
1450 static void
1451 putstr (char *string, long flags)
1452 {
1453     if (!column && lm > 0) {
1454         while (lm > 0)
1455             if (lm >= 8) {
1456                 putch ('\t', flags);
1457                 lm -= 8;
1458             }
1459             else {
1460                 putch (' ', flags);
1461                 lm--;
1462             }
1463     }
1464     lm = 0;
1465     while (*string)
1466         putch (*string++, flags);
1467 }
1468
1469
1470 static void
1471 putch (char ch, long flags)
1472 {
1473     char buf[BUFSIZ];
1474
1475     if (llim == 0)
1476         return;
1477
1478     switch (ch) {
1479         case '\n': 
1480             if (llim > 0)
1481                 llim--;
1482             column = 0;
1483             row++;
1484             if (ontty != ISTTY || row != global.c_length)
1485                 break;
1486             if (global.c_flags & BELL)
1487                 putchar ('\007');
1488             fflush (stdout);
1489             buf[0] = 0;
1490             read (fileno (stdout), buf, sizeof(buf));
1491             if (strchr(buf, '\n')) {
1492                 if (global.c_flags & CLEARSCR)
1493                     clear_screen ();
1494                 row = 0;
1495             } else {
1496                 putchar ('\n');
1497                 row = global.c_length / 3;
1498             }
1499             return;
1500
1501         case '\t': 
1502             column |= 07;
1503             column++;
1504             break;
1505
1506         case '\b': 
1507             column--;
1508             break;
1509
1510         case '\r': 
1511             column = 0;
1512             break;
1513
1514         default: 
1515             /*
1516              * If we are forwarding this message, and the first
1517              * column contains a dash, then add a dash and a space.
1518              */
1519             if (column == 0 && forwflg && (dashstuff >= 0) && ch == '-') {
1520                 putchar ('-');
1521                 putchar (' ');
1522             }
1523             if (ch >= ' ')
1524                 column++;
1525             break;
1526     }
1527
1528     if (column >= wid && (flags & NOWRAP) == 0) {
1529         putch ('\n', flags);
1530         if (ovoff > 0)
1531             lm = ovoff;
1532         putstr (ovtxt ? ovtxt : "", flags);
1533         putch (ch, flags);
1534         return;
1535     }
1536
1537     putchar (ch);
1538 }
1539
1540
1541 static void
1542 intrser (int i)
1543 {
1544     NMH_UNUSED (i);
1545
1546     discard (stdout);
1547     putchar ('\n');
1548     longjmp (env, DONE);
1549 }
1550
1551
1552 static void
1553 pipeser (int i)
1554 {
1555     NMH_UNUSED (i);
1556
1557     done (NOTOK);
1558 }
1559
1560
1561 static void
1562 quitser (int i)
1563 {
1564     NMH_UNUSED (i);
1565
1566     putchar ('\n');
1567     fflush (stdout);
1568     done (NOTOK);
1569 }
1570
1571
1572 int
1573 mhlsbr (int argc, char **argv, FILE *(*action)())
1574 {
1575     SIGNAL_HANDLER istat = NULL, pstat = NULL, qstat = NULL;
1576     char *cp = NULL;
1577     struct mcomp *c1;
1578     struct arglist *a, *a2;
1579
1580     switch (setjmp (mhlenv)) {
1581         case OK: 
1582             cp = invo_name;
1583             sleepsw = 0;        /* XXX */
1584             bellflg = clearflg = forwflg = forwall = exitstat = 0;
1585             digest = NULL;
1586             ontty = NOTTY;
1587             mhl_action = action;
1588
1589             /*
1590              * If signal is at default action, then start ignoring
1591              * it, else let it set to its current action.
1592              */
1593             if ((istat = SIGNAL (SIGINT, SIG_IGN)) != SIG_DFL)
1594                 SIGNAL (SIGINT, istat);
1595             if ((qstat = SIGNAL (SIGQUIT, SIG_IGN)) != SIG_DFL)
1596                 SIGNAL (SIGQUIT, qstat);
1597             pstat = SIGNAL (SIGPIPE, pipeser);
1598             mhl (argc, argv);                  /* FALL THROUGH! */
1599
1600         default: 
1601             SIGNAL (SIGINT, istat);
1602             SIGNAL (SIGQUIT, qstat);
1603             SIGNAL (SIGPIPE, SIG_IGN);/* should probably change to block instead */
1604             if (ontty == PITTY)
1605                 m_pclose ();
1606             SIGNAL (SIGPIPE, pstat);
1607             invo_name = cp;
1608             if (holder.c_text) {
1609                 free (holder.c_text);
1610                 holder.c_text = NULL;
1611             }
1612             free_queue (&msghd, &msgtl);
1613             for (c1 = fmthd; c1; c1 = c1->c_next)
1614                 c1->c_flags &= ~HDROUTPUT;
1615
1616             a = arglist_head;
1617             while (a) {
1618                 if (a->a_nfs)
1619                     free(a->a_nfs);
1620                 a2 = a->a_next;
1621                 free(a);
1622                 a = a2;
1623             }
1624             return exitstat;
1625     }
1626 }
1627
1628 #undef adios
1629 #undef done
1630
1631 static void
1632 mhladios (char *what, char *fmt, ...)
1633 {
1634     va_list ap;
1635
1636     va_start(ap, fmt);
1637     advertise (what, NULL, fmt, ap);
1638     va_end(ap);
1639     mhldone (1);
1640 }
1641
1642
1643 static void
1644 mhldone (int status)
1645 {
1646     exitstat = status;
1647     if (mhl_action)
1648         longjmp (mhlenv, DONE);
1649     else
1650         done (exitstat);
1651 }
1652
1653
1654 static  int m_pid = NOTOK;
1655 static  int sd = NOTOK;
1656
1657 static void
1658 m_popen (char *name)
1659 {
1660     int pd[2];
1661
1662     if (mhl_action && (sd = dup (fileno (stdout))) == NOTOK)
1663         adios ("standard output", "unable to dup()");
1664
1665     if (pipe (pd) == NOTOK)
1666         adios ("pipe", "unable to");
1667
1668     switch (m_pid = vfork()) {
1669         case NOTOK: 
1670             adios ("fork", "unable to");
1671
1672         case OK: 
1673             SIGNAL (SIGINT, SIG_DFL);
1674             SIGNAL (SIGQUIT, SIG_DFL);
1675
1676             close (pd[1]);
1677             if (pd[0] != fileno (stdin)) {
1678                 dup2 (pd[0], fileno (stdin));
1679                 close (pd[0]);
1680             }
1681             execlp (name, r1bindex (name, '/'), NULL);
1682             fprintf (stderr, "unable to exec ");
1683             perror (name);
1684             _exit (-1);
1685
1686         default: 
1687             close (pd[0]);
1688             if (pd[1] != fileno (stdout)) {
1689                 dup2 (pd[1], fileno (stdout));
1690                 close (pd[1]);
1691             }
1692     }
1693 }
1694
1695
1696 void
1697 m_pclose (void)
1698 {
1699     if (m_pid == NOTOK)
1700         return;
1701
1702     if (sd != NOTOK) {
1703         fflush (stdout);
1704         if (dup2 (sd, fileno (stdout)) == NOTOK)
1705             adios ("standard output", "unable to dup2()");
1706
1707         clearerr (stdout);
1708         close (sd);
1709         sd = NOTOK;
1710     }
1711     else
1712         fclose (stdout);
1713
1714     pidwait (m_pid, OK);
1715     m_pid = NOTOK;
1716 }
1717
1718
1719 /*
1720  * Compile a format string used by the formatfield option and save it
1721  * for later.
1722  *
1723  * We will want the {text} (and possibly {error}) components for later,
1724  * so look for them and save them if we find them.
1725  */
1726
1727 static void
1728 compile_formatfield(struct mcomp *c1)
1729 {
1730     fmt_compile(c1->c_nfs, &c1->c_fmt, 1);
1731
1732     /*
1733      * As a note to myself and any other poor bastard who is looking through
1734      * this code in the future ....
1735      *
1736      * When the format hash table is reset later on (as it almost certainly
1737      * will be), there will still be references to these components in the
1738      * compiled format instructions.  Thus these component references will
1739      * be free'd when the format instructions are free'd (by fmt_free()).
1740      *
1741      * So, in other words ... don't go free'ing them yourself!
1742      */
1743
1744     c1->c_c_text = fmt_findcomp("text");
1745     c1->c_c_error = fmt_findcomp("error");
1746 }
1747
1748 /*
1749  * Compile all of the arguments for our format list.
1750  *
1751  * Iterate through the linked list of format strings and compile them.
1752  * Note that we reset the format hash table before we start, but we do NOT
1753  * reset it between calls to fmt_compile().
1754  *
1755  */
1756
1757 static void
1758 compile_filterargs (void)
1759 {
1760     struct arglist *arg = arglist_head;
1761     struct comp *cptr;
1762     char **ap;
1763
1764     fmt_free(NULL, 1);
1765
1766     while (arg) {
1767         fmt_compile(arg->a_nfs, &arg->a_fmt, 0);
1768         arg = arg->a_next;
1769     }
1770
1771     /*
1772      * Search through and mark any components that are address components
1773      */
1774
1775     for (ap = addrcomps; *ap; ap++) {
1776         cptr = fmt_findcomp (*ap);
1777         if (cptr)
1778             cptr->c_type |= CT_ADDR;
1779     }
1780 }
1781
1782 /*
1783  * Filter the body of a message through a specified format program
1784  */
1785
1786 static void
1787 filterbody (struct mcomp *c1, char *buf, int bufsz, int state, FILE *fp)
1788 {
1789     struct mcomp holder;
1790     char name[NAMESZ];
1791     int fdinput[2], fdoutput[2], waitstat;
1792     ssize_t cc;
1793     pid_t writerpid, filterpid;
1794
1795     /*
1796      * Create pipes so we can communicate with our filter process.
1797      */
1798
1799     if (pipe(fdinput) < 0) {
1800         adios(NULL, "Unable to create input pipe");
1801     }
1802
1803     if (pipe(fdoutput) < 0) {
1804         adios(NULL, "Unable to create output pipe");
1805     }
1806
1807     /*
1808      * Here's what we're doing to do.
1809      *
1810      * - Fork ourselves and start writing data to the write side of the
1811      *   input pipe (fdinput[1]).
1812      *
1813      * - Fork and exec our filter program.  We set the standard input of
1814      *   our filter program to be the read side of our input pipe (fdinput[0]).
1815      *   Standard output is set to the write side of our output pipe
1816      *   (fdoutput[1]).
1817      *
1818      * - We read from the read side of the output pipe (fdoutput[0]).
1819      *
1820      * We're forking because that's the simplest way to prevent any deadlocks.
1821      * (without doing something like switching to non-blocking I/O and using
1822      * select or poll, and I'm not interested in doing that).
1823      */
1824
1825     switch (writerpid = fork()) {
1826     case 0:
1827         /*
1828          * Our child process - just write to the filter input (fdinput[1]).
1829          * Close all other descriptors that we don't need.
1830          */
1831
1832         close(fdinput[0]);
1833         close(fdoutput[0]);
1834         close(fdoutput[1]);
1835
1836         /*
1837          * Call m_getfld() until we're no longer in the BODY state
1838          */
1839
1840         while (state == BODY) {
1841             write(fdinput[1], buf, strlen(buf));
1842             state = m_getfld(state, name, buf, bufsz, fp);
1843         }
1844
1845         /*
1846          * We should be done; time to exit.
1847          */
1848
1849         close(fdinput[1]);
1850         /*
1851          * Make sure we call _exit(), otherwise we may flush out the stdio
1852          * buffers that we have duplicated from the parent.
1853          */
1854         _exit(0);
1855         break;
1856     case -1:
1857         adios(NULL, "Unable to fork for filter writer process");
1858         break;
1859     }
1860
1861     /*
1862      * Fork and exec() our filter program, after redirecting standard in
1863      * and standard out appropriately.
1864      */
1865
1866     switch (filterpid = fork()) {
1867         char **args;
1868         struct arglist *a;
1869         int i, dat[5], s;
1870
1871     case 0:
1872         /*
1873          * Allocate an argument array for us
1874          */
1875
1876         args = (char **) mh_xmalloc((filter_nargs + 2) * sizeof(char *));
1877         args[0] = formatproc;
1878         args[filter_nargs + 1] = NULL;
1879         dat[0] = 0;
1880         dat[1] = 0;
1881         dat[2] = 0;
1882         dat[3] = BUFSIZ;
1883         dat[4] = 0;
1884
1885         /*
1886          * Pull out each argument and scan them.
1887          */
1888
1889         for (a = arglist_head, i = 1; a != NULL; a = a->a_next, i++) {
1890             args[i] = mh_xmalloc(BUFSIZ);
1891             fmt_scan(a->a_fmt, args[i], BUFSIZ - 1, BUFSIZ, dat);
1892             /*
1893              * fmt_scan likes to put a trailing newline at the end of the
1894              * format string.  If we have one, get rid of it.
1895              */
1896             s = strlen(args[i]);
1897             if (args[i][s - 1] == '\n')
1898                 args[i][s - 1] = '\0';
1899
1900             if (mhldebug)
1901                 fprintf(stderr, "filterarg: fmt=\"%s\", output=\"%s\"\n",
1902                         a->a_nfs, args[i]);
1903         }
1904
1905         if (dup2(fdinput[0], STDIN_FILENO) < 0) {
1906             adios("formatproc", "Unable to dup2() standard input");
1907         }
1908         if (dup2(fdoutput[1], STDOUT_FILENO) < 0) {
1909             adios("formatproc", "Unable to dup2() standard output");
1910         }
1911
1912         /*
1913          * Close everything (especially the old input and output
1914          * descriptors, since they've been dup'd to stdin and stdout),
1915          * and exec the formatproc.
1916          */
1917
1918         close(fdinput[0]);
1919         close(fdinput[1]);
1920         close(fdoutput[0]);
1921         close(fdoutput[1]);
1922
1923         execvp(formatproc, args);
1924
1925         adios(formatproc, "Unable to execute filter");
1926
1927         break;
1928
1929     case -1:
1930         adios(NULL, "Unable to fork format program");
1931     }
1932
1933     /*
1934      * Close everything except our reader (fdoutput[0]);
1935      */
1936
1937     close(fdinput[0]);
1938     close(fdinput[1]);
1939     close(fdoutput[1]);
1940
1941     /*
1942      * As we read in this data, send it to putcomp
1943      */
1944
1945     holder.c_text = buf;
1946
1947     while ((cc = read(fdoutput[0], buf, bufsz - 1)) > 0) {
1948         buf[cc] = '\0';
1949         putcomp(c1, &holder, BODYCOMP);
1950     }
1951
1952     if (cc < 0) {
1953         adios(NULL, "reading from formatproc");
1954     }
1955
1956     /*
1957      * See if we got any errors along the way.  I'm a little leery of calling
1958      * waitpid() without WNOHANG, but it seems to be the most correct solution.
1959      */
1960
1961     if (waitpid(filterpid, &waitstat, 0) < 0) {
1962         if (errno != ECHILD) {
1963             adios("filterproc", "Unable to determine status");
1964         }
1965     } else {
1966         if (! (WIFEXITED(waitstat) && WEXITSTATUS(waitstat) == 0)) {
1967             pidstatus(waitstat, stderr, "filterproc");
1968         }
1969     }
1970
1971     if (waitpid(writerpid, &waitstat, 0) < 0) {
1972         if (errno != ECHILD) {
1973             adios("writer process", "Unable to determine status");
1974             done(1);
1975         }
1976     } else {
1977         if (! (WIFEXITED(waitstat) && WEXITSTATUS(waitstat) == 0)) {
1978             pidstatus(waitstat, stderr, "writer process");
1979             done(1);
1980         }
1981     }
1982
1983     close(fdoutput[0]);
1984 }