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