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