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