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