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