home *** CD-ROM | disk | FTP | other *** search
/ vsiftp.vmssoftware.com / VSIPUBLIC@vsiftp.vmssoftware.com.tar / FREEWARE / FREEWARE40.ZIP / pine / c-client / rfc822.c < prev    next >
C/C++ Source or Header  |  1994-02-01  |  52KB  |  1,592 lines

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