Update for post-1.5 release.
[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     b  = (unsigned char *) &bits;
1741     b1 = &b[endian > 0 ? 1 : 2];
1742     b2 = &b[endian > 0 ? 2 : 1];
1743     b3 = &b[endian > 0 ? 3 : 0];
1744
1745     ce = ct->c_cefile;
1746     if (ce->ce_fp) {
1747         fseek (ce->ce_fp, 0L, SEEK_SET);
1748         goto ready_to_go;
1749     }
1750
1751     if (ce->ce_file) {
1752         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1753             content_error (ce->ce_file, ct, "unable to fopen for reading");
1754             return NOTOK;
1755         }
1756         goto ready_to_go;
1757     }
1758
1759     if (*file == NULL) {
1760         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
1761         ce->ce_unlink = 1;
1762     } else {
1763         ce->ce_file = add (*file, NULL);
1764         ce->ce_unlink = 0;
1765     }
1766
1767     /* sbeck@cise.ufl.edu -- handle suffixes */
1768     ci = &ct->c_ctinfo;
1769     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1770               invo_name, ci->ci_type, ci->ci_subtype);
1771     cp = context_find (buffer);
1772     if (cp == NULL || *cp == '\0') {
1773         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1774                   ci->ci_type);
1775         cp = context_find (buffer);
1776     }
1777     if (cp != NULL && *cp != '\0') {
1778         if (ce->ce_unlink) {
1779             // Temporary file already exists, so we rename to
1780             // version with extension.
1781             char *file_org = strdup(ce->ce_file);
1782             ce->ce_file = add (cp, ce->ce_file);
1783             if (rename(file_org, ce->ce_file)) {
1784                 adios (ce->ce_file, "unable to rename %s to ", file_org);
1785             }
1786             free(file_org);
1787
1788         } else {
1789             ce->ce_file = add (cp, ce->ce_file);
1790         }
1791     }
1792
1793     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1794         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1795         return NOTOK;
1796     }
1797
1798     if ((len = ct->c_end - ct->c_begin) < 0)
1799         adios (NULL, "internal error(1)");
1800
1801     if (! ct->c_fp) {
1802         if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1803             content_error (ct->c_file, ct, "unable to open for reading");
1804             return NOTOK;
1805         }
1806         own_ct_fp = 1;
1807     }
1808     
1809     if ((digested = ct->c_digested))
1810         MD5Init (&mdContext);
1811
1812     bitno = 18;
1813     bits = 0L;
1814     skip = 0;
1815
1816     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1817     while (len > 0) {
1818         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1819         case NOTOK:
1820             content_error (ct->c_file, ct, "error reading from");
1821             goto clean_up;
1822
1823         case OK:
1824             content_error (NULL, ct, "premature eof");
1825             goto clean_up;
1826
1827         default:
1828             if (cc > len)
1829                 cc = len;
1830             len -= cc;
1831
1832             for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1833                 switch (*cp) {
1834                 default:
1835                     if (isspace (*cp))
1836                         break;
1837                     if (skip || (*cp & 0x80)
1838                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1839                         if (debugsw) {
1840                             fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1841                                 *cp,
1842                                 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1843                                 skip);
1844                         }
1845                         content_error (NULL, ct,
1846                                        "invalid BASE64 encoding -- continuing");
1847                         continue;
1848                     }
1849
1850                     bits |= value << bitno;
1851 test_end:
1852                     if ((bitno -= 6) < 0) {
1853                         putc ((char) *b1, ce->ce_fp);
1854                         if (digested)
1855                             MD5Update (&mdContext, b1, 1);
1856                         if (skip < 2) {
1857                             putc ((char) *b2, ce->ce_fp);
1858                             if (digested)
1859                                 MD5Update (&mdContext, b2, 1);
1860                             if (skip < 1) {
1861                                 putc ((char) *b3, ce->ce_fp);
1862                                 if (digested)
1863                                     MD5Update (&mdContext, b3, 1);
1864                             }
1865                         }
1866
1867                         if (ferror (ce->ce_fp)) {
1868                             content_error (ce->ce_file, ct,
1869                                            "error writing to");
1870                             goto clean_up;
1871                         }
1872                         bitno = 18, bits = 0L, skip = 0;
1873                     }
1874                     break;
1875
1876                 case '=':
1877                     if (++skip > 3)
1878                         goto self_delimiting;
1879                     goto test_end;
1880                 }
1881             }
1882         }
1883     }
1884
1885     if (bitno != 18) {
1886         if (debugsw)
1887             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1888
1889         content_error (NULL, ct, "invalid BASE64 encoding");
1890         goto clean_up;
1891     }
1892
1893 self_delimiting:
1894     fseek (ct->c_fp, 0L, SEEK_SET);
1895
1896     if (fflush (ce->ce_fp)) {
1897         content_error (ce->ce_file, ct, "error writing to");
1898         goto clean_up;
1899     }
1900
1901     if (digested) {
1902         unsigned char digest[16];
1903
1904         MD5Final (digest, &mdContext);
1905         if (memcmp((char *) digest, (char *) ct->c_digest,
1906                    sizeof(digest) / sizeof(digest[0])))
1907             content_error (NULL, ct,
1908                            "content integrity suspect (digest mismatch) -- continuing");
1909         else
1910             if (debugsw)
1911                 fprintf (stderr, "content integrity confirmed\n");
1912     }
1913
1914     fseek (ce->ce_fp, 0L, SEEK_SET);
1915
1916 ready_to_go:
1917     *file = ce->ce_file;
1918     if (own_ct_fp) {
1919       fclose (ct->c_fp);
1920       ct->c_fp = NULL;
1921     }
1922     return fileno (ce->ce_fp);
1923
1924 clean_up:
1925     if (own_ct_fp) {
1926       fclose (ct->c_fp);
1927       ct->c_fp = NULL;
1928     }
1929     free_encoding (ct, 0);
1930     return NOTOK;
1931 }
1932
1933
1934 /*
1935  * QUOTED PRINTABLE
1936  */
1937
1938 static char hex2nib[0x80] = {
1939     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1940     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1941     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1942     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1943     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1944     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1945     0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1946     0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1947     0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00, 
1948     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1949     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1950     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1951     0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 
1952     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1953     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1954     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1955 };
1956
1957
1958 static int 
1959 InitQuoted (CT ct)
1960 {
1961     return init_encoding (ct, openQuoted);
1962 }
1963
1964
1965 static int
1966 openQuoted (CT ct, char **file)
1967 {
1968     int cc, digested, len, quoted, own_ct_fp = 0;
1969     unsigned char *cp, *ep;
1970     char buffer[BUFSIZ];
1971     unsigned char mask;
1972     CE ce;
1973     /* sbeck -- handle suffixes */
1974     CI ci;
1975     MD5_CTX mdContext;
1976
1977     ce = ct->c_cefile;
1978     if (ce->ce_fp) {
1979         fseek (ce->ce_fp, 0L, SEEK_SET);
1980         goto ready_to_go;
1981     }
1982
1983     if (ce->ce_file) {
1984         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1985             content_error (ce->ce_file, ct, "unable to fopen for reading");
1986             return NOTOK;
1987         }
1988         goto ready_to_go;
1989     }
1990
1991     if (*file == NULL) {
1992         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
1993         ce->ce_unlink = 1;
1994     } else {
1995         ce->ce_file = add (*file, NULL);
1996         ce->ce_unlink = 0;
1997     }
1998
1999     /* sbeck@cise.ufl.edu -- handle suffixes */
2000     ci = &ct->c_ctinfo;
2001     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2002               invo_name, ci->ci_type, ci->ci_subtype);
2003     cp = context_find (buffer);
2004     if (cp == NULL || *cp == '\0') {
2005         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2006                   ci->ci_type);
2007         cp = context_find (buffer);
2008     }
2009     if (cp != NULL && *cp != '\0') {
2010         if (ce->ce_unlink) {
2011             // Temporary file already exists, so we rename to
2012             // version with extension.
2013             char *file_org = strdup(ce->ce_file);
2014             ce->ce_file = add (cp, ce->ce_file);
2015             if (rename(file_org, ce->ce_file)) {
2016                 adios (ce->ce_file, "unable to rename %s to ", file_org);
2017             }
2018             free(file_org);
2019
2020         } else {
2021             ce->ce_file = add (cp, ce->ce_file);
2022         }
2023     }
2024
2025     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2026         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2027         return NOTOK;
2028     }
2029
2030     if ((len = ct->c_end - ct->c_begin) < 0)
2031         adios (NULL, "internal error(2)");
2032
2033     if (! ct->c_fp) {
2034         if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2035             content_error (ct->c_file, ct, "unable to open for reading");
2036             return NOTOK;
2037         }
2038         own_ct_fp = 1;
2039     }
2040
2041     if ((digested = ct->c_digested))
2042         MD5Init (&mdContext);
2043
2044     quoted = 0;
2045 #ifdef lint
2046     mask = 0;
2047 #endif
2048
2049     fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2050     while (len > 0) {
2051         if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2052             content_error (NULL, ct, "premature eof");
2053             goto clean_up;
2054         }
2055
2056         if ((cc = strlen (buffer)) > len)
2057             cc = len;
2058         len -= cc;
2059
2060         for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2061             if (!isspace (*ep))
2062                 break;
2063         *++ep = '\n', ep++;
2064
2065         for (; cp < ep; cp++) {
2066             if (quoted > 0) {
2067                 /* in an escape sequence */
2068                 if (quoted == 1) {
2069                     /* at byte 1 of an escape sequence */
2070                     mask = hex2nib[*cp & 0x7f];
2071                     /* next is byte 2 */
2072                     quoted = 2;
2073                 } else {
2074                     /* at byte 2 of an escape sequence */
2075                     mask <<= 4;
2076                     mask |= hex2nib[*cp & 0x7f];
2077                     putc (mask, ce->ce_fp);
2078                     if (digested)
2079                         MD5Update (&mdContext, &mask, 1);
2080                     if (ferror (ce->ce_fp)) {
2081                         content_error (ce->ce_file, ct, "error writing to");
2082                         goto clean_up;
2083                     }
2084                     /* finished escape sequence; next may be literal or a new
2085                      * escape sequence */
2086                     quoted = 0;
2087                 }
2088                 /* on to next byte */
2089                 continue;
2090             }
2091
2092             /* not in an escape sequence */
2093             if (*cp == '=') {
2094                 /* starting an escape sequence, or invalid '='? */
2095                 if (cp + 1 < ep && cp[1] == '\n') {
2096                     /* "=\n" soft line break, eat the \n */
2097                     cp++;
2098                     continue;
2099                 }
2100                 if (cp + 1 >= ep || cp + 2 >= ep) {
2101                     /* We don't have 2 bytes left, so this is an invalid
2102                      * escape sequence; just show the raw bytes (below). */
2103                 } else if (isxdigit (cp[1]) && isxdigit (cp[2])) {
2104                     /* Next 2 bytes are hex digits, making this a valid escape
2105                      * sequence; let's decode it (above). */
2106                     quoted = 1;
2107                     continue;
2108                 } else {
2109                     /* One or both of the next 2 is out of range, making this
2110                      * an invalid escape sequence; just show the raw bytes
2111                      * (below). */
2112                 }
2113             }
2114
2115             /* Just show the raw byte. */
2116             putc (*cp, ce->ce_fp);
2117             if (digested) {
2118                 if (*cp == '\n') {
2119                     MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2120                 } else {
2121                     MD5Update (&mdContext, (unsigned char *) cp, 1);
2122                 }
2123             }
2124             if (ferror (ce->ce_fp)) {
2125                 content_error (ce->ce_file, ct, "error writing to");
2126                 goto clean_up;
2127             }
2128         }
2129     }
2130     if (quoted) {
2131         content_error (NULL, ct,
2132                        "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2133         goto clean_up;
2134     }
2135
2136     fseek (ct->c_fp, 0L, SEEK_SET);
2137
2138     if (fflush (ce->ce_fp)) {
2139         content_error (ce->ce_file, ct, "error writing to");
2140         goto clean_up;
2141     }
2142
2143     if (digested) {
2144         unsigned char digest[16];
2145
2146         MD5Final (digest, &mdContext);
2147         if (memcmp((char *) digest, (char *) ct->c_digest,
2148                    sizeof(digest) / sizeof(digest[0])))
2149             content_error (NULL, ct,
2150                            "content integrity suspect (digest mismatch) -- continuing");
2151         else
2152             if (debugsw)
2153                 fprintf (stderr, "content integrity confirmed\n");
2154     }
2155
2156     fseek (ce->ce_fp, 0L, SEEK_SET);
2157
2158 ready_to_go:
2159     *file = ce->ce_file;
2160     if (own_ct_fp) {
2161       fclose (ct->c_fp);
2162       ct->c_fp = NULL;
2163     }
2164     return fileno (ce->ce_fp);
2165
2166 clean_up:
2167     free_encoding (ct, 0);
2168     if (own_ct_fp) {
2169       fclose (ct->c_fp);
2170       ct->c_fp = NULL;
2171     }
2172     return NOTOK;
2173 }
2174
2175
2176 /*
2177  * 7BIT
2178  */
2179
2180 static int
2181 Init7Bit (CT ct)
2182 {
2183     if (init_encoding (ct, open7Bit) == NOTOK)
2184         return NOTOK;
2185
2186     ct->c_cesizefnx = NULL;     /* no need to decode for real size */
2187     return OK;
2188 }
2189
2190
2191 int
2192 open7Bit (CT ct, char **file)
2193 {
2194     int cc, fd, len, own_ct_fp = 0;
2195     char buffer[BUFSIZ];
2196     /* sbeck -- handle suffixes */
2197     char *cp;
2198     CI ci;
2199     CE ce;
2200
2201     ce = ct->c_cefile;
2202     if (ce->ce_fp) {
2203         fseek (ce->ce_fp, 0L, SEEK_SET);
2204         goto ready_to_go;
2205     }
2206
2207     if (ce->ce_file) {
2208         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2209             content_error (ce->ce_file, ct, "unable to fopen for reading");
2210             return NOTOK;
2211         }
2212         goto ready_to_go;
2213     }
2214
2215     if (*file == NULL) {
2216         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2217         ce->ce_unlink = 1;
2218     } else {
2219         ce->ce_file = add (*file, NULL);
2220         ce->ce_unlink = 0;
2221     }
2222
2223     /* sbeck@cise.ufl.edu -- handle suffixes */
2224     ci = &ct->c_ctinfo;
2225     snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2226               invo_name, ci->ci_type, ci->ci_subtype);
2227     cp = context_find (buffer);
2228     if (cp == NULL || *cp == '\0') {
2229         snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2230                   ci->ci_type);
2231         cp = context_find (buffer);
2232     }
2233     if (cp != NULL && *cp != '\0') {
2234         if (ce->ce_unlink) {
2235             // Temporary file already exists, so we rename to
2236             // version with extension.
2237             char *file_org = strdup(ce->ce_file);
2238             ce->ce_file = add (cp, ce->ce_file);
2239             if (rename(file_org, ce->ce_file)) {
2240                 adios (ce->ce_file, "unable to rename %s to ", file_org);
2241             }
2242             free(file_org);
2243
2244         } else {
2245             ce->ce_file = add (cp, ce->ce_file);
2246         }
2247     }
2248
2249     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2250         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2251         return NOTOK;
2252     }
2253
2254     if (ct->c_type == CT_MULTIPART) {
2255         char **ap, **ep;
2256         CI ci = &ct->c_ctinfo;
2257
2258         len = 0;
2259         fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2260         len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2261             + 1 + strlen (ci->ci_subtype);
2262         for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2263             putc (';', ce->ce_fp);
2264             len++;
2265
2266             snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2267
2268             if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2269                 fputs ("\n\t", ce->ce_fp);
2270                 len = 8;
2271             } else {
2272                 putc (' ', ce->ce_fp);
2273                 len++;
2274             }
2275             fprintf (ce->ce_fp, "%s", buffer);
2276             len += cc;
2277         }
2278
2279         if (ci->ci_comment) {
2280             if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2281                 fputs ("\n\t", ce->ce_fp);
2282                 len = 8;
2283             }
2284             else {
2285                 putc (' ', ce->ce_fp);
2286                 len++;
2287             }
2288             fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2289             len += cc;
2290         }
2291         fprintf (ce->ce_fp, "\n");
2292         if (ct->c_id)
2293             fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2294         if (ct->c_descr)
2295             fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2296         if (ct->c_dispo)
2297             fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2298         fprintf (ce->ce_fp, "\n");
2299     }
2300
2301     if ((len = ct->c_end - ct->c_begin) < 0)
2302         adios (NULL, "internal error(3)");
2303
2304     if (! ct->c_fp) {
2305         if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2306             content_error (ct->c_file, ct, "unable to open for reading");
2307             return NOTOK;
2308         }
2309         own_ct_fp = 1;
2310     }
2311
2312     lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2313     while (len > 0)
2314         switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2315         case NOTOK:
2316             content_error (ct->c_file, ct, "error reading from");
2317             goto clean_up;
2318
2319         case OK:
2320             content_error (NULL, ct, "premature eof");
2321             goto clean_up;
2322
2323         default:
2324             if (cc > len)
2325                 cc = len;
2326             len -= cc;
2327
2328             fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2329             if (ferror (ce->ce_fp)) {
2330                 content_error (ce->ce_file, ct, "error writing to");
2331                 goto clean_up;
2332             }
2333         }
2334
2335     fseek (ct->c_fp, 0L, SEEK_SET);
2336
2337     if (fflush (ce->ce_fp)) {
2338         content_error (ce->ce_file, ct, "error writing to");
2339         goto clean_up;
2340     }
2341
2342     fseek (ce->ce_fp, 0L, SEEK_SET);
2343
2344 ready_to_go:
2345     *file = ce->ce_file;
2346     if (own_ct_fp) {
2347       fclose (ct->c_fp);
2348       ct->c_fp = NULL;
2349     }
2350     return fileno (ce->ce_fp);
2351
2352 clean_up:
2353     free_encoding (ct, 0);
2354     if (own_ct_fp) {
2355       fclose (ct->c_fp);
2356       ct->c_fp = NULL;
2357     }
2358     return NOTOK;
2359 }
2360
2361
2362 /*
2363  * External
2364  */
2365
2366 static int
2367 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2368 {
2369     char cachefile[BUFSIZ];
2370
2371     if (ce->ce_fp) {
2372         fseek (ce->ce_fp, 0L, SEEK_SET);
2373         goto ready_already;
2374     }
2375
2376     if (ce->ce_file) {
2377         if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2378             content_error (ce->ce_file, ct, "unable to fopen for reading");
2379             return NOTOK;
2380         }
2381         goto ready_already;
2382     }
2383
2384     if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2385                 cachefile, sizeof(cachefile)) != NOTOK) {
2386         if ((ce->ce_fp = fopen (cachefile, "r"))) {
2387             ce->ce_file = getcpy (cachefile);
2388             ce->ce_unlink = 0;
2389             goto ready_already;
2390         } else {
2391             admonish (cachefile, "unable to fopen for reading");
2392         }
2393     }
2394
2395     return OK;
2396
2397 ready_already:
2398     *file = ce->ce_file;
2399     *fd = fileno (ce->ce_fp);
2400     return DONE;
2401 }
2402
2403 /*
2404  * File
2405  */
2406
2407 static int
2408 InitFile (CT ct)
2409 {
2410     return init_encoding (ct, openFile);
2411 }
2412
2413
2414 static int
2415 openFile (CT ct, char **file)
2416 {
2417     int fd, cachetype;
2418     char cachefile[BUFSIZ];
2419     struct exbody *e = ct->c_ctexbody;
2420     CE ce = ct->c_cefile;
2421
2422     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2423         case NOTOK:
2424             return NOTOK;
2425
2426         case OK:
2427             break;
2428
2429         case DONE:
2430             return fd;
2431     }
2432
2433     if (!e->eb_name) {
2434         content_error (NULL, ct, "missing name parameter");
2435         return NOTOK;
2436     }
2437
2438     ce->ce_file = getcpy (e->eb_name);
2439     ce->ce_unlink = 0;
2440
2441     if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2442         content_error (ce->ce_file, ct, "unable to fopen for reading");
2443         return NOTOK;
2444     }
2445
2446     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2447             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2448                 cachefile, sizeof(cachefile)) != NOTOK) {
2449         int mask;
2450         FILE *fp;
2451
2452         mask = umask (cachetype ? ~m_gmprot () : 0222);
2453         if ((fp = fopen (cachefile, "w"))) {
2454             int cc;
2455             char buffer[BUFSIZ];
2456             FILE *gp = ce->ce_fp;
2457
2458             fseek (gp, 0L, SEEK_SET);
2459
2460             while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2461                        > 0)
2462                 fwrite (buffer, sizeof(*buffer), cc, fp);
2463             fflush (fp);
2464
2465             if (ferror (gp)) {
2466                 admonish (ce->ce_file, "error reading");
2467                 unlink (cachefile);
2468             }
2469             else
2470                 if (ferror (fp)) {
2471                     admonish (cachefile, "error writing");
2472                     unlink (cachefile);
2473                 }
2474             fclose (fp);
2475         }
2476         umask (mask);
2477     }
2478
2479     fseek (ce->ce_fp, 0L, SEEK_SET);
2480     *file = ce->ce_file;
2481     return fileno (ce->ce_fp);
2482 }
2483
2484 /*
2485  * FTP
2486  */
2487
2488 static int
2489 InitFTP (CT ct)
2490 {
2491     return init_encoding (ct, openFTP);
2492 }
2493
2494
2495 static int
2496 openFTP (CT ct, char **file)
2497 {
2498     int cachetype, caching, fd;
2499     int len, buflen;
2500     char *bp, *ftp, *user, *pass;
2501     char buffer[BUFSIZ], cachefile[BUFSIZ];
2502     struct exbody *e;
2503     CE ce;
2504     static char *username = NULL;
2505     static char *password = NULL;
2506
2507     e  = ct->c_ctexbody;
2508     ce = ct->c_cefile;
2509
2510     if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2511         ftp = NULL;
2512
2513     if (!ftp)
2514         return NOTOK;
2515
2516     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2517         case NOTOK:
2518             return NOTOK;
2519
2520         case OK:
2521             break;
2522
2523         case DONE:
2524             return fd;
2525     }
2526
2527     if (!e->eb_name || !e->eb_site) {
2528         content_error (NULL, ct, "missing %s parameter",
2529                        e->eb_name ? "site": "name");
2530         return NOTOK;
2531     }
2532
2533     if (xpid) {
2534         if (xpid < 0)
2535             xpid = -xpid;
2536         pidcheck (pidwait (xpid, NOTOK));
2537         xpid = 0;
2538     }
2539
2540     /* Get the buffer ready to go */
2541     bp = buffer;
2542     buflen = sizeof(buffer);
2543
2544     /*
2545      * Construct the query message for user
2546      */
2547     snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2548     len = strlen (bp);
2549     bp += len;
2550     buflen -= len;
2551
2552     if (e->eb_partno) {
2553         snprintf (bp, buflen, " (content %s)", e->eb_partno);
2554         len = strlen (bp);
2555         bp += len;
2556         buflen -= len;
2557     }
2558
2559     snprintf (bp, buflen, "\n    using %sFTP from site %s",
2560                     e->eb_flags ? "anonymous " : "", e->eb_site);
2561     len = strlen (bp);
2562     bp += len;
2563     buflen -= len;
2564
2565     if (e->eb_size > 0) {
2566         snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2567         len = strlen (bp);
2568         bp += len;
2569         buflen -= len;
2570     }
2571     snprintf (bp, buflen, "? ");
2572
2573     /*
2574      * Now, check the answer
2575      */
2576     if (!getanswer (buffer))
2577         return NOTOK;
2578
2579     if (e->eb_flags) {
2580         user = "anonymous";
2581         snprintf (buffer, sizeof(buffer), "%s@%s", getusername (),
2582                   LocalName (1));
2583         pass = buffer;
2584     } else {
2585         ruserpass (e->eb_site, &username, &password);
2586         user = username;
2587         pass = password;
2588     }
2589
2590     ce->ce_unlink = (*file == NULL);
2591     caching = 0;
2592     cachefile[0] = '\0';
2593     if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2594             && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2595                 cachefile, sizeof(cachefile)) != NOTOK) {
2596         if (*file == NULL) {
2597             ce->ce_unlink = 0;
2598             caching = 1;
2599         }
2600     }
2601
2602     if (*file)
2603         ce->ce_file = add (*file, NULL);
2604     else if (caching)
2605         ce->ce_file = add (cachefile, NULL);
2606     else
2607         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2608
2609     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2610         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2611         return NOTOK;
2612     }
2613
2614     {
2615         int child_id, i, vecp;
2616         char *vec[9];
2617
2618         vecp = 0;
2619         vec[vecp++] = r1bindex (ftp, '/');
2620         vec[vecp++] = e->eb_site;
2621         vec[vecp++] = user;
2622         vec[vecp++] = pass;
2623         vec[vecp++] = e->eb_dir;
2624         vec[vecp++] = e->eb_name;
2625         vec[vecp++] = ce->ce_file,
2626         vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
2627                         ? "ascii" : "binary";
2628         vec[vecp] = NULL;
2629
2630         fflush (stdout);
2631
2632         for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
2633             sleep (5);
2634         switch (child_id) {
2635             case NOTOK:
2636                 adios ("fork", "unable to");
2637                 /* NOTREACHED */
2638
2639             case OK:
2640                 close (fileno (ce->ce_fp));
2641                 execvp (ftp, vec);
2642                 fprintf (stderr, "unable to exec ");
2643                 perror (ftp);
2644                 _exit (-1);
2645                 /* NOTREACHED */
2646
2647             default:
2648                 if (pidXwait (child_id, NULL)) {
2649                     username = password = NULL;
2650                     ce->ce_unlink = 1;
2651                     return NOTOK;
2652                 }
2653                 break;
2654         }
2655     }
2656
2657     if (cachefile[0]) {
2658         if (caching)
2659             chmod (cachefile, cachetype ? m_gmprot () : 0444);
2660         else {
2661             int mask;
2662             FILE *fp;
2663
2664             mask = umask (cachetype ? ~m_gmprot () : 0222);
2665             if ((fp = fopen (cachefile, "w"))) {
2666                 int cc;
2667                 FILE *gp = ce->ce_fp;
2668
2669                 fseek (gp, 0L, SEEK_SET);
2670
2671                 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2672                            > 0)
2673                     fwrite (buffer, sizeof(*buffer), cc, fp);
2674                 fflush (fp);
2675
2676                 if (ferror (gp)) {
2677                     admonish (ce->ce_file, "error reading");
2678                     unlink (cachefile);
2679                 }
2680                 else
2681                     if (ferror (fp)) {
2682                         admonish (cachefile, "error writing");
2683                         unlink (cachefile);
2684                     }
2685                 fclose (fp);
2686             }
2687             umask (mask);
2688         }
2689     }
2690
2691     fseek (ce->ce_fp, 0L, SEEK_SET);
2692     *file = ce->ce_file;
2693     return fileno (ce->ce_fp);
2694 }
2695
2696
2697 /*
2698  * Mail
2699  */
2700
2701 static int
2702 InitMail (CT ct)
2703 {
2704     return init_encoding (ct, openMail);
2705 }
2706
2707
2708 static int
2709 openMail (CT ct, char **file)
2710 {
2711     int child_id, fd, i, vecp;
2712     int len, buflen;
2713     char *bp, buffer[BUFSIZ], *vec[7];
2714     struct exbody *e = ct->c_ctexbody;
2715     CE ce = ct->c_cefile;
2716
2717     switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2718         case NOTOK:
2719             return NOTOK;
2720
2721         case OK:
2722             break;
2723
2724         case DONE:
2725             return fd;
2726     }
2727
2728     if (!e->eb_server) {
2729         content_error (NULL, ct, "missing server parameter");
2730         return NOTOK;
2731     }
2732
2733     if (xpid) {
2734         if (xpid < 0)
2735             xpid = -xpid;
2736         pidcheck (pidwait (xpid, NOTOK));
2737         xpid = 0;
2738     }
2739
2740     /* Get buffer ready to go */
2741     bp = buffer;
2742     buflen = sizeof(buffer);
2743
2744     /* Now, construct query message */
2745     snprintf (bp, buflen, "Retrieve content");
2746     len = strlen (bp);
2747     bp += len;
2748     buflen -= len;
2749
2750     if (e->eb_partno) {
2751         snprintf (bp, buflen, " %s", e->eb_partno);
2752         len = strlen (bp);
2753         bp += len;
2754         buflen -= len;
2755     }
2756
2757     snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2758                     e->eb_server,
2759                     e->eb_subject ? e->eb_subject : e->eb_body);
2760
2761     /* Now, check answer */
2762     if (!getanswer (buffer))
2763         return NOTOK;
2764
2765     vecp = 0;
2766     vec[vecp++] = r1bindex (mailproc, '/');
2767     vec[vecp++] = e->eb_server;
2768     vec[vecp++] = "-subject";
2769     vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2770     vec[vecp++] = "-body";
2771     vec[vecp++] = e->eb_body;
2772     vec[vecp] = NULL;
2773
2774     for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
2775         sleep (5);
2776     switch (child_id) {
2777         case NOTOK:
2778             advise ("fork", "unable to");
2779             return NOTOK;
2780
2781         case OK:
2782             execvp (mailproc, vec);
2783             fprintf (stderr, "unable to exec ");
2784             perror (mailproc);
2785             _exit (-1);
2786             /* NOTREACHED */
2787
2788         default:
2789             if (pidXwait (child_id, NULL) == OK)
2790                 advise (NULL, "request sent");
2791             break;
2792     }
2793
2794     if (*file == NULL) {
2795         ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2796         ce->ce_unlink = 1;
2797     } else {
2798         ce->ce_file = add (*file, NULL);
2799         ce->ce_unlink = 0;
2800     }
2801
2802     if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2803         content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2804         return NOTOK;
2805     }
2806
2807     /* showproc is for mhshow and mhstore, though mhlist -debug
2808      * prints it, too. */
2809     if (ct->c_showproc)
2810         free (ct->c_showproc);
2811     ct->c_showproc = add ("true", NULL);
2812
2813     fseek (ce->ce_fp, 0L, SEEK_SET);
2814     *file = ce->ce_file;
2815     return fileno (ce->ce_fp);
2816 }
2817
2818
2819 static int
2820 readDigest (CT ct, char *cp)
2821 {
2822     int bitno, skip;
2823     unsigned long bits;
2824     char *bp = cp;
2825     unsigned char *dp, value, *ep;
2826     unsigned char *b, *b1, *b2, *b3;
2827
2828     b  = (unsigned char *) &bits,
2829     b1 = &b[endian > 0 ? 1 : 2],
2830     b2 = &b[endian > 0 ? 2 : 1],
2831     b3 = &b[endian > 0 ? 3 : 0];
2832     bitno = 18;
2833     bits = 0L;
2834     skip = 0;
2835
2836     for (ep = (dp = ct->c_digest)
2837                  + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2838         switch (*cp) {
2839             default:
2840                 if (skip
2841                         || (*cp & 0x80)
2842                         || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2843                     if (debugsw)
2844                         fprintf (stderr, "invalid BASE64 encoding\n");
2845                     return NOTOK;
2846                 }
2847
2848                 bits |= value << bitno;
2849 test_end:
2850                 if ((bitno -= 6) < 0) {
2851                     if (dp + (3 - skip) > ep)
2852                         goto invalid_digest;
2853                     *dp++ = *b1;
2854                     if (skip < 2) {
2855                         *dp++ = *b2;
2856                         if (skip < 1)
2857                             *dp++ = *b3;
2858                     }
2859                     bitno = 18;
2860                     bits = 0L;
2861                     skip = 0;
2862                 }
2863                 break;
2864
2865             case '=':
2866                 if (++skip > 3)
2867                     goto self_delimiting;
2868                 goto test_end;
2869         }
2870     if (bitno != 18) {
2871         if (debugsw)
2872             fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2873
2874         return NOTOK;
2875     }
2876 self_delimiting:
2877     if (dp != ep) {
2878 invalid_digest:
2879         if (debugsw) {
2880             while (*cp)
2881                 cp++;
2882             fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2883                      (int)(cp - bp));
2884         }
2885
2886         return NOTOK;
2887     }
2888
2889     if (debugsw) {
2890         fprintf (stderr, "MD5 digest=");
2891         for (dp = ct->c_digest; dp < ep; dp++)
2892             fprintf (stderr, "%02x", *dp & 0xff);
2893         fprintf (stderr, "\n");
2894     }
2895
2896     return OK;
2897 }