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