summaryrefslogtreecommitdiff
path: root/imap/src/c-client/rfc822.c
diff options
context:
space:
mode:
authorEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
committerEduardo Chappa <echappa@gmx.com>2013-02-03 00:59:38 -0700
commit094ca96844842928810f14844413109fc6cdd890 (patch)
treee60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /imap/src/c-client/rfc822.c
downloadalpine-094ca96844842928810f14844413109fc6cdd890.tar.xz
Initial Alpine Version
Diffstat (limited to 'imap/src/c-client/rfc822.c')
-rw-r--r--imap/src/c-client/rfc822.c2373
1 files changed, 2373 insertions, 0 deletions
diff --git a/imap/src/c-client/rfc822.c b/imap/src/c-client/rfc822.c
new file mode 100644
index 00000000..d9239646
--- /dev/null
+++ b/imap/src/c-client/rfc822.c
@@ -0,0 +1,2373 @@
+/* ========================================================================
+ * Copyright 1988-2008 University of Washington
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
+ * ========================================================================
+ */
+
+/*
+ * Program: RFC 2822 and MIME routines
+ *
+ * Author: Mark Crispin
+ * UW Technology
+ * University of Washington
+ * Seattle, WA 98195
+ * Internet: MRC@Washington.EDU
+ *
+ * Date: 27 July 1988
+ * Last Edited: 14 May 2008
+ *
+ * This original version of this file is
+ * Copyright 1988 Stanford University
+ * and was developed in the Symbolic Systems Resources Group of the Knowledge
+ * Systems Laboratory at Stanford University in 1987-88, and was funded by the
+ * Biomedical Research Technology Program of the NationalInstitutes of Health
+ * under grant number RR-00785.
+ */
+
+
+#include <ctype.h>
+#include <stdio.h>
+#include <time.h>
+#include "c-client.h"
+
+
+/* Support for deprecated features in earlier specifications. Note that this
+ * module follows RFC 2822, and all use of "rfc822" in function names is
+ * for compatibility. Only the code identified by the conditionals below
+ * follows the earlier documents.
+ */
+
+#define RFC733 1 /* parse "at" */
+#define RFC822 0 /* generate A-D-L (MUST be 0 for 2822) */
+
+/* RFC-822 static data */
+
+#define RFC822CONT " " /* RFC 2822 continuation */
+
+ /* should have been "Remailed-" */
+#define RESENTPREFIX "ReSent-"
+static char *resentprefix = RESENTPREFIX;
+ /* syntax error host string */
+static const char *errhst = ERRHOST;
+
+
+/* Body formats constant strings, must match definitions in mail.h */
+
+char *body_types[TYPEMAX+1] = {
+ "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
+ "MODEL", "X-UNKNOWN"
+};
+
+
+char *body_encodings[ENCMAX+1] = {
+ "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
+};
+
+
+/* Token delimiting special characters */
+
+ /* RFC 2822 specials */
+const char *specials = " ()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
+ /* RFC 2822 phrase specials (no space) */
+const char *rspecials = "()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
+ /* RFC 2822 dot-atom specials (no dot) */
+const char *wspecials = " ()<>@,;:\\\"[]\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
+ /* RFC 2045 MIME body token specials */
+const char *tspecials = " ()<>@,;:\\\"[]/?=\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
+
+/* Subtype defaulting (a no-no, but regretably necessary...)
+ * Accepts: type code
+ * Returns: default subtype name
+ */
+
+char *rfc822_default_subtype (unsigned short type)
+{
+ switch (type) {
+ case TYPETEXT: /* default is TEXT/PLAIN */
+ return "PLAIN";
+ case TYPEMULTIPART: /* default is MULTIPART/MIXED */
+ return "MIXED";
+ case TYPEMESSAGE: /* default is MESSAGE/RFC822 */
+ return "RFC822";
+ case TYPEAPPLICATION: /* default is APPLICATION/OCTET-STREAM */
+ return "OCTET-STREAM";
+ case TYPEAUDIO: /* default is AUDIO/BASIC */
+ return "BASIC";
+ default: /* others have no default subtype */
+ return "UNKNOWN";
+ }
+}
+
+/* RFC 2822 parsing routines */
+
+
+/* Parse an RFC 2822 message
+ * Accepts: pointer to return envelope
+ * pointer to return body
+ * pointer to header
+ * header byte count
+ * pointer to body stringstruct
+ * pointer to local host name
+ * recursion depth
+ * source driver flags
+ */
+
+void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i,
+ STRING *bs,char *host,unsigned long depth,
+ unsigned long flags)
+{
+ char c,*t,*d;
+ char *tmp = (char *) fs_get ((size_t) i + 100);
+ ENVELOPE *env = (*en = mail_newenvelope ());
+ BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
+ long MIMEp = -1; /* flag that MIME semantics are in effect */
+ long PathP = NIL; /* flag that a Path: was seen */
+ parseline_t pl = (parseline_t) mail_parameters (NIL,GET_PARSELINE,NIL);
+ if (!host) host = BADHOST; /* make sure that host is non-null */
+ while (i && *s != '\n') { /* until end of header */
+ t = tmp; /* initialize buffer pointer */
+ c = ' '; /* and previous character */
+ while (i && c) { /* collect text until logical end of line */
+ switch (c = *s++) { /* slurp a character */
+ case '\015': /* return, possible end of logical line */
+ if (*s == '\n') break; /* ignore if LF follows */
+ case '\012': /* LF, possible end of logical line */
+ /* tie off unless next line starts with WS */
+ if (*s != ' ' && *s != '\t') *t++ = c = '\0';
+ break;
+ case '\t': /* tab */
+ *t++ = ' '; /* coerce to space */
+ break;
+ default: /* all other characters */
+ *t++ = c; /* insert the character into the line */
+ break;
+ }
+ if (!--i) *t++ = '\0'; /* see if end of header */
+ }
+
+ /* find header item type */
+ if (t = d = strchr (tmp,':')) {
+ *d++ = '\0'; /* tie off header item, point at its data */
+ while (*d == ' ') d++; /* flush whitespace */
+ while ((tmp < t--) && (*t == ' ')) *t = '\0';
+ ucase (tmp); /* coerce to uppercase */
+ /* external callback */
+ if (pl) (*pl) (env,tmp,d,host);
+ switch (*tmp) { /* dispatch based on first character */
+ case '>': /* possible >From: */
+ if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
+ break;
+ case 'B': /* possible bcc: */
+ if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
+ break;
+ case 'C': /* possible cc: or Content-<mumble>*/
+ if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
+ else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') &&
+ (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') &&
+ (tmp[7] == '-') && body)
+ switch (MIMEp) {
+ case -1: /* unknown if MIME or not */
+ if (!(MIMEp = /* see if MIME-Version header exists */
+ search ((unsigned char *) s-1,i,
+ (unsigned char *)"\012MIME-Version",(long) 13))) {
+#if 1
+ /* This is a disgusting kludge, and most of the messages which
+ * benefit from it are spam.
+ */
+ if (!strcmp (tmp+8,"TRANSFER-ENCODING") ||
+ (!strcmp (tmp+8,"TYPE") && strchr (d,'/'))) {
+ MM_LOG ("Warning: MIME header encountered in non-MIME message",
+ PARSE);
+ MIMEp = 1; /* declare MIME now */
+ }
+ else
+#endif
+ break; /* non-MIME message */
+ }
+ case T: /* definitely MIME */
+ rfc822_parse_content_header (body,tmp+8,d);
+ }
+ break;
+ case 'D': /* possible Date: */
+ if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
+ break;
+ case 'F': /* possible From: */
+ if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
+ else if (!strcmp (tmp+1,"OLLOWUP-TO")) {
+ t = env->followup_to = (char *) fs_get (1 + strlen (d));
+ while (c = *d++) if (c != ' ') *t++ = c;
+ *t++ = '\0';
+ }
+ break;
+ case 'I': /* possible In-Reply-To: */
+ if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO"))
+ env->in_reply_to = cpystr (d);
+ break;
+
+ case 'M': /* possible Message-ID: or MIME-Version: */
+ if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID"))
+ env->message_id = cpystr (d);
+ else if (!strcmp (tmp+1,"IME-VERSION")) {
+ /* tie off at end of phrase */
+ if (t = rfc822_parse_phrase (d)) *t = '\0';
+ rfc822_skipws (&d); /* skip whitespace */
+ /* known version? */
+ if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
+ MM_LOG ("Warning: message has unknown MIME version",PARSE);
+ MIMEp = T; /* note that we are MIME */
+ }
+ break;
+ case 'N': /* possible Newsgroups: */
+ if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) {
+ t = env->newsgroups = (char *) fs_get (1 + strlen (d));
+ while (c = *d++) if (c != ' ') *t++ = c;
+ *t++ = '\0';
+ }
+ break;
+ case 'P': /* possible Path: */
+ if (!strcmp (tmp+1,"ATH")) env->ngpathexists = T;
+ break;
+ case 'R': /* possible Reply-To: */
+ if (!strcmp (tmp+1,"EPLY-TO"))
+ rfc822_parse_adrlist (&env->reply_to,d,host);
+ else if (!env->references && !strcmp (tmp+1,"EFERENCES"))
+ env->references = cpystr (d);
+ break;
+ case 'S': /* possible Subject: or Sender: */
+ if (!env->subject && !strcmp (tmp+1,"UBJECT"))
+ env->subject = cpystr (d);
+ else if (!strcmp (tmp+1,"ENDER"))
+ rfc822_parse_adrlist (&env->sender,d,host);
+ break;
+ case 'T': /* possible To: */
+ if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ fs_give ((void **) &tmp); /* done with scratch buffer */
+ /* default Sender: and Reply-To: to From: */
+ if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
+ if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
+ /* now parse the body */
+ if (body) rfc822_parse_content (body,bs,host,depth,flags);
+}
+
+/* Parse a message body content
+ * Accepts: pointer to body structure
+ * body string
+ * pointer to local host name
+ * recursion depth
+ * source driver flags
+ */
+
+void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth,
+ unsigned long flags)
+{
+ char c,c1,*s,*s1;
+ int f;
+ unsigned long i,j,k,m;
+ PARAMETER *param;
+ PART *part = NIL;
+ if (depth > MAXMIMEDEPTH) { /* excessively deep recursion? */
+ body->type = TYPETEXT; /* yes, probably a malicious MIMEgram */
+ MM_LOG ("Ignoring excessively deep MIME recursion",PARSE);
+ }
+ if (!body->subtype) /* default subtype if still unknown */
+ body->subtype = cpystr (rfc822_default_subtype (body->type));
+ /* note offset and sizes */
+ body->contents.offset = GETPOS (bs);
+ /* note internal body size in all cases */
+ body->size.bytes = body->contents.text.size = i = SIZE (bs);
+ if (!(flags & DR_CRLF)) body->size.bytes = strcrlflen (bs);
+ switch (body->type) { /* see if anything else special to do */
+ case TYPETEXT: /* text content */
+ if (!body->parameter) { /* no parameters set */
+ body->parameter = mail_newbody_parameter ();
+ body->parameter->attribute = cpystr ("CHARSET");
+ while (i--) { /* count lines and guess charset */
+ c = SNX (bs); /* get current character */
+ /* charset still unknown? */
+ if (!body->parameter->value) {
+ if ((c == I2C_ESC) && (i && i--) && ((c = SNX (bs)) == I2C_MULTI) &&
+ (i && i--) && (((c = SNX (bs)) == I2CS_94x94_JIS_NEW) ||
+ (c == I2CS_94x94_JIS_OLD)))
+ body->parameter->value = cpystr ("ISO-2022-JP");
+ else if (c & 0x80) body->parameter->value = cpystr ("X-UNKNOWN");
+ }
+ if (c == '\n') body->size.lines++;
+ }
+ /* 7-bit content */
+ if (!body->parameter->value) switch (body->encoding) {
+ case ENC7BIT: /* unadorned 7-bit */
+ case ENC8BIT: /* unadorned 8-bit (but 7-bit content) */
+ case ENCBINARY: /* binary (but 7-bit content( */
+ body->parameter->value = cpystr ("US-ASCII");
+ break;
+ default: /* QUOTED-PRINTABLE, BASE64, etc. */
+ body->parameter->value = cpystr ("X-UNKNOWN");
+ break;
+ }
+ }
+ /* just count lines */
+ else while (i--) if ((SNX (bs)) == '\n') body->size.lines++;
+ break;
+
+ case TYPEMESSAGE: /* encapsulated message */
+ /* encapsulated RFC-822 message? */
+ if (!strcmp (body->subtype,"RFC822")) {
+ body->nested.msg = mail_newmsg ();
+ switch (body->encoding) { /* make sure valid encoding */
+ case ENC7BIT: /* these are valid nested encodings */
+ case ENC8BIT:
+ case ENCBINARY:
+ break;
+ default:
+ MM_LOG ("Ignoring nested encoding of message contents",PARSE);
+ }
+ /* hunt for blank line */
+ for (c = '\012',j = 0; (i > j) && ((c != '\012') || (CHR(bs) != '\012'));
+ j++) if ((c1 = SNX (bs)) != '\015') c = c1;
+ if (i > j) { /* unless no more text */
+ c1 = SNX (bs); /* body starts here */
+ j++; /* advance count */
+ }
+ /* note body text offset and header size */
+ body->nested.msg->header.text.size = j;
+ body->nested.msg->text.text.size = body->contents.text.size - j;
+ body->nested.msg->text.offset = GETPOS (bs);
+ body->nested.msg->full.offset = body->nested.msg->header.offset =
+ body->contents.offset;
+ body->nested.msg->full.text.size = body->contents.text.size;
+ /* copy header string */
+ SETPOS (bs,body->contents.offset);
+ s = (char *) fs_get ((size_t) j + 1);
+ for (s1 = s,k = j; k--; *s1++ = SNX (bs));
+ s[j] = '\0'; /* tie off string (not really necessary) */
+ /* now parse the body */
+ rfc822_parse_msg_full (&body->nested.msg->env,&body->nested.msg->body,s,
+ j,bs,h,depth+1,flags);
+ fs_give ((void **) &s); /* free header string */
+ /* restore position */
+ SETPOS (bs,body->contents.offset);
+ }
+ /* count number of lines */
+ while (i--) if (SNX (bs) == '\n') body->size.lines++;
+ break;
+ case TYPEMULTIPART: /* multiple parts */
+ switch (body->encoding) { /* make sure valid encoding */
+ case ENC7BIT: /* these are valid nested encodings */
+ case ENC8BIT:
+ case ENCBINARY:
+ break;
+ default:
+ MM_LOG ("Ignoring nested encoding of multipart contents",PARSE);
+ }
+ /* remember if digest */
+ f = !strcmp (body->subtype,"DIGEST");
+ /* find cookie */
+ for (s1 = NIL,param = body->parameter; param && !s1; param = param->next)
+ if (!strcmp (param->attribute,"BOUNDARY")) s1 = param->value;
+ if (!s1) s1 = "-"; /* yucky default */
+ j = strlen (s1) + 2; /* length of cookie and header */
+ c = '\012'; /* initially at beginning of line */
+
+ while (i > j) { /* examine data */
+ if (m = GETPOS (bs)) m--; /* get position in front of character */
+ switch (c) { /* examine each line */
+ case '\015': /* handle CRLF form */
+ if (CHR (bs) == '\012'){/* following LF? */
+ c = SNX (bs); i--; /* yes, slurp it */
+ }
+ case '\012': /* at start of a line, start with -- ? */
+ if (!(i && i-- && ((c = SNX (bs)) == '-') && i-- &&
+ ((c = SNX (bs)) == '-'))) break;
+ /* see if cookie matches */
+ if (k = j - 2) for (s = s1; i-- && *s++ == (c = SNX (bs)) && --k;);
+ if (k) break; /* strings didn't match if non-zero */
+ /* terminating delimiter? */
+ if ((c = ((i && i--) ? (SNX (bs)) : '\012')) == '-') {
+ if ((i && i--) && ((c = SNX (bs)) == '-') &&
+ ((i && i--) ? (((c = SNX (bs)) == '\015') || (c=='\012')):T)) {
+ /* if have a final part calculate its size */
+ if (part) part->body.mime.text.size =
+ (m > part->body.mime.offset) ? (m - part->body.mime.offset) :0;
+ part = NIL; i = 1; /* terminate scan */
+ }
+ break;
+ }
+ /* swallow trailing whitespace */
+ while ((c == ' ') || (c == '\t'))
+ c = ((i && i--) ? (SNX (bs)) : '\012');
+ switch (c) { /* need newline after all of it */
+ case '\015': /* handle CRLF form */
+ if (i && CHR (bs) == '\012') {
+ c = SNX (bs); i--;/* yes, slurp it */
+ }
+ case '\012': /* new line */
+ if (part) { /* calculate size of previous */
+ part->body.mime.text.size =
+ (m > part->body.mime.offset) ? (m-part->body.mime.offset) : 0;
+ /* instantiate next */
+ part = part->next = mail_newbody_part ();
+ } /* otherwise start new list */
+ else part = body->nested.part = mail_newbody_part ();
+ /* digest has a different default */
+ if (f) part->body.type = TYPEMESSAGE;
+ /* note offset from main body */
+ part->body.mime.offset = GETPOS (bs);
+ break;
+ default: /* whatever it was it wasn't valid */
+ break;
+ }
+ break;
+ default: /* not at a line */
+ c = SNX (bs); i--; /* get next character */
+ break;
+ }
+ }
+
+ /* calculate size of any final part */
+ if (part) part->body.mime.text.size = i +
+ ((GETPOS(bs) > part->body.mime.offset) ?
+ (GETPOS(bs) - part->body.mime.offset) : 0);
+ /* make a scratch buffer */
+ s1 = (char *) fs_get ((size_t) (k = MAILTMPLEN));
+ /* in case empty multipart */
+ if (!body->nested.part) body->nested.part = mail_newbody_part ();
+ /* parse non-empty body parts */
+ for (part = body->nested.part; part; part = part->next) {
+ /* part non-empty (header and/or content)? */
+ if (i = part->body.mime.text.size) {
+ /* move to that part of the body */
+ SETPOS (bs,part->body.mime.offset);
+ /* until end of header */
+ while (i && ((c = CHR (bs)) != '\015') && (c != '\012')) {
+ /* collect text until logical end of line */
+ for (j = 0,c = ' '; c; ) {
+ /* make sure buffer big enough */
+ if (j > (k - 10)) fs_resize ((void **) &s1,k += MAILTMPLEN);
+ switch (c1 = SNX (bs)) {
+ case '\015': /* return */
+ if (i && (CHR (bs) == '\012')) {
+ c1 = SNX (bs); /* eat any LF following */
+ i--;
+ }
+ case '\012': /* newline, possible end of logical line */
+ /* tie off unless continuation */
+ if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t')))
+ s1[j] = c = '\0';
+ break;
+ case '\t': /* tab */
+ case ' ': /* insert whitespace if not already there */
+ if (c != ' ') s1[j++] = c = ' ';
+ break;
+ default: /* all other characters */
+ s1[j++] = c = c1; /* insert the character into the line */
+ break;
+ }
+ /* end of data ties off the header */
+ if (!i || !--i) s1[j++] = c = '\0';
+ }
+
+ /* find header item type */
+ if (((s1[0] == 'C') || (s1[0] == 'c')) &&
+ ((s1[1] == 'O') || (s1[1] == 'o')) &&
+ ((s1[2] == 'N') || (s1[2] == 'n')) &&
+ ((s1[3] == 'T') || (s1[3] == 't')) &&
+ ((s1[4] == 'E') || (s1[4] == 'e')) &&
+ ((s1[5] == 'N') || (s1[5] == 'n')) &&
+ ((s1[6] == 'T') || (s1[6] == 't')) &&
+ (s1[7] == '-') && (s = strchr (s1+8,':'))) {
+ /* tie off and flush whitespace */
+ for (*s++ = '\0'; *s == ' '; s++);
+ /* parse the header */
+ rfc822_parse_content_header (&part->body,ucase (s1+8),s);
+ }
+ }
+ /* skip header trailing (CR)LF */
+ if (i && (CHR (bs) =='\015')) {i--; c1 = SNX (bs);}
+ if (i && (CHR (bs) =='\012')) {i--; c1 = SNX (bs);}
+ j = bs->size; /* save upper level size */
+ /* set offset for next level, fake size to i */
+ bs->size = GETPOS (bs) + i;
+ part->body.mime.text.size -= i;
+ /* now parse it */
+ rfc822_parse_content (&part->body,bs,h,depth+1,flags);
+ bs->size = j; /* restore current level size */
+ }
+ else { /* zero-length part, use default subtype */
+ part->body.subtype = cpystr (rfc822_default_subtype (part->body.type));
+ /* see if anything else special to do */
+ switch (part->body.type) {
+ case TYPETEXT: /* text content */
+ /* default parameters */
+ if (!part->body.parameter) {
+ part->body.parameter = mail_newbody_parameter ();
+ part->body.parameter->attribute = cpystr ("CHARSET");
+ /* only assume US-ASCII if 7BIT */
+ part->body.parameter->value =
+ cpystr ((part->body.encoding == ENC7BIT) ?
+ "US-ASCII" : "X-UNKNOWN");
+ }
+ break;
+ case TYPEMESSAGE: /* encapsulated message in digest */
+ part->body.nested.msg = mail_newmsg ();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ fs_give ((void **) &s1); /* finished with scratch buffer */
+ break;
+ default: /* nothing special to do in any other case */
+ break;
+ }
+}
+
+/* Parse RFC 2822 body content header
+ * Accepts: body to write to
+ * possible content name
+ * remainder of header
+ */
+
+void rfc822_parse_content_header (BODY *body,char *name,char *s)
+{
+ char c,*t,tmp[MAILTMPLEN];
+ long i;
+ STRINGLIST *stl;
+ rfc822_skipws (&s); /* skip leading comments */
+ /* flush whitespace */
+ if (t = strchr (name,' ')) *t = '\0';
+ switch (*name) { /* see what kind of content */
+ case 'I': /* possible Content-ID */
+ if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
+ break;
+ case 'D': /* possible Content-Description */
+ if (!(strcmp (name+1,"ESCRIPTION") || body->description))
+ body->description = cpystr (s);
+ if (!(strcmp (name+1,"ISPOSITION") || body->disposition.type)) {
+ /* get type word */
+ if (!(name = rfc822_parse_word (s,tspecials))) break;
+ c = *name; /* remember delimiter */
+ *name = '\0'; /* tie off type */
+ body->disposition.type = ucase (cpystr (s));
+ *name = c; /* restore delimiter */
+ rfc822_skipws (&name); /* skip whitespace */
+ rfc822_parse_parameter (&body->disposition.parameter,name);
+ }
+ break;
+ case 'L': /* possible Content-Language */
+ if (!(strcmp (name+1,"ANGUAGE") || body->language)) {
+ stl = NIL; /* process languages */
+ while (s && (name = rfc822_parse_word (s,tspecials))) {
+ c = *name; /* save delimiter */
+ *name = '\0'; /* tie off subtype */
+ if (stl) stl = stl->next = mail_newstringlist ();
+ else stl = body->language = mail_newstringlist ();
+ stl->text.data = (unsigned char *) ucase (cpystr (s));
+ stl->text.size = strlen ((char *) stl->text.data);
+ *name = c; /* restore delimiter */
+ rfc822_skipws (&name); /* skip whitespace */
+ if (*name == ',') { /* any more languages? */
+ s = ++name; /* advance to it them */
+ rfc822_skipws (&s);
+ }
+ else s = NIL; /* bogus or end of list */
+ }
+ }
+ else if (!(strcmp (name+1,"OCATION") || body->location))
+ body->location = cpystr (s);
+ break;
+ case 'M': /* possible Content-MD5 */
+ if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
+ break;
+
+ case 'T': /* possible Content-Type/Transfer-Encoding */
+ if (!(strcmp (name+1,"YPE") || body->subtype || body->parameter)) {
+ /* get type word */
+ if (!(name = rfc822_parse_word (s,tspecials))) break;
+ c = *name; /* remember delimiter */
+ *name = '\0'; /* tie off type */
+ /* search for body type */
+ for (i = 0,s = rfc822_cpy (s);
+ (i <= TYPEMAX) && body_types[i] &&
+ compare_cstring (s,body_types[i]); i++);
+ if (i > TYPEMAX) { /* fell off end of loop? */
+ body->type = TYPEOTHER; /* coerce to X-UNKNOWN */
+ sprintf (tmp,"MIME type table overflow: %.100s",s);
+ MM_LOG (tmp,PARSE);
+ }
+ else { /* record body type index */
+ body->type = (unsigned short) i;
+ /* and name if new type */
+ if (body_types[body->type]) fs_give ((void **) &s);
+ else { /* major MIME body type unknown to us */
+ body_types[body->type] = ucase (s);
+ sprintf (tmp,"Unknown MIME type: %.100s",s);
+ MM_LOG (tmp,PARSE);
+ }
+ }
+ *name = c; /* restore delimiter */
+ rfc822_skipws (&name); /* skip whitespace */
+ if ((*name == '/') && /* subtype? */
+ (name = rfc822_parse_word ((s = ++name),tspecials))) {
+ c = *name; /* save delimiter */
+ *name = '\0'; /* tie off subtype */
+ rfc822_skipws (&s); /* copy subtype */
+ if (s) body->subtype = ucase (rfc822_cpy (s));
+ *name = c; /* restore delimiter */
+ rfc822_skipws (&name); /* skip whitespace */
+ }
+ else if (!name) { /* no subtype, was a subtype delimiter? */
+ name = s; /* barf, restore pointer */
+ rfc822_skipws (&name); /* skip leading whitespace */
+ }
+ rfc822_parse_parameter (&body->parameter,name);
+ }
+
+ else if (!strcmp (name+1,"RANSFER-ENCODING")) {
+ if (!(name = rfc822_parse_word (s,tspecials))) break;
+ c = *name; /* remember delimiter */
+ *name = '\0'; /* tie off encoding */
+ /* search for body encoding */
+ for (i = 0,s = rfc822_cpy (s);
+ (i <= ENCMAX) && body_encodings[i] &&
+ compare_cstring (s,body_encodings[i]); i++);
+ if (i > ENCMAX) { /* fell off end of loop? */
+ body->encoding = ENCOTHER;
+ sprintf (tmp,"MIME encoding table overflow: %.100s",s);
+ MM_LOG (tmp,PARSE);
+ }
+ else { /* record body encoding index */
+ body->encoding = (unsigned short) i;
+ /* and name if new encoding */
+ if (body_encodings[body->encoding]) fs_give ((void **) &s);
+ else {
+ body_encodings[body->encoding] = ucase (s);
+ sprintf (tmp,"Unknown MIME transfer encoding: %.100s",s);
+ MM_LOG (tmp,PARSE);
+ }
+ }
+ *name = c; /* restore delimiter */
+ /* ??check for cruft here?? */
+ }
+ break;
+ default: /* otherwise unknown */
+ break;
+ }
+}
+
+/* Parse RFC 2822 body parameter list
+ * Accepts: parameter list to write to
+ * text of list
+ */
+
+void rfc822_parse_parameter (PARAMETER **par,char *text)
+{
+ char c,*s,tmp[MAILTMPLEN];
+ PARAMETER *param = NIL;
+ /* parameter list? */
+ while (text && (*text == ';') &&
+ (text = rfc822_parse_word ((s = ++text),tspecials))) {
+ c = *text; /* remember delimiter */
+ *text = '\0'; /* tie off attribute name */
+ rfc822_skipws (&s); /* skip leading attribute whitespace */
+ if (!*s) *text = c; /* must have an attribute name */
+ else { /* instantiate a new parameter */
+ if (*par) param = param->next = mail_newbody_parameter ();
+ else param = *par = mail_newbody_parameter ();
+ param->attribute = ucase (cpystr (s));
+ *text = c; /* restore delimiter */
+ rfc822_skipws (&text); /* skip whitespace before equal sign */
+ if ((*text == '=') && /* make sure have value */
+ (text = rfc822_parse_word ((s = ++text),tspecials))) {
+ c = *text; /* remember delimiter */
+ *text = '\0'; /* tie off value */
+ rfc822_skipws (&s); /* skip leading value whitespace */
+ if (*s) param->value = rfc822_cpy (s);
+ *text = c; /* restore delimiter */
+ rfc822_skipws (&text);
+ }
+ if (!param->value) { /* value not found? */
+ param->value = cpystr ("MISSING_PARAMETER_VALUE");
+ sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
+ MM_LOG (tmp,PARSE);
+ }
+ }
+ }
+ /* string not present */
+ if (!text) MM_LOG ("Missing parameter",PARSE);
+ else if (*text) { /* must be end of poop */
+ sprintf (tmp,"Unexpected characters at end of parameters: %.80s",text);
+ MM_LOG (tmp,PARSE);
+ }
+}
+
+/* Parse RFC 2822 address list
+ * Accepts: address list to write to
+ * input string
+ * default host name
+ */
+
+void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
+{
+ int c;
+ char *s,tmp[MAILTMPLEN];
+ ADDRESS *last = *lst;
+ ADDRESS *adr;
+ if (!string) return; /* no string */
+ rfc822_skipws (&string); /* skip leading WS */
+ if (!*string) return; /* empty string */
+ /* run to tail of list */
+ if (last) while (last->next) last = last->next;
+ while (string) { /* loop until string exhausted */
+ while (*string == ',') { /* RFC 822 allowed null addresses!! */
+ ++string; /* skip the comma */
+ rfc822_skipws (&string); /* and any leading WS */
+ }
+ if (!*string) string = NIL; /* punt if ran out of string */
+ /* got an address? */
+ else if (adr = rfc822_parse_address (lst,last,&string,host,0)) {
+ last = adr; /* new tail address */
+ if (string) { /* analyze what follows */
+ rfc822_skipws (&string);
+ switch (c = *(unsigned char *) string) {
+ case ',': /* comma? */
+ ++string; /* then another address follows */
+ break;
+ default:
+ s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
+ "Unexpected characters at end of address: %.80s";
+ sprintf (tmp,s,string);
+ MM_LOG (tmp,PARSE);
+ last = last->next = mail_newaddr ();
+ last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
+ last->host = cpystr (errhst);
+ /* falls through */
+ case '\0': /* null-specified address? */
+ string = NIL; /* punt remainder of parse */
+ break;
+ }
+ }
+ }
+ else if (string) { /* bad mailbox */
+ rfc822_skipws (&string); /* skip WS */
+ if (!*string) strcpy (tmp,"Missing address after comma");
+ else sprintf (tmp,"Invalid mailbox list: %.80s",string);
+ MM_LOG (tmp,PARSE);
+ string = NIL;
+ (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS");
+ adr->host = cpystr (errhst);
+ if (last) last = last->next = adr;
+ else *lst = last = adr;
+ break;
+ }
+ }
+}
+
+/* Parse RFC 2822 address
+ * Accepts: address list to write to
+ * tail of address list
+ * pointer to input string
+ * default host name
+ * group nesting depth
+ * Returns: new list tail
+ */
+
+ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,
+ char *defaulthost,unsigned long depth)
+{
+ ADDRESS *adr;
+ if (!*string) return NIL; /* no string */
+ rfc822_skipws (string); /* skip leading WS */
+ if (!**string) return NIL; /* empty string */
+ if (adr = rfc822_parse_group (lst,last,string,defaulthost,depth)) last = adr;
+ /* got an address? */
+ else if (adr = rfc822_parse_mailbox (string,defaulthost)) {
+ if (!*lst) *lst = adr; /* yes, first time through? */
+ else last->next = adr; /* no, append to the list */
+ /* set for subsequent linking */
+ for (last = adr; last->next; last = last->next);
+ }
+ else if (*string) return NIL;
+ return last;
+}
+
+/* Parse RFC 2822 group
+ * Accepts: address list to write to
+ * pointer to tail of address list
+ * pointer to input string
+ * default host name
+ * group nesting depth
+ */
+
+ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,
+ char *defaulthost,unsigned long depth)
+{
+ char tmp[MAILTMPLEN];
+ char *p,*s;
+ ADDRESS *adr;
+ if (depth > MAXGROUPDEPTH) { /* excessively deep recursion? */
+ MM_LOG ("Ignoring excessively deep group recursion",PARSE);
+ return NIL; /* probably abusive */
+ }
+ if (!*string) return NIL; /* no string */
+ rfc822_skipws (string); /* skip leading WS */
+ if (!**string || /* trailing whitespace or not group */
+ ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))
+ return NIL;
+ s = p; /* end of candidate phrase */
+ rfc822_skipws (&s); /* find delimiter */
+ if (*s != ':') return NIL; /* not really a group */
+ *p = '\0'; /* tie off group name */
+ p = ++s; /* continue after the delimiter */
+ rfc822_skipws (&p); /* skip subsequent whitespace */
+ /* write as address */
+ (adr = mail_newaddr ())->mailbox = rfc822_cpy (*string);
+ if (!*lst) *lst = adr; /* first time through? */
+ else last->next = adr; /* no, append to the list */
+ last = adr; /* set for subsequent linking */
+ *string = p; /* continue after this point */
+ while (*string && **string && (**string != ';')) {
+ if (adr = rfc822_parse_address (lst,last,string,defaulthost,depth+1)) {
+ last = adr; /* new tail address */
+ if (*string) { /* anything more? */
+ rfc822_skipws (string); /* skip whitespace */
+ switch (**string) { /* see what follows */
+ case ',': /* another address? */
+ ++*string; /* yes, skip past the comma */
+ case ';': /* end of group? */
+ case '\0': /* end of string */
+ break;
+ default:
+ sprintf (tmp,"Unexpected characters after address in group: %.80s",
+ *string);
+ MM_LOG (tmp,PARSE);
+ *string = NIL; /* cancel remainder of parse */
+ last = last->next = mail_newaddr ();
+ last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP");
+ last->host = cpystr (errhst);
+ }
+ }
+ }
+ else { /* bogon */
+ sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
+ MM_LOG (tmp,PARSE);
+ *string = NIL; /* cancel remainder of parse */
+ (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP");
+ adr->host = cpystr (errhst);
+ last = last->next = adr;
+ }
+ }
+ if (*string) { /* skip close delimiter */
+ if (**string == ';') ++*string;
+ rfc822_skipws (string);
+ }
+ /* append end of address mark to the list */
+ last->next = (adr = mail_newaddr ());
+ last = adr; /* set for subsequent linking */
+ return last; /* return the tail */
+}
+
+/* Parse RFC 2822 mailbox
+ * Accepts: pointer to string pointer
+ * default host
+ * Returns: address list
+ *
+ * Updates string pointer
+ */
+
+ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
+{
+ ADDRESS *adr = NIL;
+ char *s,*end;
+ parsephrase_t pp = (parsephrase_t) mail_parameters (NIL,GET_PARSEPHRASE,NIL);
+ if (!*string) return NIL; /* no string */
+ rfc822_skipws (string); /* flush leading whitespace */
+ if (!**string) return NIL; /* empty string */
+ if (*(s = *string) == '<') /* note start, handle case of phraseless RA */
+ adr = rfc822_parse_routeaddr (s,string,defaulthost);
+ /* otherwise, expect at least one word */
+ else if (end = rfc822_parse_phrase (s)) {
+ if ((adr = rfc822_parse_routeaddr (end,string,defaulthost))) {
+ /* phrase is a personal name */
+ if (adr->personal) fs_give ((void **) &adr->personal);
+ *end = '\0'; /* tie off phrase */
+ adr->personal = rfc822_cpy (s);
+ }
+ /* call external phraseparser if phrase only */
+ else if (pp && rfc822_phraseonly (end) &&
+ (adr = (*pp) (s,end,defaulthost))) {
+ *string = end; /* update parse pointer */
+ rfc822_skipws (string); /* skip WS in the normal way */
+ }
+ else adr = rfc822_parse_addrspec (s,string,defaulthost);
+ }
+ return adr; /* return the address */
+}
+
+
+/* Check if address is a phrase only
+ * Accepts: pointer to end of phrase
+ * Returns: T if phrase only, else NIL;
+ */
+
+long rfc822_phraseonly (char *end)
+{
+ while (*end == ' ') ++end; /* call rfc822_skipws() instead?? */
+ switch (*end) {
+ case '\0': case ',': case ';':
+ return LONGT; /* is a phrase only */
+ }
+ return NIL; /* something other than phase is here */
+}
+
+/* Parse RFC 2822 route-address
+ * Accepts: string pointer
+ * pointer to string pointer to update
+ * Returns: address
+ *
+ * Updates string pointer
+ */
+
+ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost)
+{
+ char tmp[MAILTMPLEN];
+ ADDRESS *adr;
+ char *s,*t,*adl;
+ size_t adllen,i;
+ if (!string) return NIL;
+ rfc822_skipws (&string); /* flush leading whitespace */
+ /* must start with open broket */
+ if (*string != '<') return NIL;
+ t = ++string; /* see if A-D-L there */
+ rfc822_skipws (&t); /* flush leading whitespace */
+ for (adl = NIL,adllen = 0; /* parse possible A-D-L */
+ (*t == '@') && (s = rfc822_parse_domain (t+1,&t));) {
+ i = strlen (s) + 2; /* @ plus domain plus delimiter or NUL */
+ if (adl) { /* have existing A-D-L? */
+ fs_resize ((void **) &adl,adllen + i);
+ sprintf (adl + adllen - 1,",@%s",s);
+ }
+ /* write initial A-D-L */
+ else sprintf (adl = (char *) fs_get (i),"@%s",s);
+ adllen += i; /* new A-D-L length */
+ fs_give ((void **) &s); /* don't need domain any more */
+ rfc822_skipws (&t); /* skip WS */
+ if (*t != ',') break; /* put if not comma */
+ t++; /* skip the comma */
+ rfc822_skipws (&t); /* skip WS */
+ }
+ if (adl) { /* got an A-D-L? */
+ if (*t != ':') { /* make sure syntax good */
+ sprintf (tmp,"Unterminated at-domain-list: %.80s%.80s",adl,t);
+ MM_LOG (tmp,PARSE);
+ }
+ else string = ++t; /* continue parse from this point */
+ }
+
+ /* parse address spec */
+ if (!(adr = rfc822_parse_addrspec (string,ret,defaulthost))) {
+ if (adl) fs_give ((void **) &adl);
+ return NIL;
+ }
+ if (adl) adr->adl = adl; /* have an A-D-L? */
+ if (*ret) if (**ret == '>') { /* make sure terminated OK */
+ ++*ret; /* skip past the broket */
+ rfc822_skipws (ret); /* flush trailing WS */
+ if (!**ret) *ret = NIL; /* wipe pointer if at end of string */
+ return adr; /* return the address */
+ }
+ sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox,
+ *adr->host == '@' ? "<null>" : adr->host);
+ MM_LOG (tmp,PARSE);
+ adr->next = mail_newaddr ();
+ adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR");
+ adr->next->host = cpystr (errhst);
+ return adr; /* return the address */
+}
+
+/* Parse RFC 2822 address-spec
+ * Accepts: string pointer
+ * pointer to string pointer to update
+ * default host
+ * Returns: address
+ *
+ * Updates string pointer
+ */
+
+ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost)
+{
+ ADDRESS *adr;
+ char c,*s,*t,*v,*end;
+ if (!string) return NIL; /* no string */
+ rfc822_skipws (&string); /* flush leading whitespace */
+ if (!*string) return NIL; /* empty string */
+ /* find end of mailbox */
+ if (!(t = rfc822_parse_word (string,wspecials))) return NIL;
+ adr = mail_newaddr (); /* create address block */
+ c = *t; /* remember delimiter */
+ *t = '\0'; /* tie off mailbox */
+ /* copy mailbox */
+ adr->mailbox = rfc822_cpy (string);
+ *t = c; /* restore delimiter */
+ end = t; /* remember end of mailbox */
+ rfc822_skipws (&t); /* skip whitespace */
+ while (*t == '.') { /* some cretin taking RFC 822 too seriously? */
+ string = ++t; /* skip past the dot and any WS */
+ rfc822_skipws (&string);
+ /* get next word of mailbox */
+ if (t = rfc822_parse_word (string,wspecials)) {
+ end = t; /* remember new end of mailbox */
+ c = *t; /* remember delimiter */
+ *t = '\0'; /* tie off word */
+ s = rfc822_cpy (string); /* copy successor part */
+ *t = c; /* restore delimiter */
+ /* build new mailbox */
+ sprintf (v = (char *) fs_get (strlen (adr->mailbox) + strlen (s) + 2),
+ "%s.%s",adr->mailbox,s);
+ fs_give ((void **) &adr->mailbox);
+ adr->mailbox = v; /* new host name */
+ rfc822_skipws (&t); /* skip WS after mailbox */
+ }
+ else { /* barf */
+ MM_LOG ("Invalid mailbox part after .",PARSE);
+ break;
+ }
+ }
+ t = end; /* remember delimiter in case no host */
+
+ rfc822_skipws (&end); /* sniff ahead at what follows */
+#if RFC733 /* RFC 733 used "at" instead of "@" */
+ if (((*end == 'a') || (*end == 'A')) &&
+ ((end[1] == 't') || (end[1] == 'T')) &&
+ ((end[2] == ' ') || (end[2] == '\t') || (end[2] == '\015') ||
+ (end[2] == '\012') || (end[2] == '(')))
+ *++end = '@';
+#endif
+ if (*end != '@') end = t; /* host name missing */
+ /* otherwise parse host name */
+ else if (!(adr->host = rfc822_parse_domain (++end,&end)))
+ adr->host = cpystr (errhst);
+ /* default host if missing */
+ if (!adr->host) adr->host = cpystr (defaulthost);
+ /* try person name in comments if missing */
+ if (end && !(adr->personal && *adr->personal)) {
+ while (*end == ' ') ++end; /* see if we can find a person name here */
+ if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s))
+ adr->personal = rfc822_cpy (s);
+ rfc822_skipws (&end); /* skip any other WS in the normal way */
+ }
+ /* set return to end pointer */
+ *ret = (end && *end) ? end : NIL;
+ return adr; /* return the address we got */
+}
+
+/* Parse RFC 2822 domain
+ * Accepts: string pointer
+ * pointer to return end of domain
+ * Returns: domain name or NIL if failure
+ */
+
+char *rfc822_parse_domain (char *string,char **end)
+{
+ char *ret = NIL;
+ char c,*s,*t,*v;
+ rfc822_skipws (&string); /* skip whitespace */
+ if (*string == '[') { /* domain literal? */
+ if (!(*end = rfc822_parse_word (string + 1,"]\\")))
+ MM_LOG ("Empty domain literal",PARSE);
+ else if (**end != ']') MM_LOG ("Unterminated domain literal",PARSE);
+ else {
+ size_t len = ++*end - string;
+ strncpy (ret = (char *) fs_get (len + 1),string,len);
+ ret[len] = '\0'; /* tie off literal */
+ }
+ }
+ /* search for end of host */
+ else if (t = rfc822_parse_word (string,wspecials)) {
+ c = *t; /* remember delimiter */
+ *t = '\0'; /* tie off host */
+ ret = rfc822_cpy (string); /* copy host */
+ *t = c; /* restore delimiter */
+ *end = t; /* remember end of domain */
+ rfc822_skipws (&t); /* skip WS after host */
+ while (*t == '.') { /* some cretin taking RFC 822 too seriously? */
+ string = ++t; /* skip past the dot and any WS */
+ rfc822_skipws (&string);
+ if (string = rfc822_parse_domain (string,&t)) {
+ *end = t; /* remember new end of domain */
+ c = *t; /* remember delimiter */
+ *t = '\0'; /* tie off host */
+ s = rfc822_cpy (string);/* copy successor part */
+ *t = c; /* restore delimiter */
+ /* build new domain */
+ sprintf (v = (char *) fs_get (strlen (ret) + strlen (s) + 2),
+ "%s.%s",ret,s);
+ fs_give ((void **) &ret);
+ ret = v; /* new host name */
+ rfc822_skipws (&t); /* skip WS after domain */
+ }
+ else { /* barf */
+ MM_LOG ("Invalid domain part after .",PARSE);
+ break;
+ }
+ }
+ }
+ else MM_LOG ("Missing or invalid host name after @",PARSE);
+ return ret;
+}
+
+/* Parse RFC 2822 phrase
+ * Accepts: string pointer
+ * Returns: pointer to end of phrase
+ */
+
+char *rfc822_parse_phrase (char *s)
+{
+ char *curpos;
+ if (!s) return NIL; /* no-op if no string */
+ /* find first word of phrase */
+ curpos = rfc822_parse_word (s,NIL);
+ if (!curpos) return NIL; /* no words means no phrase */
+ if (!*curpos) return curpos; /* check if string ends with word */
+ s = curpos; /* sniff past the end of this word and WS */
+ rfc822_skipws (&s); /* skip whitespace */
+ /* recurse to see if any more */
+ return (s = rfc822_parse_phrase (s)) ? s : curpos;
+}
+
+/* Parse RFC 2822 word
+ * Accepts: string pointer
+ * delimiter (or NIL for phrase word parsing)
+ * Returns: pointer to end of word
+ */
+
+char *rfc822_parse_word (char *s,const char *delimiters)
+{
+ char *st,*str;
+ if (!s) return NIL; /* no string */
+ rfc822_skipws (&s); /* flush leading whitespace */
+ if (!*s) return NIL; /* empty string */
+ str = s; /* hunt pointer for strpbrk */
+ while (T) { /* look for delimiter, return if none */
+ if (!(st = strpbrk (str,delimiters ? delimiters : wspecials)))
+ return str + strlen (str);
+ /* ESC in phrase */
+ if (!delimiters && (*st == I2C_ESC)) {
+ str = ++st; /* always skip past ESC */
+ switch (*st) { /* special hack for RFC 1468 (ISO-2022-JP) */
+ case I2C_MULTI: /* multi byte sequence */
+ switch (*++st) {
+ case I2CS_94x94_JIS_OLD:/* old JIS (1978) */
+ case I2CS_94x94_JIS_NEW:/* new JIS (1983) */
+ str = ++st; /* skip past the shift to JIS */
+ while (st = strchr (st,I2C_ESC))
+ if ((*++st == I2C_G0_94) && ((st[1] == I2CS_94_ASCII) ||
+ (st[1] == I2CS_94_JIS_ROMAN) ||
+ (st[1] == I2CS_94_JIS_BUGROM))) {
+ str = st += 2; /* skip past the shift back to ASCII */
+ break;
+ }
+ /* eats entire text if no shift back */
+ if (!st || !*st) return str + strlen (str);
+ }
+ break;
+ case I2C_G0_94: /* single byte sequence */
+ switch (st[1]) {
+ case I2CS_94_ASCII: /* shift to ASCII */
+ case I2CS_94_JIS_ROMAN: /* shift to JIS-Roman */
+ case I2CS_94_JIS_BUGROM:/* old buggy definition of JIS-Roman */
+ str = st + 2; /* skip past the shift */
+ break;
+ }
+ }
+ }
+
+ else switch (*st) { /* dispatch based on delimiter */
+ case '"': /* quoted string */
+ /* look for close quote */
+ while (*++st != '"') switch (*st) {
+ case '\0': /* unbalanced quoted string */
+ return NIL; /* sick sick sick */
+ case '\\': /* quoted character */
+ if (!*++st) return NIL; /* skip the next character */
+ default: /* ordinary character */
+ break; /* no special action */
+ }
+ str = ++st; /* continue parse */
+ break;
+ case '\\': /* quoted character */
+ /* This is wrong; a quoted-pair can not be part of a word. However,
+ * domain-literal is parsed as a word and quoted-pairs can be used
+ * *there*. Either way, it's pretty pathological.
+ */
+ if (st[1]) { /* not on NUL though... */
+ str = st + 2; /* skip quoted character and go on */
+ break;
+ }
+ default: /* found a word delimiter */
+ return (st == s) ? NIL : st;
+ }
+ }
+}
+
+/* Copy an RFC 2822 format string
+ * Accepts: string
+ * Returns: copy of string
+ */
+
+char *rfc822_cpy (char *src)
+{
+ /* copy and unquote */
+ return rfc822_quote (cpystr (src));
+}
+
+
+/* Unquote an RFC 2822 format string
+ * Accepts: string
+ * Returns: string
+ */
+
+char *rfc822_quote (char *src)
+{
+ char *ret = src;
+ if (strpbrk (src,"\\\"")) { /* any quoting in string? */
+ char *dst = ret;
+ while (*src) { /* copy string */
+ if (*src == '\"') src++; /* skip double quote entirely */
+ else {
+ if (*src == '\\') src++;/* skip over single quote, copy next always */
+ *dst++ = *src++; /* copy character */
+ }
+ }
+ *dst = '\0'; /* tie off string */
+ }
+ return ret; /* return our string */
+}
+
+
+/* Copy address list
+ * Accepts: address list
+ * Returns: address list
+ */
+
+ADDRESS *rfc822_cpy_adr (ADDRESS *adr)
+{
+ ADDRESS *dadr;
+ ADDRESS *ret = NIL;
+ ADDRESS *prev = NIL;
+ while (adr) { /* loop while there's still an MAP adr */
+ dadr = mail_newaddr (); /* instantiate a new address */
+ if (!ret) ret = dadr; /* note return */
+ if (prev) prev->next = dadr;/* tie on to the end of any previous */
+ dadr->personal = cpystr (adr->personal);
+ dadr->adl = cpystr (adr->adl);
+ dadr->mailbox = cpystr (adr->mailbox);
+ dadr->host = cpystr (adr->host);
+ prev = dadr; /* this is now the previous */
+ adr = adr->next; /* go to next address in list */
+ }
+ return (ret); /* return the MTP address list */
+}
+
+/* Skips RFC 2822 whitespace
+ * Accepts: pointer to string pointer
+ */
+
+void rfc822_skipws (char **s)
+{
+ while (T) switch (**s) {
+ case ' ': case '\t': case '\015': case '\012':
+ ++*s; /* skip all forms of LWSP */
+ break;
+ case '(': /* start of comment */
+ if (rfc822_skip_comment (s,(long) NIL)) break;
+ default:
+ return; /* end of whitespace */
+ }
+}
+
+
+/* Skips RFC 2822 comment
+ * Accepts: pointer to string pointer
+ * trim flag
+ * Returns: pointer to first non-blank character of comment
+ */
+
+char *rfc822_skip_comment (char **s,long trim)
+{
+ char *ret,tmp[MAILTMPLEN];
+ char *s1 = *s;
+ char *t = NIL;
+ /* skip past whitespace */
+ for (ret = ++s1; *ret == ' '; ret++);
+ do switch (*s1) { /* get character of comment */
+ case '(': /* nested comment? */
+ if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL;
+ t = --s1; /* last significant char at end of comment */
+ break;
+ case ')': /* end of comment? */
+ *s = ++s1; /* skip past end of comment */
+ if (trim) { /* if level 0, must trim */
+ if (t) t[1] = '\0'; /* tie off comment string */
+ else *ret = '\0'; /* empty comment */
+ }
+ return ret;
+ case '\\': /* quote next character? */
+ if (*++s1) { /* next character non-null? */
+ t = s1; /* update last significant character pointer */
+ break; /* all OK */
+ }
+ case '\0': /* end of string */
+ sprintf (tmp,"Unterminated comment: %.80s",*s);
+ MM_LOG (tmp,PARSE);
+ **s = '\0'; /* nuke duplicate messages in case reparse */
+ return NIL; /* this is wierd if it happens */
+ case ' ': /* whitespace isn't significant */
+ break;
+ default: /* random character */
+ t = s1; /* update last significant character pointer */
+ break;
+ } while (s1++);
+ return NIL; /* impossible, but pacify lint et al */
+}
+
+/* Buffered output routines */
+
+
+/* Output character to buffer
+ * Accepts: buffer
+ * character to write
+ * Returns: T if success, NIL if error
+ */
+
+static long rfc822_output_char (RFC822BUFFER *buf,int c)
+{
+ if ((buf->cur == buf->end) && !rfc822_output_flush (buf)) return NIL;
+ *buf->cur++ = c; /* add character, soutr buffer if full */
+ return (buf->cur == buf->end) ? rfc822_output_flush (buf) : LONGT;
+}
+
+
+/* Output data to buffer
+ * Accepts: buffer
+ * data to write
+ * size of data
+ * Returns: T if success, NIL if error
+ */
+
+static long rfc822_output_data (RFC822BUFFER *buf,char *string,long len)
+{
+ while (len) { /* until request satified */
+ long i;
+ if (i = min (len,buf->end - buf->cur)) {
+ memcpy (buf->cur,string,i);
+ buf->cur += i; /* blat data */
+ string += i;
+ len -= i;
+ }
+ /* soutr buffer now if full */
+ if ((len || (buf->cur == buf->end)) && !rfc822_output_flush (buf))
+ return NIL;
+ }
+ return LONGT;
+}
+
+/* Output string to buffer
+ * Accepts: buffer
+ * string to write
+ * Returns: T if success, NIL if error
+ */
+
+static long rfc822_output_string (RFC822BUFFER *buf,char *string)
+{
+ return rfc822_output_data (buf,string,strlen (string));
+}
+
+
+/* Flush buffer
+ * Accepts: buffer
+ * I/O routine
+ * stream for I/O routine
+ * Returns: T if success, NIL if error
+ */
+
+long rfc822_output_flush (RFC822BUFFER *buf)
+{
+ *buf->cur = '\0'; /* tie off buffer at this point */
+ return (*buf->f) (buf->s,buf->cur = buf->beg);
+}
+
+/* Message writing routines */
+
+
+/* Output RFC 822 message
+ * Accepts: temporary buffer as a SIZEDTEXT
+ * envelope
+ * body
+ * I/O routine
+ * stream for I/O routine
+ * non-zero if 8-bit output desired
+ * Returns: T if successful, NIL if failure
+ *
+ * This routine always uses standard specials for phrases and does not write
+ * bcc entries, since it is called from the SMTP and NNTP routines. If you
+ * need to do something different you need to arm an rfc822outfull_t and/or
+ * rfc822out_t function.
+ */
+
+long rfc822_output_full (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,long ok8)
+{
+ rfc822outfull_t r822of =
+ (rfc822outfull_t) mail_parameters (NIL,GET_RFC822OUTPUTFULL,NIL);
+ rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
+ /* call external RFC 2822 output generator */
+ if (r822of) return (*r822of) (buf,env,body,ok8);
+ else if (r822o) return (*r822o) (buf->cur,env,body,buf->f,buf->s,ok8);
+ /* encode body as necessary */
+ if (ok8) rfc822_encode_body_8bit (env,body);
+ else rfc822_encode_body_7bit (env,body);
+ /* output header and body */
+ return rfc822_output_header (buf,env,body,NIL,NIL) &&
+ rfc822_output_text (buf,body) && rfc822_output_flush (buf);
+}
+
+/* Output RFC 822 header
+ * Accepts: buffer
+ * envelope
+ * body
+ * non-standard specials to be used for phrases if non-NIL
+ * flags (non-zero to include bcc
+ * Returns: T if success, NIL if failure
+ */
+
+long rfc822_output_header (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,
+ const char *specials,long flags)
+{
+ long i = env->remail ? strlen (env->remail) : 0;
+ return /* write header */
+ (!i || /* snip extra CRLF from remail header */
+ rfc822_output_data (buf,env->remail,
+ ((i > 4) && (env->remail[i-4] == '\015')) ?
+ i - 2 : i)) &&
+ rfc822_output_header_line (buf,"Newsgroups",i,env->newsgroups) &&
+ rfc822_output_header_line (buf,"Date",i,env->date) &&
+ rfc822_output_address_line (buf,"From",i,env->from,specials) &&
+ rfc822_output_address_line (buf,"Sender",i,env->sender,specials) &&
+ rfc822_output_address_line (buf,"Reply-To",i,env->reply_to,specials) &&
+ rfc822_output_header_line (buf,"Subject",i,env->subject) &&
+ ((env->bcc && !(env->to || env->cc)) ?
+ rfc822_output_string (buf,"To: undisclosed recipients: ;\015\012") :
+ LONGT) &&
+ rfc822_output_address_line (buf,"To",i,env->to,specials) &&
+ rfc822_output_address_line (buf,"cc",i,env->cc,specials) &&
+ (flags ? rfc822_output_address_line (buf,"bcc",i,env->bcc,specials) : T) &&
+ rfc822_output_header_line (buf,"In-Reply-To",i,env->in_reply_to) &&
+ rfc822_output_header_line (buf,"Message-ID",i,env->message_id) &&
+ rfc822_output_header_line (buf,"Followup-to",i,env->followup_to) &&
+ rfc822_output_header_line (buf,"References",i,env->references) &&
+ (env->remail || !body ||
+ (rfc822_output_string (buf,"MIME-Version: 1.0\015\012") &&
+ rfc822_output_body_header (buf,body))) &&
+ /* write terminating blank line */
+ rfc822_output_string (buf,"\015\012");
+}
+
+/* Output RFC 2822 header text line
+ * Accepts: buffer
+ * pointer to header type
+ * non-NIL if resending
+ * pointer to text
+ * Returns: T if success, NIL if failure
+ */
+
+long rfc822_output_header_line (RFC822BUFFER *buf,char *type,long resent,
+ char *text)
+{
+ return !text ||
+ ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
+ rfc822_output_string (buf,type) && rfc822_output_string (buf,": ") &&
+ rfc822_output_string (buf,text) && rfc822_output_string (buf,"\015\012"));
+}
+
+
+/* Output RFC 2822 header address line
+ * Accepts: buffer
+ * pointer to header type
+ * non-NIL if resending
+ * address(s) to interpret
+ * non-standard specials to be used for phrases if non-NIL
+ * Returns: T if success, NIL if failure
+ */
+
+long rfc822_output_address_line (RFC822BUFFER *buf,char *type,long resent,
+ ADDRESS *adr,const char *specials)
+{
+ long pretty = strlen (type);
+ return !adr ||
+ ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
+ rfc822_output_data (buf,type,pretty) && rfc822_output_string (buf,": ") &&
+ rfc822_output_address_list (buf,adr,
+ resent ? pretty + sizeof (RESENTPREFIX) - 1 :
+ pretty,specials) &&
+ rfc822_output_string (buf,"\015\012"));
+}
+
+/* Output RFC 2822 address list
+ * Accepts: buffer
+ * pointer to address list
+ * non-zero if pretty-printing
+ * non-standard specials to be used for phrases if non-NIL
+ * Returns: T if success, NIL if failure
+ */
+
+long rfc822_output_address_list (RFC822BUFFER *buf,ADDRESS *adr,long pretty,
+ const char *specials)
+{
+ long n;
+ /* default to rspecials */
+ if (!specials) specials = rspecials;
+ for (n = 0; adr; adr = adr->next) {
+ char *base = buf->cur;
+ if (adr->host) { /* ordinary address? */
+ if (!(pretty && n)) { /* suppress if pretty and in group */
+ if ( /* use phrase <route-addr> if phrase */
+#if RFC822
+ adr->adl || /* or A-D-L */
+#endif
+ (adr->personal && *adr->personal)) {
+ if (!((adr->personal ? rfc822_output_cat (buf,adr->personal,
+ rspecials) : LONGT) &&
+ rfc822_output_string (buf," <") &&
+ rfc822_output_address (buf,adr) &&
+ rfc822_output_string (buf,">"))) return NIL;
+ }
+ else if (!rfc822_output_address (buf,adr)) return NIL;
+ if (adr->next && adr->next->mailbox &&
+ !rfc822_output_string (buf,", ")) return NIL;
+ }
+ }
+ else if (adr->mailbox) { /* start of group? */
+ /* yes, write group */
+ if (!(rfc822_output_cat (buf,adr->mailbox,rspecials) &&
+ rfc822_output_string (buf,": "))) return NIL;
+ ++n; /* in a group now */
+ }
+ else if (n) { /* must be end of group (but be paranoid) */
+ if (!rfc822_output_char (buf,';') ||
+ ((!--n && adr->next && adr->next->mailbox) &&
+ !rfc822_output_string (buf,", "))) return NIL;
+ }
+ if (pretty && adr->next && /* pretty printing? */
+ ((pretty += ((buf->cur > base) ? buf->cur - base :
+ (buf->end - base) + (buf->cur - buf->beg))) >= 78)) {
+ if (!(rfc822_output_string (buf,"\015\012") &&
+ rfc822_output_string (buf,RFC822CONT))) return NIL;
+ base = buf->cur; /* update base for pretty printing */
+ pretty = sizeof (RFC822CONT) - 1;
+ }
+ }
+ return LONGT;
+}
+
+/* Write RFC 2822 route-address to string
+ * Accepts: buffer
+ * pointer to single address
+ * Returns: T if success, NIL if failure
+ */
+
+long rfc822_output_address (RFC822BUFFER *buf,ADDRESS *adr)
+{
+ return !adr || !adr->host ||
+ (
+#if RFC822 /* old code with A-D-L support */
+ (!adr->adl || (rfc822_output_string (buf,adr->adl) &&
+ rfc822_output_char (buf,':'))) &&
+#endif
+ rfc822_output_cat (buf,adr->mailbox,NIL) &&
+ ((*adr->host == '@') || /* unless null host (HIGHLY discouraged!) */
+ (rfc822_output_char (buf,'@') &&
+ rfc822_output_cat (buf,adr->host,NIL))));
+}
+
+
+/* Output RFC 2822 string with concatenation
+ * Accepts: buffer
+ * string to concatenate
+ * list of special characters or NIL for dot-atom format
+ * Returns: T if success, NIL if failure
+ */
+
+long rfc822_output_cat (RFC822BUFFER *buf,char *src,const char *specials)
+{
+ char *s;
+ if (!*src || /* empty string or any specials present? */
+ (specials ? (T && strpbrk (src,specials)) :
+ (strpbrk (src,wspecials) || (*src == '.') || strstr (src,"..") ||
+ (src[strlen (src) - 1] == '.')))) {
+ /* yes, write as quoted string*/
+ if (!rfc822_output_char (buf,'"')) return NIL;
+ /* embedded quote characters? */
+ for (; s = strpbrk (src,"\\\""); src = s + 1) {
+ /* yes, insert quoting */
+ if (!(rfc822_output_data (buf,src,s-src) &&
+ rfc822_output_char (buf,'\\') &&
+ rfc822_output_char (buf,*s))) return NIL;
+ }
+ /* return string and trailing quote*/
+ return rfc822_output_string (buf,src) && rfc822_output_char (buf,'"');
+ }
+ /* easy case */
+ return rfc822_output_string (buf,src);
+}
+
+/* Output MIME parameter list
+ * Accepts: buffer
+ * parameter list
+ * Returns: T if success, NIL if failure
+ */
+
+long rfc822_output_parameter (RFC822BUFFER *buf,PARAMETER *param)
+{
+ while (param) {
+ if (rfc822_output_string (buf,"; ") &&
+ rfc822_output_string (buf,param->attribute) &&
+ rfc822_output_char (buf,'=') &&
+ rfc822_output_cat (buf,param->value,tspecials)) param = param->next;
+ else return NIL;
+ }
+ return LONGT;
+}
+
+
+/* Output RFC 2822 stringlist
+ * Accepts: buffer
+ * stringlist
+ * Returns: T if success, NIL if failure
+ */
+
+long rfc822_output_stringlist (RFC822BUFFER *buf,STRINGLIST *stl)
+{
+ while (stl)
+ if (!rfc822_output_cat (buf,(char *) stl->text.data,tspecials) ||
+ ((stl = stl->next) && !rfc822_output_string (buf,", ")))
+ return NIL;
+ return LONGT;
+}
+
+/* Output body content header
+ * Accepts: buffer
+ * body to interpret
+ * Returns: T if success, NIL if failure
+ */
+
+long rfc822_output_body_header (RFC822BUFFER *buf,BODY *body)
+{
+ return /* type and subtype*/
+ rfc822_output_string (buf,"Content-Type: ") &&
+ rfc822_output_string (buf,body_types[body->type]) &&
+ rfc822_output_char (buf,'/') &&
+ rfc822_output_string (buf,body->subtype ? body->subtype :
+ rfc822_default_subtype (body->type)) &&
+ /* parameters (w/ US-ASCII default */
+ (body->parameter ? rfc822_output_parameter (buf,body->parameter) :
+ ((body->type != TYPETEXT) ||
+ (rfc822_output_string (buf,"; CHARSET=") &&
+ rfc822_output_string (buf,(body->encoding == ENC7BIT) ?
+ "US-ASCII" : "X-UNKNOWN")))) &&
+ (!body->encoding || /* note: 7BIT never output as encoding! */
+ (rfc822_output_string (buf,"\015\012Content-Transfer-Encoding: ") &&
+ rfc822_output_string (buf,body_encodings[body->encoding]))) &&
+ (!body->id || /* identification */
+ (rfc822_output_string (buf,"\015\012Content-ID: ") &&
+ rfc822_output_string (buf,body->id))) &&
+ (!body->description || /* description */
+ (rfc822_output_string (buf,"\015\012Content-Description: ") &&
+ rfc822_output_string (buf,body->description))) &&
+ (!body->md5 || /* MD5 checksum */
+ (rfc822_output_string (buf,"\015\012Content-MD5: ") &&
+ rfc822_output_string (buf,body->md5))) &&
+ (!body->language || /* language */
+ (rfc822_output_string (buf,"\015\012Content-Language: ") &&
+ rfc822_output_stringlist (buf,body->language))) &&
+ (!body->location || /* location */
+ (rfc822_output_string (buf,"\015\012Content-Location: ") &&
+ rfc822_output_string (buf,body->location))) &&
+ (!body->disposition.type || /* disposition */
+ (rfc822_output_string (buf,"\015\012Content-Disposition: ") &&
+ rfc822_output_string (buf,body->disposition.type) &&
+ rfc822_output_parameter (buf,body->disposition.parameter))) &&
+ rfc822_output_string (buf,"\015\012");
+}
+
+/* Encode a body for 7BIT transmittal
+ * Accepts: envelope
+ * body
+ */
+
+void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body)
+{
+ void *f;
+ PART *part;
+ PARAMETER **param;
+ if (body) switch (body->type) {
+ case TYPEMULTIPART: /* multi-part */
+ for (param = &body->parameter;
+ *param && strcmp ((*param)->attribute,"BOUNDARY");
+ param = &(*param)->next);
+ if (!*param) { /* cookie not set up yet? */
+ char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
+ sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
+ (unsigned long) random (),(unsigned long) time (0),
+ (unsigned long) getpid ());
+ (*param) = mail_newbody_parameter ();
+ (*param)->attribute = cpystr ("BOUNDARY");
+ (*param)->value = cpystr (tmp);
+ }
+ part = body->nested.part; /* encode body parts */
+ do rfc822_encode_body_7bit (env,&part->body);
+ while (part = part->next); /* until done */
+ break;
+ case TYPEMESSAGE: /* encapsulated message */
+ switch (body->encoding) {
+ case ENC7BIT:
+ break;
+ case ENC8BIT:
+ MM_LOG ("8-bit included message in 7-bit message body",PARSE);
+ break;
+ case ENCBINARY:
+ MM_LOG ("Binary included message in 7-bit message body",PARSE);
+ break;
+ default:
+ fatal ("Invalid rfc822_encode_body_7bit message encoding");
+ }
+ break; /* can't change encoding */
+ default: /* all else has some encoding */
+ switch (body->encoding) {
+ case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
+ /* remember old 8-bit contents */
+ f = (void *) body->contents.text.data;
+ body->contents.text.data =
+ rfc822_8bit (body->contents.text.data,
+ body->contents.text.size,&body->contents.text.size);
+ body->encoding = ENCQUOTEDPRINTABLE;
+ fs_give (&f); /* flush old binary contents */
+ break;
+ case ENCBINARY: /* encode binary into BASE64 */
+ /* remember old binary contents */
+ f = (void *) body->contents.text.data;
+ body->contents.text.data =
+ rfc822_binary ((void *) body->contents.text.data,
+ body->contents.text.size,&body->contents.text.size);
+ body->encoding = ENCBASE64;
+ fs_give (&f); /* flush old binary contents */
+ default: /* otherwise OK */
+ break;
+ }
+ break;
+ }
+}
+
+/* Encode a body for 8BIT transmittal
+ * Accepts: envelope
+ * body
+ */
+
+void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body)
+{
+ void *f;
+ PART *part;
+ PARAMETER **param;
+ if (body) switch (body->type) {
+ case TYPEMULTIPART: /* multi-part */
+ for (param = &body->parameter;
+ *param && strcmp ((*param)->attribute,"BOUNDARY");
+ param = &(*param)->next);
+ if (!*param) { /* cookie not set up yet? */
+ char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
+ sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
+ (unsigned long) random (),(unsigned long) time (0),
+ (unsigned long) getpid ());
+ (*param) = mail_newbody_parameter ();
+ (*param)->attribute = cpystr ("BOUNDARY");
+ (*param)->value = cpystr (tmp);
+ }
+ part = body->nested.part; /* encode body parts */
+ do rfc822_encode_body_8bit (env,&part->body);
+ while (part = part->next); /* until done */
+ break;
+ case TYPEMESSAGE: /* encapsulated message */
+ switch (body->encoding) {
+ case ENC7BIT:
+ case ENC8BIT:
+ break;
+ case ENCBINARY:
+ MM_LOG ("Binary included message in 8-bit message body",PARSE);
+ break;
+ default:
+ fatal ("Invalid rfc822_encode_body_7bit message encoding");
+ }
+ break; /* can't change encoding */
+ default: /* other type, encode binary into BASE64 */
+ if (body->encoding == ENCBINARY) {
+ /* remember old binary contents */
+ f = (void *) body->contents.text.data;
+ body->contents.text.data =
+ rfc822_binary ((void *) body->contents.text.data,
+ body->contents.text.size,&body->contents.text.size);
+ body->encoding = ENCBASE64;
+ fs_give (&f); /* flush old binary contents */
+ }
+ break;
+ }
+}
+
+/* Output RFC 822 text
+ * Accepts: buffer
+ * body
+ * Returns: T if successful, NIL if failure
+ */
+
+long rfc822_output_text (RFC822BUFFER *buf,BODY *body)
+{
+ /* MULTIPART gets special handling */
+ if (body->type == TYPEMULTIPART) {
+ char *cookie,tmp[MAILTMPLEN];
+ PARAMETER *param;
+ PART *part;
+ /* find cookie */
+ for (param = body->parameter; param && strcmp (param->attribute,"BOUNDARY");
+ param = param->next);
+ if (param) cookie = param->value;
+ else { /* make cookie not in BASE64 or QUOTEPRINT*/
+ sprintf (cookie = tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
+ (unsigned long) random (),(unsigned long) time (0),
+ (unsigned long) getpid ());
+ (param = mail_newbody_parameter ())->attribute = cpystr ("BOUNDARY");
+ param->value = cpystr (tmp);
+ param->next = body->parameter;
+ body->parameter = param;
+ }
+ /* output each part */
+ for (part = body->nested.part; part; part = part->next)
+ if (!(rfc822_output_string (buf,"--") &&
+ rfc822_output_string (buf,cookie) &&
+ rfc822_output_string (buf,"\015\012") &&
+ rfc822_output_body_header (buf,&part->body) &&
+ rfc822_output_string (buf,"\015\012") &&
+ rfc822_output_text (buf,&part->body))) return NIL;
+ /* output trailing cookie */
+ return rfc822_output_string (buf,"--") &&
+ rfc822_output_string (buf,cookie) &&
+ rfc822_output_string (buf,"--\015\012");
+ }
+ /* output segment and trailing CRLF */
+ return (!body->contents.text.data ||
+ rfc822_output_string (buf,(char *) body->contents.text.data)) &&
+ rfc822_output_string (buf,"\015\012");
+}
+
+/* Body contents encoding/decoding routines */
+
+
+/* Convert BASE64 contents to binary
+ * Accepts: source
+ * length of source
+ * pointer to return destination length
+ * Returns: destination as binary or NIL if error
+ */
+
+#define WSP 0176 /* NUL, TAB, LF, FF, CR, SPC */
+#define JNK 0177
+#define PAD 0100
+
+void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
+{
+ char c,*s,tmp[MAILTMPLEN];
+ void *ret = fs_get ((size_t) ((*len = 4 + ((srcl * 3) / 4))) + 1);
+ char *d = (char *) ret;
+ int e;
+ static char decode[256] = {
+ WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,WSP,WSP,JNK,WSP,WSP,JNK,JNK,
+ JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
+ WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,076,JNK,JNK,JNK,077,
+ 064,065,066,067,070,071,072,073,074,075,JNK,JNK,JNK,PAD,JNK,JNK,
+ JNK,000,001,002,003,004,005,006,007,010,011,012,013,014,015,016,
+ 017,020,021,022,023,024,025,026,027,030,031,JNK,JNK,JNK,JNK,JNK,
+ JNK,032,033,034,035,036,037,040,041,042,043,044,045,046,047,050,
+ 051,052,053,054,055,056,057,060,061,062,063,JNK,JNK,JNK,JNK,JNK,
+ JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
+ JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
+ JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
+ JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
+ JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
+ JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
+ JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
+ JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK
+ };
+ /* initialize block */
+ memset (ret,0,((size_t) *len) + 1);
+ *len = 0; /* in case we return an error */
+
+ /* simple-minded decode */
+ for (e = 0; srcl--; ) switch (c = decode[*src++]) {
+ default: /* valid BASE64 data character */
+ switch (e++) { /* install based on quantum position */
+ case 0:
+ *d = c << 2; /* byte 1: high 6 bits */
+ break;
+ case 1:
+ *d++ |= c >> 4; /* byte 1: low 2 bits */
+ *d = c << 4; /* byte 2: high 4 bits */
+ break;
+ case 2:
+ *d++ |= c >> 2; /* byte 2: low 4 bits */
+ *d = c << 6; /* byte 3: high 2 bits */
+ break;
+ case 3:
+ *d++ |= c; /* byte 3: low 6 bits */
+ e = 0; /* reinitialize mechanism */
+ break;
+ }
+ break;
+ case WSP: /* whitespace */
+ break;
+ case PAD: /* padding */
+ switch (e++) { /* check quantum position */
+ case 3: /* one = is good enough in quantum 3 */
+ /* make sure no data characters in remainder */
+ for (; srcl; --srcl) switch (decode[*src++]) {
+ /* ignore space, junk and extraneous padding */
+ case WSP: case JNK: case PAD:
+ break;
+ default: /* valid BASE64 data character */
+ /* This indicates bad MIME. One way that it can be caused is if
+ a single-section message was BASE64 encoded and then something
+ (e.g. a mailing list processor) appended text. The problem is
+ that in 1 out of 3 cases, there is no padding and hence no way
+ to detect the end of the data. Consequently, prudent software
+ will always encapsulate a BASE64 segment inside a MULTIPART.
+ */
+ sprintf (tmp,"Possible data truncation in rfc822_base64(): %.80s",
+ (char *) src - 1);
+ if (s = strpbrk (tmp,"\015\012")) *s = NIL;
+ mm_log (tmp,PARSE);
+ srcl = 1; /* don't issue any more messages */
+ break;
+ }
+ break;
+ case 2: /* expect a second = in quantum 2 */
+ if (srcl && (*src == '=')) break;
+ default: /* impossible quantum position */
+ fs_give (&ret);
+ return NIL;
+ }
+ break;
+ case JNK: /* junk character */
+ fs_give (&ret);
+ return NIL;
+ }
+ *len = d - (char *) ret; /* calculate data length */
+ *d = '\0'; /* NUL terminate just in case */
+ return ret; /* return the string */
+}
+
+/* Convert binary contents to BASE64
+ * Accepts: source
+ * length of source
+ * pointer to return destination length
+ * Returns: destination as BASE64
+ */
+
+unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
+{
+ unsigned char *ret,*d;
+ unsigned char *s = (unsigned char *) src;
+ char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ unsigned long i = ((srcl + 2) / 3) * 4;
+ *len = i += 2 * ((i / 60) + 1);
+ d = ret = (unsigned char *) fs_get ((size_t) ++i);
+ /* process tuplets */
+ for (i = 0; srcl >= 3; s += 3, srcl -= 3) {
+ *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
+ /* byte 2: low 2 bits (1), high 4 bits (2) */
+ *d++ = v[((s[0] << 4) + (s[1] >> 4)) & 0x3f];
+ /* byte 3: low 4 bits (2), high 2 bits (3) */
+ *d++ = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f];
+ *d++ = v[s[2] & 0x3f]; /* byte 4: low 6 bits (3) */
+ if ((++i) == 15) { /* output 60 characters? */
+ i = 0; /* restart line break count, insert CRLF */
+ *d++ = '\015'; *d++ = '\012';
+ }
+ }
+ if (srcl) {
+ *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
+ /* byte 2: low 2 bits (1), high 4 bits (2) */
+ *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
+ /* byte 3: low 4 bits (2), high 2 bits (3) */
+ *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
+ /* byte 4: low 6 bits (3) */
+ *d++ = srcl ? v[s[2] & 0x3f] : '=';
+ if (srcl) srcl--; /* count third character if processed */
+ if ((++i) == 15) { /* output 60 characters? */
+ i = 0; /* restart line break count, insert CRLF */
+ *d++ = '\015'; *d++ = '\012';
+ }
+ }
+ *d++ = '\015'; *d++ = '\012'; /* insert final CRLF */
+ *d = '\0'; /* tie off string */
+ if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
+ return ret; /* return the resulting string */
+}
+
+/* Convert QUOTED-PRINTABLE contents to 8BIT
+ * Accepts: source
+ * length of source
+ * pointer to return destination length
+ * Returns: destination as 8-bit text or NIL if error
+ */
+
+unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,
+ unsigned long *len)
+{
+ char tmp[MAILTMPLEN];
+ unsigned int bogon = NIL;
+ unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
+ unsigned char *d = ret;
+ unsigned char *t = d;
+ unsigned char *s = src;
+ unsigned char c,e;
+ *len = 0; /* in case we return an error */
+ /* until run out of characters */
+ while (((unsigned long) (s - src)) < srcl) {
+ switch (c = *s++) { /* what type of character is it? */
+ case '=': /* quoting character */
+ if (((unsigned long) (s - src)) < srcl) switch (c = *s++) {
+ case '\0': /* end of data */
+ s--; /* back up pointer */
+ break;
+ case '\015': /* non-significant line break */
+ if ((((unsigned long) (s - src)) < srcl) && (*s == '\012')) s++;
+ case '\012': /* bare LF */
+ t = d; /* accept any leading spaces */
+ break;
+ default: /* two hex digits then */
+ if (!(isxdigit (c) && (((unsigned long) (s - src)) < srcl) &&
+ (e = *s++) && isxdigit (e))) {
+ /* This indicates bad MIME. One way that it can be caused is if
+ a single-section message was QUOTED-PRINTABLE encoded and then
+ something (e.g. a mailing list processor) appended text. The
+ problem is that there is no way to determine where the encoded
+ data ended and the appended crud began. Consequently, prudent
+ software will always encapsulate a QUOTED-PRINTABLE segment
+ inside a MULTIPART.
+ */
+ if (!bogon++) { /* only do this once */
+ sprintf (tmp,"Invalid quoted-printable sequence: =%.80s",
+ (char *) s - 1);
+ mm_log (tmp,PARSE);
+ }
+ *d++ = '='; /* treat = as ordinary character */
+ *d++ = c; /* and the character following */
+ t = d; /* note point of non-space */
+ break;
+ }
+ *d++ = hex2byte (c,e); /* merge the two hex digits */
+ t = d; /* note point of non-space */
+ break;
+ }
+ break;
+ case ' ': /* space, possibly bogus */
+ *d++ = c; /* stash the space but don't update s */
+ break;
+ case '\015': /* end of line */
+ case '\012': /* bare LF */
+ d = t; /* slide back to last non-space, drop in */
+ default:
+ *d++ = c; /* stash the character */
+ t = d; /* note point of non-space */
+ }
+ }
+ *d = '\0'; /* tie off results */
+ *len = d - ret; /* calculate length */
+ return ret; /* return the string */
+}
+
+/* Convert 8BIT contents to QUOTED-PRINTABLE
+ * Accepts: source
+ * length of source
+ * pointer to return destination length
+ * Returns: destination as quoted-printable text
+ */
+
+#define MAXL (size_t) 75 /* 76th position only used by continuation = */
+
+unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
+ unsigned long *len)
+{
+ unsigned long lp = 0;
+ unsigned char *ret = (unsigned char *)
+ fs_get ((size_t) (3*srcl + 3*(((3*srcl)/MAXL) + 1)));
+ unsigned char *d = ret;
+ char *hex = "0123456789ABCDEF";
+ unsigned char c;
+ while (srcl--) { /* for each character */
+ /* true line break? */
+ if (((c = *src++) == '\015') && (*src == '\012') && srcl) {
+ *d++ = '\015'; *d++ = *src++; srcl--;
+ lp = 0; /* reset line count */
+ }
+ else { /* not a line break */
+ /* quoting required? */
+ if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
+ ((c == ' ') && (*src == '\015'))) {
+ if ((lp += 3) > MAXL) { /* yes, would line overflow? */
+ *d++ = '='; *d++ = '\015'; *d++ = '\012';
+ lp = 3; /* set line count */
+ }
+ *d++ = '='; /* quote character */
+ *d++ = hex[c >> 4]; /* high order 4 bits */
+ *d++ = hex[c & 0xf]; /* low order 4 bits */
+ }
+ else { /* ordinary character */
+ if ((++lp) > MAXL) { /* would line overflow? */
+ *d++ = '='; *d++ = '\015'; *d++ = '\012';
+ lp = 1; /* set line count */
+ }
+ *d++ = c; /* ordinary character */
+ }
+ }
+ }
+ *d = '\0'; /* tie off destination */
+ *len = d - ret; /* calculate true size */
+ /* try to give some space back */
+ fs_resize ((void **) &ret,(size_t) *len + 1);
+ return ret;
+}
+
+/* Legacy Routines */
+
+/*
+ * WARNING: These routines are for compatibility with old software only.
+ *
+ * Their use in new software is to be avoided.
+ *
+ * These interfaces do not provide satisfactory buffer checking. In
+ * versions of c-client prior to imap-2005, they did not provide any
+ * buffer checking at all.
+ *
+ * As a half-hearted attempt, these new compatability functions for the
+ * legacy interfaces limit what they write to size SENDBUFLEN and will
+ * fatal() if more than that is written. However, that isn't good enough
+ * since several of these functions *append* to the buffer, and return an
+ * updated pointer. Consequently, there is no way of knowing what the
+ * actual available space is in the buffer, yet the function will still
+ * write up to SENDBUFLEN bytes even if there is much less space actually
+ * available. The result is a buffer overflow.
+ *
+ * You won't get a buffer overflow if you never attempt to append using
+ * these interfaces, but you can get the fatal() if it tries to write
+ * more than SENDBUFLEN bytes.
+ *
+ * To avoid this problem, use the corresponding rfc822_output_???()
+ * functions instead, e.g., rfc822_output_address() instead of
+ * rfc822_address().
+ *
+ */
+
+/* Flush routine, only called if overflow
+ * Accepts: stream
+ * string to output
+ * Returns: never
+ */
+
+static long rfc822_legacy_soutr (void *stream,char *string)
+{
+ fatal ("rfc822.c legacy routine buffer overflow");
+ return NIL;
+}
+
+/* Legacy write RFC 2822 header from message structure
+ * Accepts: scratch buffer to write into
+ * message envelope
+ * message body
+ */
+
+void rfc822_header (char *header,ENVELOPE *env,BODY *body)
+{
+ RFC822BUFFER buf;
+ /* write at start of buffer */
+ buf.end = (buf.beg = buf.cur = header) + SENDBUFLEN - 1;
+ buf.f = rfc822_legacy_soutr;
+ buf.s = NIL;
+ rfc822_output_header (&buf,env,body,NIL,NIL);
+ *buf.cur = '\0'; /* tie off buffer */
+}
+
+
+/* Legacy write RFC 2822 text from header line
+ * Accepts: pointer to destination string pointer
+ * pointer to header type
+ * message to interpret
+ * pointer to text
+ */
+
+void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text)
+{
+ RFC822BUFFER buf;
+ /* append to buffer */
+ buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
+ buf.f = rfc822_legacy_soutr;
+ buf.s = NIL;
+ rfc822_output_header_line (&buf,type,env->remail ? LONGT : NIL,text);
+ *(*header = buf.cur) = '\0'; /* tie off buffer */
+}
+
+/* Legacy write RFC 2822 address from header line
+ * Accepts: pointer to destination string pointer
+ * pointer to header type
+ * message to interpret
+ * address to interpret
+ */
+
+void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr)
+{
+ RFC822BUFFER buf;
+ /* append to buffer */
+ buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
+ buf.f = rfc822_legacy_soutr;
+ buf.s = NIL;
+ rfc822_output_address_line (&buf,type,env->remail ? LONGT : NIL,adr,NIL);
+ *(*header = buf.cur) = '\0'; /* tie off buffer */
+}
+
+
+/* Legacy write RFC 2822 address list
+ * Accepts: pointer to destination string
+ * address to interpret
+ * header base if pretty-printing
+ * Returns: end of destination string
+ */
+
+char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base)
+{
+ RFC822BUFFER buf;
+ /* append to buffer */
+ buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
+ buf.f = rfc822_legacy_soutr;
+ buf.s = NIL;
+ rfc822_output_address_list (&buf,adr,base ? dest - base : 0,NIL);
+ *buf.cur = '\0'; /* tie off buffer */
+ return buf.cur;
+}
+
+
+/* Legacy write RFC 2822 route-address to string
+ * Accepts: pointer to destination string
+ * address to interpret
+ */
+
+void rfc822_address (char *dest,ADDRESS *adr)
+{
+ RFC822BUFFER buf;
+ /* append to buffer */
+ buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
+ buf.f = rfc822_legacy_soutr;
+ buf.s = NIL;
+ rfc822_output_address (&buf,adr);
+ *buf.cur = '\0'; /* tie off buffer */
+}
+
+/* Concatenate RFC 2822 string
+ * Accepts: pointer to destination string
+ * pointer to string to concatenate
+ * list of special characters or NIL for dot-atom format
+ */
+
+void rfc822_cat (char *dest,char *src,const char *specials)
+{
+ RFC822BUFFER buf;
+ /* append to buffer */
+ buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
+ buf.f = rfc822_legacy_soutr;
+ buf.s = NIL;
+ rfc822_output_cat (&buf,src,specials);
+ *buf.cur = '\0'; /* tie off buffer */
+}
+
+
+/* Legacy write body content header
+ * Accepts: pointer to destination string pointer
+ * pointer to body to interpret
+ */
+
+void rfc822_write_body_header (char **dst,BODY *body)
+{
+ RFC822BUFFER buf;
+ /* append to buffer */
+ buf.end = (buf.beg = buf.cur = *dst + strlen (*dst)) + SENDBUFLEN - 1;
+ buf.f = rfc822_legacy_soutr;
+ buf.s = NIL;
+ rfc822_output_body_header (&buf,body);
+ *(*dst = buf.cur) = '\0'; /* tie off buffer */
+}
+
+/* Legacy output RFC 822 message
+ * Accepts: temporary buffer
+ * envelope
+ * body
+ * I/O routine
+ * stream for I/O routine
+ * non-zero if 8-bit output desired
+ * Returns: T if successful, NIL if failure
+ */
+
+long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,void *s,
+ long ok8bit)
+{
+ long ret;
+ rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
+ /* call external RFC 2822 output generator */
+ if (r822o) ret = (*r822o) (t,env,body,f,s,ok8bit);
+ else { /* output generator not armed */
+ RFC822BUFFER buf; /* use our own buffer rather than trust */
+ char tmp[SENDBUFLEN+1]; /* client to give us a big enough one */
+ buf.f = f;
+ buf.s = s;
+ buf.end = (buf.beg = buf.cur = t) + SENDBUFLEN - 1;
+ tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */
+ ret = rfc822_output_full (&buf,env,body,ok8bit);
+ }
+ return ret;
+}
+
+
+/* Legacy output RFC 822 body
+ * Accepts: body
+ * I/O routine
+ * stream for I/O routine
+ * Returns: T if successful, NIL if failure
+ */
+
+long rfc822_output_body (BODY *body,soutr_t f,void *s)
+{
+ RFC822BUFFER buf;
+ char tmp[SENDBUFLEN+1];
+ buf.f = f;
+ buf.s = s;
+ buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN;
+ tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */
+ return rfc822_output_text (&buf,body) && rfc822_output_flush (&buf);
+}