diff options
author | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
---|---|---|
committer | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
commit | 094ca96844842928810f14844413109fc6cdd890 (patch) | |
tree | e60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /imap/src/c-client/rfc822.c | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'imap/src/c-client/rfc822.c')
-rw-r--r-- | imap/src/c-client/rfc822.c | 2373 |
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); +} |