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