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