]> diplodocus.org Git - nmh/blob - uip/mhparse.c
Started revising m_getfld() code to replace direct buffer
[nmh] / uip / mhparse.c
1
2 /*
3 * mhparse.c -- routines to parse the contents of MIME messages
4 *
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
8 */
9
10 #include <h/mh.h>
11 #include <fcntl.h>
12 #include <h/signals.h>
13 #include <h/md5.h>
14 #include <errno.h>
15 #include <signal.h>
16 #include <h/mts.h>
17 #include <h/tws.h>
18 #include <h/mime.h>
19 #include <h/mhparse.h>
20 #include <h/utils.h>
21
22
23 extern int debugsw;
24
25 extern pid_t xpid; /* mhshowsbr.c */
26
27 /* cache policies */
28 extern int rcachesw; /* mhcachesbr.c */
29 extern int wcachesw; /* mhcachesbr.c */
30
31 int checksw = 0; /* check Content-MD5 field */
32
33 /*
34 * Directory to place temp files. This must
35 * be set before these routines are called.
36 */
37 char *tmp;
38
39 /*
40 * Structures for TEXT messages
41 */
42 struct k2v SubText[] = {
43 { "plain", TEXT_PLAIN },
44 { "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */
45 { "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */
46 { NULL, TEXT_UNKNOWN } /* this one must be last! */
47 };
48
49 /* Charset[] removed -- yozo. Mon Oct 8 01:03:41 JST 2012 */
50
51 /*
52 * Structures for MULTIPART messages
53 */
54 struct k2v SubMultiPart[] = {
55 { "mixed", MULTI_MIXED },
56 { "alternative", MULTI_ALTERNATE },
57 { "digest", MULTI_DIGEST },
58 { "parallel", MULTI_PARALLEL },
59 { NULL, MULTI_UNKNOWN } /* this one must be last! */
60 };
61
62 /*
63 * Structures for MESSAGE messages
64 */
65 struct k2v SubMessage[] = {
66 { "rfc822", MESSAGE_RFC822 },
67 { "partial", MESSAGE_PARTIAL },
68 { "external-body", MESSAGE_EXTERNAL },
69 { NULL, MESSAGE_UNKNOWN } /* this one must be last! */
70 };
71
72 /*
73 * Structure for APPLICATION messages
74 */
75 struct k2v SubApplication[] = {
76 { "octet-stream", APPLICATION_OCTETS },
77 { "postscript", APPLICATION_POSTSCRIPT },
78 { NULL, APPLICATION_UNKNOWN } /* this one must be last! */
79 };
80
81
82 /* mhcachesbr.c */
83 int find_cache (CT, int, int *, char *, char *, int);
84
85 /* mhmisc.c */
86 int part_ok (CT, int);
87 int type_ok (CT, int);
88 void content_error (char *, CT, char *, ...);
89
90 /* mhfree.c */
91 void free_content (CT);
92 void free_encoding (CT, int);
93
94 /*
95 * static prototypes
96 */
97 static CT get_content (FILE *, char *, int);
98 static int get_comment (CT, unsigned char **, int);
99
100 static int InitGeneric (CT);
101 static int InitText (CT);
102 static int InitMultiPart (CT);
103 static void reverse_parts (CT);
104 static int InitMessage (CT);
105 static int InitApplication (CT);
106 static int init_encoding (CT, OpenCEFunc);
107 static unsigned long size_encoding (CT);
108 static int InitBase64 (CT);
109 static int openBase64 (CT, char **);
110 static int InitQuoted (CT);
111 static int openQuoted (CT, char **);
112 static int Init7Bit (CT);
113 static int openExternal (CT, CT, CE, char **, int *);
114 static int InitFile (CT);
115 static int openFile (CT, char **);
116 static int InitFTP (CT);
117 static int openFTP (CT, char **);
118 static int InitMail (CT);
119 static int openMail (CT, char **);
120 static int readDigest (CT, char *);
121
122 struct str2init str2cts[] = {
123 { "application", CT_APPLICATION, InitApplication },
124 { "audio", CT_AUDIO, InitGeneric },
125 { "image", CT_IMAGE, InitGeneric },
126 { "message", CT_MESSAGE, InitMessage },
127 { "multipart", CT_MULTIPART, InitMultiPart },
128 { "text", CT_TEXT, InitText },
129 { "video", CT_VIDEO, InitGeneric },
130 { NULL, CT_EXTENSION, NULL }, /* these two must be last! */
131 { NULL, CT_UNKNOWN, NULL },
132 };
133
134 struct str2init str2ces[] = {
135 { "base64", CE_BASE64, InitBase64 },
136 { "quoted-printable", CE_QUOTED, InitQuoted },
137 { "8bit", CE_8BIT, Init7Bit },
138 { "7bit", CE_7BIT, Init7Bit },
139 { "binary", CE_BINARY, Init7Bit },
140 { NULL, CE_EXTENSION, NULL }, /* these two must be last! */
141 { NULL, CE_UNKNOWN, NULL },
142 };
143
144 /*
145 * NOTE WELL: si_key MUST NOT have value of NOTOK
146 *
147 * si_key is 1 if access method is anonymous.
148 */
149 struct str2init str2methods[] = {
150 { "afs", 1, InitFile },
151 { "anon-ftp", 1, InitFTP },
152 { "ftp", 0, InitFTP },
153 { "local-file", 0, InitFile },
154 { "mail-server", 0, InitMail },
155 { NULL, 0, NULL }
156 };
157
158
159 int
160 pidcheck (int status)
161 {
162 if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT)
163 return status;
164
165 fflush (stdout);
166 fflush (stderr);
167 done (1);
168 return 1;
169 }
170
171
172 /*
173 * Main entry point for parsing a MIME message or file.
174 * It returns the Content structure for the top level
175 * entity in the file.
176 */
177
178 CT
179 parse_mime (char *file)
180 {
181 int is_stdin;
182 char buffer[BUFSIZ];
183 FILE *fp;
184 CT ct;
185
186 /*
187 * Check if file is actually standard input
188 */
189 if ((is_stdin = !(strcmp (file, "-")))) {
190 char *tfile = m_mktemp2(NULL, invo_name, NULL, &fp);
191 if (tfile == NULL) {
192 advise("mhparse", "unable to create temporary file");
193 return NULL;
194 }
195 file = add (tfile, NULL);
196 chmod (file, 0600);
197
198 while (fgets (buffer, sizeof(buffer), stdin))
199 fputs (buffer, fp);
200 fflush (fp);
201
202 if (ferror (stdin)) {
203 unlink (file);
204 advise ("stdin", "error reading");
205 return NULL;
206 }
207 if (ferror (fp)) {
208 unlink (file);
209 advise (file, "error writing");
210 return NULL;
211 }
212 fseek (fp, 0L, SEEK_SET);
213 } else if ((fp = fopen (file, "r")) == NULL) {
214 advise (file, "unable to read");
215 return NULL;
216 }
217
218 if (!(ct = get_content (fp, file, 1))) {
219 if (is_stdin)
220 unlink (file);
221 advise (NULL, "unable to decode %s", file);
222 return NULL;
223 }
224
225 if (is_stdin)
226 ct->c_unlink = 1; /* temp file to remove */
227
228 ct->c_fp = NULL;
229
230 if (ct->c_end == 0L) {
231 fseek (fp, 0L, SEEK_END);
232 ct->c_end = ftell (fp);
233 }
234
235 if (ct->c_ctinitfnx && (*ct->c_ctinitfnx) (ct) == NOTOK) {
236 fclose (fp);
237 free_content (ct);
238 return NULL;
239 }
240
241 fclose (fp);
242 return ct;
243 }
244
245
246 /*
247 * Main routine for reading/parsing the headers
248 * of a message content.
249 *
250 * toplevel = 1 # we are at the top level of the message
251 * toplevel = 0 # we are inside message type or multipart type
252 * # other than multipart/digest
253 * toplevel = -1 # we are inside multipart/digest
254 * NB: on failure we will fclose(in)!
255 */
256
257 static CT
258 get_content (FILE *in, char *file, int toplevel)
259 {
260 int compnum, state;
261 char buf[BUFSIZ], name[NAMESZ];
262 char *np, *vp;
263 CT ct;
264 HF hp;
265 long filepos;
266
267 /* allocate the content structure */
268 if (!(ct = (CT) calloc (1, sizeof(*ct))))
269 adios (NULL, "out of memory");
270
271 ct->c_fp = in;
272 ct->c_file = add (file, NULL);
273 ct->c_begin = (filepos = ftell (ct->c_fp)) + 1;
274
275 /*
276 * Parse the header fields for this
277 * content into a linked list.
278 */
279 for (compnum = 1, state = FLD;;) {
280 int bufsz = sizeof buf;
281 switch (state = m_getfld (state, name, buf, &bufsz, in)) {
282 case FLD:
283 case FLDPLUS:
284 case FLDEOF:
285 compnum++;
286 filepos += bufsz;
287
288 /* get copies of the buffers */
289 np = add (name, NULL);
290 vp = add (buf, NULL);
291
292 /* if necessary, get rest of field */
293 while (state == FLDPLUS) {
294 bufsz = sizeof buf;
295 state = m_getfld (state, name, buf, &bufsz, in);
296 vp = add (buf, vp); /* add to previous value */
297 }
298
299 /* Now add the header data to the list */
300 add_header (ct, np, vp);
301
302 /* continue, if this isn't the last header field */
303 if (state != FLDEOF) {
304 ct->c_begin = filepos + 1;
305 continue;
306 }
307 /* else fall... */
308
309 case BODY:
310 case BODYEOF:
311 filepos += bufsz;
312 ct->c_begin = filepos - strlen (buf);
313 break;
314
315 case FILEEOF:
316 filepos += bufsz;
317 ct->c_begin = filepos;
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 chset = *ep;
1083 t->tx_charset = CHARSET_SPECIFIED;
1084 } else {
1085 t->tx_charset = CHARSET_UNSPECIFIED;
1086 }
1087
1088 /*
1089 * If we can not handle character set natively,
1090 * then check profile for string to modify the
1091 * terminal or display method.
1092 *
1093 * termproc is for mhshow, though mhlist -debug prints it, too.
1094 */
1095 if (chset != NULL && !check_charset (chset, strlen (chset))) {
1096 snprintf (buffer, sizeof(buffer), "%s-charset-%s", invo_name, chset);
1097 if ((cp = context_find (buffer)))
1098 ct->c_termproc = getcpy (cp);
1099 }
1100
1101 return OK;
1102 }
1103
1104
1105 /*
1106 * MULTIPART
1107 */
1108
1109 static int
1110 InitMultiPart (CT ct)
1111 {
1112 int inout;
1113 long last, pos;
1114 unsigned char *cp, *dp;
1115 char **ap, **ep;
1116 char *bp, buffer[BUFSIZ];
1117 struct multipart *m;
1118 struct k2v *kv;
1119 struct part *part, **next;
1120 CI ci = &ct->c_ctinfo;
1121 CT p;
1122 FILE *fp;
1123
1124 /*
1125 * The encoding for multipart messages must be either
1126 * 7bit, 8bit, or binary (per RFC2045).
1127 */
1128 if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT
1129 && ct->c_encoding != CE_BINARY) {
1130 admonish (NULL,
1131 "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary",
1132 ci->ci_type, ci->ci_subtype, ct->c_file);
1133 return NOTOK;
1134 }
1135
1136 /* match subtype */
1137 for (kv = SubMultiPart; kv->kv_key; kv++)
1138 if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1139 break;
1140 ct->c_subtype = kv->kv_value;
1141
1142 /*
1143 * Check for "boundary" parameter, which is
1144 * required for multipart messages.
1145 */
1146 bp = 0;
1147 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1148 if (!mh_strcasecmp (*ap, "boundary")) {
1149 bp = *ep;
1150 break;
1151 }
1152 }
1153
1154 /* complain if boundary parameter is missing */
1155 if (!*ap) {
1156 advise (NULL,
1157 "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field",
1158 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1159 return NOTOK;
1160 }
1161
1162 /* allocate primary structure for multipart info */
1163 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
1164 adios (NULL, "out of memory");
1165 ct->c_ctparams = (void *) m;
1166
1167 /* check if boundary parameter contains only whitespace characters */
1168 for (cp = bp; isspace (*cp); cp++)
1169 continue;
1170 if (!*cp) {
1171 advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field",
1172 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1173 return NOTOK;
1174 }
1175
1176 /* remove trailing whitespace from boundary parameter */
1177 for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--)
1178 if (!isspace (*dp))
1179 break;
1180 *++dp = '\0';
1181
1182 /* record boundary separators */
1183 m->mp_start = concat (bp, "\n", NULL);
1184 m->mp_stop = concat (bp, "--\n", NULL);
1185
1186 if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1187 advise (ct->c_file, "unable to open for reading");
1188 return NOTOK;
1189 }
1190
1191 fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET);
1192 last = ct->c_end;
1193 next = &m->mp_parts;
1194 part = NULL;
1195 inout = 1;
1196
1197 while (fgets (buffer, sizeof(buffer) - 1, fp)) {
1198 if (pos > last)
1199 break;
1200
1201 pos += strlen (buffer);
1202 if (buffer[0] != '-' || buffer[1] != '-')
1203 continue;
1204 if (inout) {
1205 if (strcmp (buffer + 2, m->mp_start))
1206 continue;
1207 next_part:
1208 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1209 adios (NULL, "out of memory");
1210 *next = part;
1211 next = &part->mp_next;
1212
1213 if (!(p = get_content (fp, ct->c_file,
1214 ct->c_subtype == MULTI_DIGEST ? -1 : 0))) {
1215 ct->c_fp = NULL;
1216 return NOTOK;
1217 }
1218 p->c_fp = NULL;
1219 part->mp_part = p;
1220 pos = p->c_begin;
1221 fseek (fp, pos, SEEK_SET);
1222 inout = 0;
1223 } else {
1224 if (strcmp (buffer + 2, m->mp_start) == 0) {
1225 inout = 1;
1226 end_part:
1227 p = part->mp_part;
1228 p->c_end = ftell(fp) - (strlen(buffer) + 1);
1229 if (p->c_end < p->c_begin)
1230 p->c_begin = p->c_end;
1231 if (inout)
1232 goto next_part;
1233 goto last_part;
1234 } else {
1235 if (strcmp (buffer + 2, m->mp_stop) == 0)
1236 goto end_part;
1237 }
1238 }
1239 }
1240
1241 advise (NULL, "bogus multipart content in message %s", ct->c_file);
1242 if (!inout && part) {
1243 p = part->mp_part;
1244 p->c_end = ct->c_end;
1245
1246 if (p->c_begin >= p->c_end) {
1247 for (next = &m->mp_parts; *next != part;
1248 next = &((*next)->mp_next))
1249 continue;
1250 *next = NULL;
1251 free_content (p);
1252 free ((char *) part);
1253 }
1254 }
1255
1256 last_part:
1257 /* reverse the order of the parts for multipart/alternative */
1258 if (ct->c_subtype == MULTI_ALTERNATE)
1259 reverse_parts (ct);
1260
1261 /*
1262 * label all subparts with part number, and
1263 * then initialize the content of the subpart.
1264 */
1265 {
1266 int partnum;
1267 char *pp;
1268 char partnam[BUFSIZ];
1269
1270 if (ct->c_partno) {
1271 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1272 pp = partnam + strlen (partnam);
1273 } else {
1274 pp = partnam;
1275 }
1276
1277 for (part = m->mp_parts, partnum = 1; part;
1278 part = part->mp_next, partnum++) {
1279 p = part->mp_part;
1280
1281 sprintf (pp, "%d", partnum);
1282 p->c_partno = add (partnam, NULL);
1283
1284 /* initialize the content of the subparts */
1285 if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) {
1286 fclose (ct->c_fp);
1287 ct->c_fp = NULL;
1288 return NOTOK;
1289 }
1290 }
1291 }
1292
1293 fclose (ct->c_fp);
1294 ct->c_fp = NULL;
1295 return OK;
1296 }
1297
1298
1299 /*
1300 * reverse the order of the parts of a multipart
1301 */
1302
1303 static void
1304 reverse_parts (CT ct)
1305 {
1306 int i;
1307 struct multipart *m;
1308 struct part **base, **bmp, **next, *part;
1309
1310 m = (struct multipart *) ct->c_ctparams;
1311
1312 /* if only one part, just return */
1313 if (!m->mp_parts || !m->mp_parts->mp_next)
1314 return;
1315
1316 /* count number of parts */
1317 i = 0;
1318 for (part = m->mp_parts; part; part = part->mp_next)
1319 i++;
1320
1321 /* allocate array of pointers to the parts */
1322 if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base))))
1323 adios (NULL, "out of memory");
1324 bmp = base;
1325
1326 /* point at all the parts */
1327 for (part = m->mp_parts; part; part = part->mp_next)
1328 *bmp++ = part;
1329 *bmp = NULL;
1330
1331 /* reverse the order of the parts */
1332 next = &m->mp_parts;
1333 for (bmp--; bmp >= base; bmp--) {
1334 part = *bmp;
1335 *next = part;
1336 next = &part->mp_next;
1337 }
1338 *next = NULL;
1339
1340 /* free array of pointers */
1341 free ((char *) base);
1342 }
1343
1344
1345 /*
1346 * MESSAGE
1347 */
1348
1349 static int
1350 InitMessage (CT ct)
1351 {
1352 struct k2v *kv;
1353 CI ci = &ct->c_ctinfo;
1354
1355 if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) {
1356 admonish (NULL,
1357 "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit",
1358 ci->ci_type, ci->ci_subtype, ct->c_file);
1359 return NOTOK;
1360 }
1361
1362 /* check for missing subtype */
1363 if (!*ci->ci_subtype)
1364 ci->ci_subtype = add ("rfc822", ci->ci_subtype);
1365
1366 /* match subtype */
1367 for (kv = SubMessage; kv->kv_key; kv++)
1368 if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1369 break;
1370 ct->c_subtype = kv->kv_value;
1371
1372 switch (ct->c_subtype) {
1373 case MESSAGE_RFC822:
1374 break;
1375
1376 case MESSAGE_PARTIAL:
1377 {
1378 char **ap, **ep;
1379 struct partial *p;
1380
1381 if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL)
1382 adios (NULL, "out of memory");
1383 ct->c_ctparams = (void *) p;
1384
1385 /* scan for parameters "id", "number", and "total" */
1386 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1387 if (!mh_strcasecmp (*ap, "id")) {
1388 p->pm_partid = add (*ep, NULL);
1389 continue;
1390 }
1391 if (!mh_strcasecmp (*ap, "number")) {
1392 if (sscanf (*ep, "%d", &p->pm_partno) != 1
1393 || p->pm_partno < 1) {
1394 invalid_param:
1395 advise (NULL,
1396 "invalid %s parameter for \"%s/%s\" type in message %s's %s field",
1397 *ap, ci->ci_type, ci->ci_subtype,
1398 ct->c_file, TYPE_FIELD);
1399 return NOTOK;
1400 }
1401 continue;
1402 }
1403 if (!mh_strcasecmp (*ap, "total")) {
1404 if (sscanf (*ep, "%d", &p->pm_maxno) != 1
1405 || p->pm_maxno < 1)
1406 goto invalid_param;
1407 continue;
1408 }
1409 }
1410
1411 if (!p->pm_partid
1412 || !p->pm_partno
1413 || (p->pm_maxno && p->pm_partno > p->pm_maxno)) {
1414 advise (NULL,
1415 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1416 ci->ci_type, ci->ci_subtype,
1417 ct->c_file, TYPE_FIELD);
1418 return NOTOK;
1419 }
1420 }
1421 break;
1422
1423 case MESSAGE_EXTERNAL:
1424 {
1425 int exresult;
1426 struct exbody *e;
1427 CT p;
1428 FILE *fp;
1429
1430 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
1431 adios (NULL, "out of memory");
1432 ct->c_ctparams = (void *) e;
1433
1434 if (!ct->c_fp
1435 && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1436 advise (ct->c_file, "unable to open for reading");
1437 return NOTOK;
1438 }
1439
1440 fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET);
1441
1442 if (!(p = get_content (fp, ct->c_file, 0))) {
1443 ct->c_fp = NULL;
1444 return NOTOK;
1445 }
1446
1447 e->eb_parent = ct;
1448 e->eb_content = p;
1449 p->c_ctexbody = e;
1450 if ((exresult = params_external (ct, 0)) != NOTOK
1451 && p->c_ceopenfnx == openMail) {
1452 int cc, size;
1453 char *bp;
1454
1455 if ((size = ct->c_end - p->c_begin) <= 0) {
1456 if (!e->eb_subject)
1457 content_error (NULL, ct,
1458 "empty body for access-type=mail-server");
1459 goto no_body;
1460 }
1461
1462 e->eb_body = bp = mh_xmalloc ((unsigned) size);
1463 fseek (p->c_fp, p->c_begin, SEEK_SET);
1464 while (size > 0)
1465 switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) {
1466 case NOTOK:
1467 adios ("failed", "fread");
1468
1469 case OK:
1470 adios (NULL, "unexpected EOF from fread");
1471
1472 default:
1473 bp += cc, size -= cc;
1474 break;
1475 }
1476 *bp = 0;
1477 }
1478 no_body:
1479 p->c_fp = NULL;
1480 p->c_end = p->c_begin;
1481
1482 fclose (ct->c_fp);
1483 ct->c_fp = NULL;
1484
1485 if (exresult == NOTOK)
1486 return NOTOK;
1487 if (e->eb_flags == NOTOK)
1488 return OK;
1489
1490 switch (p->c_type) {
1491 case CT_MULTIPART:
1492 break;
1493
1494 case CT_MESSAGE:
1495 if (p->c_subtype != MESSAGE_RFC822)
1496 break;
1497 /* else fall... */
1498 default:
1499 e->eb_partno = ct->c_partno;
1500 if (p->c_ctinitfnx)
1501 (*p->c_ctinitfnx) (p);
1502 break;
1503 }
1504 }
1505 break;
1506
1507 default:
1508 break;
1509 }
1510
1511 return OK;
1512 }
1513
1514
1515 int
1516 params_external (CT ct, int composing)
1517 {
1518 char **ap, **ep;
1519 struct exbody *e = (struct exbody *) ct->c_ctparams;
1520 CI ci = &ct->c_ctinfo;
1521
1522 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
1523 if (!mh_strcasecmp (*ap, "access-type")) {
1524 struct str2init *s2i;
1525 CT p = e->eb_content;
1526
1527 for (s2i = str2methods; s2i->si_key; s2i++)
1528 if (!mh_strcasecmp (*ep, s2i->si_key))
1529 break;
1530 if (!s2i->si_key) {
1531 e->eb_access = *ep;
1532 e->eb_flags = NOTOK;
1533 p->c_encoding = CE_EXTERNAL;
1534 continue;
1535 }
1536 e->eb_access = s2i->si_key;
1537 e->eb_flags = s2i->si_val;
1538 p->c_encoding = CE_EXTERNAL;
1539
1540 /* Call the Init function for this external type */
1541 if ((*s2i->si_init)(p) == NOTOK)
1542 return NOTOK;
1543 continue;
1544 }
1545 if (!mh_strcasecmp (*ap, "name")) {
1546 e->eb_name = *ep;
1547 continue;
1548 }
1549 if (!mh_strcasecmp (*ap, "permission")) {
1550 e->eb_permission = *ep;
1551 continue;
1552 }
1553 if (!mh_strcasecmp (*ap, "site")) {
1554 e->eb_site = *ep;
1555 continue;
1556 }
1557 if (!mh_strcasecmp (*ap, "directory")) {
1558 e->eb_dir = *ep;
1559 continue;
1560 }
1561 if (!mh_strcasecmp (*ap, "mode")) {
1562 e->eb_mode = *ep;
1563 continue;
1564 }
1565 if (!mh_strcasecmp (*ap, "size")) {
1566 sscanf (*ep, "%lu", &e->eb_size);
1567 continue;
1568 }
1569 if (!mh_strcasecmp (*ap, "server")) {
1570 e->eb_server = *ep;
1571 continue;
1572 }
1573 if (!mh_strcasecmp (*ap, "subject")) {
1574 e->eb_subject = *ep;
1575 continue;
1576 }
1577 if (composing && !mh_strcasecmp (*ap, "body")) {
1578 e->eb_body = getcpy (*ep);
1579 continue;
1580 }
1581 }
1582
1583 if (!e->eb_access) {
1584 advise (NULL,
1585 "invalid parameters for \"%s/%s\" type in message %s's %s field",
1586 ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD);
1587 return NOTOK;
1588 }
1589
1590 return OK;
1591 }
1592
1593
1594 /*
1595 * APPLICATION
1596 */
1597
1598 static int
1599 InitApplication (CT ct)
1600 {
1601 struct k2v *kv;
1602 CI ci = &ct->c_ctinfo;
1603
1604 /* match subtype */
1605 for (kv = SubApplication; kv->kv_key; kv++)
1606 if (!mh_strcasecmp (ci->ci_subtype, kv->kv_key))
1607 break;
1608 ct->c_subtype = kv->kv_value;
1609
1610 return OK;
1611 }
1612
1613
1614 /*
1615 * TRANSFER ENCODINGS
1616 */
1617
1618 static int
1619 init_encoding (CT ct, OpenCEFunc openfnx)
1620 {
1621 CE ce;
1622
1623 if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL)
1624 adios (NULL, "out of memory");
1625
1626 ct->c_cefile = ce;
1627 ct->c_ceopenfnx = openfnx;
1628 ct->c_ceclosefnx = close_encoding;
1629 ct->c_cesizefnx = size_encoding;
1630
1631 return OK;
1632 }
1633
1634
1635 void
1636 close_encoding (CT ct)
1637 {
1638 CE ce;
1639
1640 if (!(ce = ct->c_cefile))
1641 return;
1642
1643 if (ce->ce_fp) {
1644 fclose (ce->ce_fp);
1645 ce->ce_fp = NULL;
1646 }
1647 }
1648
1649
1650 static unsigned long
1651 size_encoding (CT ct)
1652 {
1653 int fd;
1654 unsigned long size;
1655 char *file;
1656 CE ce;
1657 struct stat st;
1658
1659 if (!(ce = ct->c_cefile))
1660 return (ct->c_end - ct->c_begin);
1661
1662 if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK)
1663 return (long) st.st_size;
1664
1665 if (ce->ce_file) {
1666 if (stat (ce->ce_file, &st) != NOTOK)
1667 return (long) st.st_size;
1668 else
1669 return 0L;
1670 }
1671
1672 if (ct->c_encoding == CE_EXTERNAL)
1673 return (ct->c_end - ct->c_begin);
1674
1675 file = NULL;
1676 if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK)
1677 return (ct->c_end - ct->c_begin);
1678
1679 if (fstat (fd, &st) != NOTOK)
1680 size = (long) st.st_size;
1681 else
1682 size = 0L;
1683
1684 (*ct->c_ceclosefnx) (ct);
1685 return size;
1686 }
1687
1688
1689 /*
1690 * BASE64
1691 */
1692
1693 static unsigned char b642nib[0x80] = {
1694 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1695 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1696 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1697 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1698 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1699 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
1700 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
1701 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
1702 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
1703 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
1704 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
1705 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
1706 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
1707 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
1708 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
1709 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
1710 };
1711
1712
1713 static int
1714 InitBase64 (CT ct)
1715 {
1716 return init_encoding (ct, openBase64);
1717 }
1718
1719
1720 static int
1721 openBase64 (CT ct, char **file)
1722 {
1723 int bitno, cc, digested;
1724 int fd, len, skip, own_ct_fp = 0;
1725 uint32_t bits;
1726 unsigned char value, b;
1727 unsigned char *cp, *ep;
1728 char buffer[BUFSIZ];
1729 /* sbeck -- handle suffixes */
1730 CI ci;
1731 CE ce;
1732 MD5_CTX mdContext;
1733
1734 ce = ct->c_cefile;
1735 if (ce->ce_fp) {
1736 fseek (ce->ce_fp, 0L, SEEK_SET);
1737 goto ready_to_go;
1738 }
1739
1740 if (ce->ce_file) {
1741 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1742 content_error (ce->ce_file, ct, "unable to fopen for reading");
1743 return NOTOK;
1744 }
1745 goto ready_to_go;
1746 }
1747
1748 if (*file == NULL) {
1749 ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
1750 ce->ce_unlink = 1;
1751 } else {
1752 ce->ce_file = add (*file, NULL);
1753 ce->ce_unlink = 0;
1754 }
1755
1756 /* sbeck@cise.ufl.edu -- handle suffixes */
1757 ci = &ct->c_ctinfo;
1758 snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1759 invo_name, ci->ci_type, ci->ci_subtype);
1760 cp = context_find (buffer);
1761 if (cp == NULL || *cp == '\0') {
1762 snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1763 ci->ci_type);
1764 cp = context_find (buffer);
1765 }
1766 if (cp != NULL && *cp != '\0') {
1767 if (ce->ce_unlink) {
1768 /* Temporary file already exists, so we rename to
1769 version with extension. */
1770 char *file_org = strdup(ce->ce_file);
1771 ce->ce_file = add (cp, ce->ce_file);
1772 if (rename(file_org, ce->ce_file)) {
1773 adios (ce->ce_file, "unable to rename %s to ", file_org);
1774 }
1775 free(file_org);
1776
1777 } else {
1778 ce->ce_file = add (cp, ce->ce_file);
1779 }
1780 }
1781
1782 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
1783 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
1784 return NOTOK;
1785 }
1786
1787 if ((len = ct->c_end - ct->c_begin) < 0)
1788 adios (NULL, "internal error(1)");
1789
1790 if (! ct->c_fp) {
1791 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
1792 content_error (ct->c_file, ct, "unable to open for reading");
1793 return NOTOK;
1794 }
1795 own_ct_fp = 1;
1796 }
1797
1798 if ((digested = ct->c_digested))
1799 MD5Init (&mdContext);
1800
1801 bitno = 18;
1802 bits = 0L;
1803 skip = 0;
1804
1805 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
1806 while (len > 0) {
1807 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
1808 case NOTOK:
1809 content_error (ct->c_file, ct, "error reading from");
1810 goto clean_up;
1811
1812 case OK:
1813 content_error (NULL, ct, "premature eof");
1814 goto clean_up;
1815
1816 default:
1817 if (cc > len)
1818 cc = len;
1819 len -= cc;
1820
1821 for (ep = (cp = buffer) + cc; cp < ep; cp++) {
1822 switch (*cp) {
1823 default:
1824 if (isspace (*cp))
1825 break;
1826 if (skip || (*cp & 0x80)
1827 || (value = b642nib[*cp & 0x7f]) > 0x3f) {
1828 if (debugsw) {
1829 fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n",
1830 *cp,
1831 (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)),
1832 skip);
1833 }
1834 content_error (NULL, ct,
1835 "invalid BASE64 encoding -- continuing");
1836 continue;
1837 }
1838
1839 bits |= value << bitno;
1840 test_end:
1841 if ((bitno -= 6) < 0) {
1842 b = (bits >> 16) & 0xff;
1843 putc ((char) b, ce->ce_fp);
1844 if (digested)
1845 MD5Update (&mdContext, &b, 1);
1846 if (skip < 2) {
1847 b = (bits >> 8) & 0xff;
1848 putc ((char) b, ce->ce_fp);
1849 if (digested)
1850 MD5Update (&mdContext, &b, 1);
1851 if (skip < 1) {
1852 b = bits & 0xff;
1853 putc ((char) b, ce->ce_fp);
1854 if (digested)
1855 MD5Update (&mdContext, &b, 1);
1856 }
1857 }
1858
1859 if (ferror (ce->ce_fp)) {
1860 content_error (ce->ce_file, ct,
1861 "error writing to");
1862 goto clean_up;
1863 }
1864 bitno = 18, bits = 0L, skip = 0;
1865 }
1866 break;
1867
1868 case '=':
1869 if (++skip > 3)
1870 goto self_delimiting;
1871 goto test_end;
1872 }
1873 }
1874 }
1875 }
1876
1877 if (bitno != 18) {
1878 if (debugsw)
1879 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
1880
1881 content_error (NULL, ct, "invalid BASE64 encoding");
1882 goto clean_up;
1883 }
1884
1885 self_delimiting:
1886 fseek (ct->c_fp, 0L, SEEK_SET);
1887
1888 if (fflush (ce->ce_fp)) {
1889 content_error (ce->ce_file, ct, "error writing to");
1890 goto clean_up;
1891 }
1892
1893 if (digested) {
1894 unsigned char digest[16];
1895
1896 MD5Final (digest, &mdContext);
1897 if (memcmp((char *) digest, (char *) ct->c_digest,
1898 sizeof(digest) / sizeof(digest[0])))
1899 content_error (NULL, ct,
1900 "content integrity suspect (digest mismatch) -- continuing");
1901 else
1902 if (debugsw)
1903 fprintf (stderr, "content integrity confirmed\n");
1904 }
1905
1906 fseek (ce->ce_fp, 0L, SEEK_SET);
1907
1908 ready_to_go:
1909 *file = ce->ce_file;
1910 if (own_ct_fp) {
1911 fclose (ct->c_fp);
1912 ct->c_fp = NULL;
1913 }
1914 return fileno (ce->ce_fp);
1915
1916 clean_up:
1917 if (own_ct_fp) {
1918 fclose (ct->c_fp);
1919 ct->c_fp = NULL;
1920 }
1921 free_encoding (ct, 0);
1922 return NOTOK;
1923 }
1924
1925
1926 /*
1927 * QUOTED PRINTABLE
1928 */
1929
1930 static char hex2nib[0x80] = {
1931 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1932 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1933 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1934 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1935 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1936 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1937 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
1938 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1939 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x00,
1940 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1941 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1942 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1943 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00,
1944 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1945 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1946 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1947 };
1948
1949
1950 static int
1951 InitQuoted (CT ct)
1952 {
1953 return init_encoding (ct, openQuoted);
1954 }
1955
1956
1957 static int
1958 openQuoted (CT ct, char **file)
1959 {
1960 int cc, digested, len, quoted, own_ct_fp = 0;
1961 unsigned char *cp, *ep;
1962 char buffer[BUFSIZ];
1963 unsigned char mask;
1964 CE ce;
1965 /* sbeck -- handle suffixes */
1966 CI ci;
1967 MD5_CTX mdContext;
1968
1969 ce = ct->c_cefile;
1970 if (ce->ce_fp) {
1971 fseek (ce->ce_fp, 0L, SEEK_SET);
1972 goto ready_to_go;
1973 }
1974
1975 if (ce->ce_file) {
1976 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
1977 content_error (ce->ce_file, ct, "unable to fopen for reading");
1978 return NOTOK;
1979 }
1980 goto ready_to_go;
1981 }
1982
1983 if (*file == NULL) {
1984 ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
1985 ce->ce_unlink = 1;
1986 } else {
1987 ce->ce_file = add (*file, NULL);
1988 ce->ce_unlink = 0;
1989 }
1990
1991 /* sbeck@cise.ufl.edu -- handle suffixes */
1992 ci = &ct->c_ctinfo;
1993 snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
1994 invo_name, ci->ci_type, ci->ci_subtype);
1995 cp = context_find (buffer);
1996 if (cp == NULL || *cp == '\0') {
1997 snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
1998 ci->ci_type);
1999 cp = context_find (buffer);
2000 }
2001 if (cp != NULL && *cp != '\0') {
2002 if (ce->ce_unlink) {
2003 /* Temporary file already exists, so we rename to
2004 version with extension. */
2005 char *file_org = strdup(ce->ce_file);
2006 ce->ce_file = add (cp, ce->ce_file);
2007 if (rename(file_org, ce->ce_file)) {
2008 adios (ce->ce_file, "unable to rename %s to ", file_org);
2009 }
2010 free(file_org);
2011
2012 } else {
2013 ce->ce_file = add (cp, ce->ce_file);
2014 }
2015 }
2016
2017 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2018 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2019 return NOTOK;
2020 }
2021
2022 if ((len = ct->c_end - ct->c_begin) < 0)
2023 adios (NULL, "internal error(2)");
2024
2025 if (! ct->c_fp) {
2026 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2027 content_error (ct->c_file, ct, "unable to open for reading");
2028 return NOTOK;
2029 }
2030 own_ct_fp = 1;
2031 }
2032
2033 if ((digested = ct->c_digested))
2034 MD5Init (&mdContext);
2035
2036 quoted = 0;
2037 #ifdef lint
2038 mask = 0;
2039 #endif
2040
2041 fseek (ct->c_fp, ct->c_begin, SEEK_SET);
2042 while (len > 0) {
2043 if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) {
2044 content_error (NULL, ct, "premature eof");
2045 goto clean_up;
2046 }
2047
2048 if ((cc = strlen (buffer)) > len)
2049 cc = len;
2050 len -= cc;
2051
2052 for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--)
2053 if (!isspace (*ep))
2054 break;
2055 *++ep = '\n', ep++;
2056
2057 for (; cp < ep; cp++) {
2058 if (quoted > 0) {
2059 /* in an escape sequence */
2060 if (quoted == 1) {
2061 /* at byte 1 of an escape sequence */
2062 mask = hex2nib[*cp & 0x7f];
2063 /* next is byte 2 */
2064 quoted = 2;
2065 } else {
2066 /* at byte 2 of an escape sequence */
2067 mask <<= 4;
2068 mask |= hex2nib[*cp & 0x7f];
2069 putc (mask, ce->ce_fp);
2070 if (digested)
2071 MD5Update (&mdContext, &mask, 1);
2072 if (ferror (ce->ce_fp)) {
2073 content_error (ce->ce_file, ct, "error writing to");
2074 goto clean_up;
2075 }
2076 /* finished escape sequence; next may be literal or a new
2077 * escape sequence */
2078 quoted = 0;
2079 }
2080 /* on to next byte */
2081 continue;
2082 }
2083
2084 /* not in an escape sequence */
2085 if (*cp == '=') {
2086 /* starting an escape sequence, or invalid '='? */
2087 if (cp + 1 < ep && cp[1] == '\n') {
2088 /* "=\n" soft line break, eat the \n */
2089 cp++;
2090 continue;
2091 }
2092 if (cp + 1 >= ep || cp + 2 >= ep) {
2093 /* We don't have 2 bytes left, so this is an invalid
2094 * escape sequence; just show the raw bytes (below). */
2095 } else if (isxdigit (cp[1]) && isxdigit (cp[2])) {
2096 /* Next 2 bytes are hex digits, making this a valid escape
2097 * sequence; let's decode it (above). */
2098 quoted = 1;
2099 continue;
2100 } else {
2101 /* One or both of the next 2 is out of range, making this
2102 * an invalid escape sequence; just show the raw bytes
2103 * (below). */
2104 }
2105 }
2106
2107 /* Just show the raw byte. */
2108 putc (*cp, ce->ce_fp);
2109 if (digested) {
2110 if (*cp == '\n') {
2111 MD5Update (&mdContext, (unsigned char *) "\r\n",2);
2112 } else {
2113 MD5Update (&mdContext, (unsigned char *) cp, 1);
2114 }
2115 }
2116 if (ferror (ce->ce_fp)) {
2117 content_error (ce->ce_file, ct, "error writing to");
2118 goto clean_up;
2119 }
2120 }
2121 }
2122 if (quoted) {
2123 content_error (NULL, ct,
2124 "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting");
2125 goto clean_up;
2126 }
2127
2128 fseek (ct->c_fp, 0L, SEEK_SET);
2129
2130 if (fflush (ce->ce_fp)) {
2131 content_error (ce->ce_file, ct, "error writing to");
2132 goto clean_up;
2133 }
2134
2135 if (digested) {
2136 unsigned char digest[16];
2137
2138 MD5Final (digest, &mdContext);
2139 if (memcmp((char *) digest, (char *) ct->c_digest,
2140 sizeof(digest) / sizeof(digest[0])))
2141 content_error (NULL, ct,
2142 "content integrity suspect (digest mismatch) -- continuing");
2143 else
2144 if (debugsw)
2145 fprintf (stderr, "content integrity confirmed\n");
2146 }
2147
2148 fseek (ce->ce_fp, 0L, SEEK_SET);
2149
2150 ready_to_go:
2151 *file = ce->ce_file;
2152 if (own_ct_fp) {
2153 fclose (ct->c_fp);
2154 ct->c_fp = NULL;
2155 }
2156 return fileno (ce->ce_fp);
2157
2158 clean_up:
2159 free_encoding (ct, 0);
2160 if (own_ct_fp) {
2161 fclose (ct->c_fp);
2162 ct->c_fp = NULL;
2163 }
2164 return NOTOK;
2165 }
2166
2167
2168 /*
2169 * 7BIT
2170 */
2171
2172 static int
2173 Init7Bit (CT ct)
2174 {
2175 if (init_encoding (ct, open7Bit) == NOTOK)
2176 return NOTOK;
2177
2178 ct->c_cesizefnx = NULL; /* no need to decode for real size */
2179 return OK;
2180 }
2181
2182
2183 int
2184 open7Bit (CT ct, char **file)
2185 {
2186 int cc, fd, len, own_ct_fp = 0;
2187 char buffer[BUFSIZ];
2188 /* sbeck -- handle suffixes */
2189 char *cp;
2190 CI ci;
2191 CE ce;
2192
2193 ce = ct->c_cefile;
2194 if (ce->ce_fp) {
2195 fseek (ce->ce_fp, 0L, SEEK_SET);
2196 goto ready_to_go;
2197 }
2198
2199 if (ce->ce_file) {
2200 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2201 content_error (ce->ce_file, ct, "unable to fopen for reading");
2202 return NOTOK;
2203 }
2204 goto ready_to_go;
2205 }
2206
2207 if (*file == NULL) {
2208 ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2209 ce->ce_unlink = 1;
2210 } else {
2211 ce->ce_file = add (*file, NULL);
2212 ce->ce_unlink = 0;
2213 }
2214
2215 /* sbeck@cise.ufl.edu -- handle suffixes */
2216 ci = &ct->c_ctinfo;
2217 snprintf (buffer, sizeof(buffer), "%s-suffix-%s/%s",
2218 invo_name, ci->ci_type, ci->ci_subtype);
2219 cp = context_find (buffer);
2220 if (cp == NULL || *cp == '\0') {
2221 snprintf (buffer, sizeof(buffer), "%s-suffix-%s", invo_name,
2222 ci->ci_type);
2223 cp = context_find (buffer);
2224 }
2225 if (cp != NULL && *cp != '\0') {
2226 if (ce->ce_unlink) {
2227 /* Temporary file already exists, so we rename to
2228 version with extension. */
2229 char *file_org = strdup(ce->ce_file);
2230 ce->ce_file = add (cp, ce->ce_file);
2231 if (rename(file_org, ce->ce_file)) {
2232 adios (ce->ce_file, "unable to rename %s to ", file_org);
2233 }
2234 free(file_org);
2235
2236 } else {
2237 ce->ce_file = add (cp, ce->ce_file);
2238 }
2239 }
2240
2241 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2242 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2243 return NOTOK;
2244 }
2245
2246 if (ct->c_type == CT_MULTIPART) {
2247 char **ap, **ep;
2248 CI ci = &ct->c_ctinfo;
2249
2250 len = 0;
2251 fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype);
2252 len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type)
2253 + 1 + strlen (ci->ci_subtype);
2254 for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
2255 putc (';', ce->ce_fp);
2256 len++;
2257
2258 snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
2259
2260 if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) {
2261 fputs ("\n\t", ce->ce_fp);
2262 len = 8;
2263 } else {
2264 putc (' ', ce->ce_fp);
2265 len++;
2266 }
2267 fprintf (ce->ce_fp, "%s", buffer);
2268 len += cc;
2269 }
2270
2271 if (ci->ci_comment) {
2272 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
2273 fputs ("\n\t", ce->ce_fp);
2274 len = 8;
2275 }
2276 else {
2277 putc (' ', ce->ce_fp);
2278 len++;
2279 }
2280 fprintf (ce->ce_fp, "(%s)", ci->ci_comment);
2281 len += cc;
2282 }
2283 fprintf (ce->ce_fp, "\n");
2284 if (ct->c_id)
2285 fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id);
2286 if (ct->c_descr)
2287 fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr);
2288 if (ct->c_dispo)
2289 fprintf (ce->ce_fp, "%s:%s", DISPO_FIELD, ct->c_dispo);
2290 fprintf (ce->ce_fp, "\n");
2291 }
2292
2293 if ((len = ct->c_end - ct->c_begin) < 0)
2294 adios (NULL, "internal error(3)");
2295
2296 if (! ct->c_fp) {
2297 if ((ct->c_fp = fopen (ct->c_file, "r")) == NULL) {
2298 content_error (ct->c_file, ct, "unable to open for reading");
2299 return NOTOK;
2300 }
2301 own_ct_fp = 1;
2302 }
2303
2304 lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET);
2305 while (len > 0)
2306 switch (cc = read (fd, buffer, sizeof(buffer) - 1)) {
2307 case NOTOK:
2308 content_error (ct->c_file, ct, "error reading from");
2309 goto clean_up;
2310
2311 case OK:
2312 content_error (NULL, ct, "premature eof");
2313 goto clean_up;
2314
2315 default:
2316 if (cc > len)
2317 cc = len;
2318 len -= cc;
2319
2320 fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp);
2321 if (ferror (ce->ce_fp)) {
2322 content_error (ce->ce_file, ct, "error writing to");
2323 goto clean_up;
2324 }
2325 }
2326
2327 fseek (ct->c_fp, 0L, SEEK_SET);
2328
2329 if (fflush (ce->ce_fp)) {
2330 content_error (ce->ce_file, ct, "error writing to");
2331 goto clean_up;
2332 }
2333
2334 fseek (ce->ce_fp, 0L, SEEK_SET);
2335
2336 ready_to_go:
2337 *file = ce->ce_file;
2338 if (own_ct_fp) {
2339 fclose (ct->c_fp);
2340 ct->c_fp = NULL;
2341 }
2342 return fileno (ce->ce_fp);
2343
2344 clean_up:
2345 free_encoding (ct, 0);
2346 if (own_ct_fp) {
2347 fclose (ct->c_fp);
2348 ct->c_fp = NULL;
2349 }
2350 return NOTOK;
2351 }
2352
2353
2354 /*
2355 * External
2356 */
2357
2358 static int
2359 openExternal (CT ct, CT cb, CE ce, char **file, int *fd)
2360 {
2361 char cachefile[BUFSIZ];
2362
2363 if (ce->ce_fp) {
2364 fseek (ce->ce_fp, 0L, SEEK_SET);
2365 goto ready_already;
2366 }
2367
2368 if (ce->ce_file) {
2369 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2370 content_error (ce->ce_file, ct, "unable to fopen for reading");
2371 return NOTOK;
2372 }
2373 goto ready_already;
2374 }
2375
2376 if (find_cache (ct, rcachesw, (int *) 0, cb->c_id,
2377 cachefile, sizeof(cachefile)) != NOTOK) {
2378 if ((ce->ce_fp = fopen (cachefile, "r"))) {
2379 ce->ce_file = getcpy (cachefile);
2380 ce->ce_unlink = 0;
2381 goto ready_already;
2382 } else {
2383 admonish (cachefile, "unable to fopen for reading");
2384 }
2385 }
2386
2387 return OK;
2388
2389 ready_already:
2390 *file = ce->ce_file;
2391 *fd = fileno (ce->ce_fp);
2392 return DONE;
2393 }
2394
2395 /*
2396 * File
2397 */
2398
2399 static int
2400 InitFile (CT ct)
2401 {
2402 return init_encoding (ct, openFile);
2403 }
2404
2405
2406 static int
2407 openFile (CT ct, char **file)
2408 {
2409 int fd, cachetype;
2410 char cachefile[BUFSIZ];
2411 struct exbody *e = ct->c_ctexbody;
2412 CE ce = ct->c_cefile;
2413
2414 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2415 case NOTOK:
2416 return NOTOK;
2417
2418 case OK:
2419 break;
2420
2421 case DONE:
2422 return fd;
2423 }
2424
2425 if (!e->eb_name) {
2426 content_error (NULL, ct, "missing name parameter");
2427 return NOTOK;
2428 }
2429
2430 ce->ce_file = getcpy (e->eb_name);
2431 ce->ce_unlink = 0;
2432
2433 if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) {
2434 content_error (ce->ce_file, ct, "unable to fopen for reading");
2435 return NOTOK;
2436 }
2437
2438 if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2439 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2440 cachefile, sizeof(cachefile)) != NOTOK) {
2441 int mask;
2442 FILE *fp;
2443
2444 mask = umask (cachetype ? ~m_gmprot () : 0222);
2445 if ((fp = fopen (cachefile, "w"))) {
2446 int cc;
2447 char buffer[BUFSIZ];
2448 FILE *gp = ce->ce_fp;
2449
2450 fseek (gp, 0L, SEEK_SET);
2451
2452 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2453 > 0)
2454 fwrite (buffer, sizeof(*buffer), cc, fp);
2455 fflush (fp);
2456
2457 if (ferror (gp)) {
2458 admonish (ce->ce_file, "error reading");
2459 unlink (cachefile);
2460 }
2461 else
2462 if (ferror (fp)) {
2463 admonish (cachefile, "error writing");
2464 unlink (cachefile);
2465 }
2466 fclose (fp);
2467 }
2468 umask (mask);
2469 }
2470
2471 fseek (ce->ce_fp, 0L, SEEK_SET);
2472 *file = ce->ce_file;
2473 return fileno (ce->ce_fp);
2474 }
2475
2476 /*
2477 * FTP
2478 */
2479
2480 static int
2481 InitFTP (CT ct)
2482 {
2483 return init_encoding (ct, openFTP);
2484 }
2485
2486
2487 static int
2488 openFTP (CT ct, char **file)
2489 {
2490 int cachetype, caching, fd;
2491 int len, buflen;
2492 char *bp, *ftp, *user, *pass;
2493 char buffer[BUFSIZ], cachefile[BUFSIZ];
2494 struct exbody *e;
2495 CE ce;
2496 static char *username = NULL;
2497 static char *password = NULL;
2498
2499 e = ct->c_ctexbody;
2500 ce = ct->c_cefile;
2501
2502 if ((ftp = context_find (nmhaccessftp)) && !*ftp)
2503 ftp = NULL;
2504
2505 if (!ftp)
2506 return NOTOK;
2507
2508 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2509 case NOTOK:
2510 return NOTOK;
2511
2512 case OK:
2513 break;
2514
2515 case DONE:
2516 return fd;
2517 }
2518
2519 if (!e->eb_name || !e->eb_site) {
2520 content_error (NULL, ct, "missing %s parameter",
2521 e->eb_name ? "site": "name");
2522 return NOTOK;
2523 }
2524
2525 if (xpid) {
2526 if (xpid < 0)
2527 xpid = -xpid;
2528 pidcheck (pidwait (xpid, NOTOK));
2529 xpid = 0;
2530 }
2531
2532 /* Get the buffer ready to go */
2533 bp = buffer;
2534 buflen = sizeof(buffer);
2535
2536 /*
2537 * Construct the query message for user
2538 */
2539 snprintf (bp, buflen, "Retrieve %s", e->eb_name);
2540 len = strlen (bp);
2541 bp += len;
2542 buflen -= len;
2543
2544 if (e->eb_partno) {
2545 snprintf (bp, buflen, " (content %s)", e->eb_partno);
2546 len = strlen (bp);
2547 bp += len;
2548 buflen -= len;
2549 }
2550
2551 snprintf (bp, buflen, "\n using %sFTP from site %s",
2552 e->eb_flags ? "anonymous " : "", e->eb_site);
2553 len = strlen (bp);
2554 bp += len;
2555 buflen -= len;
2556
2557 if (e->eb_size > 0) {
2558 snprintf (bp, buflen, " (%lu octets)", e->eb_size);
2559 len = strlen (bp);
2560 bp += len;
2561 buflen -= len;
2562 }
2563 snprintf (bp, buflen, "? ");
2564
2565 /*
2566 * Now, check the answer
2567 */
2568 if (!getanswer (buffer))
2569 return NOTOK;
2570
2571 if (e->eb_flags) {
2572 user = "anonymous";
2573 snprintf (buffer, sizeof(buffer), "%s@%s", getusername (),
2574 LocalName (1));
2575 pass = buffer;
2576 } else {
2577 ruserpass (e->eb_site, &username, &password);
2578 user = username;
2579 pass = password;
2580 }
2581
2582 ce->ce_unlink = (*file == NULL);
2583 caching = 0;
2584 cachefile[0] = '\0';
2585 if ((!e->eb_permission || mh_strcasecmp (e->eb_permission, "read-write"))
2586 && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id,
2587 cachefile, sizeof(cachefile)) != NOTOK) {
2588 if (*file == NULL) {
2589 ce->ce_unlink = 0;
2590 caching = 1;
2591 }
2592 }
2593
2594 if (*file)
2595 ce->ce_file = add (*file, NULL);
2596 else if (caching)
2597 ce->ce_file = add (cachefile, NULL);
2598 else
2599 ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2600
2601 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2602 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2603 return NOTOK;
2604 }
2605
2606 {
2607 int child_id, i, vecp;
2608 char *vec[9];
2609
2610 vecp = 0;
2611 vec[vecp++] = r1bindex (ftp, '/');
2612 vec[vecp++] = e->eb_site;
2613 vec[vecp++] = user;
2614 vec[vecp++] = pass;
2615 vec[vecp++] = e->eb_dir;
2616 vec[vecp++] = e->eb_name;
2617 vec[vecp++] = ce->ce_file,
2618 vec[vecp++] = e->eb_mode && !mh_strcasecmp (e->eb_mode, "ascii")
2619 ? "ascii" : "binary";
2620 vec[vecp] = NULL;
2621
2622 fflush (stdout);
2623
2624 for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
2625 sleep (5);
2626 switch (child_id) {
2627 case NOTOK:
2628 adios ("fork", "unable to");
2629 /* NOTREACHED */
2630
2631 case OK:
2632 close (fileno (ce->ce_fp));
2633 execvp (ftp, vec);
2634 fprintf (stderr, "unable to exec ");
2635 perror (ftp);
2636 _exit (-1);
2637 /* NOTREACHED */
2638
2639 default:
2640 if (pidXwait (child_id, NULL)) {
2641 username = password = NULL;
2642 ce->ce_unlink = 1;
2643 return NOTOK;
2644 }
2645 break;
2646 }
2647 }
2648
2649 if (cachefile[0]) {
2650 if (caching)
2651 chmod (cachefile, cachetype ? m_gmprot () : 0444);
2652 else {
2653 int mask;
2654 FILE *fp;
2655
2656 mask = umask (cachetype ? ~m_gmprot () : 0222);
2657 if ((fp = fopen (cachefile, "w"))) {
2658 int cc;
2659 FILE *gp = ce->ce_fp;
2660
2661 fseek (gp, 0L, SEEK_SET);
2662
2663 while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp))
2664 > 0)
2665 fwrite (buffer, sizeof(*buffer), cc, fp);
2666 fflush (fp);
2667
2668 if (ferror (gp)) {
2669 admonish (ce->ce_file, "error reading");
2670 unlink (cachefile);
2671 }
2672 else
2673 if (ferror (fp)) {
2674 admonish (cachefile, "error writing");
2675 unlink (cachefile);
2676 }
2677 fclose (fp);
2678 }
2679 umask (mask);
2680 }
2681 }
2682
2683 fseek (ce->ce_fp, 0L, SEEK_SET);
2684 *file = ce->ce_file;
2685 return fileno (ce->ce_fp);
2686 }
2687
2688
2689 /*
2690 * Mail
2691 */
2692
2693 static int
2694 InitMail (CT ct)
2695 {
2696 return init_encoding (ct, openMail);
2697 }
2698
2699
2700 static int
2701 openMail (CT ct, char **file)
2702 {
2703 int child_id, fd, i, vecp;
2704 int len, buflen;
2705 char *bp, buffer[BUFSIZ], *vec[7];
2706 struct exbody *e = ct->c_ctexbody;
2707 CE ce = ct->c_cefile;
2708
2709 switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) {
2710 case NOTOK:
2711 return NOTOK;
2712
2713 case OK:
2714 break;
2715
2716 case DONE:
2717 return fd;
2718 }
2719
2720 if (!e->eb_server) {
2721 content_error (NULL, ct, "missing server parameter");
2722 return NOTOK;
2723 }
2724
2725 if (xpid) {
2726 if (xpid < 0)
2727 xpid = -xpid;
2728 pidcheck (pidwait (xpid, NOTOK));
2729 xpid = 0;
2730 }
2731
2732 /* Get buffer ready to go */
2733 bp = buffer;
2734 buflen = sizeof(buffer);
2735
2736 /* Now, construct query message */
2737 snprintf (bp, buflen, "Retrieve content");
2738 len = strlen (bp);
2739 bp += len;
2740 buflen -= len;
2741
2742 if (e->eb_partno) {
2743 snprintf (bp, buflen, " %s", e->eb_partno);
2744 len = strlen (bp);
2745 bp += len;
2746 buflen -= len;
2747 }
2748
2749 snprintf (bp, buflen, " by asking %s\n\n%s\n? ",
2750 e->eb_server,
2751 e->eb_subject ? e->eb_subject : e->eb_body);
2752
2753 /* Now, check answer */
2754 if (!getanswer (buffer))
2755 return NOTOK;
2756
2757 vecp = 0;
2758 vec[vecp++] = r1bindex (mailproc, '/');
2759 vec[vecp++] = e->eb_server;
2760 vec[vecp++] = "-subject";
2761 vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request";
2762 vec[vecp++] = "-body";
2763 vec[vecp++] = e->eb_body;
2764 vec[vecp] = NULL;
2765
2766 for (i = 0; (child_id = vfork()) == NOTOK && i < 5; i++)
2767 sleep (5);
2768 switch (child_id) {
2769 case NOTOK:
2770 advise ("fork", "unable to");
2771 return NOTOK;
2772
2773 case OK:
2774 execvp (mailproc, vec);
2775 fprintf (stderr, "unable to exec ");
2776 perror (mailproc);
2777 _exit (-1);
2778 /* NOTREACHED */
2779
2780 default:
2781 if (pidXwait (child_id, NULL) == OK)
2782 advise (NULL, "request sent");
2783 break;
2784 }
2785
2786 if (*file == NULL) {
2787 ce->ce_file = add (m_mktemp(tmp, NULL, NULL), NULL);
2788 ce->ce_unlink = 1;
2789 } else {
2790 ce->ce_file = add (*file, NULL);
2791 ce->ce_unlink = 0;
2792 }
2793
2794 if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) {
2795 content_error (ce->ce_file, ct, "unable to fopen for reading/writing");
2796 return NOTOK;
2797 }
2798
2799 /* showproc is for mhshow and mhstore, though mhlist -debug
2800 * prints it, too. */
2801 if (ct->c_showproc)
2802 free (ct->c_showproc);
2803 ct->c_showproc = add ("true", NULL);
2804
2805 fseek (ce->ce_fp, 0L, SEEK_SET);
2806 *file = ce->ce_file;
2807 return fileno (ce->ce_fp);
2808 }
2809
2810
2811 static int
2812 readDigest (CT ct, char *cp)
2813 {
2814 int bitno, skip;
2815 uint32_t bits;
2816 char *bp = cp;
2817 unsigned char *dp, value, *ep;
2818
2819 bitno = 18;
2820 bits = 0L;
2821 skip = 0;
2822
2823 for (ep = (dp = ct->c_digest)
2824 + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++)
2825 switch (*cp) {
2826 default:
2827 if (skip
2828 || (*cp & 0x80)
2829 || (value = b642nib[*cp & 0x7f]) > 0x3f) {
2830 if (debugsw)
2831 fprintf (stderr, "invalid BASE64 encoding\n");
2832 return NOTOK;
2833 }
2834
2835 bits |= value << bitno;
2836 test_end:
2837 if ((bitno -= 6) < 0) {
2838 if (dp + (3 - skip) > ep)
2839 goto invalid_digest;
2840 *dp++ = (bits >> 16) & 0xff;
2841 if (skip < 2) {
2842 *dp++ = (bits >> 8) & 0xff;
2843 if (skip < 1)
2844 *dp++ = bits & 0xff;
2845 }
2846 bitno = 18;
2847 bits = 0L;
2848 skip = 0;
2849 }
2850 break;
2851
2852 case '=':
2853 if (++skip > 3)
2854 goto self_delimiting;
2855 goto test_end;
2856 }
2857 if (bitno != 18) {
2858 if (debugsw)
2859 fprintf (stderr, "premature ending (bitno %d)\n", bitno);
2860
2861 return NOTOK;
2862 }
2863 self_delimiting:
2864 if (dp != ep) {
2865 invalid_digest:
2866 if (debugsw) {
2867 while (*cp)
2868 cp++;
2869 fprintf (stderr, "invalid MD5 digest (got %d octets)\n",
2870 (int)(cp - bp));
2871 }
2872
2873 return NOTOK;
2874 }
2875
2876 if (debugsw) {
2877 fprintf (stderr, "MD5 digest=");
2878 for (dp = ct->c_digest; dp < ep; dp++)
2879 fprintf (stderr, "%02x", *dp & 0xff);
2880 fprintf (stderr, "\n");
2881 }
2882
2883 return OK;
2884 }