* h/mhparse.h: Move interfaces also used by mhbuildsbr.c here
[mmh] / uip / mhparse.c
1
2 /*
3  * mhparse.c -- routines to parse the contents of MIME messages
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
11
12 #include <h/mh.h>
13 #include <fcntl.h>
14 #include <h/signals.h>
15 #include <h/md5.h>
16 #include <errno.h>
17 #include <setjmp.h>
18 #include <signal.h>
19 #include <h/mts.h>
20 #include <h/tws.h>
21 #include <h/mime.h>
22 #include <h/mhparse.h>
23 #include <h/utils.h>
24
25 #ifdef HAVE_SYS_WAIT_H
26 # include <sys/wait.h>
27 #endif
28
29
30 extern int debugsw;
31
32 extern int endian;      /* mhmisc.c     */
33
34 extern pid_t xpid;      /* mhshowsbr.c  */
35
36 /* cache policies */
37 extern int rcachesw;    /* mhcachesbr.c */
38 extern int wcachesw;    /* mhcachesbr.c */
39
40 int checksw = 0;        /* check Content-MD5 field */
41
42 /*
43  * Directory to place temp files.  This must
44  * be set before these routines are called.
45  */
46 char *tmp;
47
48 /*
49  * Structures for TEXT messages
50  */
51 struct k2v SubText[] = {
52     { "plain",    TEXT_PLAIN },
53     { "richtext", TEXT_RICHTEXT },  /* defined in RFC-1341    */
54     { "enriched", TEXT_ENRICHED },  /* defined in RFC-1896    */
55     { NULL,       TEXT_UNKNOWN }    /* this one must be last! */
56 };
57
58 struct k2v Charset[] = {
59     { "us-ascii",   CHARSET_USASCII },
60     { "iso-8859-1", CHARSET_LATIN },
61     { NULL,         CHARSET_UNKNOWN }  /* this one must be last! */
62 };
63
64 /*
65  * Structures for MULTIPART messages
66  */
67 struct k2v SubMultiPart[] = {
68     { "mixed",       MULTI_MIXED },
69     { "alternative", MULTI_ALTERNATE },
70     { "digest",      MULTI_DIGEST },
71     { "parallel",    MULTI_PARALLEL },
72     { NULL,          MULTI_UNKNOWN }    /* this one must be last! */
73 };
74
75 /*
76  * Structures for MESSAGE messages
77  */
78 struct k2v SubMessage[] = {
79     { "rfc822",        MESSAGE_RFC822 },
80     { "partial",       MESSAGE_PARTIAL },
81     { "external-body", MESSAGE_EXTERNAL },
82     { NULL,            MESSAGE_UNKNOWN }        /* this one must be last! */
83 };
84
85 /*
86  * Structure for APPLICATION messages
87  */
88 struct k2v SubApplication[] = {
89     { "octet-stream", APPLICATION_OCTETS },
90     { "postscript",   APPLICATION_POSTSCRIPT },
91     { NULL,           APPLICATION_UNKNOWN }     /* this one must be last! */
92 };
93
94
95 /* ftpsbr.c */
96 int ftp_get (char *, char *, char *, char *, char *, char *, int, int);
97
98 /* mhcachesbr.c */
99 int find_cache (CT, int, int *, char *, char *, int);
100
101 /* mhmisc.c */
102 int part_ok (CT, int);
103 int type_ok (CT, int);
104 int make_intermediates (char *);
105 void content_error (char *, CT, char *, ...);
106
107 /* mhfree.c */
108 void free_content (CT);
109 void free_encoding (CT, int);
110
111 /*
112  * static prototypes
113  */
114 static CT get_content (FILE *, char *, int);
115 static int get_comment (CT, unsigned char **, int);
116
117 static int InitGeneric (CT);
118 static int InitText (CT);
119 static int InitMultiPart (CT);
120 static void reverse_parts (CT);
121 static int InitMessage (CT);
122 static int InitApplication (CT);
123 static int init_encoding (CT, OpenCEFunc);
124 static unsigned long size_encoding (CT);
125 static int InitBase64 (CT);
126 static int openBase64 (CT, char **);
127 static int InitQuoted (CT);
128 static int openQuoted (CT, char **);
129 static int Init7Bit (CT);
130 static int openExternal (CT, CT, CE, char **, int *);
131 static int InitFile (CT);
132 static int openFile (CT, char **);
133 static int InitFTP (CT);
134 static int openFTP (CT, char **);
135 static int InitMail (CT);
136 static int openMail (CT, char **);
137 static int readDigest (CT, char *);
138
139 struct str2init str2cts[] = {
140     { "application", CT_APPLICATION, InitApplication },
141     { "audio",       CT_AUDIO,       InitGeneric },
142     { "image",       CT_IMAGE,       InitGeneric },
143     { "message",     CT_MESSAGE,     InitMessage },
144     { "multipart",   CT_MULTIPART,   InitMultiPart },
145     { "text",        CT_TEXT,        InitText },
146     { "video",       CT_VIDEO,       InitGeneric },
147     { NULL,          CT_EXTENSION,   NULL },  /* these two must be last! */
148     { NULL,          CT_UNKNOWN,     NULL },
149 };
150
151 struct str2init str2ces[] = {
152     { "base64",           CE_BASE64,    InitBase64 },
153     { "quoted-printable", CE_QUOTED,    InitQuoted },
154     { "8bit",             CE_8BIT,      Init7Bit },
155     { "7bit",             CE_7BIT,      Init7Bit },
156     { "binary",           CE_BINARY,    Init7Bit },
157     { NULL,               CE_EXTENSION, NULL },  /* these two must be last! */
158     { NULL,               CE_UNKNOWN,   NULL },
159 };
160
161 /*
162  * NOTE WELL: si_key MUST NOT have value of NOTOK
163  *
164  * si_key is 1 if access method is anonymous.
165  */
166 struct str2init str2methods[] = {
167     { "afs",         1, InitFile },
168     { "anon-ftp",    1, InitFTP },
169     { "ftp",         0, InitFTP },
170     { "local-file",  0, InitFile },
171     { "mail-server", 0, InitMail },
172     { NULL,          0, NULL }
173 };
174
175
176 int
177 pidcheck (int status)
178 {
179     if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
180         return status;
181
182     fflush (stdout);
183     fflush (stderr);
184     done (1);
185     return 1;
186 }
187
188
189 /*
190  * Main entry point for parsing a MIME message or file.
191  * It returns the Content structure for the top level
192  * entity in the file.
193  */
194
195 CT
196 parse_mime (char *file)
197 {
198     int is_stdin;
199     char buffer[BUFSIZ];
200     FILE *fp;
201     CT ct;
202
203     /*
204      * Check if file is actually standard input
205      */
206     if ((is_stdin = !(strcmp (file, "-")))) {
207         file = add (m_tmpfil (invo_name), NULL);
208         if ((fp = fopen (file, "w+")) == NULL) {
209             advise (file, "unable to fopen for writing and reading");
210             return NULL;
211         }
212         chmod (file, 0600);
213         while (fgets (buffer, sizeof(buffer), stdin))
214             fputs (buffer, fp);
215         fflush (fp);
216
217         if (ferror (stdin)) {
218             unlink (file);
219             advise ("stdin", "error reading");
220             return NULL;
221         }
222         if (ferror (fp)) {
223             unlink (file);
224             advise (file, "error writing");
225             return NULL;
226         }
227         fseek (fp, 0L, SEEK_SET);
228     } else if ((fp = fopen (file, "r")) == NULL) {
229         advise (file, "unable to read");
230         return NULL;
231     }
232
233     if (!(ct = get_content (fp, file, 1))) {
234         if (is_stdin)
235             unlink (file);
236         advise (NULL, "unable to decode %s", file);
237         return NULL;
238     }
239
240     if (is_stdin)
241         ct->c_unlink = 1;       /* temp file to remove */
242
243     ct->c_fp = NULL;
244
245     if (ct->c_end == 0L) {
246         fseek (fp, 0L, SEEK_END);
247         ct->c_end = ftell (fp);
248     }
249
250     if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) {
251         fclose (fp);
252         free_content (ct);
253         return NULL;
254     }
255
256     fclose (fp);
257     return ct;
258 }
259
260
261 /*
262  * Main routine for reading/parsing the headers
263  * of a message content.
264  *
265  * toplevel =  1   # we are at the top level of the message
266  * toplevel =  0   # we are inside message type or multipart type
267  *                 # other than multipart/digest
268  * toplevel = -1   # we are inside multipart/digest
269  * NB: on failure we will fclose(in)!
270  */
271
272 static CT
273 get_content (FILE *in, char *file, int toplevel)
274 {
275     int compnum, state;
276     char buf[BUFSIZ], name[NAMESZ];
277     char *np, *vp;
278     CT ct;
279     HF hp;
280
281     /* allocate the content structure */
282     if (!(ct = (CT) calloc (1, sizeof(*ct))))
283         adios (NULL, "out of memory");
284
285     ct->c_fp = in;
286     ct->c_file = add (file, NULL);
287     ct->c_begin = ftell (ct->c_fp) + 1;
288
289     /*
290      * Parse the header fields for this
291      * content into a linked list.
292      */
293     for (compnum = 1, state = FLD;;) {
294         switch (state = m_getfld (state, name, buf, sizeof(buf), in)) {
295         case FLD:
296         case FLDPLUS:
297         case FLDEOF:
298             compnum++;
299
300             /* get copies of the buffers */
301             np = add (name, NULL);
302             vp = add (buf, NULL);
303
304             /* if necessary, get rest of field */
305             while (state == FLDPLUS) {
306                 state = m_getfld (state, name, buf, sizeof(buf), in);
307                 vp = add (buf, vp);     /* add to previous value */
308             }
309
310             /* Now add the header data to the list */
311             add_header (ct, np, vp);
312
313             /* continue, if this isn't the last header field */
314             if (state != FLDEOF) {
315                 ct->c_begin = ftell (in) + 1;
316                 continue;
317             }
318             /* else fall... */
319
320         case BODY:
321         case BODYEOF:
322             ct->c_begin = ftell (in) - strlen (buf);
323             break;
324
325         case FILEEOF:
326             ct->c_begin = ftell (in);
327             break;
328
329         case LENERR:
330         case FMTERR:
331             adios (NULL, "message format error in component #%d", compnum);
332
333         default:
334             adios (NULL, "getfld() returned %d", state);
335         }
336
337         /* break out of the loop */
338         break;
339     }
340
341     /*
342      * Read the content headers.  We will parse the
343      * MIME related header fields into their various
344      * structures and set internal flags related to
345      * content type/subtype, etc.
346      */
347
348     hp = ct->c_first_hf;        /* start at first header field */
349     while (hp) {
350         /* Get MIME-Version field */
351         if (!mh_strcasecmp (hp->name, VRSN_FIELD)) {
352             int ucmp;
353             char c;
354             unsigned char *cp, *dp;
355
356             if (ct->c_vrsn) {
357                 advise (NULL, "message %s has multiple %s: fields",
358                         ct->c_file, VRSN_FIELD);
359                 goto next_header;
360             }
361             ct->c_vrsn = add (hp->value, NULL);
362
363             /* Now, cleanup this field */
364             cp = ct->c_vrsn;
365
366             while (isspace (*cp))
367                 cp++;
368             for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
369                 *dp++ = ' ';
370             for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
371                 if (!isspace (*dp))
372                     break;
373             *++dp = '\0';
374             if (debugsw)
375                 fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp);
376
377             if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK)
378                 goto out;
379
380             for (dp = cp; istoken (*dp); dp++)
381                 continue;
382             c = *dp;
383             *dp = '\0';
384             ucmp = !mh_strcasecmp (cp, VRSN_VALUE);
385             *dp = c;
386             if (!ucmp) {
387                 admonish (NULL, "message %s has unknown value for %s: field (%s)",
388                 ct->c_file, VRSN_FIELD, cp);
389             }
390         }
391         else if (!mh_strcasecmp (hp->name, TYPE_FIELD)) {
392         /* Get Content-Type field */
393             struct str2init *s2i;
394             CI ci = &ct->c_ctinfo;
395
396             /* Check if we've already seen a Content-Type header */
397             if (ct->c_ctline) {
398                 advise (NULL, "message %s has multiple %s: fields",
399                         ct->c_file, TYPE_FIELD);
400                 goto next_header;
401             }
402
403             /* Parse the Content-Type field */
404             if (get_ctinfo (hp->value, ct, 0) == NOTOK)
405                 goto out;
406
407             /*
408              * Set the Init function and the internal
409              * flag for this content type.
410              */
411             for (s2i = str2cts; s2i->si_key; s2i++)
412                 if (!mh_strcasecmp (ci->ci_type, s2i->si_key))
413                     break;
414             if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
415                 s2i++;
416             ct->c_type = s2i->si_val;
417             ct->c_ctinitfnx = s2i->si_init;
418         }
419         else if (!mh_strcasecmp (hp->name, ENCODING_FIELD)) {
420         /* Get Content-Transfer-Encoding field */
421             char c;
422             unsigned char *cp, *dp;
423             struct str2init *s2i;
424
425             /*
426              * Check if we've already seen the
427              * Content-Transfer-Encoding field
428              */
429             if (ct->c_celine) {
430                 advise (NULL, "message %s has multiple %s: fields",
431                         ct->c_file, ENCODING_FIELD);
432                 goto next_header;
433             }
434
435             /* get copy of this field */
436             ct->c_celine = cp = add (hp->value, NULL);
437
438             while (isspace (*cp))
439                 cp++;
440             for (dp = cp; istoken (*dp); dp++)
441                 continue;
442             c = *dp;
443             *dp = '\0';
444
445             /*
446              * Find the internal flag and Init function
447              * for this transfer encoding.
448              */
449             for (s2i = str2ces; s2i->si_key; s2i++)
450                 if (!mh_strcasecmp (cp, s2i->si_key))
451                     break;
452             if (!s2i->si_key && !uprf (cp, "X-"))
453                 s2i++;
454             *dp = c;
455             ct->c_encoding = s2i->si_val;
456
457             /* Call the Init function for this encoding */
458             if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK)
459                 goto out;
460         }
461         else if (!mh_strcasecmp (hp->name, MD5_FIELD)) {
462         /* Get Content-MD5 field */
463             unsigned char *cp, *dp;
464             char *ep;
465
466             if (!checksw)
467                 goto next_header;
468
469             if (ct->c_digested) {
470                 advise (NULL, "message %s has multiple %s: fields",
471                         ct->c_file, MD5_FIELD);
472                 goto next_header;
473             }
474
475             ep = cp = add (hp->value, NULL);    /* get a copy */
476
477             while (isspace (*cp))
478                 cp++;
479             for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
480                 *dp++ = ' ';
481             for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
482                 if (!isspace (*dp))
483                     break;
484             *++dp = '\0';
485             if (debugsw)
486                 fprintf (stderr, "%s: %s\n", MD5_FIELD, cp);
487
488             if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) {
489                 free (ep);
490                 goto out;
491             }
492
493             for (dp = cp; *dp && !isspace (*dp); dp++)
494                 continue;
495             *dp = '\0';
496
497             readDigest (ct, cp);
498             free (ep);
499             ct->c_digested++;
500         }
501         else if (!mh_strcasecmp (hp->name, ID_FIELD)) {
502         /* Get Content-ID field */
503             ct->c_id = add (hp->value, ct->c_id);
504         }
505         else if (!mh_strcasecmp (hp->name, DESCR_FIELD)) {
506         /* Get Content-Description field */
507             ct->c_descr = add (hp->value, ct->c_descr);
508         }
509         else if (!mh_strcasecmp (hp->name, DISPO_FIELD)) {
510         /* Get Content-Disposition field */
511             ct->c_dispo = add (hp->value, ct->c_dispo);
512         }
513
514 next_header:
515         hp = hp->next;  /* next header field */
516     }
517
518     /*
519      * Check if we saw a Content-Type field.
520      * If not, then assign a default value for
521      * it, and the Init function.
522      */
523     if (!ct->c_ctline) {
524         /*
525          * If we are inside a multipart/digest message,
526          * so default type is message/rfc822
527          */
528         if (toplevel < 0) {
529             if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
530                 goto out;
531             ct->c_type = CT_MESSAGE;
532             ct->c_ctinitfnx = InitMessage;
533         } else {
534             /*
535              * Else default type is text/plain
536              */
537             if (get_ctinfo ("text/plain", ct, 0) == NOTOK)
538                 goto out;
539             ct->c_type = CT_TEXT;
540             ct->c_ctinitfnx = InitText;
541         }
542     }
543
544     /* Use default Transfer-Encoding, if necessary */
545     if (!ct->c_celine) {
546         ct->c_encoding = CE_7BIT;
547         Init7Bit (ct);
548     }
549
550     return ct;
551
552 out:
553     free_content (ct);
554     return NULL;
555 }
556
557
558 /*
559  * small routine to add header field to list
560  */
561
562 int
563 add_header (CT ct, char *name, char *value)
564 {
565     HF hp;
566
567     /* allocate header field structure */
568     hp = mh_xmalloc (sizeof(*hp));
569
570     /* link data into header structure */
571     hp->name = name;
572     hp->value = value;
573     hp->next = NULL;
574
575     /* link header structure into the list */
576     if (ct->c_first_hf == NULL) {
577         ct->c_first_hf = hp;            /* this is the first */
578         ct->c_last_hf = hp;
579     } else {
580         ct->c_last_hf->next = hp;       /* add it to the end */
581         ct->c_last_hf = hp;
582     }
583
584     return 0;
585 }
586
587
588 /* Make sure that buf contains at least one appearance of name,
589    followed by =.  If not, insert both name and value, just after
590    first semicolon, if any.  Note that name should not contain a
591    trailing =.  And quotes will be added around the value.  Typical
592    usage:  make sure that a Content-Disposition header contains
593    filename="foo".  If it doesn't and value does, use value from
594    that. */
595 static char *
596 incl_name_value (unsigned char *buf, char *name, char *value) {
597     char *newbuf = buf;
598
599     /* Assume that name is non-null. */
600     if (buf && value) {
601         char *name_plus_equal = concat (name, "=", NULL);
602
603         if (! strstr (buf, name_plus_equal)) {
604             char *insertion;
605             unsigned char *cp;
606             char *prefix, *suffix;
607
608             /* Trim trailing space, esp. newline. */
609             for (cp = &buf[strlen (buf) - 1];
610                  cp >= buf && isspace (*cp);
611                  --cp) {
612                 *cp = '\0';
613             }
614
615             insertion = concat ("; ", name, "=", "\"", value, "\"", NULL);
616
617             /* Insert at first semicolon, if any.  If none, append to
618                end. */
619             prefix = add (buf, NULL);
620             if ((cp = strchr (prefix, ';'))) {
621                 suffix = concat (cp, NULL);
622                 *cp = '\0';
623                 newbuf = concat (prefix, insertion, suffix, "\n", NULL);
624                 free (suffix);
625             } else {
626                 /* Append to end. */
627                 newbuf = concat (buf, insertion, "\n", NULL);
628             }
629
630             free (prefix);
631             free (insertion);
632             free (buf);
633         }
634
635         free (name_plus_equal);
636     }
637
638     return newbuf;
639 }
640
641 /* Extract just name_suffix="foo", if any, from value.  If there isn't
642    one, return the entire value.  Note that, for example, a name_suffix
643    of name will match filename="foo", and return foo. */
644 static char *
645 extract_name_value (char *name_suffix, char *value) {
646     char *extracted_name_value = value;
647     char *name_suffix_plus_quote = concat (name_suffix, "=\"", NULL);
648     char *name_suffix_equals = strstr (value, name_suffix_plus_quote);
649     char *cp;
650
651     free (name_suffix_plus_quote);
652     if (name_suffix_equals) {
653         char *name_suffix_begin;
654
655         /* Find first \". */
656         for (cp = name_suffix_equals; *cp != '"'; ++cp) /* empty */;
657         name_suffix_begin = ++cp;
658         /* Find second \". */
659         for (; *cp != '"'; ++cp) /* empty */;
660
661         extracted_name_value = mh_xmalloc (cp - name_suffix_begin + 1);
662         memcpy (extracted_name_value,
663                 name_suffix_begin,
664                 cp - name_suffix_begin);
665         extracted_name_value[cp - name_suffix_begin] = '\0';
666     }
667
668     return extracted_name_value;
669 }
670
671 /*
672  * Parse Content-Type line and (if `magic' is non-zero) mhbuild composition
673  * directives.  Fills in the information of the CTinfo structure.
674  */
675 int
676 get_ctinfo (unsigned char *cp, CT ct, int magic)
677 {
678     int i;
679     unsigned char *dp;
680     char **ap, **ep;
681     char c;
682     CI ci;
683
684     ci = &ct->c_ctinfo;
685     i = strlen (invo_name) + 2;
686
687     /* store copy of Content-Type line */
688     cp = ct->c_ctline = add (cp, NULL);
689
690     while (isspace (*cp))       /* trim leading spaces */
691         cp++;
692
693     /* change newlines to spaces */
694     for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n'))
695         *dp++ = ' ';
696
697     /* trim trailing spaces */
698     for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
699         if (!isspace (*dp))
700             break;
701     *++dp = '\0';
702
703     if (debugsw)
704         fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp);
705
706     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
707         return NOTOK;
708
709     for (dp = cp; istoken (*dp); dp++)
710         continue;
711     c = *dp, *dp = '\0';
712     ci->ci_type = add (cp, NULL);       /* store content type */
713     *dp = c, cp = dp;
714
715     if (!*ci->ci_type) {
716         advise (NULL, "invalid %s: field in message %s (empty type)", 
717                 TYPE_FIELD, ct->c_file);
718         return NOTOK;
719     }
720
721     /* down case the content type string */
722     for (dp = ci->ci_type; *dp; dp++)
723         if (isalpha(*dp) && isupper (*dp))
724             *dp = tolower (*dp);
725
726     while (isspace (*cp))
727         cp++;
728
729     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
730         return NOTOK;
731
732     if (*cp != '/') {
733         if (!magic)
734             ci->ci_subtype = add ("", NULL);
735         goto magic_skip;
736     }
737
738     cp++;
739     while (isspace (*cp))
740         cp++;
741
742     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
743         return NOTOK;
744
745     for (dp = cp; istoken (*dp); dp++)
746         continue;
747     c = *dp, *dp = '\0';
748     ci->ci_subtype = add (cp, NULL);    /* store the content subtype */
749     *dp = c, cp = dp;
750
751     if (!*ci->ci_subtype) {
752         advise (NULL,
753                 "invalid %s: field in message %s (empty subtype for \"%s\")",
754                 TYPE_FIELD, ct->c_file, ci->ci_type);
755         return NOTOK;
756     }
757
758     /* down case the content subtype string */
759     for (dp = ci->ci_subtype; *dp; dp++)
760         if (isalpha(*dp) && isupper (*dp))
761             *dp = tolower (*dp);
762
763 magic_skip:
764     while (isspace (*cp))
765         cp++;
766
767     if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
768         return NOTOK;
769
770     /*
771      * Parse attribute/value pairs given with Content-Type
772      */
773     ep = (ap = ci->ci_attrs) + NPARMS;
774     while (*cp == ';') {
775         char *vp;
776         unsigned char *up;
777
778         if (ap >= ep) {
779             advise (NULL,
780                     "too many parameters in message %s's %s: field (%d max)",
781                     ct->c_file, TYPE_FIELD, NPARMS);
782             return NOTOK;
783         }
784
785         cp++;
786         while (isspace (*cp))
787             cp++;
788
789         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
790             return NOTOK;
791
792         if (*cp == 0) {
793             advise (NULL,
794                     "extraneous trailing ';' in message %s's %s: parameter list",
795                     ct->c_file, TYPE_FIELD);
796             return OK;
797         }
798
799         /* down case the attribute name */
800         for (dp = cp; istoken (*dp); dp++)
801             if (isalpha(*dp) && isupper (*dp))
802                 *dp = tolower (*dp);
803
804         for (up = dp; isspace (*dp);)
805             dp++;
806         if (dp == cp || *dp != '=') {
807             advise (NULL,
808                     "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)",
809                     ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp);
810             return NOTOK;
811         }
812
813         vp = (*ap = add (cp, NULL)) + (up - cp);
814         *vp = '\0';
815         for (dp++; isspace (*dp);)
816             dp++;
817
818         /* now add the attribute value */
819         ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp);
820
821         if (*dp == '"') {
822             for (cp = ++dp, dp = vp;;) {
823                 switch (c = *cp++) {
824                     case '\0':
825 bad_quote:
826                         advise (NULL,
827                                 "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)",
828                                 ct->c_file, TYPE_FIELD, i, i, "", *ap);
829                         return NOTOK;
830
831                     case '\\':
832                         *dp++ = c;
833                         if ((c = *cp++) == '\0')
834                             goto bad_quote;
835                         /* else fall... */
836
837                     default:
838                         *dp++ = c;
839                         continue;
840
841                     case '"':
842                         *dp = '\0';
843                         break;
844                 }
845                 break;
846             }
847         } else {
848             for (cp = dp, dp = vp; istoken (*cp); cp++, dp++)
849                 continue;
850             *dp = '\0';
851         }
852         if (!*vp) {
853             advise (NULL,
854                     "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)",
855                     ct->c_file, TYPE_FIELD, i, i, "", *ap);
856             return NOTOK;
857         }
858         ap++;
859
860         while (isspace (*cp))
861             cp++;
862
863         if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK)
864             return NOTOK;
865     }
866
867     /*
868      * Get any <Content-Id> given in buffer
869      */
870     if (magic && *cp == '<') {
871         if (ct->c_id) {
872             free (ct->c_id);
873             ct->c_id = NULL;
874         }
875         if (!(dp = strchr(ct->c_id = ++cp, '>'))) {
876             advise (NULL, "invalid ID in message %s", ct->c_file);
877             return NOTOK;
878         }
879         c = *dp;
880         *dp = '\0';
881         if (*ct->c_id)
882             ct->c_id = concat ("<", ct->c_id, ">\n", NULL);
883         else
884             ct->c_id = NULL;
885         *dp++ = c;
886         cp = dp;
887
888         while (isspace (*cp))
889             cp++;
890     }
891
892     /*
893      * Get any [Content-Description] given in buffer.
894      */
895     if (magic && *cp == '[') {
896         ct->c_descr = ++cp;
897         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
898             if (*dp == ']')
899                 break;
900         if (dp < cp) {
901             advise (NULL, "invalid description in message %s", ct->c_file);
902             ct->c_descr = NULL;
903             return NOTOK;
904         }
905         
906         c = *dp;
907         *dp = '\0';
908         if (*ct->c_descr)
909             ct->c_descr = concat (ct->c_descr, "\n", NULL);
910         else
911             ct->c_descr = NULL;
912         *dp++ = c;
913         cp = dp;
914
915         while (isspace (*cp))
916             cp++;
917     }
918
919     /*
920      * Get any {Content-Disposition} given in buffer.
921      */
922     if (magic && *cp == '{') {
923         ct->c_dispo = ++cp;
924         for (dp = cp + strlen (cp) - 1; dp >= cp; dp--)
925             if (*dp == '}')
926                 break;
927         if (dp < cp) {
928             advise (NULL, "invalid disposition in message %s", ct->c_file);
929             ct->c_dispo = NULL;
930             return NOTOK;
931         }
932         
933         c = *dp;
934         *dp = '\0';
935         if (*ct->c_dispo)
936             ct->c_dispo = concat (ct->c_dispo, "\n", NULL);
937         else
938             ct->c_dispo = NULL;
939         *dp++ = c;
940         cp = dp;
941
942         while (isspace (*cp))
943             cp++;
944     }
945
946     /*
947      * Check if anything is left over
948      */
949     if (*cp) {
950         if (magic) {
951             ci->ci_magic = add (cp, NULL);
952
953             /* If there is a Content-Disposition header and it doesn't
954                have a *filename=, extract it from the magic contents.
955                The r1bindex call skips any leading directory
956                components. */
957             if (ct->c_dispo)
958                 ct->c_dispo =
959                     incl_name_value (ct->c_dispo,
960                                      "filename",
961                                      r1bindex (extract_name_value ("name",
962                                                                    ci->
963                                                                    ci_magic),
964                                                '/'));
965         }
966         else
967             advise (NULL,
968                     "extraneous information in message %s's %s: field\n%*.*s(%s)",
969                     ct->c_file, TYPE_FIELD, i, i, "", cp);
970     }
971
972     return OK;
973 }
974
975
976 static int
977 get_comment (CT ct, unsigned char **ap, int istype)
978 {
979     int i;
980     char *bp;
981     unsigned char *cp;
982     char c, buffer[BUFSIZ], *dp;
983     CI ci;
984
985     ci = &ct->c_ctinfo;
986     cp = *ap;
987     bp = buffer;
988     cp++;
989
990     for (i = 0;;) {
991         switch (c = *cp++) {
992         case '\0':
993 invalid:
994         advise (NULL, "invalid comment in message %s's %s: field",
995                 ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD);
996         return NOTOK;
997
998         case '\\':
999             *bp++ = c;
1000             if ((c = *cp++) == '\0')
1001                 goto invalid;
1002             *bp++ = c;
1003             continue;
1004
1005         case '(':
1006             i++;
1007             /* and fall... */
1008         default:
1009             *bp++ = c;
1010             continue;
1011
1012         case ')':
1013             if (--i < 0)
1014                 break;
1015             *bp++ = c;
1016             continue;
1017         }
1018         break;
1019     }
1020     *bp = '\0';
1021
1022     if (istype) {
1023         if ((dp = ci->ci_comment)) {
1024             ci->ci_comment = concat (dp, " ", buffer, NULL);
1025             free (dp);
1026         } else {
1027             ci->ci_comment = add (buffer, NULL);
1028         }
1029     }
1030
1031     while (isspace (*cp))
1032         cp++;
1033
1034     *ap = cp;
1035     return OK;
1036 }
1037
1038
1039 /*
1040  * CONTENTS
1041  *
1042  * Handles content types audio, image, and video.
1043  * There's not much to do right here.
1044  */
1045
1046 static int
1047 InitGeneric (CT ct)
1048 {
1049     return OK;          /* not much to do here */
1050 }
1051
1052
1053 /*
1054  * TEXT
1055  */
1056
1057 static int
1058 InitText (CT ct)
1059 {
1060     char buffer[BUFSIZ];
1061     char *chset;
1062     char **ap, **ep, *cp;
1063     struct k2v *kv;
1064     struct text *t;
1065     CI ci = &ct->c_ctinfo;
1066
1067     /* check for missing subtype */
1068     if (!*ci->ci_subtype)
1069         ci->ci_subtype = add ("plain", ci->ci_subtype);
1070
1071     /* match subtype */
1072     for (kv = SubText; kv->kv_key; kv++)
1073         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1074             break;
1075     ct->c_subtype = kv->kv_value;
1076
1077     /* allocate text character set structure */
1078     if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL)
1079         adios (NULL, "out of memory");
1080     ct->c_ctparams = (void *) t;
1081
1082     /* scan for charset parameter */
1083     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
1084         if (!mh_strcasecmp (*ap, "charset"))
1085             break;
1086
1087     /* check if content specified a character set */
1088     if (*ap) {
1089         /* match character set or set to CHARSET_UNKNOWN */
1090         for (kv = Charset; kv->kv_key; kv++)
1091             if (!mh_strcasecmp (*ep, kv->kv_key))
1092                 break;
1093         t->tx_charset = kv->kv_value;
1094     } else {
1095         t->tx_charset = CHARSET_UNSPECIFIED;
1096     }
1097
1098     /*
1099      * If we can not handle character set natively,
1100      * then check profile for string to modify the
1101      * terminal or display method.
1102      *
1103      * termproc is for mhshow, though mhlist -debug prints it, too.
1104      */
1105     if (chset != NULL && !check_charset (chset, strlen (chset))) {
1106         snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset);
1107         if ((cp = context_find (buffer)))
1108             ct->c_termproc = getcpy (cp);
1109     }
1110
1111     return OK;
1112 }
1113
1114
1115 /*
1116  * MULTIPART
1117  */
1118
1119 static int
1120 InitMultiPart (CT ct)
1121 {
1122     int inout;
1123     long last, pos;
1124     unsigned char *cp, *dp;
1125     char **ap, **ep;
1126     char *bp, buffer[BUFSIZ];
1127     struct multipart *m;
1128     struct k2v *kv;
1129     struct part *part, **next;
1130     CI ci = &ct->c_ctinfo;
1131     CT p;
1132     FILE *fp;
1133
1134     /*
1135      * The encoding for multipart messages must be either
1136      * 7bit, 8bit, or binary (per RFC2045).
1137      */
1138     if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
1139         && ct->c_encoding != CE_BINARY) {
1140         admonish (NULL,
1141                   "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
1142                   ci->ci_type, ci->ci_subtype, ct->c_file);
1143         return NOTOK;
1144     }
1145
1146     /* match subtype */
1147     for (kv = SubMultiPart; kv->kv_key; kv++)
1148         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1149             break;
1150     ct->c_subtype = kv->kv_value;
1151
1152     /*
1153      * Check for "boundary" parameter, which is
1154      * required for multipart messages.
1155      */
1156     bp = 0;
1157     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1158         if (!mh_strcasecmp (*ap, "boundary")) {
1159             bp = *ep;
1160             break;
1161         }
1162     }
1163
1164     /* complain if boundary parameter is missing */
1165     if (!*ap) {
1166         advise (NULL,
1167                 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1168                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1169         return NOTOK;
1170     }
1171
1172     /* allocate primary structure for multipart info */
1173     if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
1174         adios (NULL, "out of memory");
1175     ct->c_ctparams = (void *) m;
1176
1177     /* check if boundary parameter contains only whitespace characters */
1178     for (cp = bp; isspace (*cp); cp++)
1179         continue;
1180     if (!*cp) {
1181         advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1182                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1183         return NOTOK;
1184     }
1185
1186     /* remove trailing whitespace from boundary parameter */
1187     for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1188         if (!isspace (*dp))
1189             break;
1190     *++dp = '\0';
1191
1192     /* record boundary separators */
1193     m->mp_start = concat (bp, "\n", NULL);
1194     m->mp_stop = concat (bp, "--\n", NULL);
1195
1196     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1197         advise (ct->c_file, "unable to open for reading");
1198         return NOTOK;
1199     }
1200
1201     fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1202     last = ct->c_end;
1203     next = &m->mp_parts;
1204     part = NULL;
1205     inout = 1;
1206
1207     while (fgets (buffer, sizeof(buffer) - 1, fp)) {
1208         if (pos > last)
1209             break;
1210
1211         pos += strlen (buffer);
1212         if (buffer[0] != '-' || buffer[1] != '-')
1213             continue;
1214         if (inout) {
1215             if (strcmp (buffer + 2, m->mp_start))
1216                 continue;
1217 next_part:
1218             if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1219                 adios (NULL, "out of memory");
1220             *next = part;
1221             next = &part->mp_next;
1222
1223             if (!(p = get_content (fp, ct->c_file,
1224                         ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1225                 ct->c_fp = NULL;
1226                 return NOTOK;
1227             }
1228             p->c_fp = NULL;
1229             part->mp_part = p;
1230             pos = p->c_begin;
1231             fseek (fp, pos, SEEK_SET);
1232             inout = 0;
1233         } else {
1234             if (strcmp (buffer + 2, m->mp_start) == 0) {
1235                 inout = 1;
1236 end_part:
1237                 p = part->mp_part;
1238                 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1239                 if (p->c_end < p->c_begin)
1240                     p->c_begin = p->c_end;
1241                 if (inout)
1242                     goto next_part;
1243                 goto last_part;
1244             } else {
1245                 if (strcmp (buffer + 2, m->mp_stop) == 0)
1246                     goto end_part;
1247             }
1248         }
1249     }
1250
1251     advise (NULL, "bogus multipart content in message %s", ct->c_file);
1252     if (!inout && part) {
1253         p = part->mp_part;
1254         p->c_end = ct->c_end;
1255
1256         if (p->c_begin >= p->c_end) {
1257             for (next = &m->mp_parts; *next != part;
1258                      next = &((*next)->mp_next))
1259                 continue;
1260             *next = NULL;
1261             free_content (p);
1262             free ((char *) part);
1263         }
1264     }
1265
1266 last_part:
1267     /* reverse the order of the parts for multipart/alternative */
1268     if (ct->c_subtype == MULTI_ALTERNATE)
1269         reverse_parts (ct);
1270
1271     /*
1272      * label all subparts with part number, and
1273      * then initialize the content of the subpart.
1274      */
1275     {
1276         int partnum;
1277         char *pp;
1278         char partnam[BUFSIZ];
1279
1280         if (ct->c_partno) {
1281             snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1282             pp = partnam + strlen (partnam);
1283         } else {
1284             pp = partnam;
1285         }
1286
1287         for (part = m->mp_parts, partnum = 1; part;
1288                  part = part->mp_next, partnum++) {
1289             p = part->mp_part;
1290
1291             sprintf (pp, "%d", partnum);
1292             p->c_partno = add (partnam, NULL);
1293
1294             /* initialize the content of the subparts */
1295             if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1296                 fclose (ct->c_fp);
1297                 ct->c_fp = NULL;
1298                 return NOTOK;
1299             }
1300         }
1301     }
1302
1303     fclose (ct->c_fp);
1304     ct->c_fp = NULL;
1305     return OK;
1306 }
1307
1308
1309 /*
1310  * reverse the order of the parts of a multipart
1311  */
1312
1313 static void
1314 reverse_parts (CT ct)
1315 {
1316     int i;
1317     struct multipart *m;
1318     struct part **base, **bmp, **next, *part;
1319
1320     m = (struct multipart *) ct->c_ctparams;
1321
1322     /* if only one part, just return */
1323     if (!m->mp_parts || !m->mp_parts->mp_next)
1324         return;
1325
1326     /* count number of parts */
1327     i = 0;
1328     for (part = m->mp_parts; part; part = part->mp_next)
1329         i++;
1330
1331     /* allocate array of pointers to the parts */
1332     if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base))))
1333         adios (NULL, "out of memory");
1334     bmp = base;
1335
1336     /* point at all the parts */
1337     for (part = m->mp_parts; part; part = part->mp_next)
1338         *bmp++ = part;
1339     *bmp = NULL;
1340
1341     /* reverse the order of the parts */
1342     next = &m->mp_parts;
1343     for (bmp--; bmp >= base; bmp--) {
1344         part = *bmp;
1345         *next = part;
1346         next = &part->mp_next;
1347     }
1348     *next = NULL;
1349
1350     /* free array of pointers */
1351     free ((char *) base);
1352 }
1353
1354
1355 /*
1356  * MESSAGE
1357  */
1358
1359 static int
1360 InitMessage (CT ct)
1361 {
1362     struct k2v *kv;
1363     CI ci = &ct->c_ctinfo;
1364
1365     if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1366         admonish (NULL,
1367                   "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1368                   ci->ci_type, ci->ci_subtype, ct->c_file);
1369         return NOTOK;
1370     }
1371
1372     /* check for missing subtype */
1373     if (!*ci->ci_subtype)
1374         ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1375
1376     /* match subtype */
1377     for (kv = SubMessage; kv->kv_key; kv++)
1378         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1379             break;
1380     ct->c_subtype = kv->kv_value;
1381
1382     switch (ct->c_subtype) {
1383         case MESSAGE_RFC822:
1384             break;
1385
1386         case MESSAGE_PARTIAL:
1387             {
1388                 char **ap, **ep;
1389                 struct partial *p;
1390
1391                 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1392                     adios (NULL, "out of memory");
1393                 ct->c_ctparams = (void *) p;
1394
1395                 /* scan for parameters "id", "number", and "total" */
1396                 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1397                     if (!mh_strcasecmp (*ap, "id")) {
1398                         p->pm_partid = add (*ep, NULL);
1399                         continue;
1400                     }
1401                     if (!mh_strcasecmp (*ap, "number")) {
1402                         if (sscanf (*ep, "%d", &p->pm_partno) != 1
1403                                 || p->pm_partno < 1) {
1404 invalid_param:
1405                             advise (NULL,
1406                                     "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1407                                     *ap, ci->ci_type, ci->ci_subtype,
1408                                     ct->c_file, TYPE_FIELD);
1409                             return NOTOK;
1410                         }
1411                         continue;
1412                     }
1413                     if (!mh_strcasecmp (*ap, "total")) {
1414                         if (sscanf (*ep, "%d", &p->pm_maxno) != 1
1415                                 || p->pm_maxno < 1)
1416                             goto invalid_param;
1417                         continue;
1418                     }
1419                 }
1420
1421                 if (!p->pm_partid
1422                         || !p->pm_partno
1423                         || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1424                     advise (NULL,
1425                             "invalid parameters for \"%s/%s\" type in message %s's %s field",
1426                             ci->ci_type, ci->ci_subtype,
1427                             ct->c_file, TYPE_FIELD);
1428                     return NOTOK;
1429                 }
1430             }
1431             break;
1432
1433         case MESSAGE_EXTERNAL:
1434             {
1435                 int exresult;
1436                 struct exbody *e;
1437                 CT p;
1438                 FILE *fp;
1439
1440                 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1441                     adios (NULL, "out of memory");
1442                 ct->c_ctparams = (void *) e;
1443
1444                 if (!ct->c_fp
1445                         && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1446                     advise (ct->c_file, "unable to open for reading");
1447                     return NOTOK;
1448                 }
1449
1450                 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1451
1452                 if (!(p = get_content (fp, ct->c_file, 0))) {
1453                     ct->c_fp = NULL;
1454                     return NOTOK;
1455                 }
1456
1457                 e->eb_parent = ct;
1458                 e->eb_content = p;
1459                 p->c_ctexbody = e;
1460                 if ((exresult = params_external (ct, 0)) != NOTOK
1461                         && p->c_ceopenfnx == openMail) {
1462                     int cc, size;
1463                     char *bp;
1464                     
1465                     if ((size = ct->c_end - p->c_begin) <= 0) {
1466                         if (!e->eb_subject)
1467                             content_error (NULL, ct,
1468                                            "empty body for access-type=mail-server");
1469                         goto no_body;
1470                     }
1471                     
1472                     e->eb_body = bp = mh_xmalloc ((unsigned) size);
1473                     fseek (p->c_fp, p->c_begin, SEEK_SET);
1474                     while (size > 0)
1475                         switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1476                             case NOTOK:
1477                                 adios ("failed", "fread");
1478
1479                             case OK:
1480                                 adios (NULL, "unexpected EOF from fread");
1481
1482                             default:
1483                                 bp += cc, size -= cc;
1484                                 break;
1485                         }
1486                     *bp = 0;
1487                 }
1488 no_body:
1489                 p->c_fp = NULL;
1490                 p->c_end = p->c_begin;
1491
1492                 fclose (ct->c_fp);
1493                 ct->c_fp = NULL;
1494
1495                 if (exresult == NOTOK)
1496                     return NOTOK;
1497                 if (e->eb_flags == NOTOK)
1498                     return OK;
1499
1500                 switch (p->c_type) {
1501                     case CT_MULTIPART:
1502                         break;
1503
1504                     case CT_MESSAGE:
1505                         if (p->c_subtype != MESSAGE_RFC822)
1506                             break;
1507                         /* else fall... */
1508                     default:
1509                         e->eb_partno = ct->c_partno;
1510                         if (p->c_ctinitfnx)
1511                             (*p->c_ctinitfnx) (p);
1512                         break;
1513                 }
1514             }
1515             break;
1516
1517         default:
1518             break;
1519     }
1520
1521     return OK;
1522 }
1523
1524
1525 int
1526 params_external (CT ct, int composing)
1527 {
1528     char **ap, **ep;
1529     struct exbody *e = (struct exbody *) ct->c_ctparams;
1530     CI ci = &ct->c_ctinfo;
1531
1532     for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1533         if (!mh_strcasecmp (*ap, "access-type")) {
1534             struct str2init *s2i;
1535             CT p = e->eb_content;
1536
1537             for (s2i = str2methods; s2i->si_key; s2i++)
1538                 if (!mh_strcasecmp (*ep, s2i->si_key))
1539                     break;
1540             if (!s2i->si_key) {
1541                 e->eb_access = *ep;
1542                 e->eb_flags = NOTOK;
1543                 p->c_encoding = CE_EXTERNAL;
1544                 continue;
1545             }
1546             e->eb_access = s2i->si_key;
1547             e->eb_flags = s2i->si_val;
1548             p->c_encoding = CE_EXTERNAL;
1549
1550             /* Call the Init function for this external type */
1551             if ((*s2i->si_init)(p) == NOTOK)
1552                 return NOTOK;
1553             continue;
1554         }
1555         if (!mh_strcasecmp (*ap, "name")) {
1556             e->eb_name = *ep;
1557             continue;
1558         }
1559         if (!mh_strcasecmp (*ap, "permission")) {
1560             e->eb_permission = *ep;
1561             continue;
1562         }
1563         if (!mh_strcasecmp (*ap, "site")) {
1564             e->eb_site = *ep;
1565             continue;
1566         }
1567         if (!mh_strcasecmp (*ap, "directory")) {
1568             e->eb_dir = *ep;
1569             continue;
1570         }
1571         if (!mh_strcasecmp (*ap, "mode")) {
1572             e->eb_mode = *ep;
1573             continue;
1574         }
1575         if (!mh_strcasecmp (*ap, "size")) {
1576             sscanf (*ep, "%lu", &e->eb_size);
1577             continue;
1578         }
1579         if (!mh_strcasecmp (*ap, "server")) {
1580             e->eb_server = *ep;
1581             continue;
1582         }
1583         if (!mh_strcasecmp (*ap, "subject")) {
1584             e->eb_subject = *ep;
1585             continue;
1586         }
1587         if (composing && !mh_strcasecmp (*ap, "body")) {
1588             e->eb_body = getcpy (*ep);
1589             continue;
1590         }
1591     }
1592
1593     if (!e->eb_access) {
1594         advise (NULL,
1595                 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1596                 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1597         return NOTOK;
1598     }
1599
1600     return OK;
1601 }
1602
1603
1604 /*
1605  * APPLICATION
1606  */
1607
1608 static int
1609 InitApplication (CT ct)
1610 {
1611     struct k2v *kv;
1612     CI ci = &ct->c_ctinfo;
1613
1614     /* match subtype */
1615     for (kv = SubApplication; kv->kv_key; kv++)
1616         if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1617             break;
1618     ct->c_subtype = kv->kv_value;
1619
1620     return OK;
1621 }
1622
1623
1624 /*
1625  * TRANSFER ENCODINGS
1626  */
1627
1628 static int
1629 init_encoding (CT ct, OpenCEFunc openfnx)
1630 {
1631     CE ce;
1632
1633     if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1634         adios (NULL, "out of memory");
1635
1636     ct->c_cefile     = ce;
1637     ct->c_ceopenfnx  = openfnx;
1638     ct->c_ceclosefnx = close_encoding;
1639     ct->c_cesizefnx  = size_encoding;
1640
1641     return OK;
1642 }
1643
1644
1645 void
1646 close_encoding (CT ct)
1647 {
1648     CE ce;
1649
1650     if (!(ce = ct->c_cefile))
1651         return;
1652
1653     if (ce->ce_fp) {
1654         fclose (ce->ce_fp);
1655         ce->ce_fp = NULL;
1656     }
1657 }
1658
1659
1660 static unsigned long
1661 size_encoding (CT ct)
1662 {
1663     int fd;
1664     unsigned long size;
1665     char *file;
1666     CE ce;
1667     struct stat st;
1668
1669     if (!(ce = ct->c_cefile))
1670         return (ct->c_end - ct->c_begin);
1671
1672     if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1673         return (long) st.st_size;
1674
1675     if (ce->ce_file) {
1676         if (stat (ce->ce_file, &st) != NOTOK)
1677             return (long) st.st_size;
1678         else
1679             return 0L;
1680     }
1681
1682     if (ct->c_encoding == CE_EXTERNAL)
1683         return (ct->c_end - ct->c_begin);       
1684
1685     file = NULL;
1686     if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1687         return (ct->c_end - ct->c_begin);
1688
1689     if (fstat (fd, &st) != NOTOK)
1690         size = (long) st.st_size;
1691     else
1692         size = 0L;
1693
1694     (*ct->c_ceclosefnx) (ct);
1695     return size;
1696 }
1697
1698
1699 /*
1700  * BASE64
1701  */
1702
1703 static unsigned char b642nib[0x80] = {
1704     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1705     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1706     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1707     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1708     0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1709     0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1710     0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1711     0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1712     0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 
1713     0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1714     0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1715     0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1716     0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 
1717     0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1718     0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1719     0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1720 };
1721
1722
1723 static int
1724 InitBase64 (CT ct)
1725 {
1726     return init_encoding (ct, openBase64);
1727 }
1728
1729
1730 static int
1731 openBase64 (CT ct, char **file)
1732 {
1733     int bitno, cc, digested;
1734     int fd, len, skip;
1735     unsigned long bits;
1736     unsigned char value, *b, *b1, *b2, *b3;
1737     unsigned char *cp, *ep;
1738     char buffer[BUFSIZ];
1739     /* sbeck -- handle suffixes */
1740     CI ci;
1741     CE ce;
1742     MD5_CTX mdContext;
1743
1744     b  = (unsigned char *) &bits;
1745     b1 = &b[endian > 0 ? 1 : 2];
1746     b2 = &b[endian > 0 ? 2 : 1];
1747     b3 = &b[endian > 0 ? 3 : 0];
1748
1749     ce = ct->c_cefile;
1750     if (ce->ce_fp) {
1751         fseek (ce->ce_fp, 0L, SEEK_SET);
1752         goto ready_to_go;
1753     }
1754
1755     if (ce->ce_file) {
1756         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1757             content_error (ce->ce_file, ct, "unable to fopen for reading");
1758             return NOTOK;
1759         }
1760         goto ready_to_go;
1761     }
1762
1763     if (*file == NULL) {
1764         ce->ce_file = add (m_scratch ("", tmp), NULL);
1765         ce->ce_unlink = 1;
1766     } else {
1767         ce->ce_file = add (*file, NULL);
1768         ce->ce_unlink = 0;
1769     }
1770
1771     /* sbeck@cise.ufl.edu -- handle suffixes */
1772     ci = &ct->c_ctinfo;
1773     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1774               invo_name, ci->ci_type, ci->ci_subtype);
1775     cp = context_find (buffer);
1776     if (cp == NULL || *cp == '\0') {
1777         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1778                   ci->ci_type);
1779         cp = context_find (buffer);
1780     }
1781     if (cp != NULL && *cp != '\0')
1782         ce->ce_file = add (cp, ce->ce_file);
1783
1784     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1785         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1786         return NOTOK;
1787     }
1788
1789     if ((len = ct->c_end - ct->c_begin) < 0)
1790         adios (NULL, "internal error(1)");
1791
1792     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1793         content_error (ct->c_file, ct, "unable to open for reading");
1794         return NOTOK;
1795     }
1796     
1797     if ((digested = ct->c_digested))
1798         MD5Init (&mdContext);
1799
1800     bitno = 18;
1801     bits = 0L;
1802     skip = 0;
1803
1804     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1805     while (len > 0) {
1806         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1807         case NOTOK:
1808             content_error (ct->c_file, ct, "error reading from");
1809             goto clean_up;
1810
1811         case OK:
1812             content_error (NULL, ct, "premature eof");
1813             goto clean_up;
1814
1815         default:
1816             if (cc > len)
1817                 cc = len;
1818             len -= cc;
1819
1820             for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1821                 switch (*cp) {
1822                 default:
1823                     if (isspace (*cp))
1824                         break;
1825                     if (skip || (*cp & 0x80)
1826                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1827                         if (debugsw) {
1828                             fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1829                                 *cp,
1830                                 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1831                                 skip);
1832                         }
1833                         content_error (NULL, ct,
1834                                        "invalid BASE64 encoding -- continuing");
1835                         continue;
1836                     }
1837
1838                     bits |= value << bitno;
1839 test_end:
1840                     if ((bitno -= 6) < 0) {
1841                         putc ((char) *b1, ce->ce_fp);
1842                         if (digested)
1843                             MD5Update (&mdContext, b1, 1);
1844                         if (skip < 2) {
1845                             putc ((char) *b2, ce->ce_fp);
1846                             if (digested)
1847                                 MD5Update (&mdContext, b2, 1);
1848                             if (skip < 1) {
1849                                 putc ((char) *b3, ce->ce_fp);
1850                                 if (digested)
1851                                     MD5Update (&mdContext, b3, 1);
1852                             }
1853                         }
1854
1855                         if (ferror (ce->ce_fp)) {
1856                             content_error (ce->ce_file, ct,
1857                                            "error writing to");
1858                             goto clean_up;
1859                         }
1860                         bitno = 18, bits = 0L, skip = 0;
1861                     }
1862                     break;
1863
1864                 case '=':
1865                     if (++skip > 3)
1866                         goto self_delimiting;
1867                     goto test_end;
1868                 }
1869             }
1870         }
1871     }
1872
1873     if (bitno != 18) {
1874         if (debugsw)
1875             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1876
1877         content_error (NULL, ct, "invalid BASE64 encoding");
1878         goto clean_up;
1879     }
1880
1881 self_delimiting:
1882     fseek (ct->c_fp, 0L, SEEK_SET);
1883
1884     if (fflush (ce->ce_fp)) {
1885         content_error (ce->ce_file, ct, "error writing to");
1886         goto clean_up;
1887     }
1888
1889     if (digested) {
1890         unsigned char digest[16];
1891
1892         MD5Final (digest, &mdContext);
1893         if (memcmp((char *) digest, (char *) ct->c_digest,
1894                    sizeof(digest) / sizeof(digest[0])))
1895             content_error (NULL, ct,
1896                            "content integrity suspect (digest mismatch) -- continuing");
1897         else
1898             if (debugsw)
1899                 fprintf (stderr, "content integrity confirmed\n");
1900     }
1901
1902     fseek (ce->ce_fp, 0L, SEEK_SET);
1903
1904 ready_to_go:
1905     *file = ce->ce_file;
1906     return fileno (ce->ce_fp);
1907
1908 clean_up:
1909     free_encoding (ct, 0);
1910     return NOTOK;
1911 }
1912
1913
1914 /*
1915  * QUOTED PRINTABLE
1916  */
1917
1918 static char hex2nib[0x80] = {
1919     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1920     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1921     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1922     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1923     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1924     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1925     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1926     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1927     0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
1928     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1929     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1930     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1931     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
1932     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1933     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1934     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1935 };
1936
1937
1938 static int 
1939 InitQuoted (CT ct)
1940 {
1941     return init_encoding (ct, openQuoted);
1942 }
1943
1944
1945 static int
1946 openQuoted (CT ct, char **file)
1947 {
1948     int cc, digested, len, quoted;
1949     unsigned char *cp, *ep;
1950     char buffer[BUFSIZ];
1951     unsigned char mask;
1952     CE ce;
1953     /* sbeck -- handle suffixes */
1954     CI ci;
1955     MD5_CTX mdContext;
1956
1957     ce = ct->c_cefile;
1958     if (ce->ce_fp) {
1959         fseek (ce->ce_fp, 0L, SEEK_SET);
1960         goto ready_to_go;
1961     }
1962
1963     if (ce->ce_file) {
1964         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1965             content_error (ce->ce_file, ct, "unable to fopen for reading");
1966             return NOTOK;
1967         }
1968         goto ready_to_go;
1969     }
1970
1971     if (*file == NULL) {
1972         ce->ce_file = add (m_scratch ("", tmp), NULL);
1973         ce->ce_unlink = 1;
1974     } else {
1975         ce->ce_file = add (*file, NULL);
1976         ce->ce_unlink = 0;
1977     }
1978
1979     /* sbeck@cise.ufl.edu -- handle suffixes */
1980     ci = &ct->c_ctinfo;
1981     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1982               invo_name, ci->ci_type, ci->ci_subtype);
1983     cp = context_find (buffer);
1984     if (cp == NULL || *cp == '\0') {
1985         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1986                   ci->ci_type);
1987         cp = context_find (buffer);
1988     }
1989     if (cp != NULL && *cp != '\0')
1990         ce->ce_file = add (cp, ce->ce_file);
1991
1992     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1993         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1994         return NOTOK;
1995     }
1996
1997     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1998         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1999         return NOTOK;
2000     }
2001
2002     if ((len = ct->c_end - ct->c_begin) < 0)
2003         adios (NULL, "internal error(2)");
2004
2005     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2006         content_error (ct->c_file, ct, "unable to open for reading");
2007         return NOTOK;
2008     }
2009
2010     if ((digested = ct->c_digested))
2011         MD5Init (&mdContext);
2012
2013     quoted = 0;
2014 #ifdef lint
2015     mask = 0;
2016 #endif
2017
2018     fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2019     while (len > 0) {
2020         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2021             content_error (NULL, ct, "premature eof");
2022             goto clean_up;
2023         }
2024
2025         if ((cc = strlen (buffer)) > len)
2026             cc = len;
2027         len -= cc;
2028
2029         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2030             if (!isspace (*ep))
2031                 break;
2032         *++ep = '\n', ep++;
2033
2034         for (; cp < ep; cp++) {
2035             if (quoted > 0) {
2036                 /* in an escape sequence */
2037                 if (quoted == 1) {
2038                     /* at byte 1 of an escape sequence */
2039                     mask = hex2nib[*cp & 0x7f];
2040                     /* next is byte 2 */
2041                     quoted = 2;
2042                 } else {
2043                     /* at byte 2 of an escape sequence */
2044                     mask <<= 4;
2045                     mask |= hex2nib[*cp & 0x7f];
2046                     putc (mask, ce->ce_fp);
2047                     if (digested)
2048                         MD5Update (&mdContext, &mask, 1);
2049                     if (ferror (ce->ce_fp)) {
2050                         content_error (ce->ce_file, ct, "error writing to");
2051                         goto clean_up;
2052                     }
2053                     /* finished escape sequence; next may be literal or a new
2054                      * escape sequence */
2055                     quoted = 0;
2056                 }
2057                 /* on to next byte */
2058                 continue;
2059             }
2060
2061             /* not in an escape sequence */
2062             if (*cp == '=') {
2063                 /* starting an escape sequence, or invalid '='? */
2064                 if (cp + 1 < ep && cp[1] == '\n') {
2065                     /* "=\n" soft line break, eat the \n */
2066                     cp++;
2067                     continue;
2068                 }
2069                 if (cp + 1 >= ep || cp + 2 >= ep) {
2070                     /* We don't have 2 bytes left, so this is an invalid
2071                      * escape sequence; just show the raw bytes (below). */
2072                 } else if (isxdigit (cp[1]) && isxdigit (cp[2])) {
2073                     /* Next 2 bytes are hex digits, making this a valid escape
2074                      * sequence; let's decode it (above). */
2075                     quoted = 1;
2076                     continue;
2077                 } else {
2078                     /* One or both of the next 2 is out of range, making this
2079                      * an invalid escape sequence; just show the raw bytes
2080                      * (below). */
2081                 }
2082             }
2083
2084             /* Just show the raw byte. */
2085             putc (*cp, ce->ce_fp);
2086             if (digested) {
2087                 if (*cp == '\n') {
2088                     MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2089                 } else {
2090                     MD5Update (&mdContext, (unsigned char *) cp, 1);
2091                 }
2092             }
2093             if (ferror (ce->ce_fp)) {
2094                 content_error (ce->ce_file, ct, "error writing to");
2095                 goto clean_up;
2096             }
2097         }
2098     }
2099     if (quoted) {
2100         content_error (NULL, ct,
2101                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2102         goto clean_up;
2103     }
2104
2105     fseek (ct->c_fp, 0L, SEEK_SET);
2106
2107     if (fflush (ce->ce_fp)) {
2108         content_error (ce->ce_file, ct, "error writing to");
2109         goto clean_up;
2110     }
2111
2112     if (digested) {
2113         unsigned char digest[16];
2114
2115         MD5Final (digest, &mdContext);
2116         if (memcmp((char *) digest, (char *) ct->c_digest,
2117                    sizeof(digest) / sizeof(digest[0])))
2118             content_error (NULL, ct,
2119                            "content integrity suspect (digest mismatch) -- continuing");
2120         else
2121             if (debugsw)
2122                 fprintf (stderr, "content integrity confirmed\n");
2123     }
2124
2125     fseek (ce->ce_fp, 0L, SEEK_SET);
2126
2127 ready_to_go:
2128     *file = ce->ce_file;
2129     return fileno (ce->ce_fp);
2130
2131 clean_up:
2132     free_encoding (ct, 0);
2133     return NOTOK;
2134 }
2135
2136
2137 /*
2138  * 7BIT
2139  */
2140
2141 static int
2142 Init7Bit (CT ct)
2143 {
2144     if (init_encoding (ct, open7Bit) == NOTOK)
2145         return NOTOK;
2146
2147     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
2148     return OK;
2149 }
2150
2151
2152 int
2153 open7Bit (CT ct, char **file)
2154 {
2155     int cc, fd, len;
2156     char buffer[BUFSIZ];
2157     /* sbeck -- handle suffixes */
2158     char *cp;
2159     CI ci;
2160     CE ce;
2161
2162     ce = ct->c_cefile;
2163     if (ce->ce_fp) {
2164         fseek (ce->ce_fp, 0L, SEEK_SET);
2165         goto ready_to_go;
2166     }
2167
2168     if (ce->ce_file) {
2169         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2170             content_error (ce->ce_file, ct, "unable to fopen for reading");
2171             return NOTOK;
2172         }
2173         goto ready_to_go;
2174     }
2175
2176     if (*file == NULL) {
2177         ce->ce_file = add (m_scratch ("", tmp), NULL);
2178         ce->ce_unlink = 1;
2179     } else {
2180         ce->ce_file = add (*file, NULL);
2181         ce->ce_unlink = 0;
2182     }
2183
2184     /* sbeck@cise.ufl.edu -- handle suffixes */
2185     ci = &ct->c_ctinfo;
2186     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2187               invo_name, ci->ci_type, ci->ci_subtype);
2188     cp = context_find (buffer);
2189     if (cp == NULL || *cp == '\0') {
2190         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2191                   ci->ci_type);
2192         cp = context_find (buffer);
2193     }
2194     if (cp != NULL && *cp != '\0')
2195         ce->ce_file = add (cp, ce->ce_file);
2196
2197     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2198         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2199         return NOTOK;
2200     }
2201
2202     if (ct->c_type == CT_MULTIPART) {
2203         char **ap, **ep;
2204         CI ci = &ct->c_ctinfo;
2205
2206         len = 0;
2207         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2208         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2209             + 1 + strlen (ci->ci_subtype);
2210         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2211             putc (';', ce->ce_fp);
2212             len++;
2213
2214             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2215
2216             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2217                 fputs ("\n\t", ce->ce_fp);
2218                 len = 8;
2219             } else {
2220                 putc (' ', ce->ce_fp);
2221                 len++;
2222             }
2223             fprintf (ce->ce_fp, "%s", buffer);
2224             len += cc;
2225         }
2226
2227         if (ci->ci_comment) {
2228             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2229                 fputs ("\n\t", ce->ce_fp);
2230                 len = 8;
2231             }
2232             else {
2233                 putc (' ', ce->ce_fp);
2234                 len++;
2235             }
2236             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2237             len += cc;
2238         }
2239         fprintf (ce->ce_fp, "\n");
2240         if (ct->c_id)
2241             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2242         if (ct->c_descr)
2243             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2244         if (ct->c_dispo)
2245             fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2246         fprintf (ce->ce_fp, "\n");
2247     }
2248
2249     if ((len = ct->c_end - ct->c_begin) < 0)
2250         adios (NULL, "internal error(3)");
2251
2252     if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2253         content_error (ct->c_file, ct, "unable to open for reading");
2254         return NOTOK;
2255     }
2256
2257     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2258     while (len > 0)
2259         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2260         case NOTOK:
2261             content_error (ct->c_file, ct, "error reading from");
2262             goto clean_up;
2263
2264         case OK:
2265             content_error (NULL, ct, "premature eof");
2266             goto clean_up;
2267
2268         default:
2269             if (cc > len)
2270                 cc = len;
2271             len -= cc;
2272
2273             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2274             if (ferror (ce->ce_fp)) {
2275                 content_error (ce->ce_file, ct, "error writing to");
2276                 goto clean_up;
2277             }
2278         }
2279
2280     fseek (ct->c_fp, 0L, SEEK_SET);
2281
2282     if (fflush (ce->ce_fp)) {
2283         content_error (ce->ce_file, ct, "error writing to");
2284         goto clean_up;
2285     }
2286
2287     fseek (ce->ce_fp, 0L, SEEK_SET);
2288
2289 ready_to_go:
2290     *file = ce->ce_file;
2291     return fileno (ce->ce_fp);
2292
2293 clean_up:
2294     free_encoding (ct, 0);
2295     return NOTOK;
2296 }
2297
2298
2299 /*
2300  * External
2301  */
2302
2303 static int
2304 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2305 {
2306     char cachefile[BUFSIZ];
2307
2308     if (ce->ce_fp) {
2309         fseek (ce->ce_fp, 0L, SEEK_SET);
2310         goto ready_already;
2311     }
2312
2313     if (ce->ce_file) {
2314         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2315             content_error (ce->ce_file, ct, "unable to fopen for reading");
2316             return NOTOK;
2317         }
2318         goto ready_already;
2319     }
2320
2321     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2322                 cachefile, sizeof(cachefile)) != NOTOK) {
2323         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2324             ce->ce_file = getcpy (cachefile);
2325             ce->ce_unlink = 0;
2326             goto ready_already;
2327         } else {
2328             admonish (cachefile, "unable to fopen for reading");
2329         }
2330     }
2331
2332     return OK;
2333
2334 ready_already:
2335     *file = ce->ce_file;
2336     *fd = fileno (ce->ce_fp);
2337     return DONE;
2338 }
2339
2340 /*
2341  * File
2342  */
2343
2344 static int
2345 InitFile (CT ct)
2346 {
2347     return init_encoding (ct, openFile);
2348 }
2349
2350
2351 static int
2352 openFile (CT ct, char **file)
2353 {
2354     int fd, cachetype;
2355     char cachefile[BUFSIZ];
2356     struct exbody *e = ct->c_ctexbody;
2357     CE ce = ct->c_cefile;
2358
2359     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2360         case NOTOK:
2361             return NOTOK;
2362
2363         case OK:
2364             break;
2365
2366         case DONE:
2367             return fd;
2368     }
2369
2370     if (!e->eb_name) {
2371         content_error (NULL, ct, "missing name parameter");
2372         return NOTOK;
2373     }
2374
2375     ce->ce_file = getcpy (e->eb_name);
2376     ce->ce_unlink = 0;
2377
2378     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2379         content_error (ce->ce_file, ct, "unable to fopen for reading");
2380         return NOTOK;
2381     }
2382
2383     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2384             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2385                 cachefile, sizeof(cachefile)) != NOTOK) {
2386         int mask;
2387         FILE *fp;
2388
2389         mask = umask (cachetype ? ~m_gmprot () : 0222);
2390         if ((fp = fopen (cachefile, "w"))) {
2391             int cc;
2392             char buffer[BUFSIZ];
2393             FILE *gp = ce->ce_fp;
2394
2395             fseek (gp, 0L, SEEK_SET);
2396
2397             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2398                        > 0)
2399                 fwrite (buffer, sizeof(*buffer), cc, fp);
2400             fflush (fp);
2401
2402             if (ferror (gp)) {
2403                 admonish (ce->ce_file, "error reading");
2404                 unlink (cachefile);
2405             }
2406             else
2407                 if (ferror (fp)) {
2408                     admonish (cachefile, "error writing");
2409                     unlink (cachefile);
2410                 }
2411             fclose (fp);
2412         }
2413         umask (mask);
2414     }
2415
2416     fseek (ce->ce_fp, 0L, SEEK_SET);
2417     *file = ce->ce_file;
2418     return fileno (ce->ce_fp);
2419 }
2420
2421 /*
2422  * FTP
2423  */
2424
2425 static int
2426 InitFTP (CT ct)
2427 {
2428     return init_encoding (ct, openFTP);
2429 }
2430
2431
2432 static int
2433 openFTP (CT ct, char **file)
2434 {
2435     int cachetype, caching, fd;
2436     int len, buflen;
2437     char *bp, *ftp, *user, *pass;
2438     char buffer[BUFSIZ], cachefile[BUFSIZ];
2439     struct exbody *e;
2440     CE ce;
2441     static char *username = NULL;
2442     static char *password = NULL;
2443
2444     e  = ct->c_ctexbody;
2445     ce = ct->c_cefile;
2446
2447     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2448         ftp = NULL;
2449
2450 #ifndef BUILTIN_FTP
2451     if (!ftp)
2452         return NOTOK;
2453 #endif
2454
2455     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2456         case NOTOK:
2457             return NOTOK;
2458
2459         case OK:
2460             break;
2461
2462         case DONE:
2463             return fd;
2464     }
2465
2466     if (!e->eb_name || !e->eb_site) {
2467         content_error (NULL, ct, "missing %s parameter",
2468                        e->eb_name ? "site": "name");
2469         return NOTOK;
2470     }
2471
2472     if (xpid) {
2473         if (xpid < 0)
2474             xpid = -xpid;
2475         pidcheck (pidwait (xpid, NOTOK));
2476         xpid = 0;
2477     }
2478
2479     /* Get the buffer ready to go */
2480     bp = buffer;
2481     buflen = sizeof(buffer);
2482
2483     /*
2484      * Construct the query message for user
2485      */
2486     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2487     len = strlen (bp);
2488     bp += len;
2489     buflen -= len;
2490
2491     if (e->eb_partno) {
2492         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2493         len = strlen (bp);
2494         bp += len;
2495         buflen -= len;
2496     }
2497
2498     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2499                     e->eb_flags ? "anonymous " : "", e->eb_site);
2500     len = strlen (bp);
2501     bp += len;
2502     buflen -= len;
2503
2504     if (e->eb_size > 0) {
2505         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2506         len = strlen (bp);
2507         bp += len;
2508         buflen -= len;
2509     }
2510     snprintf (bp, buflen, "? ");
2511
2512     /*
2513      * Now, check the answer
2514      */
2515     if (!getanswer (buffer))
2516         return NOTOK;
2517
2518     if (e->eb_flags) {
2519         user = "anonymous";
2520         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ());
2521         pass = buffer;
2522     } else {
2523         ruserpass (e->eb_site, &username, &password);
2524         user = username;
2525         pass = password;
2526     }
2527
2528     ce->ce_unlink = (*file == NULL);
2529     caching = 0;
2530     cachefile[0] = '\0';
2531     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2532             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2533                 cachefile, sizeof(cachefile)) != NOTOK) {
2534         if (*file == NULL) {
2535             ce->ce_unlink = 0;
2536             caching = 1;
2537         }
2538     }
2539
2540     if (*file)
2541         ce->ce_file = add (*file, NULL);
2542     else if (caching)
2543         ce->ce_file = add (cachefile, NULL);
2544     else
2545         ce->ce_file = add (m_scratch ("", tmp), NULL);
2546
2547     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2548         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2549         return NOTOK;
2550     }
2551
2552 #ifdef BUILTIN_FTP
2553     if (ftp)
2554 #endif
2555     {
2556         int child_id, i, vecp;
2557         char *vec[9];
2558
2559         vecp = 0;
2560         vec[vecp++] = r1bindex (ftp, '/');
2561         vec[vecp++] = e->eb_site;
2562         vec[vecp++] = user;
2563         vec[vecp++] = pass;
2564         vec[vecp++] = e->eb_dir;
2565         vec[vecp++] = e->eb_name;
2566         vec[vecp++] = ce->ce_file,
2567         vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
2568                         ? "ascii" : "binary";
2569         vec[vecp] = NULL;
2570
2571         fflush (stdout);
2572
2573         for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2574             sleep (5);
2575         switch (child_id) {
2576             case NOTOK:
2577                 adios ("fork", "unable to");
2578                 /* NOTREACHED */
2579
2580             case OK:
2581                 close (fileno (ce->ce_fp));
2582                 execvp (ftp, vec);
2583                 fprintf (stderr, "unable to exec ");
2584                 perror (ftp);
2585                 _exit (-1);
2586                 /* NOTREACHED */
2587
2588             default:
2589                 if (pidXwait (child_id, NULL)) {
2590 #ifdef BUILTIN_FTP
2591 losing_ftp:
2592 #endif
2593                     username = password = NULL;
2594                     ce->ce_unlink = 1;
2595                     return NOTOK;
2596                 }
2597                 break;
2598         }
2599     }
2600 #ifdef BUILTIN_FTP
2601     else
2602         if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name,
2603                      ce->ce_file,
2604                      e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii"), 0)
2605                 == NOTOK)
2606             goto losing_ftp;
2607 #endif
2608
2609     if (cachefile[0]) {
2610         if (caching)
2611             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2612         else {
2613             int mask;
2614             FILE *fp;
2615
2616             mask = umask (cachetype ? ~m_gmprot () : 0222);
2617             if ((fp = fopen (cachefile, "w"))) {
2618                 int cc;
2619                 FILE *gp = ce->ce_fp;
2620
2621                 fseek (gp, 0L, SEEK_SET);
2622
2623                 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2624                            > 0)
2625                     fwrite (buffer, sizeof(*buffer), cc, fp);
2626                 fflush (fp);
2627
2628                 if (ferror (gp)) {
2629                     admonish (ce->ce_file, "error reading");
2630                     unlink (cachefile);
2631                 }
2632                 else
2633                     if (ferror (fp)) {
2634                         admonish (cachefile, "error writing");
2635                         unlink (cachefile);
2636                     }
2637                 fclose (fp);
2638             }
2639             umask (mask);
2640         }
2641     }
2642
2643     fseek (ce->ce_fp, 0L, SEEK_SET);
2644     *file = ce->ce_file;
2645     return fileno (ce->ce_fp);
2646 }
2647
2648
2649 /*
2650  * Mail
2651  */
2652
2653 static int
2654 InitMail (CT ct)
2655 {
2656     return init_encoding (ct, openMail);
2657 }
2658
2659
2660 static int
2661 openMail (CT ct, char **file)
2662 {
2663     int child_id, fd, i, vecp;
2664     int len, buflen;
2665     char *bp, buffer[BUFSIZ], *vec[7];
2666     struct exbody *e = ct->c_ctexbody;
2667     CE ce = ct->c_cefile;
2668
2669     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2670         case NOTOK:
2671             return NOTOK;
2672
2673         case OK:
2674             break;
2675
2676         case DONE:
2677             return fd;
2678     }
2679
2680     if (!e->eb_server) {
2681         content_error (NULL, ct, "missing server parameter");
2682         return NOTOK;
2683     }
2684
2685     if (xpid) {
2686         if (xpid < 0)
2687             xpid = -xpid;
2688         pidcheck (pidwait (xpid, NOTOK));
2689         xpid = 0;
2690     }
2691
2692     /* Get buffer ready to go */
2693     bp = buffer;
2694     buflen = sizeof(buffer);
2695
2696     /* Now, construct query message */
2697     snprintf (bp, buflen, "Retrieve content");
2698     len = strlen (bp);
2699     bp += len;
2700     buflen -= len;
2701
2702     if (e->eb_partno) {
2703         snprintf (bp, buflen, " %s", e->eb_partno);
2704         len = strlen (bp);
2705         bp += len;
2706         buflen -= len;
2707     }
2708
2709     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2710                     e->eb_server,
2711                     e->eb_subject ? e->eb_subject : e->eb_body);
2712
2713     /* Now, check answer */
2714     if (!getanswer (buffer))
2715         return NOTOK;
2716
2717     vecp = 0;
2718     vec[vecp++] = r1bindex (mailproc, '/');
2719     vec[vecp++] = e->eb_server;
2720     vec[vecp++] = "-subject";
2721     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2722     vec[vecp++] = "-body";
2723     vec[vecp++] = e->eb_body;
2724     vec[vecp] = NULL;
2725
2726     for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++)
2727         sleep (5);
2728     switch (child_id) {
2729         case NOTOK:
2730             advise ("fork", "unable to");
2731             return NOTOK;
2732
2733         case OK:
2734             execvp (mailproc, vec);
2735             fprintf (stderr, "unable to exec ");
2736             perror (mailproc);
2737             _exit (-1);
2738             /* NOTREACHED */
2739
2740         default:
2741             if (pidXwait (child_id, NULL) == OK)
2742                 advise (NULL, "request sent");
2743             break;
2744     }
2745
2746     if (*file == NULL) {
2747         ce->ce_file = add (m_scratch ("", tmp), NULL);
2748         ce->ce_unlink = 1;
2749     } else {
2750         ce->ce_file = add (*file, NULL);
2751         ce->ce_unlink = 0;
2752     }
2753
2754     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2755         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2756         return NOTOK;
2757     }
2758
2759     /* showproc is for mhshow and mhstore, though mhlist -debug
2760      * prints it, too. */
2761     if (ct->c_showproc)
2762         free (ct->c_showproc);
2763     ct->c_showproc = add ("true", NULL);
2764
2765     fseek (ce->ce_fp, 0L, SEEK_SET);
2766     *file = ce->ce_file;
2767     return fileno (ce->ce_fp);
2768 }
2769
2770
2771 static int
2772 readDigest (CT ct, char *cp)
2773 {
2774     int bitno, skip;
2775     unsigned long bits;
2776     char *bp = cp;
2777     unsigned char *dp, value, *ep;
2778     unsigned char *b, *b1, *b2, *b3;
2779
2780     b  = (unsigned char *) &bits,
2781     b1 = &b[endian > 0 ? 1 : 2],
2782     b2 = &b[endian > 0 ? 2 : 1],
2783     b3 = &b[endian > 0 ? 3 : 0];
2784     bitno = 18;
2785     bits = 0L;
2786     skip = 0;
2787
2788     for (ep = (dp = ct->c_digest)
2789                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2790         switch (*cp) {
2791             default:
2792                 if (skip
2793                         || (*cp & 0x80)
2794                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2795                     if (debugsw)
2796                         fprintf (stderr, "invalid BASE64 encoding\n");
2797                     return NOTOK;
2798                 }
2799
2800                 bits |= value << bitno;
2801 test_end:
2802                 if ((bitno -= 6) < 0) {
2803                     if (dp + (3 - skip) > ep)
2804                         goto invalid_digest;
2805                     *dp++ = *b1;
2806                     if (skip < 2) {
2807                         *dp++ = *b2;
2808                         if (skip < 1)
2809                             *dp++ = *b3;
2810                     }
2811                     bitno = 18;
2812                     bits = 0L;
2813                     skip = 0;
2814                 }
2815                 break;
2816
2817             case '=':
2818                 if (++skip > 3)
2819                     goto self_delimiting;
2820                 goto test_end;
2821         }
2822     if (bitno != 18) {
2823         if (debugsw)
2824             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2825
2826         return NOTOK;
2827     }
2828 self_delimiting:
2829     if (dp != ep) {
2830 invalid_digest:
2831         if (debugsw) {
2832             while (*cp)
2833                 cp++;
2834             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2835                      (int)(cp - bp));
2836         }
2837
2838         return NOTOK;
2839     }
2840
2841     if (debugsw) {
2842         fprintf (stderr, "MD5 digest=");
2843         for (dp = ct->c_digest; dp < ep; dp++)
2844             fprintf (stderr, "%02x", *dp & 0xff);
2845         fprintf (stderr, "\n");
2846     }
2847
2848     return OK;
2849 }