X-Git-Url: http://git.marmaro.de/?p=mmh;a=blobdiff_plain;f=uip%2Fmhbuildsbr.c;h=ca69fbe6e8c15c0936cd337fb54785efa22d3a7a;hp=a65736f3c7e234adc22f0cd12658038d2add420b;hb=ced6090a330d3d83d0bce709f756aa3d7d65fea4;hpb=794dab6aa5027ffaf024506140c258f6a0a9322d diff --git a/uip/mhbuildsbr.c b/uip/mhbuildsbr.c index a65736f..ca69fbe 100644 --- a/uip/mhbuildsbr.c +++ b/uip/mhbuildsbr.c @@ -1,17 +1,18 @@ - /* - * mhbuildsbr.c -- routines to expand/translate MIME composition files - * - * $Id$ - */ +** mhbuildsbr.c -- routines to expand/translate MIME composition files +** +** This code is Copyright (c) 2002, by the authors of nmh. See the +** COPYRIGHT file in the root directory of the nmh distribution for +** complete copyright information. +*/ /* - * This code was originally part of mhn.c. I split it into - * a separate program (mhbuild.c) and then later split it - * again (mhbuildsbr.c). But the code still has some of - * the mhn.c code in it. This program needs additional - * streamlining and removal of unneeded code. - */ +** This code was originally part of mhn.c. I split it into +** a separate program (mhbuild.c) and then later split it +** again (mhbuildsbr.c). But the code still has some of +** the mhn.c code in it. This program needs additional +** streamlining and removal of unneeded code. +*/ #include #include @@ -19,97 +20,52 @@ #include #include #include -#include -#include +#include +#include #include #include +#include + +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef TM_IN_SYS_TIME +# include +# else +# include +# endif +#endif #ifdef HAVE_SYS_WAIT_H # include #endif -extern int errno; - extern int debugsw; extern int verbosw; extern int ebcdicsw; extern int listsw; extern int rfc934sw; +extern int contentidsw; -extern int endian; /* mhmisc.c */ +extern int endian; /* mhmisc.c */ /* cache policies */ -extern int rcachesw; /* mhcachesbr.c */ -extern int wcachesw; /* mhcachesbr.c */ - -int checksw = 0; /* Add Content-MD5 field */ +extern int rcachesw; /* mhcachesbr.c */ +extern int wcachesw; /* mhcachesbr.c */ /* - * Directory to place tmp files. This must - * be set before these routines are called. - */ +** Directory to place tmp files. This must +** be set before these routines are called. +*/ char *tmp; pid_t xpid = 0; static char prefix[] = "----- =_aaaaaaaaaa"; -/* - * Structure for mapping types to their internal flags - */ -struct k2v { - char *kv_key; - int kv_value; -}; - -/* - * Structures for TEXT messages - */ -static struct k2v SubText[] = { - { "plain", TEXT_PLAIN }, - { "richtext", TEXT_RICHTEXT }, /* defined in RFC-1341 */ - { "enriched", TEXT_ENRICHED }, /* defined in RFC-1896 */ - { NULL, TEXT_UNKNOWN } /* this one must be last! */ -}; - -static struct k2v Charset[] = { - { "us-ascii", CHARSET_USASCII }, - { "iso-8859-1", CHARSET_LATIN }, - { NULL, CHARSET_UNKNOWN } /* this one must be last! */ -}; - -/* - * Structures for MULTIPART messages - */ -static struct k2v SubMultiPart[] = { - { "mixed", MULTI_MIXED }, - { "alternative", MULTI_ALTERNATE }, - { "digest", MULTI_DIGEST }, - { "parallel", MULTI_PARALLEL }, - { NULL, MULTI_UNKNOWN } /* this one must be last! */ -}; - -/* - * Structures for MESSAGE messages - */ -static struct k2v SubMessage[] = { - { "rfc822", MESSAGE_RFC822 }, - { "partial", MESSAGE_PARTIAL }, - { "external-body", MESSAGE_EXTERNAL }, - { NULL, MESSAGE_UNKNOWN } /* this one must be last! */ -}; - -/* - * Structure for APPLICATION messages - */ -static struct k2v SubApplication[] = { - { "octet-stream", APPLICATION_OCTETS }, - { "postscript", APPLICATION_POSTSCRIPT }, - { NULL, APPLICATION_UNKNOWN } /* this one must be last! */ -}; - /* mhmisc.c */ int make_intermediates (char *); @@ -127,42 +83,14 @@ void free_ctinfo (CT); void free_encoding (CT, int); /* - * prototypes - */ +** prototypes +*/ CT build_mime (char *); -int pidcheck (int); /* - * static prototypes - */ -static CT get_content (FILE *, char *, int); -static int add_header (CT, char *, char *); -static int get_ctinfo (char *, CT, int); -static int get_comment (CT, char **, int); -static int InitGeneric (CT); -static int InitText (CT); -static int InitMultiPart (CT); -static void reverse_parts (CT); -static int InitMessage (CT); -static int params_external (CT, int); -static int InitApplication (CT); +** static prototypes +*/ static int init_decoded_content (CT); -static int init_encoding (CT, OpenCEFunc); -static void close_encoding (CT); -static unsigned long size_encoding (CT); -static int InitBase64 (CT); -static int openBase64 (CT, char **); -static int InitQuoted (CT); -static int openQuoted (CT, char **); -static int Init7Bit (CT); -static int open7Bit (CT, char **); -static int openExternal (CT, CT, CE, char **, int *); -static int InitFile (CT); -static int openFile (CT, char **); -static int InitFTP (CT); -static int openFTP (CT, char **); -static int InitMail (CT); -static int openMail (CT, char **); static char *fgetstr (char *, int, FILE *); static int user_content (FILE *, char *, char *, CT *); static void set_id (CT, int); @@ -170,4048 +98,1623 @@ static int compose_content (CT); static int scan_content (CT); static int build_headers (CT); static char *calculate_digest (CT, int); -static int readDigest (CT, char *); - -/* - * Structures for mapping (content) types to - * the functions to handle them. - */ -struct str2init { - char *si_key; - int si_val; - InitFunc si_init; -}; - -static struct str2init str2cts[] = { - { "application", CT_APPLICATION, InitApplication }, - { "audio", CT_AUDIO, InitGeneric }, - { "image", CT_IMAGE, InitGeneric }, - { "message", CT_MESSAGE, InitMessage }, - { "multipart", CT_MULTIPART, InitMultiPart }, - { "text", CT_TEXT, InitText }, - { "video", CT_VIDEO, InitGeneric }, - { NULL, CT_EXTENSION, NULL }, /* these two must be last! */ - { NULL, CT_UNKNOWN, NULL }, -}; - -static struct str2init str2ces[] = { - { "base64", CE_BASE64, InitBase64 }, - { "quoted-printable", CE_QUOTED, InitQuoted }, - { "8bit", CE_8BIT, Init7Bit }, - { "7bit", CE_7BIT, Init7Bit }, - { "binary", CE_BINARY, NULL }, - { NULL, CE_EXTENSION, NULL }, /* these two must be last! */ - { NULL, CE_UNKNOWN, NULL }, -}; - -/* - * NOTE WELL: si_key MUST NOT have value of NOTOK - * - * si_key is 1 if access method is anonymous. - */ -static struct str2init str2methods[] = { - { "afs", 1, InitFile }, - { "anon-ftp", 1, InitFTP }, - { "ftp", 0, InitFTP }, - { "local-file", 0, InitFile }, - { "mail-server", 0, InitMail }, - { NULL, 0, NULL } -}; - - -int -pidcheck (int status) -{ - if ((status & 0xff00) == 0xff00 || (status & 0x007f) != SIGQUIT) - return status; - - fflush (stdout); - fflush (stderr); - return done (1); -} /* - * Main routine for translating composition file - * into valid MIME message. It translates the draft - * into a content structure (actually a tree of content - * structures). This message then can be manipulated - * in various ways, including being output via - * output_message(). - */ +** Main routine for translating composition file +** into valid MIME message. It translates the draft +** into a content structure (actually a tree of content +** structures). This message then can be manipulated +** in various ways, including being output via +** output_message(). +*/ CT build_mime (char *infile) { - int compnum, state; - char buf[BUFSIZ], name[NAMESZ]; - char *cp, *np, *vp; - struct multipart *m; - struct part **pp; - CT ct; - FILE *in; - - umask (~m_gmprot ()); - - /* open the composition draft */ - if ((in = fopen (infile, "r")) == NULL) - adios (infile, "unable to open for reading"); - - /* - * Allocate space for primary (outside) content - */ - if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL) - adios (NULL, "out of memory"); - - /* - * Allocate structure for handling decoded content - * for this part. We don't really need this, but - * allocate it to remain consistent. - */ - init_decoded_content (ct); - - /* - * Parse some of the header fields in the composition - * draft into the linked list of header fields for - * the new MIME message. - */ - for (compnum = 1, state = FLD;;) { - switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { - case FLD: - case FLDPLUS: - case FLDEOF: - compnum++; - - /* abort if draft has Mime-Version header field */ - if (!strcasecmp (name, VRSN_FIELD)) - adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD); - - /* abort if draft has Content-Transfer-Encoding header field */ - if (!strcasecmp (name, ENCODING_FIELD)) - adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD); - - /* ignore any Content-Type fields in the header */ - if (!strcasecmp (name, TYPE_FIELD)) { - while (state == FLDPLUS) - state = m_getfld (state, name, buf, sizeof(buf), in); - goto finish_field; - } - - /* get copies of the buffers */ - np = add (name, NULL); - vp = add (buf, NULL); - - /* if necessary, get rest of field */ - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - vp = add (buf, vp); /* add to previous value */ - } - - /* Now add the header data to the list */ - add_header (ct, np, vp); - -finish_field: - /* if this wasn't the last header field, then continue */ - if (state != FLDEOF) - continue; - /* else fall... */ - - case FILEEOF: - adios (NULL, "draft has empty body -- no directives!"); - /* NOTREACHED */ - - case BODY: - case BODYEOF: - fseek (in, (long) (-strlen (buf)), SEEK_CUR); - break; - - case LENERR: - case FMTERR: - adios (NULL, "message format error in component #%d", compnum); + int compnum, state; + char buf[BUFSIZ], name[NAMESZ]; + char *cp, *np, *vp; + struct multipart *m; + struct part **pp; + CT ct; + FILE *in; - default: - adios (NULL, "getfld() returned %d", state); - } - break; - } - - /* - * Now add the MIME-Version header field - * to the list of header fields. - */ - np = add (VRSN_FIELD, NULL); - vp = concat (" ", VRSN_VALUE, "\n", NULL); - add_header (ct, np, vp); - - /* - * We initally assume we will find multiple contents in the - * draft. So create a multipart/mixed content to hold everything. - * We can remove this later, if it is not needed. - */ - if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK) - done (1); - ct->c_type = CT_MULTIPART; - ct->c_subtype = MULTI_MIXED; - ct->c_file = add (infile, NULL); - - if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) m; - pp = &m->mp_parts; - - /* - * read and parse the composition file - * and the directives it contains. - */ - while (fgetstr (buf, sizeof(buf) - 1, in)) { - struct part *part; - CT p; - - if (user_content (in, infile, buf, &p) == DONE) { - admonish (NULL, "ignoring spurious #end"); - continue; - } - if (!p) - continue; - - if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) - adios (NULL, "out of memory"); - *pp = part; - pp = &part->mp_next; - part->mp_part = p; - } - - /* - * close the composition draft since - * it's not needed any longer. - */ - fclose (in); - - /* check if any contents were found */ - if (!m->mp_parts) - adios (NULL, "no content directives found"); - - /* - * If only one content was found, then remove and - * free the outer multipart content. - */ - if (!m->mp_parts->mp_next) { - CT p; - - p = m->mp_parts->mp_part; - m->mp_parts->mp_part = NULL; - - /* move header fields */ - p->c_first_hf = ct->c_first_hf; - p->c_last_hf = ct->c_last_hf; - ct->c_first_hf = NULL; - ct->c_last_hf = NULL; - - free_content (ct); - ct = p; - } else { - set_id (ct, 1); - } - - /* - * Fill out, or expand directives. Parse and execute - * commands specified by profile composition strings. - */ - compose_content (ct); - - if ((cp = strchr(prefix, 'a')) == NULL) - adios (NULL, "internal error(4)"); - - /* - * Scan the contents. Choose a transfer encoding, and - * check if prefix for multipart boundary clashes with - * any of the contents. - */ - while (scan_content (ct) == NOTOK) { - if (*cp < 'z') { - (*cp)++; - } else { - if (*++cp == 0) - adios (NULL, "giving up trying to find a unique delimiter string"); - else - (*cp)++; - } - } + umask (~m_gmprot ()); - /* Build the rest of the header field structures */ - build_headers (ct); + /* open the composition draft */ + if ((in = fopen (infile, "r")) == NULL) + adios (infile, "unable to open for reading"); - return ct; -} + /* + ** Allocate space for primary (outside) content + */ + if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL) + adios (NULL, "out of memory"); + /* + ** Allocate structure for handling decoded content + ** for this part. We don't really need this, but + ** allocate it to remain consistent. + */ + init_decoded_content (ct); -/* - * Main routine for reading/parsing the headers - * of a message content. - * - * toplevel = 1 # we are at the top level of the message - * toplevel = 0 # we are inside message type or multipart type - * # other than multipart/digest - * toplevel = -1 # we are inside multipart/digest - */ - -static CT -get_content (FILE *in, char *file, int toplevel) -{ - int compnum, state; - char buf[BUFSIZ], name[NAMESZ]; - CT ct; - - if (!(ct = (CT) calloc (1, sizeof(*ct)))) - adios (NULL, "out of memory"); - - ct->c_fp = in; - ct->c_file = add (file, NULL); - ct->c_begin = ftell (ct->c_fp) + 1; - - /* - * Read the content headers - */ - for (compnum = 1, state = FLD;;) { - switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { - case FLD: - case FLDPLUS: - case FLDEOF: - compnum++; - - /* Get MIME-Version field */ - if (!strcasecmp (name, VRSN_FIELD)) { - int ucmp; - char c, *cp, *dp; - - cp = add (buf, NULL); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - cp = add (buf, cp); - } + /* + ** Parse some of the header fields in the composition + ** draft into the linked list of header fields for + ** the new MIME message. + */ + for (compnum = 1, state = FLD;;) { + switch (state = m_getfld (state, name, buf, sizeof(buf), in)) { + case FLD: + case FLDPLUS: + case FLDEOF: + compnum++; + + /* abort if draft has Mime-Version header field */ + if (!mh_strcasecmp (name, VRSN_FIELD)) + adios (NULL, "draft shouldn't contain %s: field", VRSN_FIELD); - if (ct->c_vrsn) { - advise (NULL, "message %s has multiple %s: fields (%s)", - ct->c_file, VRSN_FIELD, dp = trimcpy (cp)); - free (dp); - free (cp); - goto out; - } + /* + ** abort if draft has Content-Transfer-Encoding + ** header field + */ + if (!mh_strcasecmp (name, ENCODING_FIELD)) + adios (NULL, "draft shouldn't contain %s: field", ENCODING_FIELD); + + /* ignore any Content-Type fields in the header */ + if (!mh_strcasecmp (name, TYPE_FIELD)) { + while (state == FLDPLUS) + state = m_getfld (state, name, buf, + sizeof(buf), in); + goto finish_field; + } - ct->c_vrsn = cp; - while (isspace (*cp)) - cp++; - for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) - *dp++ = ' '; - for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (!isspace (*dp)) - break; - *++dp = '\0'; - if (debugsw) - fprintf (stderr, "%s: %s\n", VRSN_FIELD, cp); - - if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) - goto out; - - for (dp = cp; istoken (*dp); dp++) - continue; - c = *dp, *dp = '\0'; - ucmp = !strcasecmp (cp, VRSN_VALUE); - *dp = c; - if (!ucmp) - admonish (NULL, - "message %s has unknown value for %s: field (%s)", - ct->c_file, VRSN_FIELD, cp); - goto got_header; - } - - /* Get Content-Type field */ - if (!strcasecmp (name, TYPE_FIELD)) { - char *cp; - struct str2init *s2i; - CI ci = &ct->c_ctinfo; + /* get copies of the buffers */ + np = add (name, NULL); + vp = add (buf, NULL); - cp = add (buf, NULL); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - cp = add (buf, cp); - } + /* if necessary, get rest of field */ + while (state == FLDPLUS) { + state = m_getfld (state, name, buf, sizeof(buf), in); + vp = add (buf, vp); /* add to prev value */ + } - /* Check if we've already seen a Content-Type header */ - if (ct->c_ctline) { - char *dp = trimcpy (cp); + /* Now add the header data to the list */ + add_header (ct, np, vp); - advise (NULL, "message %s has multiple %s: fields (%s)", - ct->c_file, TYPE_FIELD, dp); - free (dp); - free (cp); - goto out; - } +finish_field: + /* if this wasn't the last hdr field, then continue */ + if (state != FLDEOF) + continue; + /* else fall... */ - /* Parse the Content-Type field */ - if (get_ctinfo (cp, ct, 0) == NOTOK) - goto out; + case FILEEOF: + adios (NULL, "draft has empty body -- no directives!"); + /* NOTREACHED */ - /* - * Set the Init function and the internal - * flag for this content type. - */ - for (s2i = str2cts; s2i->si_key; s2i++) - if (!strcasecmp (ci->ci_type, s2i->si_key)) + case BODY: + case BODYEOF: + fseek (in, (long) (-strlen (buf)), SEEK_CUR); break; - if (!s2i->si_key && !uprf (ci->ci_type, "X-")) - s2i++; - ct->c_type = s2i->si_val; - ct->c_ctinitfnx = s2i->si_init; - goto got_header; - } - - /* Get Content-Transfer-Encoding field */ - if (!strcasecmp (name, ENCODING_FIELD)) { - char *cp, *dp; - char c; - struct str2init *s2i; - - cp = add (buf, NULL); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - cp = add (buf, cp); - } - - /* - * Check if we've already seen the - * Content-Transfer-Encoding field - */ - if (ct->c_celine) { - advise (NULL, "message %s has multiple %s: fields (%s)", - ct->c_file, ENCODING_FIELD, dp = trimcpy (cp)); - free (dp); - free (cp); - goto out; - } - ct->c_celine = cp; /* Save copy of this field */ - while (isspace (*cp)) - cp++; - for (dp = cp; istoken (*dp); dp++) - continue; - c = *dp; - *dp = '\0'; + case LENERR: + case FMTERR: + adios (NULL, "message format error in component #%d", + compnum); - /* - * Find the internal flag and Init function - * for this transfer encoding. - */ - for (s2i = str2ces; s2i->si_key; s2i++) - if (!strcasecmp (cp, s2i->si_key)) - break; - if (!s2i->si_key && !uprf (cp, "X-")) - s2i++; - *dp = c; - ct->c_encoding = s2i->si_val; - - /* Call the Init function for this encoding */ - if (s2i->si_init && (*s2i->si_init) (ct) == NOTOK) - goto out; - goto got_header; - } - - /* Get Content-ID field */ - if (!strcasecmp (name, ID_FIELD)) { - ct->c_id = add (buf, ct->c_id); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - ct->c_id = add (buf, ct->c_id); - } - goto got_header; - } - - /* Get Content-Description field */ - if (!strcasecmp (name, DESCR_FIELD)) { - ct->c_descr = add (buf, ct->c_descr); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - ct->c_descr = add (buf, ct->c_descr); + default: + adios (NULL, "getfld() returned %d", state); } - goto got_header; - } - - /* Get Content-MD5 field */ - if (!strcasecmp (name, MD5_FIELD)) { - char *cp, *dp, *ep; + break; + } - cp = add (buf, NULL); - while (state == FLDPLUS) { - state = m_getfld (state, name, buf, sizeof(buf), in); - cp = add (buf, cp); - } + /* + ** Now add the MIME-Version header field + ** to the list of header fields. + */ + np = add (VRSN_FIELD, NULL); + vp = concat (" ", VRSN_VALUE, "\n", NULL); + add_header (ct, np, vp); - if (!checksw) { - free (cp); - goto got_header; - } + /* + ** We initally assume we will find multiple contents in the + ** draft. So create a multipart/mixed content to hold everything. + ** We can remove this later, if it is not needed. + */ + if (get_ctinfo ("multipart/mixed", ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = MULTI_MIXED; + ct->c_file = add (infile, NULL); - if (ct->c_digested) { - advise (NULL, "message %s has multiple %s: fields (%s)", - ct->c_file, MD5_FIELD, dp = trimcpy (cp)); - free (dp); - free (cp); - goto out; - } + if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) m; + pp = &m->mp_parts; - ep = cp; - while (isspace (*cp)) - cp++; - for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) - *dp++ = ' '; - for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (!isspace (*dp)) - break; - *++dp = '\0'; - if (debugsw) - fprintf (stderr, "%s: %s\n", MD5_FIELD, cp); + /* + ** read and parse the composition file + ** and the directives it contains. + */ + while (fgetstr (buf, sizeof(buf) - 1, in)) { + struct part *part; + CT p; - if (*cp == '(' && get_comment (ct, &cp, 0) == NOTOK) { - free (ep); - goto out; + if (user_content (in, infile, buf, &p) == DONE) { + admonish (NULL, "ignoring spurious #end"); + continue; } + if (!p) + continue; - for (dp = cp; *dp && !isspace (*dp); dp++) - continue; - *dp = '\0'; - - readDigest (ct, cp); - free (ep); - ct->c_digested++; - goto got_header; - } - -#if 0 - if (uprf (name, XXX_FIELD_PRF)) - advise (NULL, "unknown field (%s) in message %s", - name, ct->c_file); - /* and fall... */ -#endif + if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + adios (NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; + } - while (state == FLDPLUS) - state = m_getfld (state, name, buf, sizeof(buf), in); + /* + ** close the composition draft since + ** it's not needed any longer. + */ + fclose (in); -got_header: - if (state != FLDEOF) { - ct->c_begin = ftell (in) + 1; - continue; - } - /* else fall... */ + /* check if any contents were found */ + if (!m->mp_parts) + adios (NULL, "no content directives found"); - case BODY: - case BODYEOF: - ct->c_begin = ftell (in) - strlen (buf); - break; + /* + ** If only one content was found, then remove and + ** free the outer multipart content. + */ + if (!m->mp_parts->mp_next) { + CT p; - case FILEEOF: - ct->c_begin = ftell (in); - break; + p = m->mp_parts->mp_part; + m->mp_parts->mp_part = NULL; - case LENERR: - case FMTERR: - adios (NULL, "message format error in component #%d", compnum); + /* move header fields */ + p->c_first_hf = ct->c_first_hf; + p->c_last_hf = ct->c_last_hf; + ct->c_first_hf = NULL; + ct->c_last_hf = NULL; - default: - adios (NULL, "getfld() returned %d", state); - } - break; - } - - /* - * Check if we saw a Content-Type field. - * If not, then assign a default value for - * it, and the Init function. - */ - if (!ct->c_ctline) { - /* - * If we are inside a multipart/digest message, - * so default type is message/rfc822 - */ - if (toplevel < 0) { - if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK) - goto out; - ct->c_type = CT_MESSAGE; - ct->c_ctinitfnx = InitMessage; + free_content (ct); + ct = p; } else { - /* - * Else default type is text/plain - */ - if (get_ctinfo ("text/plain", ct, 0) == NOTOK) - goto out; - ct->c_type = CT_TEXT; - ct->c_ctinitfnx = InitText; + set_id (ct, 1); } - } - /* Use default Transfer-Encoding, if necessary */ - if (!ct->c_celine) { - ct->c_encoding = CE_7BIT; - Init7Bit (ct); - } - - return ct; + /* + ** Fill out, or expand directives. Parse and execute + ** commands specified by profile composition strings. + */ + compose_content (ct); -out: - free_content (ct); - return NULL; -} + if ((cp = strchr(prefix, 'a')) == NULL) + adios (NULL, "internal error(4)"); + /* + ** Scan the contents. Choose a transfer encoding, and + ** check if prefix for multipart boundary clashes with + ** any of the contents. + */ + while (scan_content (ct) == NOTOK) { + if (*cp < 'z') { + (*cp)++; + } else { + if (*++cp == 0) + adios (NULL, "giving up trying to find a unique delimiter string"); + else + (*cp)++; + } + } -/* - * small routine to add header field to list - */ + /* Build the rest of the header field structures */ + build_headers (ct); -static int -add_header (CT ct, char *name, char *value) -{ - HF hp; - - /* allocate header field structure */ - if (!(hp = malloc (sizeof(*hp)))) - adios (NULL, "out of memory"); - - /* link data into header structure */ - hp->name = name; - hp->value = value; - hp->next = NULL; - - /* link header structure into the list */ - if (ct->c_first_hf == NULL) { - ct->c_first_hf = hp; /* this is the first */ - ct->c_last_hf = hp; - } else { - ct->c_last_hf->next = hp; /* add it to the end */ - ct->c_last_hf = hp; - } - - return 0; + return ct; } /* - * Used to parse both: - * 1) Content-Type line - * 2) composition directives - * - * and fills in the information of the CTinfo structure. - */ +** Set up structures for placing unencoded +** content when building parts. +*/ static int -get_ctinfo (char *cp, CT ct, int magic) +init_decoded_content (CT ct) { - int i; - char *dp, **ap, **ep; - char c; - CI ci; - - ci = &ct->c_ctinfo; - i = strlen (invo_name) + 2; - - /* store copy of Content-Type line */ - cp = ct->c_ctline = add (cp, NULL); - - while (isspace (*cp)) /* trim leading spaces */ - cp++; - - /* change newlines to spaces */ - for (dp = strchr(cp, '\n'); dp; dp = strchr(dp, '\n')) - *dp++ = ' '; - - /* trim trailing spaces */ - for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (!isspace (*dp)) - break; - *++dp = '\0'; - - if (debugsw) - fprintf (stderr, "%s: %s\n", TYPE_FIELD, cp); - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - for (dp = cp; istoken (*dp); dp++) - continue; - c = *dp, *dp = '\0'; - ci->ci_type = add (cp, NULL); /* store content type */ - *dp = c, cp = dp; - - if (!*ci->ci_type) { - advise (NULL, "invalid %s: field in message %s (empty type)", - TYPE_FIELD, ct->c_file); - return NOTOK; - } - - /* down case the content type string */ - for (dp = ci->ci_type; *dp; dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); - - while (isspace (*cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - if (*cp != '/') { - if (!magic) - ci->ci_subtype = add ("", NULL); - goto magic_skip; - } - - cp++; - while (isspace (*cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - for (dp = cp; istoken (*dp); dp++) - continue; - c = *dp, *dp = '\0'; - ci->ci_subtype = add (cp, NULL); /* store the content subtype */ - *dp = c, cp = dp; - - if (!*ci->ci_subtype) { - advise (NULL, - "invalid %s: field in message %s (empty subtype for \"%s\")", - TYPE_FIELD, ct->c_file, ci->ci_type); - return NOTOK; - } - - /* down case the content subtype string */ - for (dp = ci->ci_subtype; *dp; dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); - -magic_skip: - while (isspace (*cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - - /* - * Parse attribute/value pairs given with Content-Type - */ - ep = (ap = ci->ci_attrs) + NPARMS; - while (*cp == ';') { - char *vp, *up; - - if (ap >= ep) { - advise (NULL, - "too many parameters in message %s's %s: field (%d max)", - ct->c_file, TYPE_FIELD, NPARMS); - return NOTOK; - } + CE ce; - cp++; - while (isspace (*cp)) - cp++; + if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL) + adios (NULL, "out of memory"); - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; + ct->c_cefile = ce; + ct->c_ceopenfnx = open7Bit; /* since unencoded */ + ct->c_ceclosefnx = close_encoding; + ct->c_cesizefnx = NULL; /* since unencoded */ - if (*cp == 0) { - advise (NULL, - "extraneous trailing ';' in message %s's %s: parameter list", - ct->c_file, TYPE_FIELD); - return OK; - } + return OK; +} - /* down case the attribute name */ - for (dp = cp; istoken (*dp); dp++) - if (isalpha(*dp) && isupper (*dp)) - *dp = tolower (*dp); - - for (up = dp; isspace (*dp); ) - dp++; - if (dp == cp || *dp != '=') { - advise (NULL, - "invalid parameter in message %s's %s: field\n%*.*sparameter %s (error detected at offset %d)", - ct->c_file, TYPE_FIELD, i, i, "", cp, dp - cp); - return NOTOK; - } - vp = (*ap = add (cp, NULL)) + (up - cp); - *vp = '\0'; - for (dp++; isspace (*dp); ) - dp++; - - /* now add the attribute value */ - ci->ci_values[ap - ci->ci_attrs] = vp = *ap + (dp - cp); - - if (*dp == '"') { - for (cp = ++dp, dp = vp;;) { - switch (c = *cp++) { - case '\0': -bad_quote: - advise (NULL, - "invalid quoted-string in message %s's %s: field\n%*.*s(parameter %s)", - ct->c_file, TYPE_FIELD, i, i, "", *ap); - return NOTOK; - - case '\\': - *dp++ = c; - if ((c = *cp++) == '\0') - goto bad_quote; - /* else fall... */ +static char * +fgetstr (char *s, int n, FILE *stream) +{ + char *cp, *ep; + + for (ep = (cp = s) + n; cp < ep; ) { + int i; - default: - *dp++ = c; - continue; + if (!fgets (cp, n, stream)) + return (cp != s ? s : NULL); + if (cp == s && *cp != '#') + return s; - case '"': - *dp = '\0'; + cp += (i = strlen (cp)) - 1; + if (i <= 1 || *cp-- != '\n' || *cp != '\\') break; - } - break; - } - } else { - for (cp = dp, dp = vp; istoken (*cp); cp++, dp++) - continue; - *dp = '\0'; - } - if (!*vp) { - advise (NULL, - "invalid parameter in message %s's %s: field\n%*.*s(parameter %s)", - ct->c_file, TYPE_FIELD, i, i, "", *ap); - return NOTOK; - } - ap++; - - while (isspace (*cp)) - cp++; - - if (*cp == '(' && get_comment (ct, &cp, 1) == NOTOK) - return NOTOK; - } - - /* - * Get any given in buffer - */ - if (magic && *cp == '<') { - if (ct->c_id) { - free (ct->c_id); - ct->c_id = NULL; - } - if (!(dp = strchr(ct->c_id = ++cp, '>'))) { - advise (NULL, "invalid ID in message %s", ct->c_file); - return NOTOK; - } - c = *dp; - *dp = '\0'; - if (*ct->c_id) - ct->c_id = concat ("<", ct->c_id, ">\n", NULL); - else - ct->c_id = NULL; - *dp++ = c; - cp = dp; - - while (isspace (*cp)) - cp++; - } - - /* - * Get any [Content-Description] given in buffer. - */ - if (magic && *cp == '[') { - ct->c_descr = ++cp; - for (dp = cp + strlen (cp) - 1; dp >= cp; dp--) - if (*dp == ']') - break; - if (dp < cp) { - advise (NULL, "invalid description in message %s", ct->c_file); - ct->c_descr = NULL; - return NOTOK; + *cp = '\0'; + n -= (i - 2); } - - c = *dp; - *dp = '\0'; - if (*ct->c_descr) - ct->c_descr = concat (ct->c_descr, "\n", NULL); - else - ct->c_descr = NULL; - *dp++ = c; - cp = dp; - - while (isspace (*cp)) - cp++; - } - - /* - * Check if anything is left over - */ - if (*cp) { - if (magic) - ci->ci_magic = add (cp, NULL); - else - advise (NULL, - "extraneous information in message %s's %s: field\n%*.*s(%s)", - ct->c_file, TYPE_FIELD, i, i, "", cp); - } - - return OK; + + return s; } +/* +** Parse the composition draft for text and directives. +** Do initial setup of Content structure. +*/ + static int -get_comment (CT ct, char **ap, int istype) +user_content (FILE *in, char *file, char *buf, CT *ctp) { - int i; - char *bp, *cp; - char c, buffer[BUFSIZ], *dp; - CI ci; - - ci = &ct->c_ctinfo; - cp = *ap; - bp = buffer; - cp++; - - for (i = 0;;) { - switch (c = *cp++) { - case '\0': -invalid: - advise (NULL, "invalid comment in message %s's %s: field", - ct->c_file, istype ? TYPE_FIELD : VRSN_FIELD); - return NOTOK; - - case '\\': - *bp++ = c; - if ((c = *cp++) == '\0') - goto invalid; - *bp++ = c; - continue; - - case '(': - i++; - /* and fall... */ - default: - *bp++ = c; - continue; - - case ')': - if (--i < 0) - break; - *bp++ = c; - continue; + int extrnal, vrsn; + unsigned char *cp; + char **ap; + char buffer[BUFSIZ]; + struct multipart *m; + struct part **pp; + struct stat st; + struct str2init *s2i; + CI ci; + CT ct; + CE ce; + + if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) { + *ctp = NULL; + return OK; } - break; - } - *bp = '\0'; - if (istype) { - if ((dp = ci->ci_comment)) { - ci->ci_comment = concat (dp, " ", buffer, NULL); - free (dp); - } else { - ci->ci_comment = add (buffer, NULL); - } - } + /* allocate basic Content structure */ + if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL) + adios (NULL, "out of memory"); + *ctp = ct; - while (isspace (*cp)) - cp++; + /* allocate basic structure for handling decoded content */ + init_decoded_content (ct); + ce = ct->c_cefile; - *ap = cp; - return OK; -} + ci = &ct->c_ctinfo; + set_id (ct, 0); + /* + ** Handle inline text. Check if line + ** is one of the following forms: + ** + ** 1) doesn't begin with '#' (implicit directive) + ** 2) begins with "##" (implicit directive) + ** 3) begins with "#<" + */ + if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') { + int headers; + int inlineD; + long pos; + char content[BUFSIZ]; + FILE *out; + char *cp; -/* - * CONTENTS - * - * Handles content types audio, image, and video. - * There's not much to do right here. - */ + cp = m_mktemp2(NULL, invo_name, NULL, &out); + if (cp == NULL) + adios("mhbuildsbr", "unable to create temporary file"); -static int -InitGeneric (CT ct) -{ - return OK; /* not much to do here */ -} + /* use a temp file to collect the plain text lines */ + ce->ce_file = add (cp, NULL); + ce->ce_unlink = 1; + if (buf[0] == '#' && buf[1] == '<') { + strncpy (content, buf + 2, sizeof(content)); + inlineD = 1; + goto rock_and_roll; + } else { + inlineD = 0; + } -/* - * TEXT - */ + /* the directive is implicit */ + strncpy (content, "text/plain", sizeof(content)); + headers = 0; + strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer)); + for (;;) { + int i; -static int -InitText (CT ct) -{ - char **ap, **ep; - struct k2v *kv; - struct text *t; - CI ci = &ct->c_ctinfo; - - /* check for missing subtype */ - if (!*ci->ci_subtype) - ci->ci_subtype = add ("plain", ci->ci_subtype); - - /* match subtype */ - for (kv = SubText; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; - - /* allocate text character set structure */ - if ((t = (struct text *) calloc (1, sizeof(*t))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) t; - - /* initially mark character set as unspecified */ - t->tx_charset = CHARSET_UNSPECIFIED; - - /* scan for charset parameter */ - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) - if (!strcasecmp (*ap, "charset")) - break; - - /* check if content specified a character set */ - if (*ap) { - /* match character set or set to CHARSET_UNKNOWN */ - for (kv = Charset; kv->kv_key; kv++) - if (!strcasecmp (*ep, kv->kv_key)) - break; - t->tx_charset = kv->kv_value; - } + if (headers >= 0 && uprf (buffer, DESCR_FIELD) + && buffer[i = strlen (DESCR_FIELD)] == ':') { + headers = 1; - return OK; -} +again_descr: + ct->c_descr = add (buffer + i + 1, ct->c_descr); + if (!fgetstr (buffer, sizeof(buffer) - 1, in)) + adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD); + switch (buffer[0]) { + case ' ': + case '\t': + i = -1; + goto again_descr; + + case '#': + adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD); + /* NOTREACHED */ + + default: + break; + } + } + if (headers >= 0 && uprf (buffer, DISPO_FIELD) + && buffer[i = strlen (DISPO_FIELD)] == ':') { + headers = 1; + +again_dispo: + ct->c_dispo = add (buffer + i + 1, ct->c_dispo); + if (!fgetstr (buffer, sizeof(buffer) - 1, in)) + adios (NULL, "end-of-file after %s: field in plaintext", DISPO_FIELD); + switch (buffer[0]) { + case ' ': + case '\t': + i = -1; + goto again_dispo; + + case '#': + adios (NULL, "#-directive after %s: field in plaintext", DISPO_FIELD); + /* NOTREACHED */ + + default: + break; + } + } -/* - * MULTIPART - */ + if (headers != 1 || buffer[0] != '\n') + fputs (buffer, out); -static int -InitMultiPart (CT ct) -{ - int inout; - long last, pos; - char *cp, *dp, **ap, **ep; - char *bp, buffer[BUFSIZ]; - struct multipart *m; - struct k2v *kv; - struct part *part, **next; - CI ci = &ct->c_ctinfo; - CT p; - FILE *fp; - - /* - * The encoding for multipart messages must be either - * 7bit, 8bit, or binary (per RFC2045). - */ - if (ct->c_encoding != CE_7BIT && ct->c_encoding != CE_8BIT - && ct->c_encoding != CE_BINARY) { - admonish (NULL, - "\"%s/%s\" type in message %s must be encoded in 7bit, 8bit, or binary", - ci->ci_type, ci->ci_subtype, ct->c_file); - return NOTOK; - } - - /* match subtype */ - for (kv = SubMultiPart; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; - - /* - * Check for "boundary" parameter, which is - * required for multipart messages. - */ - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!strcasecmp (*ap, "boundary")) { - bp = *ep; - break; - } - } - - /* complain if boundary parameter is missing */ - if (!*ap) { - advise (NULL, - "a \"boundary\" parameter is mandatory for \"%s/%s\" type in message %s's %s: field", - ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); - return NOTOK; - } - - /* allocate primary structure for multipart info */ - if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) m; - - /* check if boundary parameter contains only whitespace characters */ - for (cp = bp; isspace (*cp); cp++) - continue; - if (!*cp) { - advise (NULL, "invalid \"boundary\" parameter for \"%s/%s\" type in message %s's %s: field", - ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); - return NOTOK; - } - - /* remove trailing whitespace from boundary parameter */ - for (cp = bp, dp = cp + strlen (cp) - 1; dp > cp; dp--) - if (!isspace (*dp)) - break; - *++dp = '\0'; - - /* record boundary separators */ - m->mp_start = concat (bp, "\n", NULL); - m->mp_stop = concat (bp, "--\n", NULL); - - if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - advise (ct->c_file, "unable to open for reading"); - return NOTOK; - } - - fseek (fp = ct->c_fp, pos = ct->c_begin, SEEK_SET); - last = ct->c_end; - next = &m->mp_parts; - part = NULL; - inout = 1; - - while (fgets (buffer, sizeof(buffer) - 1, fp)) { - if (pos > last) - break; - - pos += strlen (buffer); - if (buffer[0] != '-' || buffer[1] != '-') - continue; - if (inout) { - if (strcmp (buffer + 2, m->mp_start)) - continue; -next_part: - if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) - adios (NULL, "out of memory"); - *next = part; - next = &part->mp_next; - - if (!(p = get_content (fp, ct->c_file, - rfc934sw && ct->c_subtype == MULTI_DIGEST ? -1 : 0))) { - fclose (ct->c_fp); - ct->c_fp = NULL; - return NOTOK; - } - p->c_fp = NULL; - part->mp_part = p; - pos = p->c_begin; - fseek (fp, pos, SEEK_SET); - inout = 0; - } else { - if (strcmp (buffer + 2, m->mp_start) == 0) { - inout = 1; -end_part: - p = part->mp_part; - p->c_end = ftell(fp) - (strlen(buffer) + 1); - if (p->c_end < p->c_begin) - p->c_begin = p->c_end; - if (inout) - goto next_part; - goto last_part; - } else { - if (strcmp (buffer + 2, m->mp_stop) == 0) - goto end_part; - } - } - } - - advise (NULL, "bogus multipart content in message %s", ct->c_file); - if (!inout && part) { - p = part->mp_part; - p->c_end = ct->c_end; - - if (p->c_begin >= p->c_end) { - for (next = &m->mp_parts; *next != part; - next = &((*next)->mp_next)) - continue; - *next = NULL; - free_content (p); - free ((char *) part); - } - } - -last_part: - /* reverse the order of the parts for multipart/alternative */ - if (ct->c_subtype == MULTI_ALTERNATE) - reverse_parts (ct); - - /* - * label all subparts with part number, and - * then initialize the content of the subpart. - */ - { - int partnum; - char *pp; - char partnam[BUFSIZ]; - - if (ct->c_partno) { - snprintf (partnam, sizeof(partnam), "%s.", ct->c_partno); - pp = partnam + strlen (partnam); - } else { - pp = partnam; - } +rock_and_roll: + headers = -1; + pos = ftell (in); + if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL) + break; + if (buffer[0] == '#') { + char *bp; + + if (buffer[1] != '#') + break; + for (cp = (bp = buffer) + 1; *cp; cp++) + *bp++ = *cp; + *bp = '\0'; + } + } - for (part = m->mp_parts, partnum = 1; part; - part = part->mp_next, partnum++) { - p = part->mp_part; + if (listsw) + ct->c_end = ftell (out); + fclose (out); - sprintf (pp, "%d", partnum); - p->c_partno = add (partnam, NULL); + /* parse content type */ + if (get_ctinfo (content, ct, inlineD) == NOTOK) + done (1); - /* initialize the content of the subparts */ - if (p->c_ctinitfnx && (*p->c_ctinitfnx) (p) == NOTOK) { - fclose (ct->c_fp); - ct->c_fp = NULL; - return NOTOK; - } - } - } + for (s2i = str2cts; s2i->si_key; s2i++) + if (!mh_strcasecmp (ci->ci_type, s2i->si_key)) + break; + if (!s2i->si_key && !uprf (ci->ci_type, "X-")) + s2i++; - fclose (ct->c_fp); - ct->c_fp = NULL; - return OK; -} + /* + ** check type specified (possibly implicitly) + */ + switch (ct->c_type = s2i->si_val) { + case CT_MESSAGE: + if (!mh_strcasecmp (ci->ci_subtype, "rfc822")) { + ct->c_encoding = CE_7BIT; + goto call_init; + } + /* else fall... */ + case CT_MULTIPART: + adios (NULL, "it doesn't make sense to define an in-line %s content", + ct->c_type == CT_MESSAGE ? "message" : + "multipart"); + /* NOTREACHED */ + default: +call_init: + if ((ct->c_ctinitfnx = s2i->si_init)) + (*ct->c_ctinitfnx) (ct); + break; + } -/* - * reverse the order of the parts of a multipart - */ + if (cp) + fseek (in, pos, SEEK_SET); + return OK; + } -static void -reverse_parts (CT ct) -{ - int i; - struct multipart *m; - struct part **base, **bmp, **next, *part; - - m = (struct multipart *) ct->c_ctparams; - - /* if only one part, just return */ - if (!m->mp_parts || !m->mp_parts->mp_next) - return; - - /* count number of parts */ - i = 0; - for (part = m->mp_parts; part; part = part->mp_next) - i++; - - /* allocate array of pointers to the parts */ - if (!(base = (struct part **) calloc ((size_t) (i + 1), sizeof(*base)))) - adios (NULL, "out of memory"); - bmp = base; - - /* point at all the parts */ - for (part = m->mp_parts; part; part = part->mp_next) - *bmp++ = part; - *bmp = NULL; - - /* reverse the order of the parts */ - next = &m->mp_parts; - for (bmp--; bmp >= base; bmp--) { - part = *bmp; - *next = part; - next = &part->mp_next; - } - *next = NULL; - - /* free array of pointers */ - free ((char *) base); -} + /* + ** If we've reached this point, the next line + ** must be some type of explicit directive. + */ + /* check if directive is external-type */ + extrnal = (buf[1] == '@'); -/* - * MESSAGE - */ + /* parse directive */ + if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK) + done (1); -static int -InitMessage (CT ct) -{ - struct k2v *kv; - CI ci = &ct->c_ctinfo; - - if ((ct->c_encoding != CE_7BIT) && (ct->c_encoding != CE_8BIT)) { - admonish (NULL, - "\"%s/%s\" type in message %s should be encoded in 7bit or 8bit", - ci->ci_type, ci->ci_subtype, ct->c_file); - return NOTOK; - } - - /* check for missing subtype */ - if (!*ci->ci_subtype) - ci->ci_subtype = add ("rfc822", ci->ci_subtype); - - /* match subtype */ - for (kv = SubMessage; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; - - switch (ct->c_subtype) { - case MESSAGE_RFC822: - break; - - case MESSAGE_PARTIAL: - { - char **ap, **ep; - struct partial *p; - - if ((p = (struct partial *) calloc (1, sizeof(*p))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) p; - - /* scan for parameters "id", "number", and "total" */ - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!strcasecmp (*ap, "id")) { - p->pm_partid = add (*ep, NULL); - continue; - } - if (!strcasecmp (*ap, "number")) { - if (sscanf (*ep, "%d", &p->pm_partno) != 1 - || p->pm_partno < 1) { -invalid_param: - advise (NULL, - "invalid %s parameter for \"%s/%s\" type in message %s's %s field", - *ap, ci->ci_type, ci->ci_subtype, - ct->c_file, TYPE_FIELD); - return NOTOK; - } - continue; - } - if (!strcasecmp (*ap, "total")) { - if (sscanf (*ep, "%d", &p->pm_maxno) != 1 - || p->pm_maxno < 1) - goto invalid_param; - continue; - } - } + /* check directive against the list of MIME types */ + for (s2i = str2cts; s2i->si_key; s2i++) + if (!mh_strcasecmp (ci->ci_type, s2i->si_key)) + break; + + /* + ** Check if the directive specified a valid type. + ** This will happen if it was one of the following forms: + ** + ** #type/subtype (or) + ** #@type/subtype + */ + if (s2i->si_key) { + if (!ci->ci_subtype) + adios (NULL, "missing subtype in \"#%s\"", ci->ci_type); + + switch (ct->c_type = s2i->si_val) { + case CT_MULTIPART: + adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", + ci->ci_type, ci->ci_subtype); + /* NOTREACHED */ + + case CT_MESSAGE: + if (!mh_strcasecmp (ci->ci_subtype, "partial")) + adios (NULL, "sorry, \"#%s/%s\" isn't supported", + ci->ci_type, ci->ci_subtype); + if (!mh_strcasecmp (ci->ci_subtype, "external-body")) + adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"", + ci->ci_type, ci->ci_subtype); +use_forw: + adios (NULL, + "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", + ci->ci_type, ci->ci_subtype); + /* NOTREACHED */ - if (!p->pm_partid - || !p->pm_partno - || (p->pm_maxno && p->pm_partno > p->pm_maxno)) { - advise (NULL, - "invalid parameters for \"%s/%s\" type in message %s's %s field", - ci->ci_type, ci->ci_subtype, - ct->c_file, TYPE_FIELD); - return NOTOK; + default: + if ((ct->c_ctinitfnx = s2i->si_init)) + (*ct->c_ctinitfnx) (ct); + break; } - } - break; - case MESSAGE_EXTERNAL: - { - int exresult; - struct exbody *e; - CT p; - FILE *fp; + /* + ** #@type/subtype (external types directive) + */ + if (extrnal) { + struct exbody *e; + CT p; - if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) e; + if (!ci->ci_magic) + adios (NULL, "need external information for \"#@%s/%s\"", + ci->ci_type, ci->ci_subtype); + p = ct; - if (!ct->c_fp - && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - advise (ct->c_file, "unable to open for reading"); - return NOTOK; - } + snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic); + free (ci->ci_magic); + ci->ci_magic = NULL; - fseek (fp = ct->c_fp, ct->c_begin, SEEK_SET); + /* + ** Since we are using the current Content structure to + ** hold information about the type of the external + ** reference, we need to create another Content + ** structure for the message/external-body to wrap + ** it in. + */ + if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL) + adios (NULL, "out of memory"); + *ctp = ct; + ci = &ct->c_ctinfo; + if (get_ctinfo (buffer, ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MESSAGE; + ct->c_subtype = MESSAGE_EXTERNAL; + + if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) e; + + e->eb_parent = ct; + e->eb_content = p; + p->c_ctexbody = e; + + if (params_external (ct, 1) == NOTOK) + done (1); + + return OK; + } - if (!(p = get_content (fp, ct->c_file, 0))) { - fclose (ct->c_fp); - ct->c_fp = NULL; - return NOTOK; + /* Handle [file] argument */ + if (ci->ci_magic) { + /* check if specifies command to execute */ + if (*ci->ci_magic == '|' || *ci->ci_magic == '!') { + for (cp = ci->ci_magic + 1; isspace (*cp); cp++) + continue; + if (!*cp) + adios (NULL, "empty pipe command for #%s directive", ci->ci_type); + cp = add (cp, NULL); + free (ci->ci_magic); + ci->ci_magic = cp; + } else { + /* record filename of decoded contents */ + ce->ce_file = ci->ci_magic; + if (access (ce->ce_file, R_OK) == NOTOK) + adios ("reading", "unable to access %s for", ce->ce_file); + if (listsw && stat (ce->ce_file, &st) != NOTOK) + ct->c_end = (long) st.st_size; + ci->ci_magic = NULL; + } + return OK; } - e->eb_parent = ct; - e->eb_content = p; - p->c_ctexbody = e; - if ((exresult = params_external (ct, 0)) != NOTOK - && p->c_ceopenfnx == openMail) { - int cc, size; - char *bp; - - if ((size = ct->c_end - p->c_begin) <= 0) { - if (!e->eb_subject) - content_error (NULL, ct, - "empty body for access-type=mail-server"); - goto no_body; - } - - if ((e->eb_body = bp = malloc ((unsigned) size)) == NULL) - adios (NULL, "out of memory"); - fseek (p->c_fp, p->c_begin, SEEK_SET); - while (size > 0) - switch (cc = fread (bp, sizeof(*bp), size, p->c_fp)) { - case NOTOK: - adios ("failed", "fread"); + /* + ** No [file] argument, so check profile for + ** method to compose content. + */ + snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s", + invo_name, ci->ci_type, ci->ci_subtype); + if ((cp = context_find (buffer)) == NULL || *cp == '\0') { + snprintf (buffer, sizeof(buffer), "%s-compose-%s", + invo_name, ci->ci_type); + if ((cp = context_find (buffer)) == NULL || *cp == '\0') { + content_error (NULL, ct, "don't know how to compose content"); + done (1); + } + } + ci->ci_magic = add (cp, NULL); + return OK; + } - case OK: - adios (NULL, "unexpected EOF from fread"); + if (extrnal) + adios (NULL, "external definition not allowed for \"#%s\"", + ci->ci_type); - default: - bp += cc, size -= cc; - break; + /* + ** Message directive + ** #forw [+folder] [msgs] + */ + if (!mh_strcasecmp (ci->ci_type, "forw")) { + int msgnum; + char *folder, *arguments[MAXARGS]; + struct msgs *mp; + + if (ci->ci_magic) { + ap = brkstring (ci->ci_magic, " ", "\n"); + copyip (ap, arguments, MAXARGS); + } else { + arguments[0] = "cur"; + arguments[1] = NULL; + } + folder = NULL; + + /* search the arguments for a folder name */ + for (ap = arguments; *ap; ap++) { + cp = *ap; + if (*cp == '+' || *cp == '@') { + if (folder) + adios (NULL, "only one folder per #forw directive"); + else + folder = pluspath (cp); } - *bp = 0; } -no_body: - p->c_fp = NULL; - p->c_end = p->c_begin; - fclose (ct->c_fp); - ct->c_fp = NULL; + /* else, use the current folder */ + if (!folder) + folder = add (getfolder (1), NULL); + + if (!(mp = folder_read (folder))) + adios (NULL, "unable to read folder %s", folder); + for (ap = arguments; *ap; ap++) { + cp = *ap; + if (*cp != '+' && *cp != '@') + if (!m_convert (mp, cp)) + done (1); + } + free (folder); + free_ctinfo (ct); + + /* + ** If there is more than one message to include, make this + ** a content of type "multipart/digest" and insert each message + ** as a subpart. If there is only one message, then make this + ** a content of type "message/rfc822". + */ + if (mp->numsel > 1) { + /* we are forwarding multiple messages */ + if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = MULTI_DIGEST; + + if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) m; + pp = &m->mp_parts; + + for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { + if (is_selected(mp, msgnum)) { + struct part *part; + CT p; + CE pe; + + if ((p = (CT) calloc (1, sizeof(*p))) == NULL) + adios (NULL, "out of memory"); + init_decoded_content (p); + pe = p->c_cefile; + if (get_ctinfo ("message/rfc822", p, 0) == NOTOK) + done (1); + p->c_type = CT_MESSAGE; + p->c_subtype = MESSAGE_RFC822; + + snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum); + pe->ce_file = add (buffer, NULL); + if (listsw && stat (pe->ce_file, &st) != NOTOK) + p->c_end = (long) st.st_size; + + if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + adios (NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; + } + } + } else { + /* we are forwarding one message */ + if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MESSAGE; + ct->c_subtype = MESSAGE_RFC822; + + msgnum = mp->lowsel; + snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum); + ce->ce_file = add (buffer, NULL); + if (listsw && stat (ce->ce_file, &st) != NOTOK) + ct->c_end = (long) st.st_size; + } - if (exresult == NOTOK) - return NOTOK; - if (e->eb_flags == NOTOK) - return OK; + folder_free (mp); /* free folder/message structure */ + return OK; + } - switch (p->c_type) { - case CT_MULTIPART: - break; + /* + ** #end + */ + if (!mh_strcasecmp (ci->ci_type, "end")) { + free_content (ct); + *ctp = NULL; + return DONE; + } - case CT_MESSAGE: - if (p->c_subtype != MESSAGE_RFC822) - break; - /* else fall... */ - default: - e->eb_partno = ct->c_partno; - if (p->c_ctinitfnx) - (*p->c_ctinitfnx) (p); - break; + /* + ** #begin [ alternative | parallel ] + */ + if (!mh_strcasecmp (ci->ci_type, "begin")) { + if (!ci->ci_magic) { + vrsn = MULTI_MIXED; + cp = SubMultiPart[vrsn - 1].kv_key; + } else if (!mh_strcasecmp (ci->ci_magic, "alternative")) { + vrsn = MULTI_ALTERNATE; + cp = SubMultiPart[vrsn - 1].kv_key; + } else if (!mh_strcasecmp (ci->ci_magic, "parallel")) { + vrsn = MULTI_PARALLEL; + cp = SubMultiPart[vrsn - 1].kv_key; + } else if (uprf (ci->ci_magic, "digest")) { + goto use_forw; + } else { + vrsn = MULTI_UNKNOWN; + cp = ci->ci_magic; } - } - break; - default: - break; - } + free_ctinfo (ct); + snprintf (buffer, sizeof(buffer), "multipart/%s", cp); + if (get_ctinfo (buffer, ct, 0) == NOTOK) + done (1); + ct->c_type = CT_MULTIPART; + ct->c_subtype = vrsn; - return OK; -} + if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) + adios (NULL, "out of memory"); + ct->c_ctparams = (void *) m; + pp = &m->mp_parts; + while (fgetstr (buffer, sizeof(buffer) - 1, in)) { + struct part *part; + CT p; -static int -params_external (CT ct, int composing) -{ - char **ap, **ep; - struct exbody *e = (struct exbody *) ct->c_ctparams; - CI ci = &ct->c_ctinfo; - - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - if (!strcasecmp (*ap, "access-type")) { - struct str2init *s2i; - CT p = e->eb_content; - - for (s2i = str2methods; s2i->si_key; s2i++) - if (!strcasecmp (*ep, s2i->si_key)) - break; - - if (!s2i->si_key) { - e->eb_access = *ep; - e->eb_flags = NOTOK; - p->c_encoding = CE_EXTERNAL; - continue; - } - e->eb_access = s2i->si_key; - e->eb_flags = s2i->si_val; - p->c_encoding = CE_EXTERNAL; - - /* Call the Init function for this external type */ - if ((*s2i->si_init)(p) == NOTOK) - return NOTOK; - continue; - } - if (!strcasecmp (*ap, "name")) { - e->eb_name = *ep; - continue; - } - if (!strcasecmp (*ap, "permission")) { - e->eb_permission = *ep; - continue; - } - if (!strcasecmp (*ap, "site")) { - e->eb_site = *ep; - continue; - } - if (!strcasecmp (*ap, "directory")) { - e->eb_dir = *ep; - continue; - } - if (!strcasecmp (*ap, "mode")) { - e->eb_mode = *ep; - continue; - } - if (!strcasecmp (*ap, "size")) { - sscanf (*ep, "%lu", &e->eb_size); - continue; - } - if (!strcasecmp (*ap, "server")) { - e->eb_server = *ep; - continue; - } - if (!strcasecmp (*ap, "subject")) { - e->eb_subject = *ep; - continue; - } - if (composing && !strcasecmp (*ap, "body")) { - e->eb_body = getcpy (*ep); - continue; + if (user_content (in, file, buffer, &p) == DONE) { + if (!m->mp_parts) + adios (NULL, "empty \"#begin ... #end\" sequence"); + return OK; + } + if (!p) + continue; + + if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) + adios (NULL, "out of memory"); + *pp = part; + pp = &part->mp_next; + part->mp_part = p; + } + admonish (NULL, "premature end-of-file, missing #end"); + return OK; } - } - - if (!e->eb_access) { - advise (NULL, - "invalid parameters for \"%s/%s\" type in message %s's %s field", - ci->ci_type, ci->ci_subtype, ct->c_file, TYPE_FIELD); - return NOTOK; - } - - return OK; -} - - -/* - * APPLICATION - */ - -static int -InitApplication (CT ct) -{ - struct k2v *kv; - CI ci = &ct->c_ctinfo; - - /* match subtype */ - for (kv = SubApplication; kv->kv_key; kv++) - if (!strcasecmp (ci->ci_subtype, kv->kv_key)) - break; - ct->c_subtype = kv->kv_value; - - return OK; -} - - -/* - * Set up structures for placing unencoded - * content when building parts. - */ - -static int -init_decoded_content (CT ct) -{ - CE ce; - - if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL) - adios (NULL, "out of memory"); - - ct->c_cefile = ce; - ct->c_ceopenfnx = open7Bit; /* since unencoded */ - ct->c_ceclosefnx = close_encoding; - ct->c_cesizefnx = NULL; /* since unencoded */ - - return OK; -} - - -/* - * TRANSFER ENCODINGS - */ - -static int -init_encoding (CT ct, OpenCEFunc openfnx) -{ - CE ce; - if ((ce = (CE) calloc (1, sizeof(*ce))) == NULL) - adios (NULL, "out of memory"); - - ct->c_cefile = ce; - ct->c_ceopenfnx = openfnx; - ct->c_ceclosefnx = close_encoding; - ct->c_cesizefnx = size_encoding; - - return OK; + /* + ** Unknown directive + */ + adios (NULL, "unknown directive \"#%s\"", ci->ci_type); + return NOTOK; /* NOT REACHED */ } static void -close_encoding (CT ct) -{ - CE ce; - - if (!(ce = ct->c_cefile)) - return; - - if (ce->ce_fp) { - fclose (ce->ce_fp); - ce->ce_fp = NULL; - } -} - - -static unsigned long -size_encoding (CT ct) +set_id (CT ct, int top) { - int fd; - unsigned long size; - char *file; - CE ce; - struct stat st; - - if (!(ce = ct->c_cefile)) - return (ct->c_end - ct->c_begin); - - if (ce->ce_fp && fstat (fileno (ce->ce_fp), &st) != NOTOK) - return (long) st.st_size; - - if (ce->ce_file) { - if (stat (ce->ce_file, &st) != NOTOK) - return (long) st.st_size; - else - return 0L; - } - - if (ct->c_encoding == CE_EXTERNAL) - return (ct->c_end - ct->c_begin); - - file = NULL; - if ((fd = (*ct->c_ceopenfnx) (ct, &file)) == NOTOK) - return (ct->c_end - ct->c_begin); - - if (fstat (fd, &st) != NOTOK) - size = (long) st.st_size; - else - size = 0L; - - (*ct->c_ceclosefnx) (ct); - return size; + 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); } -/* - * BASE64 - */ - -static unsigned char b642nib[0x80] = { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, - 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, - 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, - 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff +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 }; -static int -InitBase64 (CT ct) -{ - return init_encoding (ct, openBase64); -} - +/* +** 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 -openBase64 (CT ct, char **file) +compose_content (CT ct) { - int bitno, cc, digested; - int fd, len, skip; - unsigned long bits; - unsigned char value, *b, *b1, *b2, *b3; - char *cp, *ep, buffer[BUFSIZ]; - CE ce; - MD5_CTX mdContext; - - b = (unsigned char *) &bits; - b1 = &b[endian > 0 ? 1 : 2]; - b2 = &b[endian > 0 ? 2 : 1]; - b3 = &b[endian > 0 ? 3 : 0]; - - ce = ct->c_cefile; - if (ce->ce_fp) { - fseek (ce->ce_fp, 0L, SEEK_SET); - goto ready_to_go; - } - - if (ce->ce_file) { - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; - } - goto ready_to_go; - } - - if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); - ce->ce_unlink = 1; - } else { - ce->ce_file = add (*file, NULL); - ce->ce_unlink = 0; - } - - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; - } - - if ((len = ct->c_end - ct->c_begin) < 0) - adios (NULL, "internal error(1)"); - - if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - content_error (ct->c_file, ct, "unable to open for reading"); - return NOTOK; - } - - if ((digested = ct->c_digested)) - MD5Init (&mdContext); - - bitno = 18; - bits = 0L; - skip = 0; + CE ce = ct->c_cefile; - lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET); - while (len > 0) { - switch (cc = read (fd, buffer, sizeof(buffer) - 1)) { - case NOTOK: - content_error (ct->c_file, ct, "error reading from"); - goto clean_up; + 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; + } - case OK: - content_error (NULL, ct, "premature eof"); - goto clean_up; + /* 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; - default: - if (cc > len) - cc = len; - len -= cc; + sprintf (pp, "%d", partnum); + p->c_partno = add (partnam, NULL); + if (compose_content (p) == NOTOK) + return NOTOK; + } - for (ep = (cp = buffer) + cc; cp < ep; cp++) { - switch (*cp) { - default: - if (isspace (*cp)) - break; - if (skip || (*cp & 0x80) - || (value = b642nib[*cp & 0x7f]) > 0x3f) { - if (debugsw) { - fprintf (stderr, "*cp=0x%x pos=%ld skip=%d\n", - *cp, - (long) (lseek (fd, (off_t) 0, SEEK_CUR) - (ep - cp)), - skip); - } - content_error (NULL, ct, - "invalid BASE64 encoding -- continuing"); - continue; - } - - bits |= value << bitno; -test_end: - if ((bitno -= 6) < 0) { - putc ((char) *b1, ce->ce_fp); - if (digested) - MD5Update (&mdContext, b1, 1); - if (skip < 2) { - putc ((char) *b2, ce->ce_fp); - if (digested) - MD5Update (&mdContext, b2, 1); - if (skip < 1) { - putc ((char) *b3, ce->ce_fp); - if (digested) - MD5Update (&mdContext, b3, 1); - } + /* + ** 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 (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, - "error writing to"); - goto clean_up; + if ((p->c_rfc934 = is934)) + p->c_end++; } - bitno = 18, bits = 0L, skip = 0; - } - break; - - case '=': - if (++skip > 3) - goto self_delimiting; - goto test_end; } - } - } - } - - if (bitno != 18) { - if (debugsw) - fprintf (stderr, "premature ending (bitno %d)\n", bitno); - - content_error (NULL, ct, "invalid BASE64 encoding"); - goto clean_up; - } - -self_delimiting: - fseek (ct->c_fp, 0L, SEEK_SET); - if (fflush (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } + if (listsw) { + ct->c_end = (partnum = strlen (prefix) + 2) + 2; + if (ct->c_rfc934) + ct->c_end += 1; - if (digested) { - unsigned char digest[16]; - - MD5Final (digest, &mdContext); - if (memcmp((char *) digest, (char *) ct->c_digest, - sizeof(digest) / sizeof(digest[0]))) - content_error (NULL, ct, - "content integrity suspect (digest mismatch) -- continuing"); - else - if (debugsw) - fprintf (stderr, "content integrity confirmed\n"); - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - -ready_to_go: - *file = ce->ce_file; - return fileno (ce->ce_fp); - -clean_up: - free_encoding (ct, 0); - return NOTOK; -} - - -/* - * QUOTED PRINTABLE - */ - -static char hex2nib[0x80] = { - 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, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 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, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 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 -}; - - -static int -InitQuoted (CT ct) -{ - return init_encoding (ct, openQuoted); -} - - -static int -openQuoted (CT ct, char **file) -{ - int cc, digested, len, quoted; - char *cp, *ep; - char buffer[BUFSIZ]; - unsigned char mask; - CE ce; - MD5_CTX mdContext; - - ce = ct->c_cefile; - if (ce->ce_fp) { - fseek (ce->ce_fp, 0L, SEEK_SET); - goto ready_to_go; - } - - if (ce->ce_file) { - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; + for (part = m->mp_parts; part; part = part->mp_next) + ct->c_end += part->mp_part->c_end + partnum; + } } - goto ready_to_go; - } - - if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); - ce->ce_unlink = 1; - } else { - ce->ce_file = add (*file, NULL); - ce->ce_unlink = 0; - } - - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; - } - - if ((len = ct->c_end - ct->c_begin) < 0) - adios (NULL, "internal error(2)"); - - if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - content_error (ct->c_file, ct, "unable to open for reading"); - return NOTOK; - } - - if ((digested = ct->c_digested)) - MD5Init (&mdContext); + break; - quoted = 0; -#ifdef lint - mask = 0; -#endif + case CT_MESSAGE: + /* Nothing to do for type message */ + break; - fseek (ct->c_fp, ct->c_begin, SEEK_SET); - while (len > 0) { - char *dp; + /* + ** Discrete types (text/application/audio/image/video) + */ + default: + if (!ce->ce_file) { + pid_t child_id; + int i, 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("mhbuildsbr", "unable to create temporary file"); + } + ce->ce_file = add (tfile, NULL); + ce->ce_unlink = 1; - if (fgets (buffer, sizeof(buffer) - 1, ct->c_fp) == NULL) { - content_error (NULL, ct, "premature eof"); - goto clean_up; - } + xstdout = 0; - if ((cc = strlen (buffer)) > len) - cc = len; - len -= cc; + /* Get buffer ready to go */ + bp = buffer; + bp[0] = '\0'; + buflen = sizeof(buffer); - for (ep = (cp = buffer) + cc - 1; cp <= ep; ep--) - if (!isspace (*ep)) - break; - *++ep = '\n', ep++; - - for (; cp < ep; cp++) { - if (quoted) { - if (quoted > 1) { - if (!isxdigit (*cp)) { -invalid_hex: - dp = "expecting hexidecimal-digit"; - goto invalid_encoding; - } - mask <<= 4; - mask |= hex2nib[*cp & 0x7f]; - putc (mask, ce->ce_fp); - if (digested) - MD5Update (&mdContext, &mask, 1); - } else { - switch (*cp) { - case ':': - putc (*cp, ce->ce_fp); - if (digested) - MD5Update (&mdContext, (unsigned char *) ":", 1); - break; + /* + ** 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--; + } + } - default: - if (!isxdigit (*cp)) - goto invalid_hex; - mask = hex2nib[*cp & 0x7f]; - quoted = 2; - continue; - } + 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"); + + for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++) + sleep (5); + switch (child_id) { + 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; + } } - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - quoted = 0; - continue; - } - - switch (*cp) { - default: - if (*cp < '!' || *cp > '~') { - int i; - dp = "expecting character in range [!..~]"; - -invalid_encoding: - i = strlen (invo_name) + 2; - content_error (NULL, ct, - "invalid QUOTED-PRINTABLE encoding -- %s,\n%*.*sbut got char 0x%x", - dp, i, i, "", *cp); - goto clean_up; - } - /* and fall...*/ - case ' ': - case '\t': - case '\n': - putc (*cp, ce->ce_fp); - if (digested) { - if (*cp == '\n') - MD5Update (&mdContext, (unsigned char *) "\r\n",2); - else - MD5Update (&mdContext, (unsigned char *) cp, 1); - } - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - break; + /* Check size of file */ + if (listsw && ct->c_end == 0L) { + struct stat st; - case '=': - if (*++cp != '\n') { - quoted = 1; - cp--; + if (stat (ce->ce_file, &st) != NOTOK) + ct->c_end = (long) st.st_size; } break; - } } - } - if (quoted) { - content_error (NULL, ct, - "invalid QUOTED-PRINTABLE encoding -- end-of-content while still quoting"); - goto clean_up; - } - - fseek (ct->c_fp, 0L, SEEK_SET); - if (fflush (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - - if (digested) { - unsigned char digest[16]; - - MD5Final (digest, &mdContext); - if (memcmp((char *) digest, (char *) ct->c_digest, - sizeof(digest) / sizeof(digest[0]))) - content_error (NULL, ct, - "content integrity suspect (digest mismatch) -- continuing"); - else - if (debugsw) - fprintf (stderr, "content integrity confirmed\n"); - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - -ready_to_go: - *file = ce->ce_file; - return fileno (ce->ce_fp); - -clean_up: - free_encoding (ct, 0); - return NOTOK; + return OK; } /* - * 7BIT - */ +** 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 -Init7Bit (CT ct) -{ - if (init_encoding (ct, open7Bit) == NOTOK) - return NOTOK; - - ct->c_cesizefnx = NULL; /* no need to decode for real size */ - return OK; -} - - -static int -open7Bit (CT ct, char **file) +scan_content (CT ct) { - int cc, fd, len; - char buffer[BUFSIZ]; - CE ce; - - ce = ct->c_cefile; - if (ce->ce_fp) { - fseek (ce->ce_fp, 0L, SEEK_SET); - goto ready_to_go; - } - - if (ce->ce_file) { - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; - } - goto ready_to_go; - } - - if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); - ce->ce_unlink = 1; - } else { - ce->ce_file = add (*file, NULL); - ce->ce_unlink = 0; - } - - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; - } - - if (ct->c_type == CT_MULTIPART) { - char **ap, **ep; - CI ci = &ct->c_ctinfo; - - len = 0; - fprintf (ce->ce_fp, "%s: %s/%s", TYPE_FIELD, ci->ci_type, ci->ci_subtype); - len += strlen (TYPE_FIELD) + 2 + strlen (ci->ci_type) - + 1 + strlen (ci->ci_subtype); - for (ap = ci->ci_attrs, ep = ci->ci_values; *ap; ap++, ep++) { - putc (';', ce->ce_fp); - len++; - - snprintf (buffer, sizeof(buffer), "%s=\"%s\"", *ap, *ep); - - if (len + 1 + (cc = strlen (buffer)) >= CPERLIN) { - fputs ("\n\t", ce->ce_fp); - len = 8; - } else { - putc (' ', ce->ce_fp); - len++; - } - fprintf (ce->ce_fp, "%s", buffer); - len += cc; - } - - if (ci->ci_comment) { - if (len + 1 + (cc = 2 + strlen (ci->ci_comment)) >= CPERLIN) { - fputs ("\n\t", ce->ce_fp); - len = 8; - } - else { - putc (' ', ce->ce_fp); - len++; - } - fprintf (ce->ce_fp, "(%s)", ci->ci_comment); - len += cc; - } - fprintf (ce->ce_fp, "\n"); - if (ct->c_id) - fprintf (ce->ce_fp, "%s:%s", ID_FIELD, ct->c_id); - if (ct->c_descr) - fprintf (ce->ce_fp, "%s:%s", DESCR_FIELD, ct->c_descr); - fprintf (ce->ce_fp, "\n"); - } - - if ((len = ct->c_end - ct->c_begin) < 0) - adios (NULL, "internal error(3)"); - - if (!ct->c_fp && (ct->c_fp = fopen (ct->c_file, "r")) == NULL) { - content_error (ct->c_file, ct, "unable to open for reading"); - return NOTOK; - } - - lseek (fd = fileno (ct->c_fp), (off_t) ct->c_begin, SEEK_SET); - while (len > 0) - switch (cc = read (fd, buffer, sizeof(buffer) - 1)) { - case NOTOK: - content_error (ct->c_file, ct, "error reading from"); - goto clean_up; - - case OK: - content_error (NULL, ct, "premature eof"); - goto clean_up; - - default: - if (cc > len) - cc = len; - len -= cc; - - fwrite (buffer, sizeof(*buffer), cc, ce->ce_fp); - if (ferror (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - } - - fseek (ct->c_fp, 0L, SEEK_SET); - - if (fflush (ce->ce_fp)) { - content_error (ce->ce_file, ct, "error writing to"); - goto clean_up; - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - -ready_to_go: - *file = ce->ce_file; - return fileno (ce->ce_fp); - -clean_up: - free_encoding (ct, 0); - return NOTOK; -} + 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; -/* - * External - */ + for (part = m->mp_parts; part; part = part->mp_next) { + CT p = part->mp_part; -static int -openExternal (CT ct, CT cb, CE ce, char **file, int *fd) -{ - char cachefile[BUFSIZ]; + if (scan_content (p) == NOTOK) /* choose encoding for subpart */ + return NOTOK; - if (ce->ce_fp) { - fseek (ce->ce_fp, 0L, SEEK_SET); - goto ready_already; - } + /* + ** 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; + } - if (ce->ce_file) { - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; - } - goto ready_already; - } - - if (find_cache (ct, rcachesw, (int *) 0, cb->c_id, - cachefile, sizeof(cachefile)) != NOTOK) { - if ((ce->ce_fp = fopen (cachefile, "r"))) { - ce->ce_file = getcpy (cachefile); - ce->ce_unlink = 0; - goto ready_already; - } else { - admonish (cachefile, "unable to fopen for reading"); + return OK; } - } - return OK; - -ready_already: - *file = ce->ce_file; - *fd = fileno (ce->ce_fp); - return DONE; -} - -/* - * File - */ - -static int -InitFile (CT ct) -{ - return init_encoding (ct, openFile); -} - - -static int -openFile (CT ct, char **file) -{ - int fd, cachetype; - char cachefile[BUFSIZ]; - struct exbody *e = ct->c_ctexbody; - CE ce = ct->c_cefile; - - switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { - case NOTOK: - return NOTOK; - - case OK: - break; - - case DONE: - return fd; - } - - if (!e->eb_name) { - content_error (NULL, ct, "missing name parameter"); - return NOTOK; - } - - ce->ce_file = getcpy (e->eb_name); - ce->ce_unlink = 0; - - if ((ce->ce_fp = fopen (ce->ce_file, "r")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading"); - return NOTOK; - } - - if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) - && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, - cachefile, sizeof(cachefile)) != NOTOK) { - int mask; - FILE *fp; - - mask = umask (cachetype ? ~m_gmprot () : 0222); - if ((fp = fopen (cachefile, "w"))) { - int cc; - char buffer[BUFSIZ]; - FILE *gp = ce->ce_fp; - - fseek (gp, 0L, SEEK_SET); - - while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) - > 0) - fwrite (buffer, sizeof(*buffer), cc, fp); - fflush (fp); - - if (ferror (gp)) { - admonish (ce->ce_file, "error reading"); - unlink (cachefile); - } - else - if (ferror (fp)) { - admonish (cachefile, "error writing"); - unlink (cachefile); + /* + ** 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; } - fclose (fp); - } - umask (mask); - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - *file = ce->ce_file; - return fileno (ce->ce_fp); -} - -/* - * FTP - */ - -static int -InitFTP (CT ct) -{ - return init_encoding (ct, openFTP); -} + break; + case CT_APPLICATION: + check8bit = 1; + checkebcdic = ebcdicsw; + checklinelen = 1; + checklinespace = 1; + checkboundary = 1; + break; -static int -openFTP (CT ct, char **file) -{ - int cachetype, caching, fd; - int len, buflen; - char *bp, *ftp, *user, *pass; - char buffer[BUFSIZ], cachefile[BUFSIZ]; - struct exbody *e; - CE ce; - static char *username = NULL; - static char *password = NULL; - - e = ct->c_ctexbody; - ce = ct->c_cefile; - - if ((ftp = context_find (nmhaccessftp)) && !*ftp) - ftp = NULL; - -#ifndef BUILTIN_FTP - if (!ftp) - return NOTOK; -#endif + 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; - switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { - case NOTOK: - return NOTOK; - - case OK: - break; - - case DONE: - return fd; - } - - if (!e->eb_name || !e->eb_site) { - content_error (NULL, ct, "missing %s parameter", - e->eb_name ? "site": "name"); - return NOTOK; - } - - if (xpid) { - if (xpid < 0) - xpid = -xpid; - pidcheck (pidwait (xpid, NOTOK)); - xpid = 0; - } - - /* Get the buffer ready to go */ - bp = buffer; - buflen = sizeof(buffer); - - /* - * Construct the query message for user - */ - snprintf (bp, buflen, "Retrieve %s", e->eb_name); - len = strlen (bp); - bp += len; - buflen -= len; - - if (e->eb_partno) { - snprintf (bp, buflen, " (content %s)", e->eb_partno); - len = strlen (bp); - bp += len; - buflen -= len; - } - - snprintf (bp, buflen, "\n using %sFTP from site %s", - e->eb_flags ? "anonymous " : "", e->eb_site); - len = strlen (bp); - bp += len; - buflen -= len; - - if (e->eb_size > 0) { - snprintf (bp, buflen, " (%lu octets)", e->eb_size); - len = strlen (bp); - bp += len; - buflen -= len; - } - snprintf (bp, buflen, "? "); - - /* - * Now, check the answer - */ - if (!getanswer (buffer)) - return NOTOK; - - if (e->eb_flags) { - user = "anonymous"; - snprintf (buffer, sizeof(buffer), "%s@%s", getusername (), LocalName ()); - pass = buffer; - } else { - ruserpass (e->eb_site, &username, &password); - user = username; - pass = password; - } - - ce->ce_unlink = (*file == NULL); - caching = 0; - cachefile[0] = '\0'; - if ((!e->eb_permission || strcasecmp (e->eb_permission, "read-write")) - && find_cache (NULL, wcachesw, &cachetype, e->eb_content->c_id, - cachefile, sizeof(cachefile)) != NOTOK) { - if (*file == NULL) { - ce->ce_unlink = 0; - caching = 1; - } - } - - if (*file) - ce->ce_file = add (*file, NULL); - else if (caching) - ce->ce_file = add (cachefile, NULL); - else - ce->ce_file = add (m_scratch ("", tmp), NULL); - - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; - } - -#ifdef BUILTIN_FTP - if (ftp) -#endif - { - int child_id, i, vecp; - char *vec[9]; - - vecp = 0; - vec[vecp++] = r1bindex (ftp, '/'); - vec[vecp++] = e->eb_site; - vec[vecp++] = user; - vec[vecp++] = pass; - vec[vecp++] = e->eb_dir; - vec[vecp++] = e->eb_name; - vec[vecp++] = ce->ce_file, - vec[vecp++] = e->eb_mode && !strcasecmp (e->eb_mode, "ascii") - ? "ascii" : "binary"; - vec[vecp] = NULL; - - fflush (stdout); - - for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) - sleep (5); - switch (child_id) { - case NOTOK: - adios ("fork", "unable to"); - /* NOTREACHED */ - - case OK: - close (fileno (ce->ce_fp)); - execvp (ftp, vec); - fprintf (stderr, "unable to exec "); - perror (ftp); - _exit (-1); - /* NOTREACHED */ - - default: - if (pidXwait (child_id, NULL)) { -#ifdef BUILTIN_FTP -losing_ftp: -#endif - username = password = NULL; - ce->ce_unlink = 1; - return NOTOK; - } + 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; } - } -#ifdef BUILTIN_FTP - else - if (ftp_get (e->eb_site, user, pass, e->eb_dir, e->eb_name, - ce->ce_file, - e->eb_mode && !strcasecmp (e->eb_mode, "ascii"), 0) - == NOTOK) - goto losing_ftp; -#endif - if (cachefile[0]) { - if (caching) - chmod (cachefile, cachetype ? m_gmprot () : 0444); - else { - int mask; - FILE *fp; - - mask = umask (cachetype ? ~m_gmprot () : 0222); - if ((fp = fopen (cachefile, "w"))) { - int cc; - FILE *gp = ce->ce_fp; + /* + ** 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; + } + } + } - fseek (gp, 0L, SEEK_SET); + /* + ** Check line length. + */ + if (checklinelen && (strlen (buffer) > CPERLIN + 1)) { + linelen = 1; + checklinelen = 0; /* no need to keep checking */ + } - while ((cc= fread (buffer, sizeof(*buffer), sizeof(buffer), gp)) - > 0) - fwrite (buffer, sizeof(*buffer), cc, fp); - fflush (fp); + /* + ** 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; + } - if (ferror (gp)) { - admonish (ce->ce_file, "error reading"); - unlink (cachefile); + /* + ** 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) && isdigit(buffer[2 + len])) { + boundaryclash = 1; + /* no need to keep checking */ + checkboundary = 0; + } + } } - else - if (ferror (fp)) { - admonish (cachefile, "error writing"); - unlink (cachefile); - } - fclose (fp); - } - umask (mask); + fclose (in); } - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - *file = ce->ce_file; - return fileno (ce->ce_fp); -} - - -/* - * Mail - */ - -static int -InitMail (CT ct) -{ - return init_encoding (ct, openMail); -} - -static int -openMail (CT ct, char **file) -{ - int child_id, fd, i, vecp; - int len, buflen; - char *bp, buffer[BUFSIZ], *vec[7]; - struct exbody *e = ct->c_ctexbody; - CE ce = ct->c_cefile; - - switch (openExternal (e->eb_parent, e->eb_content, ce, file, &fd)) { - case NOTOK: - return NOTOK; - - case OK: - break; - - case DONE: - return fd; - } - - if (!e->eb_server) { - content_error (NULL, ct, "missing server parameter"); - return NOTOK; - } - - if (xpid) { - if (xpid < 0) - xpid = -xpid; - pidcheck (pidwait (xpid, NOTOK)); - xpid = 0; - } - - /* Get buffer ready to go */ - bp = buffer; - buflen = sizeof(buffer); - - /* Now construct query message */ - snprintf (bp, buflen, "Retrieve content"); - len = strlen (bp); - bp += len; - buflen -= len; - - if (e->eb_partno) { - snprintf (bp, buflen, " %s", e->eb_partno); - len = strlen (bp); - bp += len; - buflen -= len; - } - - snprintf (bp, buflen, " by asking %s\n\n%s\n? ", - e->eb_server, - e->eb_subject ? e->eb_subject : e->eb_body); - - /* Now, check answer */ - if (!getanswer (buffer)) - return NOTOK; - - vecp = 0; - vec[vecp++] = r1bindex (mailproc, '/'); - vec[vecp++] = e->eb_server; - vec[vecp++] = "-subject"; - vec[vecp++] = e->eb_subject ? e->eb_subject : "mail-server request"; - vec[vecp++] = "-body"; - vec[vecp++] = e->eb_body; - vec[vecp] = NULL; - - for (i = 0; (child_id = vfork ()) == NOTOK && i < 5; i++) - sleep (5); - switch (child_id) { - case NOTOK: - advise ("fork", "unable to"); - return NOTOK; - - case OK: - execvp (mailproc, vec); - fprintf (stderr, "unable to exec "); - perror (mailproc); - _exit (-1); - /* NOTREACHED */ - - default: - if (pidXwait (child_id, NULL) == OK) - advise (NULL, "request sent"); - break; - } - - if (*file == NULL) { - ce->ce_file = add (m_scratch ("", tmp), NULL); - ce->ce_unlink = 1; - } else { - ce->ce_file = add (*file, NULL); - ce->ce_unlink = 0; - } - - if ((ce->ce_fp = fopen (ce->ce_file, "w+")) == NULL) { - content_error (ce->ce_file, ct, "unable to fopen for reading/writing"); - return NOTOK; - } - - fseek (ce->ce_fp, 0L, SEEK_SET); - *file = ce->ce_file; - return fileno (ce->ce_fp); -} + /* + ** 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 = add ("charset=us-ascii", NULL); + } + cp = strchr(*ap++, '='); + *ap = NULL; + *cp++ = '\0'; + *ep = cp; + } -static char * -fgetstr (char *s, int n, FILE *stream) -{ - char *cp, *ep; + if (contains8bit || ebcdicunsafe || linelen || linespace || checksw) + ct->c_encoding = CE_QUOTED; + else + ct->c_encoding = CE_7BIT; + break; - for (ep = (cp = s) + n; cp < ep; ) { - int i; + case CT_APPLICATION: + /* For application type, use base64, except when postscript */ + if (contains8bit || ebcdicunsafe || linelen || linespace || checksw) + ct->c_encoding = (ct->c_subtype == APPLICATION_POSTSCRIPT) + ? CE_QUOTED : CE_BASE64; + else + ct->c_encoding = CE_7BIT; + break; - if (!fgets (cp, n, stream)) - return (cp != s ? s : NULL); - if (cp == s && *cp != '#') - return s; + case CT_MESSAGE: + ct->c_encoding = CE_7BIT; + break; - cp += (i = strlen (cp)) - 1; - if (i <= 1 || *cp-- != '\n' || *cp != '\\') - break; - *cp = '\0'; - n -= (i - 2); - } + 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 s; + return (boundaryclash ? NOTOK : OK); } /* - * Parse the composition draft for text and directives. - * Do initial setup of Content structure. - */ +** Scan the content structures, and build header +** fields that will need to be output into the +** message. +*/ static int -user_content (FILE *in, char *file, char *buf, CT *ctp) +build_headers (CT ct) { - int extrnal, vrsn; - char *cp, **ap; - char buffer[BUFSIZ]; - struct multipart *m; - struct part **pp; - struct stat st; - struct str2init *s2i; - CI ci; - CT ct; - CE ce; - - if (buf[0] == '\n' || strcmp (buf, "#\n") == 0) { - *ctp = NULL; - return OK; - } - - /* allocate basic Content structure */ - if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL) - adios (NULL, "out of memory"); - *ctp = ct; - - /* allocate basic structure for handling decoded content */ - init_decoded_content (ct); - ce = ct->c_cefile; - - ci = &ct->c_ctinfo; - set_id (ct, 0); - - /* - * Handle inline text. Check if line - * is one of the following forms: - * - * 1) doesn't begin with '#' (implicit directive) - * 2) begins with "##" (implicit directive) - * 3) begins with "#<" - */ - if (buf[0] != '#' || buf[1] == '#' || buf[1] == '<') { - int headers; - int inlineD; - long pos; - char content[BUFSIZ]; - FILE *out; - - /* use a temp file to collect the plain text lines */ - ce->ce_file = add (m_tmpfil (invo_name), NULL); - ce->ce_unlink = 1; - - if ((out = fopen (ce->ce_file, "w")) == NULL) - adios (ce->ce_file, "unable to open for writing"); - - if (buf[0] == '#' && buf[1] == '<') { - strncpy (content, buf + 2, sizeof(content)); - inlineD = 1; - goto rock_and_roll; - } else { - inlineD = 0; - } - - /* the directive is implicit */ - strncpy (content, "text/plain", sizeof(content)); - headers = 0; - strncpy (buffer, buf[0] != '#' ? buf : buf + 1, sizeof(buffer)); - for (;;) { - int i; - - if (headers >= 0 && uprf (buffer, DESCR_FIELD) - && buffer[i = strlen (DESCR_FIELD)] == ':') { - headers = 1; - -again_descr: - ct->c_descr = add (buffer + i + 1, ct->c_descr); - if (!fgetstr (buffer, sizeof(buffer) - 1, in)) - adios (NULL, "end-of-file after %s: field in plaintext", DESCR_FIELD); - switch (buffer[0]) { - case ' ': - case '\t': - i = -1; - goto again_descr; - - case '#': - adios (NULL, "#-directive after %s: field in plaintext", DESCR_FIELD); - /* NOTREACHED */ - - default: - break; - } - } - - if (headers != 1 || buffer[0] != '\n') - fputs (buffer, out); - -rock_and_roll: - headers = -1; - pos = ftell (in); - if ((cp = fgetstr (buffer, sizeof(buffer) - 1, in)) == NULL) - break; - if (buffer[0] == '#') { - char *bp; - - if (buffer[1] != '#') - break; - for (cp = (bp = buffer) + 1; *cp; cp++) - *bp++ = *cp; - *bp = '\0'; - } - } - - if (listsw) - ct->c_end = ftell (out); - fclose (out); - - /* parse content type */ - if (get_ctinfo (content, ct, inlineD) == NOTOK) - done (1); - - for (s2i = str2cts; s2i->si_key; s2i++) - if (!strcasecmp (ci->ci_type, s2i->si_key)) - break; - if (!s2i->si_key && !uprf (ci->ci_type, "X-")) - s2i++; + int cc, mailbody, len; + char **ap, **ep; + char *np, *vp, buffer[BUFSIZ]; + CI ci = &ct->c_ctinfo; /* - * check type specified (possibly implicitly) - */ - switch (ct->c_type = s2i->si_val) { - case CT_MESSAGE: - if (!strcasecmp (ci->ci_subtype, "rfc822")) { - ct->c_encoding = CE_7BIT; - goto call_init; - } - /* else fall... */ - case CT_MULTIPART: - adios (NULL, "it doesn't make sense to define an in-line %s content", - ct->c_type == CT_MESSAGE ? "message" : "multipart"); - /* NOTREACHED */ + ** 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 */ - default: -call_init: - if ((ct->c_ctinitfnx = s2i->si_init)) - (*ct->c_ctinitfnx) (ct); - break; + ap = ci->ci_attrs; + ep = ci->ci_values; + snprintf (buffer, sizeof(buffer), "boundary=%s%d", prefix, level++); + cp = strchr(*ap++ = add (buffer, NULL), '='); + *ap = NULL; + *cp++ = '\0'; + *ep = cp; } - if (cp) - fseek (in, pos, SEEK_SET); - return OK; - } - - /* - * If we've reached this point, the next line - * must be some type of explicit directive. - */ - - /* check if directive is external-type */ - extrnal = (buf[1] == '@'); - - /* parse directive */ - if (get_ctinfo (buf + (extrnal ? 2 : 1), ct, 1) == NOTOK) - done (1); - - /* check directive against the list of MIME types */ - for (s2i = str2cts; s2i->si_key; s2i++) - if (!strcasecmp (ci->ci_type, s2i->si_key)) - break; - - /* - * Check if the directive specified a valid type. - * This will happen if it was one of the following forms: - * - * #type/subtype (or) - * #@type/subtype - */ - if (s2i->si_key) { - if (!ci->ci_subtype) - adios (NULL, "missing subtype in \"#%s\"", ci->ci_type); - - switch (ct->c_type = s2i->si_val) { - case CT_MULTIPART: - adios (NULL, "use \"#begin ... #end\" instead of \"#%s/%s\"", - ci->ci_type, ci->ci_subtype); - /* NOTREACHED */ - - case CT_MESSAGE: - if (!strcasecmp (ci->ci_subtype, "partial")) - adios (NULL, "sorry, \"#%s/%s\" isn't supported", - ci->ci_type, ci->ci_subtype); - if (!strcasecmp (ci->ci_subtype, "external-body")) - adios (NULL, "use \"#@type/subtype ... [] ...\" instead of \"#%s/%s\"", - ci->ci_type, ci->ci_subtype); -use_forw: - adios (NULL, - "use \"#forw [+folder] [msgs]\" instead of \"#%s/%s\"", - ci->ci_type, ci->ci_subtype); - /* NOTREACHED */ - - default: - if ((ct->c_ctinitfnx = s2i->si_init)) - (*ct->c_ctinitfnx) (ct); - break; - } + /* + ** 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; /* - * #@type/subtype (external types directive) - */ - if (extrnal) { - struct exbody *e; - CT p; - - if (!ci->ci_magic) - adios (NULL, "need external information for \"#@%s/%s\"", - ci->ci_type, ci->ci_subtype); - p = ct; - - snprintf (buffer, sizeof(buffer), "message/external-body; %s", ci->ci_magic); - free (ci->ci_magic); - ci->ci_magic = NULL; - - /* - * Since we are using the current Content structure to - * hold information about the type of the external - * reference, we need to create another Content structure - * for the message/external-body to wrap it in. - */ - if ((ct = (CT) calloc (1, sizeof(*ct))) == NULL) - adios (NULL, "out of memory"); - *ctp = ct; - ci = &ct->c_ctinfo; - if (get_ctinfo (buffer, ct, 0) == NOTOK) - done (1); - ct->c_type = CT_MESSAGE; - ct->c_subtype = MESSAGE_EXTERNAL; + ** output the content type and subtype + */ + np = add (TYPE_FIELD, NULL); + vp = concat (" ", ci->ci_type, "/", ci->ci_subtype, NULL); - if ((e = (struct exbody *) calloc (1, sizeof(*e))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) e; + /* keep track of length of line */ + len = strlen (TYPE_FIELD) + strlen (ci->ci_type) + + strlen (ci->ci_subtype) + 3; - e->eb_parent = ct; - e->eb_content = p; - p->c_ctexbody = e; + mailbody = ct->c_type == CT_MESSAGE + && ct->c_subtype == MESSAGE_EXTERNAL + && ((struct exbody *) ct->c_ctparams)->eb_body; - if (params_external (ct, 1) == NOTOK) - done (1); + /* + ** 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; - return OK; - } + vp = add (";", vp); + len++; - /* Handle [file] argument */ - if (ci->ci_magic) { - /* check if specifies command to execute */ - if (*ci->ci_magic == '|' || *ci->ci_magic == '!') { - for (cp = ci->ci_magic + 1; isspace (*cp); cp++) - continue; - if (!*cp) - adios (NULL, "empty pipe command for #%s directive", ci->ci_type); - cp = add (cp, NULL); - free (ci->ci_magic); - ci->ci_magic = cp; - } else { - /* record filename of decoded contents */ - ce->ce_file = ci->ci_magic; - if (access (ce->ce_file, R_OK) == NOTOK) - adios ("reading", "unable to access %s for", ce->ce_file); - if (listsw && stat (ce->ce_file, &st) != NOTOK) - ct->c_end = (long) st.st_size; - ci->ci_magic = NULL; - } - return OK; + 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; } /* - * No [file] argument, so check profile for - * method to compose content. - */ - snprintf (buffer, sizeof(buffer), "%s-compose-%s/%s", - invo_name, ci->ci_type, ci->ci_subtype); - if ((cp = context_find (buffer)) == NULL || *cp == '\0') { - snprintf (buffer, sizeof(buffer), "%s-compose-%s", invo_name, ci->ci_type); - if ((cp = context_find (buffer)) == NULL || *cp == '\0') { - content_error (NULL, ct, "don't know how to compose content"); - done (1); - } - } - ci->ci_magic = add (cp, NULL); - return OK; - } - - if (extrnal) - adios (NULL, "external definition not allowed for \"#%s\"", ci->ci_type); - - /* - * Message directive - * #forw [+folder] [msgs] - */ - if (!strcasecmp (ci->ci_type, "forw")) { - int msgnum; - char *folder, *arguments[MAXARGS]; - struct msgs *mp; - - if (ci->ci_magic) { - ap = brkstring (ci->ci_magic, " ", "\n"); - copyip (ap, arguments, MAXARGS); - } else { - arguments[0] = "cur"; - arguments[1] = NULL; - } - folder = NULL; - - /* search the arguments for a folder name */ - for (ap = arguments; *ap; ap++) { - cp = *ap; - if (*cp == '+' || *cp == '@') { - if (folder) - adios (NULL, "only one folder per #forw directive"); - else - folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF); - } + ** 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); - /* else, use the current folder */ - if (!folder) - folder = add (getfolder (1), NULL); - - if (!(mp = folder_read (folder))) - adios (NULL, "unable to read folder %s", folder); - for (ap = arguments; *ap; ap++) { - cp = *ap; - if (*cp != '+' && *cp != '@') - if (!m_convert (mp, cp)) - done (1); + /* + ** output the Content-ID, unless disabled by -nocontentid + */ + if (contentidsw && ct->c_id) { + np = add (ID_FIELD, NULL); + vp = concat (" ", ct->c_id, NULL); + add_header (ct, np, vp); } - free (folder); - free_ctinfo (ct); /* - * If there is more than one message to include, make this - * a content of type "multipart/digest" and insert each message - * as a subpart. If there is only one message, then make this - * a content of type "message/rfc822". - */ - if (mp->numsel > 1) { - /* we are forwarding multiple messages */ - if (get_ctinfo ("multipart/digest", ct, 0) == NOTOK) - done (1); - ct->c_type = CT_MULTIPART; - ct->c_subtype = MULTI_DIGEST; - - if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) m; - pp = &m->mp_parts; - - for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) { - if (is_selected(mp, msgnum)) { - struct part *part; - CT p; - CE pe; - - if ((p = (CT) calloc (1, sizeof(*p))) == NULL) - adios (NULL, "out of memory"); - init_decoded_content (p); - pe = p->c_cefile; - if (get_ctinfo ("message/rfc822", p, 0) == NOTOK) - done (1); - p->c_type = CT_MESSAGE; - p->c_subtype = MESSAGE_RFC822; - - snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum); - pe->ce_file = add (buffer, NULL); - if (listsw && stat (pe->ce_file, &st) != NOTOK) - p->c_end = (long) st.st_size; - - if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) - adios (NULL, "out of memory"); - *pp = part; - pp = &part->mp_next; - part->mp_part = p; - } - } - } else { - /* we are forwarding one message */ - if (get_ctinfo ("message/rfc822", ct, 0) == NOTOK) - done (1); - ct->c_type = CT_MESSAGE; - ct->c_subtype = MESSAGE_RFC822; - - msgnum = mp->lowsel; - snprintf (buffer, sizeof(buffer), "%s/%d", mp->foldpath, msgnum); - ce->ce_file = add (buffer, NULL); - if (listsw && stat (ce->ce_file, &st) != NOTOK) - ct->c_end = (long) st.st_size; + ** output the Content-Description + */ + if (ct->c_descr) { + np = add (DESCR_FIELD, NULL); + vp = concat (" ", ct->c_descr, NULL); + add_header (ct, np, vp); } - folder_free (mp); /* free folder/message structure */ - return OK; - } - - /* - * #end - */ - if (!strcasecmp (ci->ci_type, "end")) { - free_content (ct); - *ctp = NULL; - return DONE; - } - - /* - * #begin [ alternative | parallel ] - */ - if (!strcasecmp (ci->ci_type, "begin")) { - if (!ci->ci_magic) { - vrsn = MULTI_MIXED; - cp = SubMultiPart[vrsn - 1].kv_key; - } else if (!strcasecmp (ci->ci_magic, "alternative")) { - vrsn = MULTI_ALTERNATE; - cp = SubMultiPart[vrsn - 1].kv_key; - } else if (!strcasecmp (ci->ci_magic, "parallel")) { - vrsn = MULTI_PARALLEL; - cp = SubMultiPart[vrsn - 1].kv_key; - } else if (uprf (ci->ci_magic, "digest")) { - goto use_forw; - } else { - vrsn = MULTI_UNKNOWN; - cp = ci->ci_magic; + /* + ** output the Content-Disposition + */ + if (ct->c_dispo) { + np = add (DISPO_FIELD, NULL); + vp = concat (" ", ct->c_dispo, NULL); + add_header (ct, np, vp); } - free_ctinfo (ct); - snprintf (buffer, sizeof(buffer), "multipart/%s", cp); - if (get_ctinfo (buffer, ct, 0) == NOTOK) - done (1); - ct->c_type = CT_MULTIPART; - ct->c_subtype = vrsn; - - if ((m = (struct multipart *) calloc (1, sizeof(*m))) == NULL) - adios (NULL, "out of memory"); - ct->c_ctparams = (void *) m; - - pp = &m->mp_parts; - while (fgetstr (buffer, sizeof(buffer) - 1, in)) { - struct part *part; - CT p; - - if (user_content (in, file, buffer, &p) == DONE) { - if (!m->mp_parts) - adios (NULL, "empty \"#begin ... #end\" sequence"); +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; - } - if (!p) - continue; - if ((part = (struct part *) calloc (1, sizeof(*part))) == NULL) - adios (NULL, "out of memory"); - *pp = part; - pp = &part->mp_next; - part->mp_part = p; + /* + ** output the Content-MD5 + */ + if (checksw) { + np = add (MD5_FIELD, NULL); + vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0); + add_header (ct, np, vp); } - admonish (NULL, "premature end-of-file, missing #end"); - return OK; - } - /* - * Unknown directive - */ - adios (NULL, "unknown directive \"#%s\"", ci->ci_type); - return NOTOK; /* NOT REACHED */ -} + /* + ** output the Content-Transfer-Encoding + */ + switch (ct->c_encoding) { + case CE_7BIT: + /* Nothing to output */ +#if 0 + np = add (ENCODING_FIELD, NULL); + vp = concat (" ", "7bit", "\n", NULL); + add_header (ct, np, vp); +#endif + break; + case CE_8BIT: + if (ct->c_type == CT_MESSAGE) + adios (NULL, "internal error, invalid encoding"); -static void -set_id (CT ct, int top) -{ - 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); -} + np = add (ENCODING_FIELD, NULL); + 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"); -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 -}; + np = add (ENCODING_FIELD, NULL); + 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"); -/* - * 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. - */ + np = add (ENCODING_FIELD, NULL); + vp = concat (" ", "base64", "\n", NULL); + add_header (ct, np, vp); + break; -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; - } + case CE_BINARY: + if (ct->c_type == CT_MESSAGE) + adios (NULL, "internal error, invalid encoding"); - /* 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; + np = add (ENCODING_FIELD, NULL); + vp = concat (" ", "binary", "\n", NULL); + add_header (ct, np, vp); + break; - sprintf (pp, "%d", partnum); - p->c_partno = add (partnam, NULL); - if (compose_content (p) == NOTOK) - return NOTOK; + default: + adios (NULL, "unknown transfer encoding in content"); + break; } /* - * 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++; - } - } + ** Additional content specific header processing + */ + switch (ct->c_type) { + case CT_MULTIPART: + { + struct multipart *m; + struct part *part; - if (listsw) { - ct->c_end = (partnum = strlen (prefix) + 2) + 2; - if (ct->c_rfc934) - ct->c_end += 1; + m = (struct multipart *) ct->c_ctparams; + for (part = m->mp_parts; part; part = part->mp_next) { + CT p; - for (part = m->mp_parts; part; part = part->mp_next) - ct->c_end += part->mp_part->c_end + partnum; + p = part->mp_part; + build_headers (p); + } } - } - 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 i, xstdout, len, buflen; - char *bp, **ap, *cp; - char *vec[4], buffer[BUFSIZ]; - FILE *out; - CI ci = &ct->c_ctinfo; - - if (!(cp = ci->ci_magic)) - adios (NULL, "internal error(5)"); - - ce->ce_file = add (m_tmpfil (invo_name), NULL); - 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; + break; - case '%': - /* insert character % */ - goto raw; + case CT_MESSAGE: + if (ct->c_subtype == MESSAGE_EXTERNAL) { + struct exbody *e; - default: - *bp++ = *--cp; - *bp = '\0'; - buflen--; - continue; - } - len = strlen (bp); - bp += len; - buflen -= len; - } else { -raw: - *bp++ = *cp; - *bp = '\0'; - buflen--; + e = (struct exbody *) ct->c_ctparams; + build_headers (e->eb_content); } - } - - 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"); - - for (i = 0; (child_id = vfork()) == NOTOK && i > 5; i++) - sleep (5); - switch (child_id) { - 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; + default: + /* Nothing to do */ + break; } - break; - } - return OK; + 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 char nib2b64[0x40+1] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -static int -scan_content (CT ct) +static char * +calculate_digest (CT ct, int asciiP) { - int len; - int check8bit, contains8bit = 0; /* check if contains 8bit data */ - int checklinelen, linelen = 0; /* check for long lines */ - int checkboundary, boundaryclash = 0; /* check if clashes with multipart boundary */ - int checklinespace, linespace = 0; /* check if any line ends with space */ - int checkebcdic, ebcdicunsafe = 0; /* check if contains ebcdic unsafe characters */ - char *cp, buffer[BUFSIZ]; - struct text *t; - FILE *in; - 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; - } + int cc; + char buffer[BUFSIZ], *vp, *op; + unsigned char *dp; + unsigned char digest[16]; + unsigned char outbuf[25]; + FILE *in; + MD5_CTX mdContext; + CE ce = ct->c_cefile; - /* - * Scan the unencoded content - */ - if (check8bit || checklinelen || checklinespace || checkboundary) { + /* open content */ 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; - check8bit = 0; /* no need to keep checking */ - } - /* - * Check if character is ebcdic-safe. We only check - * this if also checking for 8bit data. - */ - if (checkebcdic && !ebcdicsafe[*cp & 0xff]) { - ebcdicunsafe = 1; - checkebcdic = 0; /* no need to keep checking */ - } - } - } - - /* - * 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; - checklinespace = 0; /* no need to keep checking */ - } - - /* - * 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) && isdigit(buffer[2 + len])) { - boundaryclash = 1; - checkboundary = 0; /* no need to keep checking */ - } - } - } - 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 = add ("charset=us-ascii", NULL); - } - - cp = strchr(*ap++, '='); - *ap = NULL; - *cp++ = '\0'; - *ep = cp; - } - - if (contains8bit || ebcdicunsafe || linelen || linespace || checksw) - 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 || checksw) - 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; + adios (ce->ce_file, "unable to open for reading"); - 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); -} + /* Initialize md5 context */ + MD5Init (&mdContext); + /* calculate md5 message digest */ + if (asciiP) { + while (fgets (buffer, sizeof(buffer) - 1, in)) { + char c, *cp; -/* - * Scan the content structures, and build header - * fields that will need to be output into the - * message. - */ + cp = buffer + strlen (buffer) - 1; + if ((c = *cp) == '\n') + *cp = '\0'; -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++ = add (buffer, NULL), '='); - *ap = NULL; - *cp++ = '\0'; - *ep = cp; - } - - /* - * Skip the output of Content-Type, parameters, content - * description, 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 = add (TYPE_FIELD, NULL); - 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; + MD5Update (&mdContext, (unsigned char *) buffer, + (unsigned int) strlen (buffer)); - 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 && !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; + if (c == '\n') + MD5Update (&mdContext, (unsigned char *) "\r\n", 2); + } } else { - vp = add (" ", vp); - len++; + while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0) + MD5Update (&mdContext, (unsigned char *) buffer, + (unsigned int) cc); } - vp = add (buffer, vp); - len += cc; - } - vp = add ("\n", vp); - add_header (ct, np, vp); - - /* - * output the Content-ID - */ - if (ct->c_id) { - np = add (ID_FIELD, NULL); - vp = concat (" ", ct->c_id, NULL); - add_header (ct, np, vp); - } - - /* - * output the Content-Description - */ - if (ct->c_descr) { - np = add (DESCR_FIELD, NULL); - vp = concat (" ", ct->c_descr, 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-MD5 - */ - if (checksw) { - np = add (MD5_FIELD, NULL); - vp = calculate_digest (ct, (ct->c_encoding == CE_QUOTED) ? 1 : 0); - add_header (ct, np, vp); - } - - /* - * output the Content-Transfer-Encoding - */ - switch (ct->c_encoding) { - case CE_7BIT: - /* Nothing to output */ -#if 0 - np = add (ENCODING_FIELD, NULL); - vp = concat (" ", "7bit", "\n", NULL); - add_header (ct, np, vp); -#endif - break; - - case CE_8BIT: - if (ct->c_type == CT_MESSAGE) - adios (NULL, "internal error, invalid encoding"); - - np = add (ENCODING_FIELD, NULL); - 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 = add (ENCODING_FIELD, NULL); - 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 = add (ENCODING_FIELD, NULL); - 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 = add (ENCODING_FIELD, NULL); - 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; + /* md5 finalization. Write digest and zero md5 context */ + MD5Final (digest, &mdContext); - p = part->mp_part; - build_headers (p); - } - } - break; + /* close content */ + fclose (in); - case CT_MESSAGE: - if (ct->c_subtype == MESSAGE_EXTERNAL) { - struct exbody *e; + /* print debugging info */ + if (debugsw) { + unsigned char *ep; - e = (struct exbody *) ct->c_ctparams; - build_headers (e->eb_content); + fprintf (stderr, "MD5 digest="); + for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]); + dp < ep; dp++) + fprintf (stderr, "%02x", *dp & 0xff); + fprintf (stderr, "\n"); } - break; - - default: - /* Nothing to do */ - break; - } - - return OK; -} - - -static char nib2b64[0x40+1] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -static char * -calculate_digest (CT ct, int asciiP) -{ - int cc; - char buffer[BUFSIZ], *vp, *op; - unsigned char *dp; - unsigned char digest[16]; - unsigned char outbuf[25]; - FILE *in; - MD5_CTX mdContext; - CE ce = ct->c_cefile; - - /* open content */ - if ((in = fopen (ce->ce_file, "r")) == NULL) - adios (ce->ce_file, "unable to open for reading"); - - /* Initialize md5 context */ - MD5Init (&mdContext); - - /* calculate md5 message digest */ - if (asciiP) { - while (fgets (buffer, sizeof(buffer) - 1, in)) { - char c, *cp; - - cp = buffer + strlen (buffer) - 1; - if ((c = *cp) == '\n') - *cp = '\0'; - MD5Update (&mdContext, (unsigned char *) buffer, - (unsigned int) strlen (buffer)); - - if (c == '\n') - MD5Update (&mdContext, (unsigned char *) "\r\n", 2); - } - } else { - while ((cc = fread (buffer, sizeof(*buffer), sizeof(buffer), in)) > 0) - MD5Update (&mdContext, (unsigned char *) buffer, (unsigned int) cc); - } - - /* md5 finalization. Write digest and zero md5 context */ - MD5Final (digest, &mdContext); - - /* close content */ - fclose (in); - - /* print debugging info */ - if (debugsw) { - unsigned char *ep; - - fprintf (stderr, "MD5 digest="); - for (ep = (dp = digest) + sizeof(digest) / sizeof(digest[0]); - dp < ep; dp++) - fprintf (stderr, "%02x", *dp & 0xff); - fprintf (stderr, "\n"); - } - - /* encode the digest using base64 */ - for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]); + /* encode the digest using base64 */ + for (dp = digest, op = outbuf, cc = sizeof(digest) / sizeof(digest[0]); cc > 0; cc -= 3, op += 4) { - unsigned long bits; - char *bp; - - bits = (*dp++ & 0xff) << 16; - if (cc > 1) { - bits |= (*dp++ & 0xff) << 8; - if (cc > 2) - bits |= *dp++ & 0xff; - } - - for (bp = op + 4; bp > op; bits >>= 6) - *--bp = nib2b64[bits & 0x3f]; - if (cc < 3) { - *(op + 3) = '='; - if (cc < 2) - *(op + 2) = '='; - } - } - - /* null terminate string */ - outbuf[24] = '\0'; - - /* now make copy and return string */ - vp = concat (" ", outbuf, "\n", NULL); - return vp; -} - + unsigned long bits; + char *bp; -static int -readDigest (CT ct, char *cp) -{ - int bitno, skip; - unsigned long bits; - char *bp = cp; - unsigned char *dp, value, *ep; - unsigned char *b, *b1, *b2, *b3; - - b = (unsigned char *) &bits, - b1 = &b[endian > 0 ? 1 : 2], - b2 = &b[endian > 0 ? 2 : 1], - b3 = &b[endian > 0 ? 3 : 0]; - bitno = 18; - bits = 0L; - skip = 0; - - for (ep = (dp = ct->c_digest) - + sizeof(ct->c_digest) / sizeof(ct->c_digest[0]); *cp; cp++) - switch (*cp) { - default: - if (skip - || (*cp & 0x80) - || (value = b642nib[*cp & 0x7f]) > 0x3f) { - if (debugsw) - fprintf (stderr, "invalid BASE64 encoding\n"); - return NOTOK; + bits = (*dp++ & 0xff) << 16; + if (cc > 1) { + bits |= (*dp++ & 0xff) << 8; + if (cc > 2) + bits |= *dp++ & 0xff; } - bits |= value << bitno; -test_end: - if ((bitno -= 6) < 0) { - if (dp + (3 - skip) > ep) - goto invalid_digest; - *dp++ = *b1; - if (skip < 2) { - *dp++ = *b2; - if (skip < 1) - *dp++ = *b3; - } - bitno = 18; - bits = 0L; - skip = 0; + for (bp = op + 4; bp > op; bits >>= 6) + *--bp = nib2b64[bits & 0x3f]; + if (cc < 3) { + *(op + 3) = '='; + if (cc < 2) + *(op + 2) = '='; } - break; - - case '=': - if (++skip > 3) - goto self_delimiting; - goto test_end; } - if (bitno != 18) { - if (debugsw) - fprintf (stderr, "premature ending (bitno %d)\n", bitno); - - return NOTOK; - } -self_delimiting: - if (dp != ep) { -invalid_digest: - if (debugsw) { - while (*cp) - cp++; - fprintf (stderr, "invalid MD5 digest (got %d octets)\n", - cp - bp); - } - - return NOTOK; - } - if (debugsw) { - fprintf (stderr, "MD5 digest="); - for (dp = ct->c_digest; dp < ep; dp++) - fprintf (stderr, "%02x", *dp & 0xff); - fprintf (stderr, "\n"); - } + /* null terminate string */ + outbuf[24] = '\0'; - return OK; + /* now make copy and return string */ + vp = concat (" ", outbuf, "\n", NULL); + return vp; }