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