+ char msgid[BUFSIZ];
+ static int partno;
+ static time_t clock = 0;
+ static char *msgfmt;
+
+ if (clock == 0) {
+ time(&clock);
+ snprintf(msgid, sizeof(msgid), "<%d.%ld.%%d@%s>\n",
+ (int) getpid(), (long) clock, LocalName());
+ partno = 0;
+ msgfmt = getcpy(msgid);
+ }
+ snprintf(msgid, sizeof(msgid), msgfmt, top ? 0 : ++partno);
+ ct->c_id = getcpy(msgid);
+}
+
+
+static char ebcdicsafe[0x100] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+
+/*
+** Fill out, or expand the various contents in the composition
+** draft. Read-in any necessary files. Parse and execute any
+** commands specified by profile composition strings.
+*/
+
+static int
+compose_content(CT ct)
+{
+ CE ce = ct->c_cefile;
+
+ switch (ct->c_type) {
+ case CT_MULTIPART:
+ {
+ int partnum;
+ char *pp;
+ char partnam[BUFSIZ];
+ struct multipart *m = (struct multipart *) ct->c_ctparams;
+ struct part *part;
+
+ if (ct->c_partno) {
+ snprintf(partnam, sizeof(partnam), "%s.",
+ ct->c_partno);
+ pp = partnam + strlen(partnam);
+ } else {
+ pp = partnam;
+ }
+
+ /* first, we call compose_content on all the subparts */
+ for (part = m->mp_parts, partnum = 1; part;
+ part = part->mp_next, partnum++) {
+ CT p = part->mp_part;
+
+ sprintf(pp, "%d", partnum);
+ p->c_partno = getcpy(partnam);
+ if (compose_content(p) == NOTOK)
+ return NOTOK;
+ }
+
+ /*
+ ** If the -rfc934mode switch is given, then check all
+ ** the subparts of a multipart/digest. If they are all
+ ** message/rfc822, then mark this content and all
+ ** subparts with the rfc934 compatibility mode flag.
+ */
+ if (rfc934sw && ct->c_subtype == MULTI_DIGEST) {
+ int is934 = 1;
+
+ for (part = m->mp_parts; part; part = part->mp_next) {
+ CT p = part->mp_part;
+
+ if (p->c_subtype != MESSAGE_RFC822) {
+ is934 = 0;
+ break;
+ }
+ }
+ ct->c_rfc934 = is934;
+ for (part = m->mp_parts; part; part = part->mp_next) {
+ CT p = part->mp_part;
+
+ if ((p->c_rfc934 = is934))
+ p->c_end++;
+ }
+ }
+
+ if (listsw) {
+ ct->c_end = (partnum = strlen(prefix) + 2) + 2;
+ if (ct->c_rfc934)
+ ct->c_end += 1;
+
+ for (part = m->mp_parts; part; part = part->mp_next)
+ ct->c_end += part->mp_part->c_end + partnum;
+ }
+ }
+ break;
+
+ case CT_MESSAGE:
+ /* Nothing to do for type message */
+ break;
+
+ /*
+ ** Discrete types (text/application/audio/image/video)
+ */
+ default:
+ if (!ce->ce_file) {
+ pid_t child_id;
+ int xstdout, len, buflen;
+ char *bp, **ap, *cp;
+ char *vec[4], buffer[BUFSIZ];
+ FILE *out;
+ CI ci = &ct->c_ctinfo;
+ char *tfile = NULL;
+
+ if (!(cp = ci->ci_magic))
+ adios(NULL, "internal error(5)");
+
+ tfile = m_mktemp2(NULL, invo_name, NULL, NULL);
+ if (tfile == NULL) {
+ adios("mhbuild", "unable to create temporary file");
+ }
+ ce->ce_file = getcpy(tfile);
+ ce->ce_unlink = 1;
+
+ xstdout = 0;
+
+ /* Get buffer ready to go */
+ bp = buffer;
+ bp[0] = '\0';
+ buflen = sizeof(buffer);
+
+ /*
+ ** Parse composition string into buffer
+ */
+ for ( ; *cp; cp++) {
+ if (*cp == '%') {
+ switch (*++cp) {
+ case 'a':
+ {
+ /*
+ ** insert parameters from
+ ** directive
+ */
+ char **ep;
+ char *s = "";
+
+ for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
+ snprintf(bp, buflen, "%s%s=\"%s\"", s, *ap, *ep);
+ len = strlen(bp);
+ bp += len;
+ buflen -= len;
+ s = " ";
+ }
+ }
+ break;
+
+ case 'F':
+ /* %f, and stdout is not-redirected */
+ xstdout = 1;
+ /* and fall... */
+
+ case 'f':
+ /*
+ ** insert temporary filename
+ ** where content should be
+ ** written
+ */
+ snprintf(bp, buflen, "%s", ce->ce_file);
+ break;
+
+ case 's':
+ /* insert content subtype */
+ strncpy(bp, ci->ci_subtype, buflen);
+ break;
+
+ case '%':
+ /* insert character % */
+ goto raw;
+
+ default:
+ *bp++ = *--cp;
+ *bp = '\0';
+ buflen--;
+ continue;
+ }
+ len = strlen(bp);
+ bp += len;
+ buflen -= len;
+ } else {
+raw:
+ *bp++ = *cp;
+ *bp = '\0';
+ buflen--;
+ }
+ }
+
+ if (verbosw)
+ printf("composing content %s/%s from command\n\t%s\n", ci->ci_type, ci->ci_subtype, buffer);
+
+ fflush(stdout); /* not sure if need for -noverbose */
+
+ vec[0] = "/bin/sh";
+ vec[1] = "-c";
+ vec[2] = buffer;
+ vec[3] = NULL;
+
+ if ((out = fopen(ce->ce_file, "w")) == NULL)
+ adios(ce->ce_file, "unable to open for writing");
+
+ switch (child_id = fork()) {
+ case NOTOK:
+ adios("fork", "unable to fork");
+ /* NOTREACHED */
+
+ case OK:
+ if (!xstdout)
+ dup2(fileno(out), 1);
+ close(fileno(out));
+ execvp("/bin/sh", vec);
+ fprintf(stderr, "unable to exec ");
+ perror("/bin/sh");
+ _exit(-1);
+ /* NOTREACHED */
+
+ default:
+ fclose(out);
+ if (pidXwait(child_id, NULL))
+ done(1);
+ break;
+ }
+ }
+
+ /* Check size of file */
+ if (listsw && ct->c_end == 0L) {
+ struct stat st;
+
+ if (stat(ce->ce_file, &st) != NOTOK)
+ ct->c_end = (long) st.st_size;
+ }
+ break;
+ }
+
+ return OK;
+}
+
+
+/*
+** Scan the content.
+**
+** 1) choose a transfer encoding.
+** 2) check for clashes with multipart boundary string.
+** 3) for text content, figure out which character set is being used.
+**
+** If there is a clash with one of the contents and the multipart boundary,
+** this function will exit with NOTOK. This will cause the scanning process
+** to be repeated with a different multipart boundary. It is possible
+** (although highly unlikely) that this scan will be repeated multiple times.
+*/
+
+static int
+scan_content(CT ct)
+{
+ int len;
+ int check8bit = 0, contains8bit = 0; /* check if contains 8bit data */
+ int checklinelen = 0, linelen = 0; /* check for long lines */
+ int checkboundary = 0, boundaryclash = 0; /* check if clashes with multipart boundary */
+ int checklinespace = 0, linespace = 0; /* check if any line ends with space */
+ int checkebcdic = 0, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */
+ unsigned char *cp = NULL, buffer[BUFSIZ];
+ struct text *t = NULL;
+ FILE *in = NULL;
+ CE ce = ct->c_cefile;
+
+ /*
+ ** handle multipart by scanning all subparts
+ ** and then checking their encoding.
+ */
+ if (ct->c_type == CT_MULTIPART) {
+ struct multipart *m = (struct multipart *) ct->c_ctparams;
+ struct part *part;
+
+ /* initially mark the domain of enclosing multipart as 7bit */
+ ct->c_encoding = CE_7BIT;
+
+ for (part = m->mp_parts; part; part = part->mp_next) {
+ CT p = part->mp_part;
+
+ if (scan_content(p) == NOTOK) {
+ /* choose encoding for subpart */
+ return NOTOK;
+ }
+
+ /*
+ ** if necessary, enlarge encoding for enclosing
+ ** multipart
+ */
+ if (p->c_encoding == CE_BINARY)
+ ct->c_encoding = CE_BINARY;
+ if (p->c_encoding == CE_8BIT &&
+ ct->c_encoding != CE_BINARY)
+ ct->c_encoding = CE_8BIT;
+ }
+
+ return OK;
+ }
+
+ /*
+ ** Decide what to check while scanning this content.
+ */
+ switch (ct->c_type) {
+ case CT_TEXT:
+ check8bit = 1;
+ checkboundary = 1;
+ if (ct->c_subtype == TEXT_PLAIN) {
+ checkebcdic = 0;
+ checklinelen = 0;
+ checklinespace = 0;
+ } else {
+ checkebcdic = ebcdicsw;
+ checklinelen = 1;
+ checklinespace = 1;
+ }
+ break;
+
+ case CT_APPLICATION:
+ check8bit = 1;
+ checkebcdic = ebcdicsw;
+ checklinelen = 1;
+ checklinespace = 1;
+ checkboundary = 1;
+ break;
+
+ case CT_MESSAGE:
+ check8bit = 0;
+ checkebcdic = 0;
+ checklinelen = 0;
+ checklinespace = 0;
+
+ /* don't check anything for message/external */
+ if (ct->c_subtype == MESSAGE_EXTERNAL)
+ checkboundary = 0;
+ else
+ checkboundary = 1;
+ break;
+
+ case CT_AUDIO:
+ case CT_IMAGE:
+ case CT_VIDEO:
+ /*
+ ** Don't check anything for these types,
+ ** since we are forcing use of base64.
+ */
+ check8bit = 0;
+ checkebcdic = 0;
+ checklinelen = 0;
+ checklinespace = 0;
+ checkboundary = 0;
+ break;
+ }
+
+ /*
+ ** Scan the unencoded content
+ */
+ if (check8bit || checklinelen || checklinespace || checkboundary) {
+ if ((in = fopen(ce->ce_file, "r")) == NULL)
+ adios(ce->ce_file, "unable to open for reading");
+ len = strlen(prefix);
+
+ while (fgets(buffer, sizeof(buffer) - 1, in)) {
+ /*
+ ** Check for 8bit data.
+ */
+ if (check8bit) {
+ for (cp = buffer; *cp; cp++) {
+ if (!isascii(*cp)) {
+ contains8bit = 1;
+ /* no need to keep checking */
+ check8bit = 0;
+ }
+ /*
+ ** Check if character is ebcdic-safe.
+ ** We only check this if also checking
+ ** for 8bit data.
+ */
+ if (checkebcdic && !ebcdicsafe[*cp & 0xff]) {
+ ebcdicunsafe = 1;
+ /* no need to keep checking */
+ checkebcdic = 0;
+ }
+ }
+ }
+
+ /*
+ ** Check line length.
+ */
+ if (checklinelen && (strlen(buffer) > CPERLIN + 1)) {
+ linelen = 1;
+ checklinelen = 0; /* no need to keep checking */
+ }
+
+ /*
+ ** Check if line ends with a space.
+ */
+ if (checklinespace &&
+ (cp = buffer + strlen(buffer) - 2) >
+ buffer && isspace(*cp)) {
+ linespace = 1;
+ /* no need to keep checking */
+ checklinespace = 0;
+ }
+
+ /*
+ ** Check if content contains a line that clashes
+ ** with our standard boundary for multipart messages.
+ */
+ if (checkboundary && buffer[0] == '-' &&
+ buffer[1] == '-') {
+ for (cp = buffer + strlen(buffer) - 1;
+ cp >= buffer; cp--)
+ if (!isspace(*cp))
+ break;
+ *++cp = '\0';
+ if (strncmp(buffer + 2, prefix, len)==0 &&
+ isdigit(buffer[2 + len])) {
+ boundaryclash = 1;
+ /* no need to keep checking */
+ checkboundary = 0;
+ }
+ }
+ }
+ fclose(in);
+ }
+
+ /*
+ ** Decide which transfer encoding to use.
+ */
+ switch (ct->c_type) {
+ case CT_TEXT:
+ /*
+ ** If the text content didn't specify a character
+ ** set, we need to figure out which one was used.
+ */
+ t = (struct text *) ct->c_ctparams;
+ if (t->tx_charset == CHARSET_UNSPECIFIED) {
+ CI ci = &ct->c_ctinfo;
+ char **ap, **ep;
+
+ for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++)
+ continue;
+
+ if (contains8bit) {
+ t->tx_charset = CHARSET_UNKNOWN;
+ *ap = concat("charset=", write_charset_8bit(),
+ NULL);
+ } else {
+ t->tx_charset = CHARSET_USASCII;
+ *ap = getcpy("charset=us-ascii");
+ }
+
+ cp = strchr(*ap++, '=');
+ *ap = NULL;
+ *cp++ = '\0';
+ *ep = cp;
+ }
+
+ if (contains8bit || ebcdicunsafe || linelen || linespace)
+ ct->c_encoding = CE_QUOTED;
+ else
+ ct->c_encoding = CE_7BIT;
+ break;
+
+ case CT_APPLICATION:
+ /* For application type, use base64, except when postscript */
+ if (contains8bit || ebcdicunsafe || linelen || linespace)
+ ct->c_encoding = (ct->c_subtype ==
+ APPLICATION_POSTSCRIPT) ?
+ CE_QUOTED : CE_BASE64;
+ else
+ ct->c_encoding = CE_7BIT;
+ break;
+
+ case CT_MESSAGE:
+ ct->c_encoding = CE_7BIT;
+ break;
+
+ case CT_AUDIO:
+ case CT_IMAGE:
+ case CT_VIDEO:
+ /* For audio, image, and video contents, just use base64 */
+ ct->c_encoding = CE_BASE64;
+ break;
+ }
+
+ return (boundaryclash ? NOTOK : OK);
+}
+
+
+/*
+** Scan the content structures, and build header
+** fields that will need to be output into the
+** message.
+*/
+
+static int
+build_headers(CT ct)
+{
+ int cc, mailbody, len;
+ char **ap, **ep;
+ char *np, *vp, buffer[BUFSIZ];
+ CI ci = &ct->c_ctinfo;
+
+ /*
+ ** If message is type multipart, then add the multipart
+ ** boundary to the list of attribute/value pairs.
+ */
+ if (ct->c_type == CT_MULTIPART) {
+ char *cp;
+ static int level = 0; /* store nesting level */
+
+ ap = ci->ci_attrs;
+ ep = ci->ci_values;
+ snprintf(buffer, sizeof(buffer), "boundary=%s%d",
+ prefix, level++);
+ cp = strchr(*ap++ = getcpy(buffer), '=');
+ *ap = NULL;
+ *cp++ = '\0';
+ *ep = cp;
+ }
+
+ /*
+ ** Skip the output of Content-Type, parameters, content
+ ** description and disposition, and Content-ID if the
+ ** content is of type "message" and the rfc934 compatibility
+ ** flag is set (which means we are inside multipart/digest
+ ** and the switch -rfc934mode was given).
+ */
+ if (ct->c_type == CT_MESSAGE && ct->c_rfc934)
+ goto skip_headers;
+
+ /*
+ ** output the content type and subtype
+ */
+ np = getcpy(TYPE_FIELD);
+ vp = concat(" ", ci->ci_type, "/", ci->ci_subtype, NULL);
+
+ /* keep track of length of line */
+ len = strlen(TYPE_FIELD) + strlen(ci->ci_type) +
+ strlen(ci->ci_subtype) + 3;
+
+ mailbody = ct->c_type == CT_MESSAGE &&
+ ct->c_subtype == MESSAGE_EXTERNAL &&
+ ((struct exbody *) ct->c_ctparams)->eb_body;
+
+ /*
+ ** Append the attribute/value pairs to
+ ** the end of the Content-Type line.
+ */
+ for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) {
+ if (mailbody && !mh_strcasecmp(*ap, "body"))
+ continue;
+
+ vp = add(";", vp);
+ len++;
+
+ snprintf(buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep);
+ if (len + 1 + (cc = strlen(buffer)) >= CPERLIN) {
+ vp = add("\n\t", vp);
+ len = 8;
+ } else {
+ vp = add(" ", vp);
+ len++;
+ }
+ vp = add(buffer, vp);
+ len += cc;
+ }
+
+ /*
+ ** Append any RFC-822 comment to the end of
+ ** the Content-Type line.
+ */
+ if (ci->ci_comment) {
+ snprintf(buffer, sizeof(buffer), "(%s)", ci->ci_comment);
+ if (len + 1 + (cc = 2 + strlen(ci->ci_comment)) >= CPERLIN) {
+ vp = add("\n\t", vp);
+ len = 8;
+ } else {
+ vp = add(" ", vp);
+ len++;
+ }
+ vp = add(buffer, vp);
+ len += cc;
+ }
+ vp = add("\n", vp);
+ add_header(ct, np, vp);
+
+ /*
+ ** output the Content-ID, unless disabled by -nocontentid
+ */
+ if (contentidsw && ct->c_id) {
+ np = getcpy(ID_FIELD);
+ vp = concat(" ", ct->c_id, NULL);
+ add_header(ct, np, vp);
+ }
+
+ /*
+ ** output the Content-Description
+ */
+ if (ct->c_descr) {
+ np = getcpy(DESCR_FIELD);
+ vp = concat(" ", ct->c_descr, NULL);
+ add_header(ct, np, vp);
+ }
+
+ /*
+ ** output the Content-Disposition
+ */
+ if (ct->c_dispo) {
+ np = getcpy(DISPO_FIELD);
+ vp = concat(" ", ct->c_dispo, NULL);
+ add_header(ct, np, vp);
+ }
+
+skip_headers:
+ /*
+ ** If this is the internal content structure for a
+ ** "message/external", then we are done with the
+ ** headers (since it has no body).
+ */
+ if (ct->c_ctexbody)
+ return OK;
+
+ /*
+ ** output the Content-Transfer-Encoding
+ */
+ switch (ct->c_encoding) {
+ case CE_7BIT:
+ /* Nothing to output */
+ break;
+
+ case CE_8BIT:
+ if (ct->c_type == CT_MESSAGE)
+ adios(NULL, "internal error, invalid encoding");
+
+ np = getcpy(ENCODING_FIELD);
+ vp = concat(" ", "8bit", "\n", NULL);
+ add_header(ct, np, vp);
+ break;
+
+ case CE_QUOTED:
+ if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
+ adios(NULL, "internal error, invalid encoding");
+
+ np = getcpy(ENCODING_FIELD);
+ vp = concat(" ", "quoted-printable", "\n", NULL);
+ add_header(ct, np, vp);
+ break;
+
+ case CE_BASE64:
+ if (ct->c_type == CT_MESSAGE || ct->c_type == CT_MULTIPART)
+ adios(NULL, "internal error, invalid encoding");
+
+ np = getcpy(ENCODING_FIELD);
+ vp = concat(" ", "base64", "\n", NULL);
+ add_header(ct, np, vp);
+ break;
+
+ case CE_BINARY:
+ if (ct->c_type == CT_MESSAGE)
+ adios(NULL, "internal error, invalid encoding");
+
+ np = getcpy(ENCODING_FIELD);
+ vp = concat(" ", "binary", "\n", NULL);
+ add_header(ct, np, vp);
+ break;
+
+ default:
+ adios(NULL, "unknown transfer encoding in content");
+ break;
+ }
+
+ /*
+ ** Additional content specific header processing
+ */
+ switch (ct->c_type) {
+ case CT_MULTIPART:
+ {
+ struct multipart *m;
+ struct part *part;
+
+ m = (struct multipart *) ct->c_ctparams;
+ for (part = m->mp_parts; part; part = part->mp_next) {
+ CT p;
+
+ p = part->mp_part;
+ build_headers(p);
+ }
+ }
+ break;
+
+ case CT_MESSAGE:
+ if (ct->c_subtype == MESSAGE_EXTERNAL) {
+ struct exbody *e;
+
+ e = (struct exbody *) ct->c_ctparams;
+ build_headers(e->eb_content);
+ }
+ break;
+
+ default:
+ /* Nothing to do */
+ break;
+ }
+
+ return OK;