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