]> diplodocus.org Git - nmh/blob - uip/mhbuildsbr.c
555e3b5c673d9fe70cac83acd493a09c5f71badb
[nmh] / uip / mhbuildsbr.c
1
2 /*
3 * mhbuildsbr.c -- routines to expand/translate MIME composition files
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 /*
11 * This code was originally part of mhn.c. I split it into
12 * a separate program (mhbuild.c) and then later split it
13 * again (mhbuildsbr.c). But the code still has some of
14 * the mhn.c code in it. This program needs additional
15 * streamlining and removal of unneeded code.
16 */
17
18 #include <h/mh.h>
19 #include <fcntl.h>
20 #include <h/signals.h>
21 #include <h/md5.h>
22 #include <h/mts.h>
23 #include <h/tws.h>
24 #include <h/mime.h>
25 #include <h/mhparse.h>
26 #include <h/utils.h>
27
28 #ifdef HAVE_SYS_TIME_H
29 # include <sys/time.h>
30 #endif
31 #include <time.h>
32
33
34 extern int debugsw;
35
36 extern int listsw;
37 extern int rfc934sw;
38 extern int contentidsw;
39
40 /* cache policies */
41 extern int rcachesw; /* mhcachesbr.c */
42 extern int wcachesw; /* mhcachesbr.c */
43
44 pid_t xpid = 0;
45
46 static char prefix[] = "----- =_aaaaaaaaaa";
47
48 struct attach_list {
49 char *filename;
50 struct attach_list *next;
51 };
52
53 /*
54 * Maximum size of URL token in message/external-body
55 */
56
57 #define MAXURLTOKEN 40
58
59
60 /* mhmisc.c */
61 void content_error (char *, CT, char *, ...);
62
63 /* mhcachesbr.c */
64 int find_cache (CT, int, int *, char *, char *, int);
65
66 /* mhfree.c */
67 void free_ctinfo (CT);
68 void free_encoding (CT, int);
69
70 /*
71 * static prototypes
72 */
73 static int init_decoded_content (CT);
74 static void setup_attach_content(CT, char *);
75 static char *fgetstr (char *, int, FILE *);
76 static int user_content (FILE *, char *, CT *);
77 static void set_id (CT, int);
78 static int compose_content (CT, int);
79 static int scan_content (CT, size_t);
80 static int build_headers (CT, int);
81 static char *calculate_digest (CT, int);
82
83
84 static unsigned char directives_stack[32];
85 static unsigned int directives_index;
86
87 static int do_direct(void)
88 {
89 return directives_stack[directives_index];
90 }
91
92 static void directive_onoff(int onoff)
93 {
94 if (directives_index >= sizeof(directives_stack) - 1) {
95 fprintf(stderr, "mhbuild: #on/off overflow, continuing\n");
96 return;
97 }
98 directives_stack[++directives_index] = onoff;
99 }
100
101 static void directive_init(int onoff)
102 {
103 directives_index = 0;
104 directives_stack[0] = onoff;
105 }
106
107 static void directive_pop(void)
108 {
109 if (directives_index > 0)
110 directives_index--;
111 else
112 fprintf(stderr, "mhbuild: #pop underflow, continuing\n");
113 }
114
115 /*
116 * Main routine for translating composition file
117 * into valid MIME message. It translates the draft
118 * into a content structure (actually a tree of content
119 * structures). This message then can be manipulated
120 * in various ways, including being output via
121 * output_message().
122 */
123
124 CT
125 build_mime (char *infile, int autobuild, int dist, int directives,
126 int header_encoding, size_t maxunencoded, int verbose)
127 {
128 int compnum, state;
129 char buf[BUFSIZ], name[NAMESZ];
130 char *cp, *np, *vp;
131 struct multipart *m;
132 struct part **pp;
133 CT ct;
134 FILE *in;
135 HF hp;
136 m_getfld_state_t gstate = 0;
137 struct attach_list *attach_head = NULL, *attach_tail = NULL, *at_entry;
138
139 directive_init(directives);
140
141 umask (~m_gmprot ());
142
143 /* open the composition draft */
144 if ((in = fopen (infile, "r")) == NULL)
145 adios (infile, "unable to open for reading");
146
147 /*
148 * Allocate space for primary (outside) content
149 */
150 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
151 adios (NULL, "out of memory");
152
153 /*
154 * Allocate structure for handling decoded content
155 * for this part. We don't really need this, but
156 * allocate it to remain consistent.
157 */
158 init_decoded_content (ct);
159
160 /*
161 * Parse some of the header fields in the composition
162 * draft into the linked list of header fields for
163 * the new MIME message.
164 */
165 m_getfld_track_filepos (&gstate, in);
166 for (compnum = 1;;) {
167 int bufsz = sizeof buf;
168 switch (state = m_getfld (&gstate, name, buf, &bufsz, in)) {
169 case FLD:
170 case FLDPLUS:
171 compnum++;
172
173 /* abort if draft has Mime-Version or C-T-E header field */
174 if (strcasecmp (name, VRSN_FIELD) == 0 ||
175 strcasecmp (name, ENCODING_FIELD) == 0) {
176 if (autobuild) {
177 fclose(in);
178 return NULL;
179 } else {
180 adios (NULL, "draft shouldn't contain %s: field", name);
181 }
182 }
183
184 /* ignore any Content-Type fields in the header */
185 if (!strcasecmp (name, TYPE_FIELD)) {
186 while (state == FLDPLUS) {
187 bufsz = sizeof buf;
188 state = m_getfld (&gstate, name, buf, &bufsz, in);
189 }
190 goto finish_field;
191 }
192
193 /* get copies of the buffers */
194 np = add (name, NULL);
195 vp = add (buf, NULL);
196
197 /* if necessary, get rest of field */
198 while (state == FLDPLUS) {
199 bufsz = sizeof buf;
200 state = m_getfld (&gstate, name, buf, &bufsz, in);
201 vp = add (buf, vp); /* add to previous value */
202 }
203
204 /*
205 * Now add the header data to the list, unless it's an attach
206 * header; in that case, add it to our attach list
207 */
208
209 if (strcasecmp(ATTACH_FIELD, np) == 0) {
210 struct attach_list *entry;
211 char *s = vp, *e = vp + strlen(vp) - 1;
212 free(np);
213
214 /*
215 * Make sure we can find the start of this filename.
216 * If it's blank, we skip completely. Otherwise, strip
217 * off any leading spaces and trailing newlines.
218 */
219
220 while (isspace((unsigned char) *s))
221 s++;
222
223 while (e > s && *e == '\n')
224 *e-- = '\0';
225
226 if (*s == '\0') {
227 free(vp);
228 goto finish_field;
229 }
230
231 entry = mh_xmalloc(sizeof(*entry));
232 entry->filename = getcpy(s);
233 entry->next = NULL;
234 free(vp);
235
236 if (attach_tail) {
237 attach_tail->next = entry;
238 attach_tail = entry;
239 } else {
240 attach_head = attach_tail = entry;
241 }
242 } else {
243 add_header (ct, np, vp);
244 }
245
246 finish_field:
247 /* if this wasn't the last header field, then continue */
248 continue;
249
250 case BODY:
251 fseek (in, (long) (-strlen (buf)), SEEK_CUR);
252 /* fall through */
253 case FILEEOF:
254 break;
255
256 case LENERR:
257 case FMTERR:
258 adios (NULL, "message format error in component #%d", compnum);
259
260 default:
261 adios (NULL, "getfld() returned %d", state);
262 }
263 break;
264 }
265 m_getfld_state_destroy (&gstate);
266
267 /*
268 * Iterate through the list of headers and call the function to MIME-ify
269 * them if required.
270 */
271
272 for (hp = ct->c_first_hf; hp != NULL; hp = hp->next) {
273 if (encode_rfc2047(hp->name, &hp->value, header_encoding, NULL)) {
274 adios(NULL, "Unable to encode header \"%s\"", hp->name);
275 }
276 }
277
278 /*
279 * Now add the MIME-Version header field
280 * to the list of header fields.
281 */
282
283 if (! dist) {
284 np = add (VRSN_FIELD, NULL);
285 vp = concat (" ", VRSN_VALUE, "\n", NULL);
286 add_header (ct, np, vp);
287 }
288
289 /*
290 * We initally assume we will find multiple contents in the
291 * draft. So create a multipart/mixed content to hold everything.
292 * We can remove this later, if it is not needed.
293 */
294 if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK)
295 done (1);
296 ct->c_type = CT_MULTIPART;
297 ct->c_subtype = MULTI_MIXED;
298 ct->c_file = add (infile, NULL);
299
300 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
301 adios (NULL, "out of memory");
302 ct->c_ctparams = (void *) m;
303 pp = &m->mp_parts;
304
305 /*
306 * read and parse the composition file
307 * and the directives it contains.
308 */
309 while (fgetstr (buf, sizeof(buf) - 1, in)) {
310 struct part *part;
311 CT p;
312
313 if (user_content (in, buf, &p) == DONE) {
314 admonish (NULL, "ignoring spurious #end");
315 continue;
316 }
317 if (!p)
318 continue;
319
320 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
321 adios (NULL, "out of memory");
322 *pp = part;
323 pp = &part->mp_next;
324 part->mp_part = p;
325 }
326
327 /*
328 * Add any Attach headers to the list of MIME parts at the end of the
329 * message.
330 */
331
332 for (at_entry = attach_head; at_entry; ) {
333 struct attach_list *at_prev = at_entry;
334 struct part *part;
335 CT p;
336
337 if (access(at_entry->filename, R_OK) != 0) {
338 adios("reading", "Unable to open %s for", at_entry->filename);
339 }
340
341 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
342 adios(NULL, "out of memory");
343
344 init_decoded_content(p);
345
346 /*
347 * Initialize our content structure based on the filename,
348 * and fill in all of the relevant fields. Also place MIME
349 * parameters in the attributes array.
350 */
351
352 setup_attach_content(p, at_entry->filename);
353
354 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
355 adios (NULL, "out of memory");
356 *pp = part;
357 pp = &part->mp_next;
358 part->mp_part = p;
359
360 at_entry = at_entry->next;
361 free(at_prev->filename);
362 free(at_prev);
363 }
364
365 /*
366 * To allow for empty message bodies, if we've found NO content at all
367 * yet cook up an empty text/plain part.
368 */
369
370 if (!m->mp_parts) {
371 CT p;
372 struct part *part;
373 struct text *t;
374
375 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
376 adios(NULL, "out of memory");
377
378 init_decoded_content(p);
379
380 if (get_ctinfo ("text/plain", p, 0) == NOTOK)
381 done (1);
382
383 p->c_type = CT_TEXT;
384 p->c_subtype = TEXT_PLAIN;
385 p->c_encoding = CE_7BIT;
386 p->c_file = getcpy(infile);
387 /*
388 * Sigh. ce_file contains the "decoded" contents of this part.
389 * So this seems like the best option available since we're going
390 * to call scan_content() on this.
391 */
392 p->c_cefile.ce_file = getcpy("/dev/null");
393 p->c_begin = ftell(in);
394 p->c_end = ftell(in);
395
396 if ((t = (struct text *) calloc (1, sizeof (*t))) == NULL)
397 adios (NULL, "out of memory");
398
399 t->tx_charset = CHARSET_SPECIFIED;
400 p->c_ctparams = t;
401
402 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
403 adios (NULL, "out of memory");
404 *pp = part;
405 pp = &part->mp_next;
406 part->mp_part = p;
407 }
408
409 /*
410 * close the composition draft since
411 * it's not needed any longer.
412 */
413 fclose (in);
414
415 /*
416 * If only one content was found, then remove and
417 * free the outer multipart content.
418 */
419 if (!m->mp_parts->mp_next) {
420 CT p;
421
422 p = m->mp_parts->mp_part;
423 m->mp_parts->mp_part = NULL;
424
425 /* move header fields */
426 p->c_first_hf = ct->c_first_hf;
427 p->c_last_hf = ct->c_last_hf;
428 ct->c_first_hf = NULL;
429 ct->c_last_hf = NULL;
430
431 free_content (ct);
432 ct = p;
433 } else {
434 set_id (ct, 1);
435 }
436
437 /*
438 * Fill out, or expand directives. Parse and execute
439 * commands specified by profile composition strings.
440 */
441 compose_content (ct, verbose);
442
443 if ((cp = strchr(prefix, 'a')) == NULL)
444 adios (NULL, "internal error(4)");
445
446 /*
447 * Scan the contents. Choose a transfer encoding, and
448 * check if prefix for multipart boundary clashes with
449 * any of the contents.
450 */
451 while (scan_content (ct, maxunencoded) == NOTOK) {
452 if (*cp < 'z') {
453 (*cp)++;
454 } else {
455 if (*++cp == 0)
456 adios (NULL, "giving up trying to find a unique delimiter string");
457 else
458 (*cp)++;
459 }
460 }
461
462 /* Build the rest of the header field structures */
463 if (! dist)
464 build_headers (ct, header_encoding);
465
466 return ct;
467 }
468
469
470 /*
471 * Set up structures for placing unencoded
472 * content when building parts.
473 */
474
475 static int
476 init_decoded_content (CT ct)
477 {
478 ct->c_ceopenfnx = open7Bit; /* since unencoded */
479 ct->c_ceclosefnx = close_encoding;
480 ct->c_cesizefnx = NULL; /* since unencoded */
481
482 return OK;
483 }
484
485
486 static char *
487 fgetstr (char *s, int n, FILE *stream)
488 {
489 char *cp, *ep;
490 int o_n = n;
491
492 while(1) {
493 for (ep = (cp = s) + o_n; cp < ep; ) {
494 int i;
495
496 if (!fgets (cp, n, stream))
497 return (cp != s ? s : NULL);
498
499 if (cp == s && *cp != '#')
500 return s;
501
502 cp += (i = strlen (cp)) - 1;
503 if (i <= 1 || *cp-- != '\n' || *cp != '\\')
504 break;
505 *cp = '\0';
506 n -= (i - 2);
507 }
508
509 if (strcmp(s, "#on\n") == 0) {
510 directive_onoff(1);
511 } else if (strcmp(s, "#off\n") == 0) {
512 directive_onoff(0);
513 } else if (strcmp(s, "#pop\n") == 0) {
514 directive_pop();
515 } else {
516 break;
517 }
518 }
519
520 return s;
521 }
522
523
524 /*
525 * Parse the composition draft for text and directives.
526 * Do initial setup of Content structure.
527 */
528
529 static int
530 user_content (FILE *in, char *buf, CT *ctp)
531 {
532 int extrnal, vrsn;
533 char *cp, **ap;
534 char buffer[BUFSIZ];
535 struct multipart *m;
536 struct part **pp;
537 struct stat st;
538 struct str2init *s2i;
539 CI ci;
540 CT ct;
541 CE ce;
542
543 if (buf[0] == '\n' || (do_direct() && strcmp (buf, "#\n") == 0)) {
544 *ctp = NULL;
545 return OK;
546 }
547
548 /* allocate basic Content structure */
549 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
550 adios (NULL, "out of memory");
551 *ctp = ct;
552
553 /* allocate basic structure for handling decoded content */
554 init_decoded_content (ct);
555 ce = &ct->c_cefile;
556
557 ci = &ct->c_ctinfo;
558 set_id (ct, 0);
559
560 /*
561 * Handle inline text. Check if line
562 * is one of the following forms:
563 *
564 * 1) doesn't begin with '#' (implicit directive)
565 * 2) begins with "##" (implicit directive)
566 * 3) begins with "#<"
567 */
568 if (!do_direct() || buf[0] != '#' || buf[1] == '#' || buf[1] == '<') {
569 int headers;
570 int inlineD;
571 long pos;
572 char content[BUFSIZ];
573 FILE *out;
574 char *cp;
575
576 if ((cp = m_mktemp2(NULL, invo_name, NULL, &out)) == NULL) {
577 adios("mhbuildsbr", "unable to create temporary file in %s",
578 get_temp_dir());
579 }
580
581 /* use a temp file to collect the plain text lines */
582 ce->ce_file = add (cp, NULL);
583 ce->ce_unlink = 1;
584
585 if (do_direct() && (buf[0] == '#' && buf[1] == '<')) {
586 strncpy (content, buf + 2, sizeof(content));
587 inlineD = 1;
588 goto rock_and_roll;
589 } else {
590 inlineD = 0;
591 }
592
593 /* the directive is implicit */
594 strncpy (content, "text/plain", sizeof(content));
595 headers = 0;
596 strncpy (buffer, (!do_direct() || buf[0] != '#') ? buf : buf + 1, sizeof(buffer));
597 for (;;) {
598 int i;
599
600 if (headers >= 0 && do_direct() && uprf (buffer, DESCR_FIELD)
601 && buffer[i = strlen (DESCR_FIELD)] == ':') {
602 headers = 1;
603
604 again_descr:
605 ct->c_descr = add (buffer + i + 1, ct->c_descr);
606 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
607 adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD);
608 switch (buffer[0]) {
609 case ' ':
610 case '\t':
611 i = -1;
612 goto again_descr;
613
614 case '#':
615 adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD);
616 /* NOTREACHED */
617
618 default:
619 break;
620 }
621 }
622
623 if (headers >= 0 && do_direct() && uprf (buffer, DISPO_FIELD)
624 && buffer[i = strlen (DISPO_FIELD)] == ':') {
625 headers = 1;
626
627 again_dispo:
628 ct->c_dispo = add (buffer + i + 1, ct->c_dispo);
629 if (!fgetstr (buffer, sizeof(buffer) - 1, in))
630 adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD);
631 switch (buffer[0]) {
632 case ' ':
633 case '\t':
634 i = -1;
635 goto again_dispo;
636
637 case '#':
638 adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD);
639 /* NOTREACHED */
640
641 default:
642 break;
643 }
644 }
645
646 if (headers != 1 || buffer[0] != '\n')
647 fputs (buffer, out);
648
649 rock_and_roll:
650 headers = -1;
651 pos = ftell (in);
652 if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL)
653 break;
654 if (do_direct() && buffer[0] == '#') {
655 char *bp;
656
657 if (buffer[1] != '#')
658 break;
659 for (cp = (bp = buffer) + 1; *cp; cp++)
660 *bp++ = *cp;
661 *bp = '\0';
662 }
663 }
664
665 if (listsw)
666 ct->c_end = ftell (out);
667 fclose (out);
668
669 /* parse content type */
670 if (get_ctinfo (content, ct, inlineD) == NOTOK)
671 done (1);
672
673 for (s2i = str2cts; s2i->si_key; s2i++)
674 if (!strcasecmp (ci->ci_type, s2i->si_key))
675 break;
676 if (!s2i->si_key && !uprf (ci->ci_type, "X-"))
677 s2i++;
678
679 /*
680 * check type specified (possibly implicitly)
681 */
682 switch (ct->c_type = s2i->si_val) {
683 case CT_MESSAGE:
684 if (!strcasecmp (ci->ci_subtype, "rfc822")) {
685 ct->c_encoding = CE_7BIT;
686 goto call_init;
687 }
688 /* else fall... */
689 case CT_MULTIPART:
690 adios (NULL, "it doesn't make sense to define an in-line %s content",
691 ct->c_type == CT_MESSAGE ? "message" : "multipart");
692 /* NOTREACHED */
693
694 default:
695 call_init:
696 if ((ct->c_ctinitfnx = s2i->si_init))
697 (*ct->c_ctinitfnx) (ct);
698 break;
699 }
700
701 if (cp)
702 fseek (in, pos, SEEK_SET);
703 return OK;
704 }
705
706 /*
707 * If we've reached this point, the next line
708 * must be some type of explicit directive.
709 */
710
711 /* check if directive is external-type */
712 extrnal = (buf[1] == '@');
713
714 /* parse directive */
715 if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK)
716 done (1);
717
718 /* check directive against the list of MIME types */
719 for (s2i = str2cts; s2i->si_key; s2i++)
720 if (!strcasecmp (ci->ci_type, s2i->si_key))
721 break;
722
723 /*
724 * Check if the directive specified a valid type.
725 * This will happen if it was one of the following forms:
726 *
727 * #type/subtype (or)
728 * #@type/subtype
729 */
730 if (s2i->si_key) {
731 if (!ci->ci_subtype)
732 adios (NULL, "missing subtype in \"#%s\"", ci->ci_type);
733
734 switch (ct->c_type = s2i->si_val) {
735 case CT_MULTIPART:
736 adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"",
737 ci->ci_type, ci->ci_subtype);
738 /* NOTREACHED */
739
740 case CT_MESSAGE:
741 if (!strcasecmp (ci->ci_subtype, "partial"))
742 adios (NULL, "sorry, \"#%s/%s\" isn't supported",
743 ci->ci_type, ci->ci_subtype);
744 if (!strcasecmp (ci->ci_subtype, "external-body"))
745 adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"",
746 ci->ci_type, ci->ci_subtype);
747 use_forw:
748 adios (NULL,
749 "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"",
750 ci->ci_type, ci->ci_subtype);
751 /* NOTREACHED */
752
753 default:
754 if ((ct->c_ctinitfnx = s2i->si_init))
755 (*ct->c_ctinitfnx) (ct);
756 break;
757 }
758
759 /*
760 * #@type/subtype (external types directive)
761 */
762 if (extrnal) {
763 struct exbody *e;
764 CT p;
765
766 if (!ci->ci_magic)
767 adios (NULL, "need external information for \"#@%s/%s\"",
768 ci->ci_type, ci->ci_subtype);
769 p = ct;
770
771 snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic);
772 free (ci->ci_magic);
773 ci->ci_magic = NULL;
774
775 /*
776 * Since we are using the current Content structure to
777 * hold information about the type of the external
778 * reference, we need to create another Content structure
779 * for the message/external-body to wrap it in.
780 */
781 if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL)
782 adios (NULL, "out of memory");
783 *ctp = ct;
784 ci = &ct->c_ctinfo;
785 if (get_ctinfo (buffer, ct, 0) == NOTOK)
786 done (1);
787 ct->c_type = CT_MESSAGE;
788 ct->c_subtype = MESSAGE_EXTERNAL;
789
790 if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL)
791 adios (NULL, "out of memory");
792 ct->c_ctparams = (void *) e;
793
794 e->eb_parent = ct;
795 e->eb_content = p;
796 p->c_ctexbody = e;
797
798 if (params_external (ct, 1) == NOTOK)
799 done (1);
800
801 return OK;
802 }
803
804 /* Handle [file] argument */
805 if (ci->ci_magic) {
806 /* check if specifies command to execute */
807 if (*ci->ci_magic == '|' || *ci->ci_magic == '!') {
808 for (cp = ci->ci_magic + 1; isspace ((unsigned char) *cp); cp++)
809 continue;
810 if (!*cp)
811 adios (NULL, "empty pipe command for #%s directive", ci->ci_type);
812 cp = add (cp, NULL);
813 free (ci->ci_magic);
814 ci->ci_magic = cp;
815 } else {
816 /* record filename of decoded contents */
817 ce->ce_file = ci->ci_magic;
818 if (access (ce->ce_file, R_OK) == NOTOK)
819 adios ("reading", "unable to access %s for", ce->ce_file);
820 if (listsw && stat (ce->ce_file, &st) != NOTOK)
821 ct->c_end = (long) st.st_size;
822 ci->ci_magic = NULL;
823 }
824 return OK;
825 }
826
827 /*
828 * No [file] argument, so check profile for
829 * method to compose content.
830 */
831 snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s",
832 invo_name, ci->ci_type, ci->ci_subtype);
833 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
834 snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type);
835 if ((cp = context_find (buffer)) == NULL || *cp == '\0') {
836 content_error (NULL, ct, "don't know how to compose content");
837 done (1);
838 }
839 }
840 ci->ci_magic = add (cp, NULL);
841 return OK;
842 }
843
844 if (extrnal)
845 adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type);
846
847 /*
848 * Message directive
849 * #forw [+folder] [msgs]
850 */
851 if (!strcasecmp (ci->ci_type, "forw")) {
852 int msgnum;
853 char *folder, *arguments[MAXARGS];
854 struct msgs *mp;
855
856 if (ci->ci_magic) {
857 ap = brkstring (ci->ci_magic, " ", "\n");
858 copyip (ap, arguments, MAXARGS);
859 } else {
860 arguments[0] = "cur";
861 arguments[1] = NULL;
862 }
863 folder = NULL;
864
865 /* search the arguments for a folder name */
866 for (ap = arguments; *ap; ap++) {
867 cp = *ap;
868 if (*cp == '+' || *cp == '@') {
869 if (folder)
870 adios (NULL, "only one folder per #forw directive");
871 else
872 folder = pluspath (cp);
873 }
874 }
875
876 /* else, use the current folder */
877 if (!folder)
878 folder = add (getfolder (1), NULL);
879
880 if (!(mp = folder_read (folder, 0)))
881 adios (NULL, "unable to read folder %s", folder);
882 for (ap = arguments; *ap; ap++) {
883 cp = *ap;
884 if (*cp != '+' && *cp != '@')
885 if (!m_convert (mp, cp))
886 done (1);
887 }
888 free (folder);
889 free_ctinfo (ct);
890
891 /*
892 * If there is more than one message to include, make this
893 * a content of type "multipart/digest" and insert each message
894 * as a subpart. If there is only one message, then make this
895 * a content of type "message/rfc822".
896 */
897 if (mp->numsel > 1) {
898 /* we are forwarding multiple messages */
899 if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK)
900 done (1);
901 ct->c_type = CT_MULTIPART;
902 ct->c_subtype = MULTI_DIGEST;
903
904 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
905 adios (NULL, "out of memory");
906 ct->c_ctparams = (void *) m;
907 pp = &m->mp_parts;
908
909 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
910 if (is_selected(mp, msgnum)) {
911 struct part *part;
912 CT p;
913 CE pe;
914
915 if ((p = (CT) calloc (1, sizeof(*p))) == NULL)
916 adios (NULL, "out of memory");
917 init_decoded_content (p);
918 pe = &p->c_cefile;
919 if (get_ctinfo ("message/rfc822", p, 0) == NOTOK)
920 done (1);
921 p->c_type = CT_MESSAGE;
922 p->c_subtype = MESSAGE_RFC822;
923
924 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
925 pe->ce_file = add (buffer, NULL);
926 if (listsw && stat (pe->ce_file, &st) != NOTOK)
927 p->c_end = (long) st.st_size;
928
929 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
930 adios (NULL, "out of memory");
931 *pp = part;
932 pp = &part->mp_next;
933 part->mp_part = p;
934 }
935 }
936 } else {
937 /* we are forwarding one message */
938 if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK)
939 done (1);
940 ct->c_type = CT_MESSAGE;
941 ct->c_subtype = MESSAGE_RFC822;
942
943 msgnum = mp->lowsel;
944 snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum);
945 ce->ce_file = add (buffer, NULL);
946 if (listsw && stat (ce->ce_file, &st) != NOTOK)
947 ct->c_end = (long) st.st_size;
948 }
949
950 folder_free (mp); /* free folder/message structure */
951 return OK;
952 }
953
954 /*
955 * #end
956 */
957 if (!strcasecmp (ci->ci_type, "end")) {
958 free_content (ct);
959 *ctp = NULL;
960 return DONE;
961 }
962
963 /*
964 * #begin [ alternative | parallel ]
965 */
966 if (!strcasecmp (ci->ci_type, "begin")) {
967 if (!ci->ci_magic) {
968 vrsn = MULTI_MIXED;
969 cp = SubMultiPart[vrsn - 1].kv_key;
970 } else if (!strcasecmp (ci->ci_magic, "alternative")) {
971 vrsn = MULTI_ALTERNATE;
972 cp = SubMultiPart[vrsn - 1].kv_key;
973 } else if (!strcasecmp (ci->ci_magic, "parallel")) {
974 vrsn = MULTI_PARALLEL;
975 cp = SubMultiPart[vrsn - 1].kv_key;
976 } else if (uprf (ci->ci_magic, "digest")) {
977 goto use_forw;
978 } else {
979 vrsn = MULTI_UNKNOWN;
980 cp = ci->ci_magic;
981 }
982
983 free_ctinfo (ct);
984 snprintf (buffer, sizeof(buffer), "multipart/%s", cp);
985 if (get_ctinfo (buffer, ct, 0) == NOTOK)
986 done (1);
987 ct->c_type = CT_MULTIPART;
988 ct->c_subtype = vrsn;
989
990 if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL)
991 adios (NULL, "out of memory");
992 ct->c_ctparams = (void *) m;
993
994 pp = &m->mp_parts;
995 while (fgetstr (buffer, sizeof(buffer) - 1, in)) {
996 struct part *part;
997 CT p;
998
999 if (user_content (in, buffer, &p) == DONE) {
1000 if (!m->mp_parts)
1001 adios (NULL, "empty \"#begin ... #end\" sequence");
1002 return OK;
1003 }
1004 if (!p)
1005 continue;
1006
1007 if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL)
1008 adios (NULL, "out of memory");
1009 *pp = part;
1010 pp = &part->mp_next;
1011 part->mp_part = p;
1012 }
1013 admonish (NULL, "premature end-of-file, missing #end");
1014 return OK;
1015 }
1016
1017 /*
1018 * Unknown directive
1019 */
1020 adios (NULL, "unknown directive \"#%s\"", ci->ci_type);
1021 return NOTOK; /* NOT REACHED */
1022 }
1023
1024
1025 static void
1026 set_id (CT ct, int top)
1027 {
1028 char contentid[BUFSIZ];
1029 static int partno;
1030 static time_t clock = 0;
1031 static char *msgfmt;
1032
1033 if (clock == 0) {
1034 time (&clock);
1035 snprintf (contentid, sizeof(contentid), "%s\n", message_id (clock, 1));
1036 partno = 0;
1037 msgfmt = getcpy(contentid);
1038 }
1039 snprintf (contentid, sizeof(contentid), msgfmt, top ? 0 : ++partno);
1040 ct->c_id = getcpy (contentid);
1041 }
1042
1043
1044 /*
1045 * Fill out, or expand the various contents in the composition
1046 * draft. Read-in any necessary files. Parse and execute any
1047 * commands specified by profile composition strings.
1048 */
1049
1050 static int
1051 compose_content (CT ct, int verbose)
1052 {
1053 CE ce = &ct->c_cefile;
1054
1055 switch (ct->c_type) {
1056 case CT_MULTIPART:
1057 {
1058 int partnum;
1059 char *pp;
1060 char partnam[BUFSIZ];
1061 struct multipart *m = (struct multipart *) ct->c_ctparams;
1062 struct part *part;
1063
1064 if (ct->c_partno) {
1065 snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno);
1066 pp = partnam + strlen (partnam);
1067 } else {
1068 pp = partnam;
1069 }
1070
1071 /* first, we call compose_content on all the subparts */
1072 for (part = m->mp_parts, partnum = 1; part; part = part->mp_next, partnum++) {
1073 CT p = part->mp_part;
1074
1075 sprintf (pp, "%d", partnum);
1076 p->c_partno = add (partnam, NULL);
1077 if (compose_content (p, verbose) == NOTOK)
1078 return NOTOK;
1079 }
1080
1081 /*
1082 * If the -rfc934mode switch is given, then check all
1083 * the subparts of a multipart/digest. If they are all
1084 * message/rfc822, then mark this content and all
1085 * subparts with the rfc934 compatibility mode flag.
1086 */
1087 if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
1088 int is934 = 1;
1089
1090 for (part = m->mp_parts; part; part = part->mp_next) {
1091 CT p = part->mp_part;
1092
1093 if (p->c_subtype != MESSAGE_RFC822) {
1094 is934 = 0;
1095 break;
1096 }
1097 }
1098 ct->c_rfc934 = is934;
1099 for (part = m->mp_parts; part; part = part->mp_next) {
1100 CT p = part->mp_part;
1101
1102 if ((p->c_rfc934 = is934))
1103 p->c_end++;
1104 }
1105 }
1106
1107 if (listsw) {
1108 ct->c_end = (partnum = strlen (prefix) + 2) + 2;
1109 if (ct->c_rfc934)
1110 ct->c_end += 1;
1111
1112 for (part = m->mp_parts; part; part = part->mp_next)
1113 ct->c_end += part->mp_part->c_end + partnum;
1114 }
1115 }
1116 break;
1117
1118 case CT_MESSAGE:
1119 /* Nothing to do for type message */
1120 break;
1121
1122 /*
1123 * Discrete types (text/application/audio/image/video)
1124 */
1125 default:
1126 if (!ce->ce_file) {
1127 pid_t child_id;
1128 int i, xstdout, len, buflen;
1129 char *bp, *cp;
1130 char *vec[4], buffer[BUFSIZ];
1131 FILE *out;
1132 CI ci = &ct->c_ctinfo;
1133 char *tfile = NULL;
1134
1135 if (!(cp = ci->ci_magic))
1136 adios (NULL, "internal error(5)");
1137
1138 if ((tfile = m_mktemp2(NULL, invo_name, NULL, NULL)) == NULL) {
1139 adios("mhbuildsbr", "unable to create temporary file in %s",
1140 get_temp_dir());
1141 }
1142 ce->ce_file = add (tfile, NULL);
1143 ce->ce_unlink = 1;
1144
1145 xstdout = 0;
1146
1147 /* Get buffer ready to go */
1148 bp = buffer;
1149 bp[0] = '\0';
1150 buflen = sizeof(buffer);
1151
1152 /*
1153 * Parse composition string into buffer
1154 */
1155 for ( ; *cp; cp++) {
1156 if (*cp == '%') {
1157 switch (*++cp) {
1158 case 'a':
1159 {
1160 /* insert parameters from directive */
1161 char *s = "";
1162 PM pm;
1163
1164 for (pm = ci->ci_first_pm; pm; pm = pm->pm_next) {
1165 snprintf (bp, buflen, "%s%s=\"%s\"", s,
1166 pm->pm_name, get_param_value(pm, '?'));
1167 len = strlen (bp);
1168 bp += len;
1169 buflen -= len;
1170 s = " ";
1171 }
1172 }
1173 break;
1174
1175 case 'F':
1176 /* %f, and stdout is not-redirected */
1177 xstdout = 1;
1178 /* and fall... */
1179
1180 case 'f':
1181 /*
1182 * insert temporary filename where
1183 * content should be written
1184 */
1185 snprintf (bp, buflen, "%s", ce->ce_file);
1186 break;
1187
1188 case 's':
1189 /* insert content subtype */
1190 strncpy (bp, ci->ci_subtype, buflen);
1191 break;
1192
1193 case '%':
1194 /* insert character % */
1195 goto raw;
1196
1197 default:
1198 *bp++ = *--cp;
1199 *bp = '\0';
1200 buflen--;
1201 continue;
1202 }
1203 len = strlen (bp);
1204 bp += len;
1205 buflen -= len;
1206 } else {
1207 raw:
1208 *bp++ = *cp;
1209 *bp = '\0';
1210 buflen--;
1211 }
1212 }
1213
1214 if (verbose)
1215 printf ("composing content %s/%s from command\n\t%s\n",
1216 ci->ci_type, ci->ci_subtype, buffer);
1217
1218 fflush (stdout); /* not sure if need for -noverbose */
1219
1220 vec[0] = "/bin/sh";
1221 vec[1] = "-c";
1222 vec[2] = buffer;
1223 vec[3] = NULL;
1224
1225 if ((out = fopen (ce->ce_file, "w")) == NULL)
1226 adios (ce->ce_file, "unable to open for writing");
1227
1228 for (i = 0; (child_id = fork()) == NOTOK && i > 5; i++)
1229 sleep (5);
1230 switch (child_id) {
1231 case NOTOK:
1232 adios ("fork", "unable to fork");
1233 /* NOTREACHED */
1234
1235 case OK:
1236 if (!xstdout)
1237 dup2 (fileno (out), 1);
1238 close (fileno (out));
1239 execvp ("/bin/sh", vec);
1240 fprintf (stderr, "unable to exec ");
1241 perror ("/bin/sh");
1242 _exit (-1);
1243 /* NOTREACHED */
1244
1245 default:
1246 fclose (out);
1247 if (pidXwait(child_id, NULL))
1248 done (1);
1249 break;
1250 }
1251 }
1252
1253 /* Check size of file */
1254 if (listsw && ct->c_end == 0L) {
1255 struct stat st;
1256
1257 if (stat (ce->ce_file, &st) != NOTOK)
1258 ct->c_end = (long) st.st_size;
1259 }
1260 break;
1261 }
1262
1263 return OK;
1264 }
1265
1266
1267 /*
1268 * Scan the content.
1269 *
1270 * 1) choose a transfer encoding.
1271 * 2) check for clashes with multipart boundary string.
1272 * 3) for text content, figure out which character set is being used.
1273 *
1274 * If there is a clash with one of the contents and the multipart boundary,
1275 * this function will exit with NOTOK. This will cause the scanning process
1276 * to be repeated with a different multipart boundary. It is possible
1277 * (although highly unlikely) that this scan will be repeated multiple times.
1278 */
1279
1280 static int
1281 scan_content (CT ct, size_t maxunencoded)
1282 {
1283 int len;
1284 int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
1285 int checklinelen = 0, linelen = 0; /* check for long lines */
1286 int checkllinelen = 0; /* check for extra-long lines */
1287 int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
1288 int checklinespace = 0, linespace = 0; /* check if any line ends with space */
1289 char *cp = NULL, buffer[BUFSIZ];
1290 struct text *t = NULL;
1291 FILE *in = NULL;
1292 CE ce = &ct->c_cefile;
1293
1294 /*
1295 * handle multipart by scanning all subparts
1296 * and then checking their encoding.
1297 */
1298 if (ct->c_type == CT_MULTIPART) {
1299 struct multipart *m = (struct multipart *) ct->c_ctparams;
1300 struct part *part;
1301
1302 /* initially mark the domain of enclosing multipart as 7bit */
1303 ct->c_encoding = CE_7BIT;
1304
1305 for (part = m->mp_parts; part; part = part->mp_next) {
1306 CT p = part->mp_part;
1307
1308 if (scan_content (p, maxunencoded) == NOTOK) /* choose encoding for subpart */
1309 return NOTOK;
1310
1311 /* if necessary, enlarge encoding for enclosing multipart */
1312 if (p->c_encoding == CE_BINARY)
1313 ct->c_encoding = CE_BINARY;
1314 if (p->c_encoding == CE_8BIT && ct->c_encoding != CE_BINARY)
1315 ct->c_encoding = CE_8BIT;
1316 }
1317
1318 return OK;
1319 }
1320
1321 /*
1322 * Decide what to check while scanning this content. Note that
1323 * for text content we always check for 8bit characters if the
1324 * charset is unspecified, because that controls whether or not the
1325 * character set is us-ascii or retrieved from the locale.
1326 */
1327
1328 if (ct->c_type == CT_TEXT) {
1329 t = (struct text *) ct->c_ctparams;
1330 if (t->tx_charset == CHARSET_UNSPECIFIED)
1331 check8bit = 1;
1332 }
1333
1334 switch (ct->c_reqencoding) {
1335 case CE_8BIT:
1336 checkllinelen = 1;
1337 checkboundary = 1;
1338 break;
1339 case CE_QUOTED:
1340 checkboundary = 1;
1341 break;
1342 case CE_BASE64:
1343 break;
1344 case CE_UNKNOWN:
1345 /* Use the default rules based on content-type */
1346 switch (ct->c_type) {
1347 case CT_TEXT:
1348 checkboundary = 1;
1349 checklinelen = 1;
1350 if (ct->c_subtype == TEXT_PLAIN) {
1351 checklinespace = 0;
1352 } else {
1353 checklinespace = 1;
1354 }
1355 break;
1356
1357 case CT_APPLICATION:
1358 check8bit = 1;
1359 checklinelen = 1;
1360 checklinespace = 1;
1361 checkboundary = 1;
1362 break;
1363
1364 case CT_MESSAGE:
1365 check8bit = 0;
1366 checklinelen = 0;
1367 checklinespace = 0;
1368
1369 /* don't check anything for message/external */
1370 if (ct->c_subtype == MESSAGE_EXTERNAL)
1371 checkboundary = 0;
1372 else
1373 checkboundary = 1;
1374 break;
1375
1376 case CT_AUDIO:
1377 case CT_IMAGE:
1378 case CT_VIDEO:
1379 /*
1380 * Don't check anything for these types,
1381 * since we are forcing use of base64, unless
1382 * the content-type was specified by a mhbuild directive.
1383 */
1384 check8bit = 0;
1385 checklinelen = 0;
1386 checklinespace = 0;
1387 checkboundary = 0;
1388 break;
1389 }
1390 }
1391
1392 /*
1393 * Scan the unencoded content
1394 */
1395 if (check8bit || checklinelen || checklinespace || checkboundary ||
1396 checkllinelen) {
1397 if ((in = fopen (ce->ce_file, "r")) == NULL)
1398 adios (ce->ce_file, "unable to open for reading");
1399 len = strlen (prefix);
1400
1401 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1402 /*
1403 * Check for 8bit data.
1404 */
1405 if (check8bit) {
1406 for (cp = buffer; *cp; cp++) {
1407 if (!isascii ((unsigned char) *cp)) {
1408 contains8bit = 1;
1409 check8bit = 0; /* no need to keep checking */
1410 }
1411 }
1412 }
1413
1414 /*
1415 * Check line length.
1416 */
1417 if (checklinelen && (strlen (buffer) > maxunencoded + 1)) {
1418 linelen = 1;
1419 checklinelen = 0; /* no need to keep checking */
1420 }
1421
1422 /*
1423 * RFC 5322 specifies that a message cannot contain a line
1424 * greater than 998 characters (excluding the CRLF). If we
1425 * get one of those lines and linelen is NOT set, then abort.
1426 */
1427
1428 if (checkllinelen && !linelen &&
1429 (strlen(buffer) > MAXLONGLINE + 1)) {
1430 adios(NULL, "Line in content exceeds maximum line limit (%d)",
1431 MAXLONGLINE);
1432 }
1433
1434 /*
1435 * Check if line ends with a space.
1436 */
1437 if (checklinespace && (cp = buffer + strlen (buffer) - 2) > buffer && isspace ((unsigned char) *cp)) {
1438 linespace = 1;
1439 checklinespace = 0; /* no need to keep checking */
1440 }
1441
1442 /*
1443 * Check if content contains a line that clashes
1444 * with our standard boundary for multipart messages.
1445 */
1446 if (checkboundary && buffer[0] == '-' && buffer[1] == '-') {
1447 for (cp = buffer + strlen (buffer) - 1; cp >= buffer; cp--)
1448 if (!isspace ((unsigned char) *cp))
1449 break;
1450 *++cp = '\0';
1451 if (!strncmp(buffer + 2, prefix, len) && isdigit((unsigned char) buffer[2 + len])) {
1452 boundaryclash = 1;
1453 checkboundary = 0; /* no need to keep checking */
1454 }
1455 }
1456 }
1457 fclose (in);
1458 }
1459
1460 /*
1461 * If the content is text and didn't specify a character set,
1462 * we need to figure out which one was used.
1463 */
1464
1465 if (ct->c_type == CT_TEXT) {
1466 t = (struct text *) ct->c_ctparams;
1467 if (t->tx_charset == CHARSET_UNSPECIFIED) {
1468 CI ci = &ct->c_ctinfo;
1469
1470 add_param(&ci->ci_first_pm, &ci->ci_last_pm, "charset",
1471 contains8bit ? write_charset_8bit() : "us-ascii", 0);
1472 t->tx_charset = CHARSET_SPECIFIED;
1473 }
1474 }
1475
1476 /*
1477 * Decide which transfer encoding to use.
1478 */
1479
1480 if (ct->c_reqencoding != CE_UNKNOWN)
1481 ct->c_encoding = ct->c_reqencoding;
1482 else
1483 switch (ct->c_type) {
1484 case CT_TEXT:
1485 if (contains8bit && !linelen && !linespace && !checksw)
1486 ct->c_encoding = CE_8BIT;
1487 else if (contains8bit || linelen || linespace || checksw)
1488 ct->c_encoding = CE_QUOTED;
1489 else
1490 ct->c_encoding = CE_7BIT;
1491 break;
1492
1493 case CT_APPLICATION:
1494 /* For application type, use base64, except when postscript */
1495 if (contains8bit || linelen || linespace || checksw)
1496 ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT)
1497 ? CE_QUOTED : CE_BASE64;
1498 else
1499 ct->c_encoding = CE_7BIT;
1500 break;
1501
1502 case CT_MESSAGE:
1503 ct->c_encoding = CE_7BIT;
1504 break;
1505
1506 case CT_AUDIO:
1507 case CT_IMAGE:
1508 case CT_VIDEO:
1509 /* For audio, image, and video contents, just use base64 */
1510 ct->c_encoding = CE_BASE64;
1511 break;
1512 }
1513
1514 return (boundaryclash ? NOTOK : OK);
1515 }
1516
1517
1518 /*
1519 * Scan the content structures, and build header
1520 * fields that will need to be output into the
1521 * message.
1522 */
1523
1524 static int
1525 build_headers (CT ct, int header_encoding)
1526 {
1527 int cc, mailbody, extbody, len;
1528 char *np, *vp, buffer[BUFSIZ];
1529 CI ci = &ct->c_ctinfo;
1530
1531 /*
1532 * If message is type multipart, then add the multipart
1533 * boundary to the list of attribute/value pairs.
1534 */
1535 if (ct->c_type == CT_MULTIPART) {
1536 static int level = 0; /* store nesting level */
1537
1538 snprintf (buffer, sizeof(buffer), "%s%d", prefix, level++);
1539 add_param(&ci->ci_first_pm, &ci->ci_last_pm, "boundary", buffer, 0);
1540 }
1541
1542 /*
1543 * Skip the output of Content-Type, parameters, content
1544 * description and disposition, and Content-ID if the
1545 * content is of type "message" and the rfc934 compatibility
1546 * flag is set (which means we are inside multipart/digest
1547 * and the switch -rfc934mode was given).
1548 */
1549 if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
1550 goto skip_headers;
1551
1552 /*
1553 * output the content type and subtype
1554 */
1555 np = add (TYPE_FIELD, NULL);
1556 vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL);
1557
1558 /* keep track of length of line */
1559 len = strlen (TYPE_FIELD) + strlen (ci->ci_type)
1560 + strlen (ci->ci_subtype) + 3;
1561
1562 extbody = ct->c_type == CT_MESSAGE && ct->c_subtype == MESSAGE_EXTERNAL;
1563 mailbody = extbody && ((struct exbody *) ct->c_ctparams)->eb_body;
1564
1565 /*
1566 * Append the attribute/value pairs to
1567 * the end of the Content-Type line.
1568 */
1569
1570 if (ci->ci_first_pm) {
1571 char *s = output_params(len, ci->ci_first_pm, &len, mailbody);
1572
1573 if (!s)
1574 adios(NULL, "Internal error: failed outputting Content-Type "
1575 "parameters");
1576
1577 vp = add (s, vp);
1578 free(s);
1579 }
1580
1581 /*
1582 * Append any RFC-822 comment to the end of
1583 * the Content-Type line.
1584 */
1585 if (ci->ci_comment) {
1586 snprintf (buffer, sizeof(buffer), "(%s)", ci->ci_comment);
1587 if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) {
1588 vp = add ("\n\t", vp);
1589 len = 8;
1590 } else {
1591 vp = add (" ", vp);
1592 len++;
1593 }
1594 vp = add (buffer, vp);
1595 len += cc;
1596 }
1597 vp = add ("\n", vp);
1598 add_header (ct, np, vp);
1599
1600 /*
1601 * output the Content-ID, unless disabled by -nocontentid
1602 */
1603 if (contentidsw && ct->c_id) {
1604 np = add (ID_FIELD, NULL);
1605 vp = concat (" ", ct->c_id, NULL);
1606 add_header (ct, np, vp);
1607 }
1608 /*
1609 * output the Content-Description
1610 */
1611 if (ct->c_descr) {
1612 np = add (DESCR_FIELD, NULL);
1613 vp = concat (" ", ct->c_descr, NULL);
1614 if (encode_rfc2047(DESCR_FIELD, &vp, header_encoding, NULL))
1615 adios(NULL, "Unable to encode %s header", DESCR_FIELD);
1616 add_header (ct, np, vp);
1617 }
1618
1619 /*
1620 * output the Content-Disposition. If it's NULL but c_dispo_type is
1621 * set, then we need to build it.
1622 */
1623 if (ct->c_dispo) {
1624 np = add (DISPO_FIELD, NULL);
1625 vp = concat (" ", ct->c_dispo, NULL);
1626 add_header (ct, np, vp);
1627 } else if (ct->c_dispo_type) {
1628 vp = concat (" ", ct->c_dispo_type, NULL);
1629 len = strlen(DISPO_FIELD) + strlen(vp) + 1;
1630 np = output_params(len, ct->c_dispo_first, NULL, 0);
1631 vp = add(np, vp);
1632 vp = add("\n", vp);
1633 if (np)
1634 free(np);
1635 add_header (ct, getcpy(DISPO_FIELD), vp);
1636 }
1637
1638 skip_headers:
1639 /*
1640 * If this is the internal content structure for a
1641 * "message/external", then we are done with the
1642 * headers (since it has no body).
1643 */
1644 if (ct->c_ctexbody)
1645 return OK;
1646
1647 /*
1648 * output the Content-MD5
1649 */
1650 if (checksw) {
1651 np = add (MD5_FIELD, NULL);
1652 vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0);
1653 add_header (ct, np, vp);
1654 }
1655
1656 /*
1657 * output the Content-Transfer-Encoding
1658 */
1659 switch (ct->c_encoding) {
1660 case CE_7BIT:
1661 /* Nothing to output */
1662 break;
1663
1664 case CE_8BIT:
1665 if (ct->c_type == CT_MESSAGE)
1666 adios (NULL, "internal error, invalid encoding");
1667
1668 np = add (ENCODING_FIELD, NULL);
1669 vp = concat (" ", "8bit", "\n", NULL);
1670 add_header (ct, np, vp);
1671 break;
1672
1673 case CE_QUOTED:
1674 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1675 adios (NULL, "internal error, invalid encoding");
1676
1677 np = add (ENCODING_FIELD, NULL);
1678 vp = concat (" ", "quoted-printable", "\n", NULL);
1679 add_header (ct, np, vp);
1680 break;
1681
1682 case CE_BASE64:
1683 if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
1684 adios (NULL, "internal error, invalid encoding");
1685
1686 np = add (ENCODING_FIELD, NULL);
1687 vp = concat (" ", "base64", "\n", NULL);
1688 add_header (ct, np, vp);
1689 break;
1690
1691 case CE_BINARY:
1692 if (ct->c_type == CT_MESSAGE)
1693 adios (NULL, "internal error, invalid encoding");
1694
1695 np = add (ENCODING_FIELD, NULL);
1696 vp = concat (" ", "binary", "\n", NULL);
1697 add_header (ct, np, vp);
1698 break;
1699
1700 default:
1701 adios (NULL, "unknown transfer encoding in content");
1702 break;
1703 }
1704
1705 /*
1706 * Additional content specific header processing
1707 */
1708 switch (ct->c_type) {
1709 case CT_MULTIPART:
1710 {
1711 struct multipart *m;
1712 struct part *part;
1713
1714 m = (struct multipart *) ct->c_ctparams;
1715 for (part = m->mp_parts; part; part = part->mp_next) {
1716 CT p;
1717
1718 p = part->mp_part;
1719 build_headers (p, header_encoding);
1720 }
1721 }
1722 break;
1723
1724 case CT_MESSAGE:
1725 if (ct->c_subtype == MESSAGE_EXTERNAL) {
1726 struct exbody *e;
1727
1728 e = (struct exbody *) ct->c_ctparams;
1729 build_headers (e->eb_content, header_encoding);
1730 }
1731 break;
1732
1733 default:
1734 /* Nothing to do */
1735 break;
1736 }
1737
1738 return OK;
1739 }
1740
1741
1742 static char nib2b64[0x40+1] =
1743 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1744
1745 static char *
1746 calculate_digest (CT ct, int asciiP)
1747 {
1748 int cc;
1749 char buffer[BUFSIZ], *vp, *op;
1750 unsigned char *dp;
1751 unsigned char digest[16];
1752 unsigned char outbuf[25];
1753 MD5_CTX mdContext;
1754 CE ce = &ct->c_cefile;
1755 char *infilename = ce->ce_file ? ce->ce_file : ct->c_file;
1756 FILE *in;
1757
1758 /* open content */
1759 if ((in = fopen (infilename, "r")) == NULL)
1760 adios (infilename, "unable to open for reading");
1761
1762 /* Initialize md5 context */
1763 MD5Init (&mdContext);
1764
1765 /* calculate md5 message digest */
1766 if (asciiP) {
1767 while (fgets (buffer, sizeof(buffer) - 1, in)) {
1768 char c, *cp;
1769
1770 cp = buffer + strlen (buffer) - 1;
1771 if ((c = *cp) == '\n')
1772 *cp = '\0';
1773
1774 MD5Update (&mdContext, (unsigned char *) buffer,
1775 (unsigned int) strlen (buffer));
1776
1777 if (c == '\n')
1778 MD5Update (&mdContext, (unsigned char *) "\r\n", 2);
1779 }
1780 } else {
1781 while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0)
1782 MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc);
1783 }
1784
1785 /* md5 finalization. Write digest and zero md5 context */
1786 MD5Final (digest, &mdContext);
1787
1788 /* close content */
1789 fclose (in);
1790
1791 /* print debugging info */
1792 if (debugsw) {
1793 unsigned char *ep;
1794
1795 fprintf (stderr, "MD5 digest=");
1796 for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]);
1797 dp < ep; dp++)
1798 fprintf (stderr, "%02x", *dp & 0xff);
1799 fprintf (stderr, "\n");
1800 }
1801
1802 /* encode the digest using base64 */
1803 for (dp = digest, op = (char *) outbuf,
1804 cc = sizeof(digest) / sizeof(digest[0]);
1805 cc > 0; cc -= 3, op += 4) {
1806 unsigned long bits;
1807 char *bp;
1808
1809 bits = (*dp++ & 0xff) << 16;
1810 if (cc > 1) {
1811 bits |= (*dp++ & 0xff) << 8;
1812 if (cc > 2)
1813 bits |= *dp++ & 0xff;
1814 }
1815
1816 for (bp = op + 4; bp > op; bits >>= 6)
1817 *--bp = nib2b64[bits & 0x3f];
1818 if (cc < 3) {
1819 *(op + 3) = '=';
1820 if (cc < 2)
1821 *(op + 2) = '=';
1822 }
1823 }
1824
1825 /* null terminate string */
1826 outbuf[24] = '\0';
1827
1828 /* now make copy and return string */
1829 vp = concat (" ", outbuf, "\n", NULL);
1830 return vp;
1831 }
1832
1833 /*
1834 * Set things up for the content structure for file "filename" that
1835 * we want to attach
1836 */
1837
1838 static void
1839 setup_attach_content(CT ct, char *filename)
1840 {
1841 char *type, *simplename = r1bindex(filename, '/');
1842 struct str2init *s2i;
1843 PM pm;
1844
1845 if (! (type = mime_type(filename))) {
1846 adios(NULL, "Unable to determine MIME type of \"%s\"", filename);
1847 }
1848
1849 /*
1850 * Parse the Content-Type. get_ctinfo() parses MIME parameters, but
1851 * since we're just feeding it a MIME type we have to add those ourself.
1852 * Map that to a valid content-type label and call any initialization
1853 * function.
1854 */
1855
1856 if (get_ctinfo(type, ct, 0) == NOTOK)
1857 done(1);
1858
1859 free(type);
1860
1861 for (s2i = str2cts; s2i->si_key; s2i++)
1862 if (strcasecmp(ct->c_ctinfo.ci_type, s2i->si_key) == 0)
1863 break;
1864 if (!s2i->si_key && !uprf(ct->c_ctinfo.ci_type, "X-"))
1865 s2i++;
1866
1867 /*
1868 * Make sure the type isn't incompatible with what we can handle
1869 */
1870
1871 switch (ct->c_type = s2i->si_val) {
1872 case CT_MULTIPART:
1873 adios (NULL, "multipart types must be specified by mhbuild directives");
1874 /* NOTREACHED */
1875
1876 case CT_MESSAGE:
1877 if (strcasecmp(ct->c_ctinfo.ci_subtype, "partial") == 0)
1878 adios(NULL, "Sorry, %s/%s isn't supported", ct->c_ctinfo.ci_type,
1879 ct->c_ctinfo.ci_subtype);
1880 if (strcasecmp(ct->c_ctinfo.ci_subtype, "external-body") == 0)
1881 adios(NULL, "external-body messages must be specified "
1882 "by mhbuild directives");
1883 /* Fall through */
1884
1885 default:
1886 /*
1887 * This sets the subtype, if it's significant
1888 */
1889 if ((ct->c_ctinitfnx = s2i->si_init))
1890 (*ct->c_ctinitfnx)(ct);
1891 break;
1892 }
1893
1894 /*
1895 * Feed in a few attributes; specifically, the name attribute, the
1896 * content-description, and the content-disposition.
1897 */
1898
1899 for (pm = ct->c_ctinfo.ci_first_pm; pm; pm = pm->pm_next) {
1900 if (strcasecmp(pm->pm_name, "name") == 0) {
1901 if (pm->pm_value)
1902 free(pm->pm_value);
1903 pm->pm_value = getcpy(simplename);
1904 break;
1905 }
1906 }
1907
1908 if (pm == NULL)
1909 add_param(&ct->c_ctinfo.ci_first_pm, &ct->c_ctinfo.ci_last_pm,
1910 "name", simplename, 0);
1911
1912 ct->c_descr = getcpy(simplename);
1913 ct->c_descr = add("\n", ct->c_descr);
1914 ct->c_cefile.ce_file = getcpy(filename);
1915
1916 /*
1917 * If it's a text/calendar, we need to make sure it's an inline,
1918 * otherwise it won't work with some calendar programs. Otherwise
1919 * assume attachment
1920 */
1921
1922 if (strcasecmp(ct->c_ctinfo.ci_type, "text") == 0 &&
1923 strcasecmp(ct->c_ctinfo.ci_subtype, "calendar") == 0) {
1924 ct->c_dispo_type = getcpy("inline");
1925 } else {
1926 ct->c_dispo_type = getcpy("attachment");
1927 }
1928
1929 add_param(&ct->c_dispo_first, &ct->c_dispo_last, "filename", simplename, 0);
1930 }