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