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