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