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