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