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