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