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