rfc822.c
上传用户:ycwykj01
上传日期:2007-01-04
资源大小:1819k
文件大小:61k
源码类别:

网络编程

开发平台:

Unix_Linux

  1. /*
  2.  * Program: RFC-822 routines (originally from SMTP)
  3.  *
  4.  * Author: Mark Crispin
  5.  * Networks and Distributed Computing
  6.  * Computing & Communications
  7.  * University of Washington
  8.  * Administration Building, AG-44
  9.  * Seattle, WA  98195
  10.  * Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date: 27 July 1988
  13.  * Last Edited: 13 July 1999
  14.  *
  15.  * Sponsorship: The original version of this work was developed in the
  16.  * Symbolic Systems Resources Group of the Knowledge Systems
  17.  * Laboratory at Stanford University in 1987-88, and was funded
  18.  * by the Biomedical Research Technology Program of the National
  19.  * Institutes of Health under grant number RR-00785.
  20.  *
  21.  * Original version Copyright 1988 by The Leland Stanford Junior University
  22.  * Copyright 1999 by the University of Washington
  23.  *
  24.  *  Permission to use, copy, modify, and distribute this software and its
  25.  * documentation for any purpose and without fee is hereby granted, provided
  26.  * that the above copyright notices appear in all copies and that both the
  27.  * above copyright notices and this permission notice appear in supporting
  28.  * documentation, and that the name of the University of Washington or The
  29.  * Leland Stanford Junior University not be used in advertising or publicity
  30.  * pertaining to distribution of the software without specific, written prior
  31.  * permission.  This software is made available "as is", and
  32.  * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
  33.  * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
  34.  * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  35.  * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
  36.  * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
  37.  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  38.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  39.  * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
  40.  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  41.  *
  42.  */
  43. #include <ctype.h>
  44. #include <stdio.h>
  45. #include <time.h>
  46. #include "mail.h"
  47. #include "osdep.h"
  48. #include "rfc822.h"
  49. #include "misc.h"
  50. /* RFC-822 static data */
  51. char *errhst = ERRHOST; /* syntax error host string */
  52. /* Body formats constant strings, must match definitions in mail.h */
  53. char *body_types[TYPEMAX+1] = {
  54.   "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
  55.   "MODEL", "X-UNKNOWN"
  56. };
  57. char *body_encodings[ENCMAX+1] = {
  58.   "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
  59. };
  60. /* Token delimiting special characters */
  61. /* full RFC-822 specials */
  62. const char *rspecials =  "()<>@,;:\"[].";
  63. /* body token specials */
  64. const char *tspecials = " ()<>@,;:\"[]./?=";
  65. /* Once upon a time, CSnet had a mailer which assigned special semantics to
  66.  * dot in e-mail addresses.  For the sake of that mailer, dot was added to
  67.  * the RFC-822 definition of `specials', even though it had numerous bad side
  68.  * effects:
  69.  *   1) It broke mailbox names on systems which had dots in user names, such as
  70.  * Multics and TOPS-20.  RFC-822's syntax rules require that `Admin . MRC'
  71.  * be considered equivalent to `Admin.MRC'.  Fortunately, few people ever
  72.  * tried this in practice.
  73.  *   2) It required that all personal names with an initial be quoted, a widely
  74.  * detested user interface misfeature.
  75.  *   3) It made the parsing of host names be non-atomic for no good reason.
  76.  * To work around these problems, the following alternate specials lists are
  77.  * defined.  hspecials and wspecials are used in lieu of rspecials, and
  78.  * ptspecials are used in lieu of tspecials.  These alternate specials lists
  79.  * make the parser work a lot better in the real world.  It ain't politically
  80.  * correct, but it lets the users get their job done!
  81.  */
  82. /* parse-word specials */
  83. const char *wspecials = " ()<>@,;:\"[]";
  84. /* parse-token specials for parsing */
  85. const char *ptspecials = " ()<>@,;:\"[]/?=";
  86. /* RFC822 writing routines */
  87. /* Write RFC822 header from message structure
  88.  * Accepts: scratch buffer to write into
  89.  *     message envelope
  90.  *     message body
  91.  */
  92. void rfc822_header (char *header,ENVELOPE *env,BODY *body)
  93. {
  94.   if (env->remail) { /* if remailing */
  95.     long i = strlen (env->remail);
  96.     strcpy (header,env->remail);/* start with remail header */
  97. /* flush extra blank line */
  98.     if (i > 4 && header[i-4] == '15') header[i-2] = '';
  99.   }
  100.   else *header = ''; /* else initialize header to null */
  101.   rfc822_header_line (&header,"Newsgroups",env,env->newsgroups);
  102.   rfc822_header_line (&header,"Date",env,env->date);
  103.   rfc822_address_line (&header,"From",env,env->from);
  104.   rfc822_address_line (&header,"Sender",env,env->sender);
  105.   rfc822_address_line (&header,"Reply-To",env,env->reply_to);
  106.   rfc822_header_line (&header,"Subject",env,env->subject);
  107.   if (env->bcc && !(env->to || env->cc))
  108.     strcat (header,"To: undisclosed recipients: ;1512");
  109.   rfc822_address_line (&header,"To",env,env->to);
  110.   rfc822_address_line (&header,"cc",env,env->cc);
  111. /* bcc's are never written...
  112.  * rfc822_address_line (&header,"bcc",env,env->bcc);
  113.  */
  114.   rfc822_header_line (&header,"In-Reply-To",env,env->in_reply_to);
  115.   rfc822_header_line (&header,"Message-ID",env,env->message_id);
  116.   rfc822_header_line (&header,"Followup-to",env,env->followup_to);
  117.   rfc822_header_line (&header,"References",env,env->references);
  118.   if (body && !env->remail) { /* not if remail or no body structure */
  119.     strcat (header,"MIME-Version: 1.01512");
  120.     rfc822_write_body_header (&header,body);
  121.   }
  122.   strcat (header,"1512"); /* write terminating blank line */
  123. }
  124. /* Write RFC822 address from header line
  125.  * Accepts: pointer to destination string pointer
  126.  *     pointer to header type
  127.  *     message to interpret
  128.  *     address to interpret
  129.  */
  130. void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr)
  131. {
  132.   char *s = (*header += strlen (*header));
  133.   if (adr) { /* do nothing if no addresses */
  134.     if (env && env->remail) strcat (s,"ReSent-");
  135.     strcat (s,type); /* write header name */
  136.     strcat (s,": ");
  137.     s = rfc822_write_address_full (s + strlen (s),adr,*header);
  138. /* tie off header line */
  139.     *s++ = '15'; *s++ = '12'; *s = '';
  140.     *header = s; /* set return value */
  141.   }
  142. }
  143. /* Write RFC822 text from header line
  144.  * Accepts: pointer to destination string pointer
  145.  *     pointer to header type
  146.  *     message to interpret
  147.  *     pointer to text
  148.  */
  149. void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text)
  150. {
  151.   if (text) sprintf ((*header += strlen (*header)),"%s%s: %s1512",
  152.      env->remail ? "ReSent-" : "",type,text);
  153. }
  154. /* Write RFC822 address list
  155.  * Accepts: pointer to destination string
  156.  *     address to interpret
  157.  *     header base if pretty-printing
  158.  * Returns: end of destination string
  159.  */
  160. /* RFC822 continuation, must start with CRLF */
  161. #define RFC822CONT "1512    "
  162. char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base)
  163. {
  164.   long i,n;
  165.   for (n = 0; adr; adr = adr->next) {
  166.     if (adr->host) { /* ordinary address? */
  167.       if (!(base && n)) { /* only write if exact form or not in group */
  168. /* simple case? */
  169. if (!(adr->personal || adr->adl)) rfc822_address (dest,adr);
  170. else { /* no, must use phrase <route-addr> form */
  171.   if (adr->personal) rfc822_cat (dest,adr->personal,rspecials);
  172.   strcat (dest," <"); /* write address delimiter */
  173.   rfc822_address (dest,adr);
  174.   strcat (dest,">"); /* closing delimiter */
  175. }
  176. if (adr->next && adr->next->mailbox) strcat (dest,", ");
  177.       }
  178.     }
  179.     else if (adr->mailbox) { /* start of group? */
  180. /* yes, write group name */
  181.       rfc822_cat (dest,adr->mailbox,rspecials);
  182.       strcat (dest,": "); /* write group identifier */
  183.       n++; /* in a group */
  184.     }
  185.     else if (n) { /* must be end of group (but be paranoid) */
  186.       strcat (dest,";");
  187. /* no longer in that group */
  188.       if (!--n && adr->next && adr->next->mailbox) strcat (dest,", ");
  189.     }
  190.     i = strlen (dest); /* length of what we just wrote */
  191. /* write continuation if doesn't fit */
  192.     if (base && (dest > (base + 4)) && ((dest + i) > (base + 78))) {
  193.       memmove (dest + sizeof (RFC822CONT) - 1,dest,i + 1);
  194.       memcpy (dest,RFC822CONT,sizeof (RFC822CONT) - 1);
  195.       base = dest + 2; /* new base */
  196.       dest += i + sizeof (RFC822CONT) - 1;
  197.     }
  198.     else dest += i; /* new end of string */
  199.   }
  200.   return dest; /* return end of string */
  201. }
  202. /* Write RFC822 route-address to string
  203.  * Accepts: pointer to destination string
  204.  *     address to interpret
  205.  */
  206. void rfc822_address (char *dest,ADDRESS *adr)
  207. {
  208.   if (adr && adr->host) { /* no-op if no address */
  209.     if (adr->adl) { /* have an A-D-L? */
  210.       strcat (dest,adr->adl);
  211.       strcat (dest,":");
  212.     }
  213. /* write mailbox name */
  214.     rfc822_cat (dest,adr->mailbox,wspecials);
  215.     if (*adr->host != '@') { /* unless null host (HIGHLY discouraged!) */
  216.       strcat (dest,"@"); /* host delimiter */
  217.       strcat (dest,adr->host); /* write host name */
  218.     }
  219.   }
  220. }
  221. /* Concatenate RFC822 string
  222.  * Accepts: pointer to destination string
  223.  *     pointer to string to concatenate
  224.  *     list of special characters
  225.  */
  226. void rfc822_cat (char *dest,char *src,const char *specials)
  227. {
  228.   char *s;
  229.   if (strpbrk (src,specials)) { /* any specials present? */
  230.     strcat (dest,"""); /* opening quote */
  231. /* truly bizarre characters in there? */
  232.     while (s = strpbrk (src,"\"")) {
  233.       strncat (dest,src,s-src); /* yes, output leader */
  234.       strcat (dest,"\"); /* quoting */
  235.       strncat (dest,s,1); /* output the bizarre character */
  236.       src = ++s; /* continue after the bizarre character */
  237.     }
  238.     if (*src) strcat (dest,src);/* output non-bizarre string */
  239.     strcat (dest,"""); /* closing quote */
  240.   }
  241.   else strcat (dest,src); /* otherwise it's the easy case */
  242. }
  243. /* Write body content header
  244.  * Accepts: pointer to destination string pointer
  245.  *     pointer to body to interpret
  246.  */
  247. void rfc822_write_body_header (char **dst,BODY *body)
  248. {
  249.   char *s;
  250.   STRINGLIST *stl;
  251.   PARAMETER *param = body->parameter;
  252.   sprintf (*dst += strlen (*dst),"Content-Type: %s",body_types[body->type]);
  253.   s = body->subtype ? body->subtype : rfc822_default_subtype (body->type);
  254.   sprintf (*dst += strlen (*dst),"/%s",s);
  255.   if (param) do {
  256.     sprintf (*dst += strlen (*dst),"; %s=",param->attribute);
  257.     rfc822_cat (*dst,param->value,tspecials);
  258.   } while (param = param->next);
  259.   else if (body->type == TYPETEXT) strcat (*dst,"; CHARSET=US-ASCII");
  260.   strcpy (*dst += strlen (*dst),"1512");
  261.   if (body->encoding) /* note: encoding 7BIT never output! */
  262.     sprintf (*dst += strlen (*dst),"Content-Transfer-Encoding: %s1512",
  263.      body_encodings[body->encoding]);
  264.   if (body->id) sprintf (*dst += strlen (*dst),"Content-ID: %s1512",
  265.  body->id);
  266.   if (body->description)
  267.     sprintf (*dst += strlen (*dst),"Content-Description: %s1512",
  268.      body->description);
  269.   if (body->md5)
  270.     sprintf (*dst += strlen (*dst),"Content-MD5: %s1512",body->md5);
  271.   if (stl = body->language) {
  272.     strcpy (*dst += strlen (*dst),"Content-Language: ");
  273.     do {
  274.       rfc822_cat (*dst,(char *) stl->text.data,tspecials);
  275.       if (stl = stl->next) strcat (*dst += strlen (*dst),", ");
  276.     }
  277.     while (stl);
  278.     strcpy (*dst += strlen (*dst),"1512");
  279.   }
  280.   if (body->disposition.type) {
  281.     sprintf (*dst += strlen (*dst),"Content-Disposition: %s",
  282.      body->disposition.type);
  283.     if (param = body->disposition.parameter) do {
  284.       sprintf (*dst += strlen (*dst),"; %s=",param->attribute);
  285.       rfc822_cat (*dst,param->value,tspecials);
  286.     } while (param = param->next);
  287.     strcpy (*dst += strlen (*dst),"1512");
  288.   }
  289. }
  290. /* Subtype defaulting (a no-no, but regretably necessary...)
  291.  * Accepts: type code
  292.  * Returns: default subtype name
  293.  */
  294. char *rfc822_default_subtype (unsigned short type)
  295. {
  296.   switch (type) {
  297.   case TYPETEXT: /* default is TEXT/PLAIN */
  298.     return "PLAIN";
  299.   case TYPEMULTIPART: /* default is MULTIPART/MIXED */
  300.     return "MIXED";
  301.   case TYPEMESSAGE: /* default is MESSAGE/RFC822 */
  302.     return "RFC822";
  303.   case TYPEAPPLICATION: /* default is APPLICATION/OCTET-STREAM */
  304.     return "OCTET-STREAM";
  305.   case TYPEAUDIO: /* default is AUDIO/BASIC */
  306.     return "BASIC";
  307.   default: /* others have no default subtype */
  308.     return "UNKNOWN";
  309.   }
  310. }
  311. /* RFC822 parsing routines */
  312. /* Parse an RFC822 message
  313.  * Accepts: pointer to return envelope
  314.  *     pointer to return body
  315.  *     pointer to header
  316.  *     header byte count
  317.  *     pointer to body stringstruct
  318.  *     pointer to local host name
  319.  *     recursion depth
  320.  *     source driver flags
  321.  */
  322. void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i,
  323.     STRING *bs,char *host,unsigned long depth,
  324.     unsigned long flags)
  325. {
  326.   char c,*t,*d;
  327.   char *tmp = (char *) fs_get ((size_t) i + 100);
  328.   ENVELOPE *env = (*en = mail_newenvelope ());
  329.   BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
  330.   long MIMEp = -1; /* flag that MIME semantics are in effect */
  331.   long PathP = NIL; /* flag that a Path: was seen */
  332.   parseline_t pl = (parseline_t) mail_parameters (NIL,GET_PARSELINE,NIL);
  333.   while (i && *s != 'n') { /* until end of header */
  334.     t = tmp; /* initialize buffer pointer */
  335.     c = ' '; /* and previous character */
  336.     while (i && c) { /* collect text until logical end of line */
  337.       switch (c = *s++) { /* slurp a character */
  338.       case '15': /* return, possible end of logical line */
  339. if (*s == 'n') break; /* ignore if LF follows */
  340.       case '12': /* LF, possible end of logical line */
  341. /* tie off unless next line starts with WS */
  342. if (*s != ' ' && *s != 't') *t++ = c = '';
  343. break;
  344.       case 't': /* tab */
  345. *t++ = ' '; /* coerce to space */
  346. break;
  347.       default: /* all other characters */
  348. *t++ = c; /* insert the character into the line */
  349. break;
  350.       }
  351.       if (!--i) *t++ = ''; /* see if end of header */
  352.     }
  353. /* find header item type */
  354.     if (t = d = strchr (tmp,':')) {
  355.       *d++ = ''; /* tie off header item, point at its data */
  356.       while (*d == ' ') d++; /* flush whitespace */
  357.       while ((tmp < t--) && (*t == ' ')) *t = '';
  358.       ucase (tmp); /* coerce to uppercase */
  359. /* external callback */
  360.       if (pl) (*pl) (env,tmp,d,host);
  361.       switch (*tmp) { /* dispatch based on first character */
  362.       case '>': /* possible >From: */
  363. if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
  364. break;
  365.       case 'B': /* possible bcc: */
  366. if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
  367. break;
  368.       case 'C': /* possible cc: or Content-<mumble>*/
  369. if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
  370. else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') &&
  371.  (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') &&
  372.  (tmp[7] == '-') && body)
  373.   switch (MIMEp) {
  374.   case -1: /* unknown if MIME or not */
  375.     if (MIMEp = search ((unsigned char *) s-1,i,
  376. (unsigned char *)"12MIME-Version",
  377. (long) 13))
  378.   case T: /* definitely MIME */
  379.       rfc822_parse_content_header (body,tmp+8,d);
  380.   }
  381. break;
  382.       case 'D': /* possible Date: */
  383. if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
  384. break;
  385.       case 'F': /* possible From: */
  386. if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
  387. else if (!strcmp (tmp+1,"OLLOWUP-TO")) {
  388.   t = env->followup_to = (char *) fs_get (1 + strlen (d));
  389.   while (c = *d++) if (c != ' ') *t++ = c;
  390.   *t++ = '';
  391. }
  392. break;
  393.       case 'I': /* possible In-Reply-To: */
  394. if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO"))
  395.   env->in_reply_to = cpystr (d);
  396. break;
  397.       case 'M': /* possible Message-ID: or MIME-Version: */
  398. if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID"))
  399.   env->message_id = cpystr (d);
  400. else if (!strcmp (tmp+1,"IME-VERSION")) {
  401. /* tie off at end of phrase */
  402.   if (t = rfc822_parse_phrase (d)) *t = '';
  403.   rfc822_skipws (&d); /* skip whitespace */
  404. /* known version? */
  405.   if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
  406.     mm_log ("Warning: message has unknown MIME version",PARSE);
  407.   MIMEp = T; /* note that we are MIME */
  408. }
  409. break;
  410.       case 'N': /* possible Newsgroups: */
  411. if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) {
  412.   t = env->newsgroups = (char *) fs_get (1 + strlen (d));
  413.   while (c = *d++) if (c != ' ') *t++ = c;
  414.   *t++ = '';
  415. }
  416. break;
  417.       case 'P': /* possible Path: */
  418. if (!strcmp (tmp+1,"ATH")) PathP = T;
  419. break;
  420.       case 'R': /* possible Reply-To: */
  421. if (!strcmp (tmp+1,"EPLY-TO"))
  422.   rfc822_parse_adrlist (&env->reply_to,d,host);
  423. else if (!env->references && !strcmp (tmp+1,"EFERENCES"))
  424.   env->references = cpystr (d);
  425. break;
  426.       case 'S': /* possible Subject: or Sender: */
  427. if (!env->subject && !strcmp (tmp+1,"UBJECT"))
  428.   env->subject = cpystr (d);
  429. else if (!strcmp (tmp+1,"ENDER"))
  430.   rfc822_parse_adrlist (&env->sender,d,host);
  431. break;
  432.       case 'T': /* possible To: */
  433. if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
  434. break;
  435.       default:
  436. break;
  437.       }
  438.     }
  439.   }
  440.   /* We require a Path: header and/or a Message-ID belonging to a known
  441.    * winning mail program, in order to believe Newsgroups:.  This is because
  442.    * of the unfortunate existance of certain cretins who believe that it
  443.    * is reasonable to transmit messages via SMTP with a "Newsgroups" header
  444.    * that were not actually posted to any of the named newsgroups.
  445.    * The authors of other high-quality email/news software are encouraged to
  446.    * use similar methods to indentify messages as coming from their software,
  447.    * and having done so, to tell us so they too can be blessed in this list.
  448.    *
  449.    * May 1998 update: as was predicted back in March 1995 when this kludge was
  450.    * first added, mail/news unifying programs are now the norm.  However, the
  451.    * encouragement in the previous paragraph didn't take, and there's no good
  452.    * way to determine bogons accurately.  Consequently, we no longer remove the
  453.    * newsgroup information from the envelope on the bogon test; we just light
  454.    * a bogon bit and let the main program worry about what to do.
  455.    */
  456.   if (env->newsgroups && !PathP && env->message_id &&
  457.       strncmp (env->message_id,"<Pine.",6) &&
  458.       strncmp (env->message_id,"<MS-C.",6) &&
  459.       strncmp (env->message_id,"<MailManager.",13) &&
  460.       strncmp (env->message_id,"<EasyMail.",11) &&
  461.       strncmp (env->message_id,"<ML-",4)) env->ngbogus = T;
  462.   fs_give ((void **) &tmp); /* done with scratch buffer */
  463. /* default Sender: and Reply-To: to From: */
  464.   if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
  465.   if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
  466. /* now parse the body */
  467.   if (body) rfc822_parse_content (body,bs,host,depth,flags);
  468. }
  469. /* Parse a message body content
  470.  * Accepts: pointer to body structure
  471.  *     body string
  472.  *     pointer to local host name
  473.  *     recursion depth
  474.  *     source driver flags
  475.  */
  476. void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth,
  477.    unsigned long flags)
  478. {
  479.   char c,c1,*s,*s1;
  480.   int f;
  481.   unsigned long i,j,k,m;
  482.   PARAMETER *param;
  483.   PART *part = NIL;
  484.   if (depth > MAXMIMEDEPTH) { /* excessively deep recursion? */
  485.     body->type = TYPETEXT; /* yes, probably a malicious MIMEgram */
  486.     mm_log ("Ignoring excessively deep MIME recursion",PARSE);
  487.   }
  488.   if (!body->subtype) /* default subtype if still unknown */
  489.     body->subtype = cpystr (rfc822_default_subtype (body->type));
  490. /* note offset and sizes */
  491.   body->contents.offset = GETPOS (bs);
  492. /* note internal body size in all cases */
  493.   body->size.bytes = body->contents.text.size = i = SIZE (bs);
  494.   if (!(flags & DR_CRLF)) body->size.bytes = strcrlflen (bs);
  495.   switch (body->type) { /* see if anything else special to do */
  496.   case TYPETEXT: /* text content */
  497.     if (!body->parameter) { /* default parameters */
  498.       body->parameter = mail_newbody_parameter ();
  499.       body->parameter->attribute = cpystr ("CHARSET");
  500.       body->parameter->value = cpystr ("US-ASCII");
  501.     }
  502. /* count number of lines */
  503.     while (i--) if ((SNX (bs)) == 'n') body->size.lines++;
  504.     break;
  505.   case TYPEMESSAGE: /* encapsulated message */
  506.     body->nested.msg = mail_newmsg ();
  507. /* encapsulated RFC-822 message? */
  508.     if (!strcmp (body->subtype,"RFC822")) {
  509.       switch (body->encoding) { /* make sure valid encoding */
  510.       case ENC7BIT: /* these are valid nested encodings */
  511.       case ENC8BIT:
  512.       case ENCBINARY:
  513. break;
  514.       default:
  515. mm_log ("Ignoring nested encoding of message contents",PARSE);
  516.       }
  517. /* hunt for blank line */
  518.       for (c = '12',j = 0; (i > j) && ((c != '12') || (CHR(bs) != '12'));
  519.    j++) if ((c1 = SNX (bs)) != '15') c = c1;
  520.       if (i > j) { /* unless no more text */
  521. c1 = SNX (bs); /* body starts here */
  522. j++; /* advance count */
  523.       }
  524. /* note body text offset and header size */
  525.       body->nested.msg->header.text.size = j;
  526.       body->nested.msg->text.text.size = body->contents.text.size - j;
  527.       body->nested.msg->text.offset = GETPOS (bs);
  528.       body->nested.msg->full.offset = body->nested.msg->header.offset =
  529. body->contents.offset;
  530.       body->nested.msg->full.text.size = body->contents.text.size;
  531. /* copy header string */
  532.       SETPOS (bs,body->contents.offset);
  533.       s = (char *) fs_get ((size_t) j + 1);
  534.       for (s1 = s,k = j; k--; *s1++ = SNX (bs));
  535.       s[j] = ''; /* tie off string (not really necessary) */
  536. /* now parse the body */
  537.       rfc822_parse_msg_full (&body->nested.msg->env,&body->nested.msg->body,s,
  538.      j,bs,h,depth+1,flags);
  539.       fs_give ((void **) &s); /* free header string */
  540. /* restore position */
  541.       SETPOS (bs,body->contents.offset);
  542.     }
  543. /* count number of lines */
  544.     while (i--) if (SNX (bs) == 'n') body->size.lines++;
  545.     break;
  546.   case TYPEMULTIPART: /* multiple parts */
  547.     switch (body->encoding) { /* make sure valid encoding */
  548.     case ENC7BIT: /* these are valid nested encodings */
  549.     case ENC8BIT:
  550.     case ENCBINARY:
  551.       break;
  552.     default:
  553.       mm_log ("Ignoring nested encoding of multipart contents",PARSE);
  554.     }
  555. /* remember if digest */
  556.     f = !strcmp (body->subtype,"DIGEST");
  557. /* find cookie */
  558.     for (s1 = NIL,param = body->parameter; param && !s1; param = param->next)
  559.       if (!strcmp (param->attribute,"BOUNDARY")) s1 = param->value;
  560.     if (!(s1 && *s1)) s1 = "-"; /* yucky default */
  561.     j = strlen (s1); /* length of cookie and header */
  562.     c = '12'; /* initially at beginning of line */
  563.     while (i > j) { /* examine data */
  564.       if (m = GETPOS (bs)) m--; /* get position in front of character */
  565.       switch (c) { /* examine each line */
  566.       case '15': /* handle CRLF form */
  567. if (CHR (bs) == '12'){/* following LF? */
  568.   c = SNX (bs); i--; /* yes, slurp it */
  569. }
  570.       case '12': /* at start of a line, start with -- ? */
  571. if (i-- && ((c = SNX (bs)) == '-') && i-- && ((c = SNX (bs)) == '-')) {
  572. /* see if cookie matches */
  573.   for (k = j,s = s1; i-- && *s++ == (c = SNX (bs)) && --k;);
  574.   if (k) break; /* strings didn't match if non-zero */
  575. /* look at what follows cookie */
  576.   if (i && i--) switch (c = SNX (bs)) {
  577.   case '-': /* at end if two dashes */
  578.     if ((i && i--) && ((c = SNX (bs)) == '-') &&
  579. ((i && i--) ? (((c = SNX (bs)) == '15') || (c=='12')):T)) {
  580. /* if have a final part calculate its size */
  581.       if (part) part->body.mime.text.size =
  582. (m > part->body.mime.offset) ? (m - part->body.mime.offset) :0;
  583.       part = NIL; i = 1; /* terminate scan */
  584.     }
  585.     break;
  586.   case '15': /* handle CRLF form */
  587.     if (i && CHR (bs) == '12') {
  588.       c = SNX (bs); i--;/* yes, slurp it */
  589.     }
  590.   case '12': /* new line */
  591.     if (part) { /* calculate size of previous */
  592.       part->body.mime.text.size =
  593. (m > part->body.mime.offset) ? (m-part->body.mime.offset) : 0;
  594. /* instantiate next */
  595.       part = part->next = mail_newbody_part ();
  596.     } /* otherwise start new list */
  597.     else part = body->nested.part = mail_newbody_part ();
  598. /* digest has a different default */
  599.     if (f) part->body.type = TYPEMESSAGE;
  600. /* note offset from main body */
  601.     part->body.mime.offset = GETPOS (bs);
  602.     break;
  603.   default: /* whatever it was it wasn't valid */
  604.     break;
  605.   }
  606. }
  607. break;
  608.       default: /* not at a line */
  609. c = SNX (bs); i--; /* get next character */
  610. break;
  611.       }
  612.     }
  613. /* calculate size of any final part */
  614.     if (part) part->body.mime.text.size = i +
  615.       ((GETPOS(bs) > part->body.mime.offset) ?
  616.        (GETPOS(bs) - part->body.mime.offset) : 0);
  617. /* make a scratch buffer */
  618.     s1 = (char *) fs_get ((size_t) (k = MAILTMPLEN));
  619. /* parse non-empty body parts */
  620.     for (part = body->nested.part; part; part = part->next) {
  621.       if (i = part->body.mime.text.size) {
  622. /* move to that part of the body */
  623. SETPOS (bs,part->body.mime.offset);
  624. /* until end of header */
  625. while (i && ((c = CHR (bs)) != '15') && (c != '12')) {
  626. /* collect text until logical end of line */
  627.   for (j = 0,c = ' '; c; ) {
  628. /* make sure buffer big enough */
  629.     if (j > (k - 10)) fs_resize ((void *) &s1,k += MAILTMPLEN);
  630.     switch (c1 = SNX (bs)) {
  631.     case '15': /* return */
  632.       if (i && (CHR (bs) == '12')) {
  633. c1 = SNX (bs); /* eat any LF following */
  634. i--;
  635.       }
  636.     case '12': /* newline, possible end of logical line */
  637. /* tie off unless continuation */
  638.       if (!i || ((CHR (bs) != ' ') && (CHR (bs) != 't')))
  639. s1[j] = c = '';
  640.       break;
  641.     case 't': /* tab */
  642.     case ' ': /* insert whitespace if not already there */
  643.       if (c != ' ') s1[j++] = c = ' ';
  644.       break;
  645.     default: /* all other characters */
  646.       s1[j++] = c = c1; /* insert the character into the line */
  647.       break;
  648.     }
  649. /* end of data ties off the header */
  650.     if (!--i) s1[j++] = c = '';
  651.   }
  652. /* find header item type */
  653.   if (((s1[0] == 'C') || (s1[0] == 'c')) &&
  654.       ((s1[1] == 'O') || (s1[1] == 'o')) &&
  655.       ((s1[2] == 'N') || (s1[2] == 'n')) &&
  656.       ((s1[3] == 'T') || (s1[3] == 't')) &&
  657.       ((s1[4] == 'E') || (s1[4] == 'e')) &&
  658.       ((s1[5] == 'N') || (s1[5] == 'n')) &&
  659.       ((s1[6] == 'T') || (s1[6] == 't')) &&
  660.       (s1[7] == '-') && (s = strchr (s1+8,':'))) {
  661. /* tie off and flush whitespace */
  662.     for (*s++ = ''; *s == ' '; s++);
  663. /* parse the header */
  664.     rfc822_parse_content_header (&part->body,ucase (s1+8),s);
  665.   }
  666. }
  667. /* skip header trailing (CR)LF */
  668. if (i && (CHR (bs) =='15')) {i--; c1 = SNX (bs);}
  669. if (i && (CHR (bs) =='12')) {i--; c1 = SNX (bs);}
  670. j = bs->size; /* save upper level size */
  671. /* set offset for next level, fake size to i */
  672. bs->size = GETPOS (bs) + i;
  673. part->body.mime.text.size -= i;
  674. /* now parse it */
  675. rfc822_parse_content (&part->body,bs,h,depth+1,flags);
  676. bs->size = j; /* restore current level size */
  677.       }
  678.       else part->body.subtype = /* default subtype if necessary */
  679. cpystr (rfc822_default_subtype (part->body.type));
  680.     }
  681.     fs_give ((void **) &s1); /* finished with scratch buffer */
  682.     break;
  683.   default: /* nothing special to do in any other case */
  684.     break;
  685.   }
  686. }
  687. /* Parse RFC822 body content header
  688.  * Accepts: body to write to
  689.  *     possible content name
  690.  *     remainder of header
  691.  */
  692. void rfc822_parse_content_header (BODY *body,char *name,char *s)
  693. {
  694.   char c,*t;
  695.   long i;
  696.   STRINGLIST *stl;
  697. /* flush whitespace */
  698.   if (t = strchr (name,' ')) *t = '';
  699.   switch (*name) { /* see what kind of content */
  700.   case 'I': /* possible Content-ID */
  701.     if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
  702.     break;
  703.   case 'D': /* possible Content-Description */
  704.     if (!(strcmp (name+1,"ESCRIPTION") || body->description))
  705.       body->description = cpystr (s);
  706.     if (!(strcmp (name+1,"ISPOSITION") || body->disposition.type)) {
  707. /* get type word */
  708.       if (!(name = rfc822_parse_word (s,ptspecials))) break;
  709.       c = *name; /* remember delimiter */
  710.       *name = ''; /* tie off type */
  711.       body->disposition.type = ucase (cpystr (s));
  712.       *name = c; /* restore delimiter */
  713.       rfc822_skipws (&name); /* skip whitespace */
  714.       rfc822_parse_parameter (&body->disposition.parameter,name);
  715.     }
  716.     break;
  717.   case 'L': /* possible Content-Language */
  718.     if (!(strcmp (name+1,"ANGUAGE") || body->language)) {
  719.       stl = NIL; /* process languages */
  720.       while (s && (name = rfc822_parse_word (s,ptspecials))) {
  721. c = *name; /* save delimiter */
  722. *name = ''; /* tie off subtype */
  723. if (stl) stl = stl->next = mail_newstringlist ();
  724. else stl = body->language = mail_newstringlist ();
  725. stl->text.data = (unsigned char *) ucase (cpystr (s));
  726. stl->text.size = strlen ((char *) stl->text.data);
  727. *name = c; /* restore delimiter */
  728. rfc822_skipws (&name); /* skip whitespace */
  729. if (*name == ',') { /* any more languages? */
  730.   s = ++name; /* advance to it them */
  731.   rfc822_skipws (&s);
  732. }
  733. else s = NIL; /* bogus or end of list */
  734.       }
  735.     }
  736.     break;
  737.   case 'M': /* possible Content-MD5 */
  738.     if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
  739.     break;
  740.   case 'T': /* possible Content-Type/Transfer-Encoding */
  741.     if (!(strcmp (name+1,"YPE") || body->subtype || body->parameter)) {
  742. /* get type word */
  743.       if (!(name = rfc822_parse_word (s,ptspecials))) break;
  744.       c = *name; /* remember delimiter */
  745.       *name = ''; /* tie off type */
  746.       ucase (s); /* search for body type */
  747.       for (i=0; (i<=TYPEMAX) && body_types[i] && strcmp(s,body_types[i]); i++);
  748.       if (i > TYPEMAX) body->type = TYPEOTHER;
  749.       else { /* if empty slot, assign it to this type */
  750. if (!body_types[i]) body_types[i] = cpystr (s);
  751. body->type = (unsigned short) i;
  752.       }
  753.       *name = c; /* restore delimiter */
  754.       rfc822_skipws (&name); /* skip whitespace */
  755.       if ((*name == '/') && /* subtype? */
  756.   (name = rfc822_parse_word ((s = ++name),ptspecials))) {
  757. c = *name; /* save delimiter */
  758. *name = ''; /* tie off subtype */
  759. rfc822_skipws (&s); /* copy subtype */
  760. if (s) body->subtype = ucase (cpystr (s));
  761. *name = c; /* restore delimiter */
  762. rfc822_skipws (&name); /* skip whitespace */
  763.       }
  764.       else { /* no subtype */
  765. if (!name) { /* did the fool have a subtype delimiter? */
  766.   name = s; /* barf, restore pointer */
  767.   rfc822_skipws (&name);/* skip leading whitespace */
  768. }
  769.       }
  770.       rfc822_parse_parameter (&body->parameter,name);
  771.     }
  772.     else if (!strcmp (name+1,"RANSFER-ENCODING")) {
  773. /* flush out any confusing whitespace */
  774.       if (t = strchr (ucase (s),' ')) *t = '';
  775. /* search for body encoding */
  776.       for (i = 0; (i <= ENCMAX) && body_encodings[i] &&
  777.    strcmp (s,body_encodings[i]); i++);
  778.       if (i > ENCMAX) body->type = ENCOTHER;
  779.       else { /* if empty slot, assign it to this type */
  780. if (!body_encodings[i]) body_encodings[i] = cpystr (s);
  781. body->encoding = (unsigned short) i;
  782.       }
  783.     }
  784.     break;
  785.   default: /* otherwise unknown */
  786.     break;
  787.   }
  788. }
  789. /* Parse RFC822 body parameter list
  790.  * Accepts: parameter list to write to
  791.  *     text of list
  792.  */
  793. void rfc822_parse_parameter (PARAMETER **par,char *text)
  794. {
  795.   char c,*s,tmp[MAILTMPLEN];
  796.   PARAMETER *param = NIL;
  797. /* parameter list? */
  798.   while (text && (*text == ';') &&
  799.  (text = rfc822_parse_word ((s = ++text),ptspecials))) {
  800.     c = *text; /* remember delimiter */
  801.     *text = ''; /* tie off attribute name */
  802.     rfc822_skipws (&s); /* skip leading attribute whitespace */
  803.     if (!*s) *text = c; /* must have an attribute name */
  804.     else { /* instantiate a new parameter */
  805.       if (*par) param = param->next = mail_newbody_parameter ();
  806.       else param = *par = mail_newbody_parameter ();
  807.       param->attribute = ucase (cpystr (s));
  808.       *text = c; /* restore delimiter */
  809.       rfc822_skipws (&text); /* skip whitespace before equal sign */
  810.       if ((*text != '=') || /* missing value is a no-no too */
  811.   !(text = rfc822_parse_word ((s = ++text),ptspecials)))
  812. param->value = cpystr ("UNKNOWN_PARAMETER_VALUE");
  813.       else { /* good, have equals sign */
  814. c = *text; /* remember delimiter */
  815. *text = ''; /* tie off value */
  816. rfc822_skipws (&s); /* skip leading value whitespace */
  817. if (*s) param->value = rfc822_cpy (s);
  818. *text = c; /* restore delimiter */
  819. rfc822_skipws (&text);
  820.       }
  821.     }
  822.   }
  823.   if (!text) { /* must be end of poop */
  824.     if (param && param->attribute)
  825.       sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
  826.     else strcpy (tmp,"Missing parameter");
  827.     mm_log (tmp,PARSE);
  828.   }
  829.   else if (*text) { /* must be end of poop */
  830.     sprintf (tmp,"Unexpected characters at end of parameters: %.80s",text);
  831.     mm_log (tmp,PARSE);
  832.   }
  833. }
  834. /* Parse RFC822 address list
  835.  * Accepts: address list to write to
  836.  *     input string
  837.  *     default host name
  838.  */
  839. void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
  840. {
  841.   char c,*s,tmp[MAILTMPLEN];
  842.   ADDRESS *last = *lst;
  843.   ADDRESS *adr;
  844.   if (!string) return; /* no string */
  845.   rfc822_skipws (&string); /* skip leading WS */
  846.   if (!*string) return; /* empty string */
  847. /* run to tail of list */
  848.   if (last) while (last->next) last = last->next;
  849.   while (string) { /* loop until string exhausted */
  850. /* got an address? */
  851.     if (adr = rfc822_parse_address (lst,last,&string,host,0)) {
  852.       last = adr; /* new tail address */
  853.       if (string) { /* analyze what follows */
  854. rfc822_skipws (&string);
  855. switch (c = *string) {
  856. case ',': /* comma? */
  857.   ++string; /* then another address follows */
  858.   break;
  859. default:
  860.   s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
  861.     "Unexpected characters at end of address: %.80s";
  862.   sprintf (tmp,s,string);
  863.   mm_log (tmp,PARSE);
  864.   last = last->next = mail_newaddr ();
  865.   last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
  866.   last->host = cpystr (errhst);
  867. /* falls through */
  868. case '': /* null-specified address? */
  869.   string = NIL; /* punt remainder of parse */
  870.   break;
  871. }
  872.       }
  873.     }
  874.     else if (string) { /* bad mailbox */
  875.       rfc822_skipws (&string); /* skip WS */
  876.       if (!*string) strcpy (tmp,"Missing address after comma");
  877.       else sprintf (tmp,"Invalid mailbox list: %.80s",string);
  878.       mm_log (tmp,PARSE);
  879.       string = NIL;
  880.       (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS");
  881.       adr->host = cpystr (errhst);
  882.       if (last) last = last->next = adr;
  883.       else *lst = last = adr;
  884.       break;
  885.     }
  886.   }
  887. }
  888. /* Parse RFC822 address
  889.  * Accepts: address list to write to
  890.  *     tail of address list
  891.  *     pointer to input string
  892.  *     default host name
  893.  *     group nesting depth
  894.  * Returns: new list tail
  895.  */
  896. ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,
  897.        char *defaulthost,unsigned long depth)
  898. {
  899.   ADDRESS *adr;
  900.   if (!*string) return NIL; /* no string */
  901.   rfc822_skipws (string); /* skip leading WS */
  902.   if (!**string) return NIL; /* empty string */
  903.   if (adr = rfc822_parse_group (lst,last,string,defaulthost,depth)) last = adr;
  904. /* got an address? */
  905.   else if (adr = rfc822_parse_mailbox (string,defaulthost)) {
  906.     if (!*lst) *lst = adr; /* yes, first time through? */
  907.     else last->next = adr; /* no, append to the list */
  908. /* set for subsequent linking */
  909.     for (last = adr; last->next; last = last->next);
  910.   }
  911.   else if (*string) return NIL;
  912.   return last;
  913. }
  914. /* Parse RFC822 group
  915.  * Accepts: address list to write to
  916.  *     pointer to tail of address list
  917.  *     pointer to input string
  918.  *     default host name
  919.  *     group nesting depth
  920.  */
  921. ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,
  922.      char *defaulthost,unsigned long depth)
  923. {
  924.   char tmp[MAILTMPLEN];
  925.   char *p,*s;
  926.   ADDRESS *adr;
  927.   if (depth > MAXGROUPDEPTH) { /* excessively deep recursion? */
  928.     mm_log ("Ignoring excessively deep group recursion",PARSE);
  929.     return NIL; /* probably abusive */
  930.   }
  931.   if (!*string) return NIL; /* no string */
  932.   rfc822_skipws (string); /* skip leading WS */
  933.   if (!**string || /* trailing whitespace or not group */
  934.       ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))
  935.     return NIL;
  936.   s = p; /* end of candidate phrase */
  937.   rfc822_skipws (&s); /* find delimiter */
  938.   if (*s != ':') return NIL; /* not really a group */
  939.   *p = ''; /* tie off group name */
  940.   p = ++s; /* continue after the delimiter */
  941.   rfc822_skipws (&p); /* skip subsequent whitespace */
  942. /* write as address */
  943.   (adr = mail_newaddr ())->mailbox = rfc822_cpy (*string);
  944.   if (!*lst) *lst = adr; /* first time through? */
  945.   else last->next = adr; /* no, append to the list */
  946.   last = adr; /* set for subsequent linking */
  947.   *string = p; /* continue after this point */
  948.   while (*string && **string && (**string != ';')) {
  949.     if (adr = rfc822_parse_address (lst,last,string,defaulthost,depth+1)) {
  950.       last = adr; /* new tail address */
  951.       if (*string) { /* anything more? */
  952. rfc822_skipws (string); /* skip whitespace */
  953. switch (**string) { /* see what follows */
  954. case ',': /* another address? */
  955.   ++*string; /* yes, skip past the comma */
  956. case ';': /* end of group? */
  957. case '': /* end of string */
  958.   break;
  959. default:
  960.   sprintf (tmp,"Unexpected characters after address in group: %.80s",
  961.    *string);
  962.   mm_log (tmp,PARSE);
  963.   *string = NIL; /* cancel remainder of parse */
  964.   last = last->next = mail_newaddr ();
  965.   last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP");
  966.   last->host = cpystr (errhst);
  967. }
  968.       }
  969.     }
  970.     else { /* bogon */
  971.       sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
  972.       mm_log (tmp,PARSE);
  973.       *string = NIL; /* cancel remainder of parse */
  974.       (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP");
  975.       adr->host = cpystr (errhst);
  976.       last = last->next = adr;
  977.     }
  978.   }
  979.   if (*string) { /* skip close delimiter */
  980.     if (**string == ';') ++*string;
  981.     rfc822_skipws (string);
  982.   }
  983. /* append end of address mark to the list */
  984.   last->next = (adr = mail_newaddr ());
  985.   last = adr; /* set for subsequent linking */
  986.   return last; /* return the tail */
  987. }
  988. /* Parse RFC822 mailbox
  989.  * Accepts: pointer to string pointer
  990.  *     default host
  991.  * Returns: address list
  992.  *
  993.  * Updates string pointer
  994.  */
  995. ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
  996. {
  997.   ADDRESS *adr = NIL;
  998.   char *s,*end;
  999.   parsephrase_t pp = (parsephrase_t) mail_parameters (NIL,GET_PARSEPHRASE,NIL);
  1000.   if (!*string) return NIL; /* no string */
  1001.   rfc822_skipws (string); /* flush leading whitespace */
  1002.   if (!**string) return NIL; /* empty string */
  1003.   if (*(s = *string) == '<')  /* note start, handle case of phraseless RA */
  1004.     adr = rfc822_parse_routeaddr (s,string,defaulthost);
  1005. /* otherwise, expect at least one word */
  1006.   else if (end = rfc822_parse_phrase (s)) {
  1007.     if ((adr = rfc822_parse_routeaddr (end,string,defaulthost))) {
  1008. /* phrase is a personal name */
  1009.       if (adr->personal) fs_give ((void **) &adr->personal);
  1010.       *end = ''; /* tie off phrase */
  1011.       adr->personal = rfc822_cpy (s);
  1012.     }
  1013. /* call external phraseparser if phrase only */
  1014.     else if (pp && rfc822_phraseonly (end) &&
  1015.      (adr = (*pp) (s,end,defaulthost))) {
  1016.       *string = end; /* update parse pointer */
  1017.       rfc822_skipws (string); /* skip WS in the normal way */
  1018.     }
  1019.     else adr = rfc822_parse_addrspec (s,string,defaulthost);
  1020.   }
  1021.   return adr; /* return the address */
  1022. }
  1023. /* Check if address is a phrase only
  1024.  * Accepts: pointer to end of phrase
  1025.  * Returns: T if phrase only, else NIL;
  1026.  */
  1027. long rfc822_phraseonly (char *end)
  1028. {
  1029.   while (*end == ' ') ++end; /* call rfc822_skipws() instead?? */
  1030.   switch (*end) {
  1031.   case '': case ',': case ';':
  1032.     return LONGT; /* is a phrase only */
  1033.   }
  1034.   return NIL; /* something other than phase is here */
  1035. }
  1036. /* Parse RFC822 route-address
  1037.  * Accepts: string pointer
  1038.  *     pointer to string pointer to update
  1039.  * Returns: address
  1040.  *
  1041.  * Updates string pointer
  1042.  */
  1043. ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost)
  1044. {
  1045.   char tmp[MAILTMPLEN];
  1046.   ADDRESS *adr;
  1047.   char *adl = NIL;
  1048.   char *routeend = NIL;
  1049.   if (!string) return NIL;
  1050.   rfc822_skipws (&string); /* flush leading whitespace */
  1051. /* must start with open broket */
  1052.   if (*string != '<') return NIL;
  1053.   if (string[1] == '@') { /* have an A-D-L? */
  1054.     adl = ++string; /* yes, remember that fact */
  1055.     while (*string != ':') { /* search for end of A-D-L */
  1056. /* punt if never found */
  1057.       if (!*string) return NIL;
  1058.       ++string; /* try next character */
  1059.     }
  1060.     *string = ''; /* tie off A-D-L */
  1061.     routeend = string; /* remember in case need to put back */
  1062.   }
  1063. /* parse address spec */
  1064.   if (!(adr = rfc822_parse_addrspec (++string,ret,defaulthost))) {
  1065.     if (adl) *routeend = ':'; /* put colon back since parse barfed */
  1066.     return NIL;
  1067.   }
  1068. /* have an A-D-L? */
  1069.   if (adl) adr->adl = cpystr (adl);
  1070.   if (*ret) if (**ret == '>') { /* make sure terminated OK */
  1071.     ++*ret; /* skip past the broket */
  1072.     rfc822_skipws (ret); /* flush trailing WS */
  1073.     if (!**ret) *ret = NIL; /* wipe pointer if at end of string */
  1074.     return adr; /* return the address */
  1075.   }
  1076.   sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox,
  1077.    *adr->host == '@' ? "<null>" : adr->host);
  1078.   mm_log (tmp,PARSE);
  1079.   adr->next = mail_newaddr ();
  1080.   adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR");
  1081.   adr->next->host = cpystr (errhst);
  1082.   return adr; /* return the address */
  1083. }
  1084. /* Parse RFC822 address-spec
  1085.  * Accepts: string pointer
  1086.  *     pointer to string pointer to update
  1087.  *     default host
  1088.  * Returns: address
  1089.  *
  1090.  * Updates string pointer
  1091.  */
  1092. ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost)
  1093. {
  1094.   ADDRESS *adr;
  1095.   char *end;
  1096.   char c,*s,*t;
  1097.   if (!string) return NIL; /* no string */
  1098.   rfc822_skipws (&string); /* flush leading whitespace */
  1099.   if (!*string) return NIL; /* empty string */
  1100. /* find end of mailbox */
  1101.   if (!(end = rfc822_parse_word (string,NIL))) return NIL;
  1102.   adr = mail_newaddr (); /* create address block */
  1103.   c = *end; /* remember delimiter */
  1104.   *end = ''; /* tie off mailbox */
  1105. /* copy mailbox */
  1106.   adr->mailbox = rfc822_cpy (string);
  1107.   *end = c; /* restore delimiter */
  1108.   t = end; /* remember end of mailbox for no host case */
  1109.   rfc822_skipws (&end); /* skip whitespace */
  1110.   if (*end == '@') { /* have host name? */
  1111.     ++end; /* skip delimiter */
  1112.     rfc822_skipws (&end); /* skip whitespace */
  1113.     if (*end == '[') { /* domain literal? */
  1114.       string = end; /* start of domain literal */
  1115.       if (end = rfc822_parse_word (string + 1,"]\")) {
  1116. size_t len = ++end - string;
  1117. strncpy (adr->host = (char *) fs_get (len + 1),string,len);
  1118. adr->host[len] = ''; /* tie off literal */
  1119.       }
  1120.       else {
  1121. mm_log ("Invalid domain literal after @",PARSE);
  1122. adr->host = cpystr (errhst);
  1123.       }
  1124.     }
  1125.      /* search for end of host */
  1126.     else if (end = rfc822_parse_word ((string = end),wspecials)) {
  1127.       c = *end; /* remember delimiter */
  1128.       *end = ''; /* tie off host */
  1129. /* copy host */
  1130.       adr->host = rfc822_cpy (string);
  1131.       *end = c; /* restore delimiter */
  1132.     }
  1133.     else {
  1134.       mm_log ("Missing or invalid host name after @",PARSE);
  1135.       adr->host = cpystr (errhst);
  1136.     }
  1137.   }
  1138.   else end = t; /* make person name default start after mbx */
  1139. /* default host if missing */
  1140.   if (!adr->host) adr->host = cpystr (defaulthost);
  1141.   if (end && !adr->personal) { /* try person name in comments if missing */
  1142.     while (*end == ' ') ++end; /* see if we can find a person name here */
  1143.     if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s))
  1144.       adr->personal = rfc822_cpy (s);
  1145.     rfc822_skipws (&end); /* skip any other WS in the normal way */
  1146.   }
  1147. /* set return to end pointer */
  1148.   *ret = (end && *end) ? end : NIL;
  1149.   return adr; /* return the address we got */
  1150. }
  1151. /* Parse RFC822 phrase
  1152.  * Accepts: string pointer
  1153.  * Returns: pointer to end of phrase
  1154.  */
  1155. char *rfc822_parse_phrase (char *s)
  1156. {
  1157.   char *curpos;
  1158.   if (!s) return NIL; /* no-op if no string */
  1159. /* find first word of phrase */
  1160.   curpos = rfc822_parse_word (s,NIL);
  1161.   if (!curpos) return NIL; /* no words means no phrase */
  1162.   if (!*curpos) return curpos; /* check if string ends with word */
  1163.   s = curpos; /* sniff past the end of this word and WS */
  1164.   rfc822_skipws (&s); /* skip whitespace */
  1165. /* recurse to see if any more */
  1166.   return (s = rfc822_parse_phrase (s)) ? s : curpos;
  1167. }
  1168. /* Parse RFC822 word
  1169.  * Accepts: string pointer
  1170.  * Returns: pointer to end of word
  1171.  */
  1172. char *rfc822_parse_word (char *s,const char *delimiters)
  1173. {
  1174.   char *st,*str;
  1175.   if (!s) return NIL; /* no string */
  1176.   rfc822_skipws (&s); /* flush leading whitespace */
  1177.   if (!*s) return NIL; /* empty string */
  1178. /* default delimiters to standard */
  1179.   if (!delimiters) delimiters = wspecials;
  1180.   str = s; /* hunt pointer for strpbrk */
  1181.   while (T) { /* look for delimiter */
  1182.     if (!(st = strpbrk (str,delimiters))) {
  1183.       while (*s) ++s; /* no delimiter, hunt for end */
  1184.       return s; /* return it */
  1185.     }
  1186.     switch (*st) { /* dispatch based on delimiter */
  1187.     case '"': /* quoted string */
  1188. /* look for close quote */
  1189.       while (*++st != '"') switch (*st) {
  1190.       case '': /* unbalanced quoted string */
  1191. return NIL; /* sick sick sick */
  1192.       case '\': /* quoted character */
  1193. if (!*++st) return NIL; /* skip the next character */
  1194.       default: /* ordinary character */
  1195. break; /* no special action */
  1196.       }
  1197.       str = ++st; /* continue parse */
  1198.       break;
  1199.     case '\': /* quoted character */
  1200.       /* This is wrong; a quoted-pair can not be part of a word.  However,
  1201.        * domain-literal is parsed as a word and quoted-pairs can be used
  1202.        * *there*.  Either way, it's pretty pathological.
  1203.        */
  1204.       if (st[1]) { /* not on NUL though... */
  1205. str = st + 2; /* skip quoted character and go on */
  1206. break;
  1207.       }
  1208.     default: /* found a word delimiter */
  1209.       return (st == s) ? NIL : st;
  1210.     }
  1211.   }
  1212. }
  1213. /* Copy an RFC822 format string
  1214.  * Accepts: string
  1215.  * Returns: copy of string
  1216.  */
  1217. char *rfc822_cpy (char *src)
  1218. {
  1219. /* copy and unquote */
  1220.   return rfc822_quote (cpystr (src));
  1221. }
  1222. /* Unquote an RFC822 format string
  1223.  * Accepts: string
  1224.  * Returns: string
  1225.  */
  1226. char *rfc822_quote (char *src)
  1227. {
  1228.   char *ret = src;
  1229.   if (strpbrk (src,"\"")) { /* any quoting in string? */
  1230.     char *dst = ret;
  1231.     while (*src) { /* copy string */
  1232.       if (*src == '"') src++; /* skip double quote entirely */
  1233.       else {
  1234. if (*src == '\') src++;/* skip over single quote, copy next always */
  1235. *dst++ = *src++; /* copy character */
  1236.       }
  1237.     }
  1238.     *dst = ''; /* tie off string */
  1239.   }
  1240.   return ret; /* return our string */
  1241. }
  1242. /* Copy address list
  1243.  * Accepts: address list
  1244.  * Returns: address list
  1245.  */
  1246. ADDRESS *rfc822_cpy_adr (ADDRESS *adr)
  1247. {
  1248.   ADDRESS *dadr;
  1249.   ADDRESS *ret = NIL;
  1250.   ADDRESS *prev = NIL;
  1251.   while (adr) { /* loop while there's still an MAP adr */
  1252.     dadr = mail_newaddr (); /* instantiate a new address */
  1253.     if (!ret) ret = dadr; /* note return */
  1254.     if (prev) prev->next = dadr;/* tie on to the end of any previous */
  1255.     dadr->personal = cpystr (adr->personal);
  1256.     dadr->adl = cpystr (adr->adl);
  1257.     dadr->mailbox = cpystr (adr->mailbox);
  1258.     dadr->host = cpystr (adr->host);
  1259.     prev = dadr; /* this is now the previous */
  1260.     adr = adr->next; /* go to next address in list */
  1261.   }
  1262.   return (ret); /* return the MTP address list */
  1263. }
  1264. /* Skips RFC822 whitespace
  1265.  * Accepts: pointer to string pointer
  1266.  */
  1267. void rfc822_skipws (char **s)
  1268. {
  1269.   while (T) {
  1270.     if (**s == ' ') ++*s; /* skip space */
  1271.     else if ((**s != '(') || !rfc822_skip_comment (s,(long) NIL)) return;
  1272.   }
  1273. }
  1274. /* Skips RFC822 comment
  1275.  * Accepts: pointer to string pointer
  1276.  *     trim flag
  1277.  * Returns: pointer to first non-blank character of comment
  1278.  */
  1279. char *rfc822_skip_comment (char **s,long trim)
  1280. {
  1281.   char *ret,tmp[MAILTMPLEN];
  1282.   char *s1 = *s;
  1283.   char *t = NIL;
  1284. /* skip past whitespace */
  1285.   for (ret = ++s1; *ret == ' '; ret++);
  1286.   do switch (*s1) { /* get character of comment */
  1287.   case '(': /* nested comment? */
  1288.     if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL;
  1289.     t = --s1; /* last significant char at end of comment */
  1290.     break;
  1291.   case ')': /* end of comment? */
  1292.     *s = ++s1; /* skip past end of comment */
  1293.     if (trim) { /* if level 0, must trim */
  1294.       if (t) t[1] = ''; /* tie off comment string */
  1295.       else *ret = ''; /* empty comment */
  1296.     }
  1297.     return ret;
  1298.   case '\': /* quote next character? */
  1299.     if (*++s1) { /* next character non-null? */
  1300.       t = s1; /* update last significant character pointer */
  1301.       break; /* all OK */
  1302.     }
  1303.   case '': /* end of string */
  1304.     sprintf (tmp,"Unterminated comment: %.80s",*s);
  1305.     mm_log (tmp,PARSE);
  1306.     **s = ''; /* nuke duplicate messages in case reparse */
  1307.     return NIL; /* this is wierd if it happens */
  1308.   case ' ': /* whitespace isn't significant */
  1309.     break;
  1310.   default: /* random character */
  1311.     t = s1; /* update last significant character pointer */
  1312.     break;
  1313.   } while (s1++);
  1314.   return NIL; /* impossible, but pacify lint et al */
  1315. }
  1316. /* Body contents utility and encoding/decoding routines */
  1317. /* Output RFC 822 message
  1318.  * Accepts: temporary buffer
  1319.  *     envelope
  1320.  *     body
  1321.  *     I/O routine
  1322.  *     stream for I/O routine
  1323.  *     non-zero if 8-bit output desired
  1324.  * Returns: T if successful, NIL if failure
  1325.  */
  1326. long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,void *s,
  1327.     long ok8bit)
  1328. {
  1329.   rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
  1330. /* call external RFC822 output generator */
  1331.   if (r822o) return (*r822o) (t,env,body,f,s,ok8bit);
  1332. /* encode body as necessary */
  1333.   if (ok8bit) rfc822_encode_body_8bit (env,body);
  1334.   else rfc822_encode_body_7bit (env,body);
  1335.   rfc822_header (t,env,body); /* build RFC822 header */
  1336. /* output header and body */
  1337.   return (*f) (s,t) && (body ? rfc822_output_body (body,f,s) : T);
  1338. }
  1339. /* Encode a body for 7BIT transmittal
  1340.  * Accepts: envelope
  1341.  *     body
  1342.  */
  1343. void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body)
  1344. {
  1345.   void *f;
  1346.   PART *part;
  1347.   PARAMETER **param;
  1348.   if (body) switch (body->type) {
  1349.   case TYPEMULTIPART: /* multi-part */
  1350.     for (param = &body->parameter;
  1351.  *param && strcmp ((*param)->attribute,"BOUNDARY");
  1352.  param = &(*param)->next);
  1353.     if (!*param) { /* cookie not set up yet? */
  1354.       char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
  1355.       sprintf (tmp,"%ld-%ld-%ld=:%ld",(long) gethostid (),random (),time (0),
  1356.        (long) getpid ());
  1357.       (*param) = mail_newbody_parameter ();
  1358.       (*param)->attribute = cpystr ("BOUNDARY");
  1359.       (*param)->value = cpystr (tmp);
  1360.     }
  1361.     part = body->nested.part; /* encode body parts */
  1362.     do rfc822_encode_body_7bit (env,&part->body);
  1363.     while (part = part->next); /* until done */
  1364.     break;
  1365.   case TYPEMESSAGE: /* encapsulated message */
  1366.     switch (body->encoding) {
  1367.     case ENC7BIT:
  1368.       break;
  1369.     case ENC8BIT:
  1370.       mm_log ("8-bit included message in 7-bit message body",WARN);
  1371.       break;
  1372.     case ENCBINARY:
  1373.       mm_log ("Binary included message in 7-bit message body",WARN);
  1374.       break;
  1375.     default:
  1376.       fatal ("Invalid rfc822_encode_body_7bit message encoding");
  1377.     }
  1378.     break; /* can't change encoding */
  1379.   default: /* all else has some encoding */
  1380.     switch (body->encoding) {
  1381.     case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
  1382. /* remember old 8-bit contents */
  1383.       f = (void *) body->contents.text.data;
  1384.       body->contents.text.data =
  1385. rfc822_8bit (body->contents.text.data,
  1386.      body->contents.text.size,&body->contents.text.size);
  1387.       body->encoding = ENCQUOTEDPRINTABLE;
  1388.       fs_give (&f); /* flush old binary contents */
  1389.       break;
  1390.     case ENCBINARY: /* encode binary into BASE64 */
  1391. /* remember old binary contents */
  1392.       f = (void *) body->contents.text.data;
  1393.       body->contents.text.data =
  1394. rfc822_binary ((void *) body->contents.text.data,
  1395.        body->contents.text.size,&body->contents.text.size);
  1396.       body->encoding = ENCBASE64;
  1397.       fs_give (&f); /* flush old binary contents */
  1398.     default: /* otherwise OK */
  1399.       break;
  1400.     }
  1401.     break;
  1402.   }
  1403. }
  1404. /* Encode a body for 8BIT transmittal
  1405.  * Accepts: envelope
  1406.  *     body
  1407.  */
  1408. void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body)
  1409. {
  1410.   void *f;
  1411.   PART *part;
  1412.   PARAMETER **param;
  1413.   if (body) switch (body->type) {
  1414.   case TYPEMULTIPART: /* multi-part */
  1415.     for (param = &body->parameter;
  1416.  *param && strcmp ((*param)->attribute,"BOUNDARY");
  1417.  param = &(*param)->next);
  1418.     if (!*param) { /* cookie not set up yet? */
  1419.       char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
  1420.       sprintf (tmp,"%ld-%ld-%ld=:%ld",(long) gethostid (),random (),time (0),
  1421.        (long) getpid ());
  1422.       (*param) = mail_newbody_parameter ();
  1423.       (*param)->attribute = cpystr ("BOUNDARY");
  1424.       (*param)->value = cpystr (tmp);
  1425.     }
  1426.     part = body->nested.part; /* encode body parts */
  1427.     do rfc822_encode_body_8bit (env,&part->body);
  1428.     while (part = part->next); /* until done */
  1429.     break;
  1430.   case TYPEMESSAGE: /* encapsulated message */
  1431.     switch (body->encoding) {
  1432.     case ENC7BIT:
  1433.     case ENC8BIT:
  1434.       break;
  1435.     case ENCBINARY:
  1436.       mm_log ("Binary included message in 8-bit message body",WARN);
  1437.       break;
  1438.     default:
  1439.       fatal ("Invalid rfc822_encode_body_7bit message encoding");
  1440.     }
  1441.     break; /* can't change encoding */
  1442.   default: /* other type, encode binary into BASE64 */
  1443.     if (body->encoding == ENCBINARY) {
  1444. /* remember old binary contents */
  1445.       f = (void *) body->contents.text.data;
  1446.       body->contents.text.data =
  1447. rfc822_binary ((void *) body->contents.text.data,
  1448.        body->contents.text.size,&body->contents.text.size);
  1449.       body->encoding = ENCBASE64;
  1450.       fs_give (&f); /* flush old binary contents */
  1451.     }
  1452.     break;
  1453.   }
  1454. }
  1455. /* Output RFC 822 body
  1456.  * Accepts: body
  1457.  *     I/O routine
  1458.  *     stream for I/O routine
  1459.  * Returns: T if successful, NIL if failure
  1460.  */
  1461. long rfc822_output_body (BODY *body,soutr_t f,void *s)
  1462. {
  1463.   PART *part;
  1464.   PARAMETER *param;
  1465.   char *cookie = NIL;
  1466.   char tmp[MAILTMPLEN];
  1467.   char *t;
  1468.   switch (body->type) {
  1469.   case TYPEMULTIPART: /* multipart gets special handling */
  1470.     part = body->nested.part; /* first body part */
  1471. /* find cookie */
  1472.     for (param = body->parameter; param && !cookie; param = param->next)
  1473.       if (!strcmp (param->attribute,"BOUNDARY")) cookie = param->value;
  1474.     if (!cookie) cookie = "-"; /* yucky default */
  1475.     do { /* for each part */
  1476. /* build cookie */
  1477.       sprintf (t = tmp,"--%s1512",cookie);
  1478. /* append mini-header */
  1479.       rfc822_write_body_header (&t,&part->body);
  1480.       strcat (t,"1512"); /* write terminating blank line */
  1481. /* output cookie, mini-header, and contents */
  1482.       if (!((*f) (s,tmp) && rfc822_output_body (&part->body,f,s))) return NIL;
  1483.     } while (part = part->next);/* until done */
  1484. /* output trailing cookie */
  1485.     sprintf (t = tmp,"--%s--",cookie);
  1486.     break;
  1487.   default: /* all else is text now */
  1488.     t = (char *) body->contents.text.data;
  1489.     break;
  1490.   }
  1491. /* output final stuff */
  1492.   if (t && *t && !((*f) (s,t) && (*f) (s,"1512"))) return NIL;
  1493.   return LONGT;
  1494. }
  1495. /* Convert BASE64 contents to binary
  1496.  * Accepts: source
  1497.  *     length of source
  1498.  *     pointer to return destination length
  1499.  * Returns: destination as binary or NIL if error
  1500.  */
  1501. void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
  1502. {
  1503.   char c;
  1504.   void *ret = fs_get ((size_t) (*len = 4 + ((srcl * 3) / 4)));
  1505.   char *d = (char *) ret;
  1506.   int e = 0;
  1507.   memset (ret,0,(size_t) *len); /* initialize block */
  1508.   *len = 0; /* in case we return an error */
  1509.   while (srcl--) { /* until run out of characters */
  1510.     c = *src++; /* simple-minded decode */
  1511.     if (isupper (c)) c -= 'A';
  1512.     else if (islower (c)) c -= 'a' - 26;
  1513.     else if (isdigit (c)) c -= '0' - 52;
  1514.     else if (c == '+') c = 62;
  1515.     else if (c == '/') c = 63;
  1516.     else if (c == '=') { /* padding */
  1517.       switch (e++) { /* check quantum position */
  1518.       case 3:
  1519. e = 0; /* restart quantum */
  1520. break;
  1521.       case 2:
  1522. if (*src == '=') break;
  1523.       default: /* impossible quantum position */
  1524. fs_give (&ret);
  1525. return NIL;
  1526.       }
  1527.       continue;
  1528.     }
  1529.     else continue; /* junk character */
  1530.     switch (e++) { /* install based on quantum position */
  1531.     case 0:
  1532.       *d = c << 2; /* byte 1: high 6 bits */
  1533.       break;
  1534.     case 1:
  1535.       *d++ |= c >> 4; /* byte 1: low 2 bits */
  1536.       *d = c << 4; /* byte 2: high 4 bits */
  1537.       break;
  1538.     case 2:
  1539.       *d++ |= c >> 2; /* byte 2: low 4 bits */
  1540.       *d = c << 6; /* byte 3: high 2 bits */
  1541.       break;
  1542.     case 3:
  1543.       *d++ |= c; /* byte 3: low 6 bits */
  1544.       e = 0; /* reinitialize mechanism */
  1545.       break;
  1546.     }
  1547.   }
  1548.   *len = d - (char *) ret; /* calculate data length */
  1549.   return ret; /* return the string */
  1550. }
  1551. /* Convert binary contents to BASE64
  1552.  * Accepts: source
  1553.  *     length of source
  1554.  *     pointer to return destination length
  1555.  * Returns: destination as BASE64
  1556.  */
  1557. unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
  1558. {
  1559.   unsigned char *ret,*d;
  1560.   unsigned char *s = (unsigned char *) src;
  1561.   char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  1562.   unsigned long i = ((srcl + 2) / 3) * 4;
  1563.   *len = i += 2 * ((i / 60) + 1);
  1564.   d = ret = (unsigned char *) fs_get ((size_t) ++i);
  1565.   for (i = 0; srcl; s += 3) { /* process tuplets */
  1566.     *d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
  1567. /* byte 2: low 2 bits (1), high 4 bits (2) */
  1568.     *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
  1569. /* byte 3: low 4 bits (2), high 2 bits (3) */
  1570.     *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
  1571. /* byte 4: low 6 bits (3) */
  1572.     *d++ = srcl ? v[s[2] & 0x3f] : '=';
  1573.     if (srcl) srcl--; /* count third character if processed */
  1574.     if ((++i) == 15) { /* output 60 characters? */
  1575.       i = 0; /* restart line break count, insert CRLF */
  1576.       *d++ = '15'; *d++ = '12';
  1577.     }
  1578.   }
  1579.   *d++ = '15'; *d++ = '12'; /* insert final CRLF */
  1580.   *d = ''; /* tie off string */
  1581.   if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
  1582.   return ret; /* return the resulting string */
  1583. }
  1584. /* Convert QUOTED-PRINTABLE contents to 8BIT
  1585.  * Accepts: source
  1586.  *     length of source
  1587.  *      pointer to return destination length
  1588.  * Returns: destination as 8-bit text or NIL if error
  1589.  */
  1590. unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,
  1591.       unsigned long *len)
  1592. {
  1593.   unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
  1594.   unsigned char *d = ret;
  1595.   unsigned char *t = d;
  1596.   unsigned char *s = src;
  1597.   unsigned char c,e;
  1598.   *len = 0; /* in case we return an error */
  1599. /* until run out of characters */
  1600.   while (((unsigned long) (s - src)) < srcl) {
  1601.     switch (c = *s++) { /* what type of character is it? */
  1602.     case '=': /* quoting character */
  1603.       if (((unsigned long) (s - src)) < srcl) switch (c = *s++) {
  1604.       case '': /* end of data */
  1605. s--; /* back up pointer */
  1606. break;
  1607.       case '15': /* non-significant line break */
  1608. t = d; /* accept any leading spaces */
  1609. if ((((unsigned long) (s - src)) < srcl) && (*s == '12')) s++;
  1610. break;
  1611.       default: /* two hex digits then */
  1612. if (!(isxdigit (c) && (((unsigned long) (s - src)) < srcl) &&
  1613.       (e = *s++) && isxdigit (e))) {
  1614.   fs_give ((void **) &ret);
  1615.   return NIL;
  1616. }
  1617. if (isdigit (c)) c -= '0';
  1618. else c -= (isupper (c) ? 'A' - 10 : 'a' - 10);
  1619. if (isdigit (e)) e -= '0';
  1620. else e -= (isupper (e) ? 'A' - 10 : 'a' - 10);
  1621. *d++ = e + (c << 4); /* merge the two hex digits */
  1622. t = d; /* note point of non-space */
  1623. break;
  1624.       }
  1625.       break;
  1626.     case ' ': /* space, possibly bogus */
  1627.       *d++ = c; /* stash the space but don't update s */
  1628.       break;
  1629.     case '15': /* end of line */
  1630.       d = t; /* slide back to last non-space, drop in */
  1631.     default:
  1632.       *d++ = c; /* stash the character */
  1633.       t = d; /* note point of non-space */
  1634.     }      
  1635.   }
  1636.   *d = ''; /* tie off results */
  1637.   *len = d - ret; /* calculate length */
  1638.   return ret; /* return the string */
  1639. }
  1640. /* Convert 8BIT contents to QUOTED-PRINTABLE
  1641.  * Accepts: source
  1642.  *     length of source
  1643.  *      pointer to return destination length
  1644.  * Returns: destination as quoted-printable text
  1645.  */
  1646. #define MAXL (size_t) 75 /* 76th position only used by continuation = */
  1647. unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
  1648.     unsigned long *len)
  1649. {
  1650.   unsigned long lp = 0;
  1651.   unsigned char *ret = (unsigned char *)
  1652.     fs_get ((size_t) (3*srcl + (6*srcl)/MAXL + 3));
  1653.   unsigned char *d = ret;
  1654.   char *hex = "0123456789ABCDEF";
  1655.   unsigned char c;
  1656.   while (srcl--) { /* for each character */
  1657. /* true line break? */
  1658.     if (((c = *src++) == '15') && (*src == '12') && srcl) {
  1659.       *d++ = '15'; *d++ = *src++; srcl--;
  1660.       lp = 0; /* reset line count */
  1661.     }
  1662.     else { /* not a line break */
  1663. /* quoting required? */
  1664.       if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
  1665.   ((c == ' ') && (*src == '15'))) {
  1666. if ((lp += 3) > MAXL) { /* yes, would line overflow? */
  1667.   *d++ = '='; *d++ = '15'; *d++ = '12';
  1668.   lp = 3; /* set line count */
  1669. }
  1670. *d++ = '='; /* quote character */
  1671. *d++ = hex[c >> 4]; /* high order 4 bits */
  1672. *d++ = hex[c & 0xf]; /* low order 4 bits */
  1673.       }
  1674.       else { /* ordinary character */
  1675. if ((++lp) > MAXL) { /* would line overflow? */
  1676.   *d++ = '='; *d++ = '15'; *d++ = '12';
  1677.   lp = 1; /* set line count */
  1678. }
  1679. *d++ = c; /* ordinary character */
  1680.       }
  1681.     }
  1682.   }
  1683.   *d = ''; /* tie off destination */
  1684.   *len = d - ret; /* calculate true size */
  1685. /* try to give some space back */
  1686.   fs_resize ((void **) &ret,(size_t) *len + 1);
  1687.   return ret;
  1688. }