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