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