merge with latest
[external/uw-imap-toolkit.git] / imap-2007e / c-client / rfc822.c
1 /* ========================================================================
2  * Copyright 1988-2008 University of Washington
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * 
11  * ========================================================================
12  */
13
14 /*
15  * Program:     RFC 2822 and MIME routines
16  *
17  * Author:      Mark Crispin
18  *              UW Technology
19  *              University of Washington
20  *              Seattle, WA  98195
21  *              Internet: MRC@Washington.EDU
22  *
23  * Date:        27 July 1988
24  * Last Edited: 14 May 2008
25  *
26  * This original version of this file is
27  * Copyright 1988 Stanford University
28  * and was developed in the Symbolic Systems Resources Group of the Knowledge
29  * Systems Laboratory at Stanford University in 1987-88, and was funded by the
30  * Biomedical Research Technology Program of the NationalInstitutes of Health
31  * under grant number RR-00785.
32  */
33
34
35 #include <ctype.h>
36 #include <stdio.h>
37 #include <time.h>
38 #include "c-client.h"
39
40
41 /* Support for deprecated features in earlier specifications.  Note that this
42  * module follows RFC 2822, and all use of "rfc822" in function names is
43  * for compatibility.  Only the code identified by the conditionals below
44  * follows the earlier documents.
45  */
46
47 #define RFC733 1                /* parse "at" */
48 #define RFC822 0                /* generate A-D-L (MUST be 0 for 2822) */
49 \f
50 /* RFC-822 static data */
51
52 #define RFC822CONT "    "       /* RFC 2822 continuation */
53
54                                 /* should have been "Remailed-" */
55 #define RESENTPREFIX "ReSent-"
56 static char *resentprefix = RESENTPREFIX;
57                                 /* syntax error host string */
58 static const char *errhst = ERRHOST;
59
60
61 /* Body formats constant strings, must match definitions in mail.h */
62
63 char *body_types[TYPEMAX+1] = {
64   "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
65   "MODEL", "X-UNKNOWN"
66 };
67
68
69 char *body_encodings[ENCMAX+1] = {
70   "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
71 };
72
73
74 /* Token delimiting special characters */
75
76                                 /* RFC 2822 specials */
77 const char *specials = " ()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
78                                 /* RFC 2822 phrase specials (no space) */
79 const char *rspecials = "()<>@,;:\\\"[].\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
80                                 /* RFC 2822 dot-atom specials (no dot) */
81 const char *wspecials = " ()<>@,;:\\\"[]\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
82                                 /* RFC 2045 MIME body token specials */
83 const char *tspecials = " ()<>@,;:\\\"[]/?=\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\177";
84 \f
85 /* Subtype defaulting (a no-no, but regretably necessary...)
86  * Accepts: type code
87  * Returns: default subtype name
88  */
89
90 char *rfc822_default_subtype (unsigned short type)
91 {
92   switch (type) {
93   case TYPETEXT:                /* default is TEXT/PLAIN */
94     return "PLAIN";
95   case TYPEMULTIPART:           /* default is MULTIPART/MIXED */
96     return "MIXED";
97   case TYPEMESSAGE:             /* default is MESSAGE/RFC822 */
98     return "RFC822";
99   case TYPEAPPLICATION:         /* default is APPLICATION/OCTET-STREAM */
100     return "OCTET-STREAM";
101   case TYPEAUDIO:               /* default is AUDIO/BASIC */
102     return "BASIC";
103   default:                      /* others have no default subtype */
104     return "UNKNOWN";
105   }
106 }
107 \f
108 /* RFC 2822 parsing routines */
109
110
111 /* Parse an RFC 2822 message
112  * Accepts: pointer to return envelope
113  *          pointer to return body
114  *          pointer to header
115  *          header byte count
116  *          pointer to body stringstruct
117  *          pointer to local host name
118  *          recursion depth
119  *          source driver flags
120  */
121
122 void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i,
123                             STRING *bs,char *host,unsigned long depth,
124                             unsigned long flags)
125 {
126   char c,*t,*d;
127   char *tmp = (char *) fs_get ((size_t) i + 100);
128   ENVELOPE *env = (*en = mail_newenvelope ());
129   BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
130   long MIMEp = -1;              /* flag that MIME semantics are in effect */
131   parseline_t pl = (parseline_t) mail_parameters (NIL,GET_PARSELINE,NIL);
132   if (!host) host = BADHOST;    /* make sure that host is non-null */
133   while (i && *s != '\n') {     /* until end of header */
134     t = tmp;                    /* initialize buffer pointer */
135     c = ' ';                    /* and previous character */
136     while (i && c) {            /* collect text until logical end of line */
137       switch (c = *s++) {       /* slurp a character */
138       case '\015':              /* return, possible end of logical line */
139         if (*s == '\n') break;  /* ignore if LF follows */
140       case '\012':              /* LF, possible end of logical line */
141                                 /* tie off unless next line starts with WS */
142         if (*s != ' ' && *s != '\t') *t++ = c = '\0';
143         break;
144       case '\t':                /* tab */
145         *t++ = ' ';             /* coerce to space */
146         break;
147       default:                  /* all other characters */
148         *t++ = c;               /* insert the character into the line */
149         break;
150       }
151       if (!--i) *t++ = '\0';    /* see if end of header */
152     }
153 \f
154                                 /* find header item type */
155     if (t = d = strchr (tmp,':')) {
156       *d++ = '\0';              /* tie off header item, point at its data */
157       while (*d == ' ') d++;    /* flush whitespace */
158       while ((tmp < t--) && (*t == ' ')) *t = '\0';
159       ucase (tmp);              /* coerce to uppercase */
160                                 /* external callback */
161       if (pl) (*pl) (env,tmp,d,host);
162       switch (*tmp) {           /* dispatch based on first character */
163       case '>':                 /* possible >From: */
164         if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
165         break;
166       case 'B':                 /* possible bcc: */
167         if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
168         break;
169       case 'C':                 /* possible cc: or Content-<mumble>*/
170         if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
171         else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') &&
172                  (tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') &&
173                  (tmp[7] == '-') && body)
174           switch (MIMEp) {
175           case -1:              /* unknown if MIME or not */
176             if (!(MIMEp =       /* see if MIME-Version header exists */
177                   search ((unsigned char *) s-1,i,
178                           (unsigned char *)"\012MIME-Version",(long) 13))) {
179 #if 1
180               /* This is a disgusting kludge, and most of the messages which
181                * benefit from it are spam.
182                */
183               if (!strcmp (tmp+8,"TRANSFER-ENCODING") ||
184                   (!strcmp (tmp+8,"TYPE") && strchr (d,'/'))) {
185                 MM_LOG ("Warning: MIME header encountered in non-MIME message",
186                         PARSE);
187                 MIMEp = 1;      /* declare MIME now */
188               }
189               else
190 #endif
191                 break;          /* non-MIME message */
192             }
193           case T:               /* definitely MIME */
194             rfc822_parse_content_header (body,tmp+8,d);
195           }
196         break;
197       case 'D':                 /* possible Date: */
198         if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
199         break;
200       case 'F':                 /* possible From: */
201         if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
202         else if (!strcmp (tmp+1,"OLLOWUP-TO")) {
203           t = env->followup_to = (char *) fs_get (1 + strlen (d));
204           while (c = *d++) if (c != ' ') *t++ = c;
205           *t++ = '\0';
206         }
207         break;
208       case 'I':                 /* possible In-Reply-To: */
209         if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO"))
210           env->in_reply_to = cpystr (d);
211         break;
212 \f
213       case 'M':                 /* possible Message-ID: or MIME-Version: */
214         if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID"))
215           env->message_id = cpystr (d);
216         else if (!strcmp (tmp+1,"IME-VERSION")) {
217                                 /* tie off at end of phrase */
218           if (t = rfc822_parse_phrase (d)) *t = '\0';
219           rfc822_skipws (&d);   /* skip whitespace */
220                                 /* known version? */
221           if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
222             MM_LOG ("Warning: message has unknown MIME version",PARSE);
223           MIMEp = T;            /* note that we are MIME */
224         }
225         break;
226       case 'N':                 /* possible Newsgroups: */
227         if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) {
228           t = env->newsgroups = (char *) fs_get (1 + strlen (d));
229           while (c = *d++) if (c != ' ') *t++ = c;
230           *t++ = '\0';
231         }
232         break;
233       case 'R':                 /* possible Reply-To: */
234         if (!strcmp (tmp+1,"EPLY-TO"))
235           rfc822_parse_adrlist (&env->reply_to,d,host);
236         else if (!env->references && !strcmp (tmp+1,"EFERENCES"))
237           env->references = cpystr (d);
238         break;
239       case 'S':                 /* possible Subject: or Sender: */
240         if (!env->subject && !strcmp (tmp+1,"UBJECT"))
241           env->subject = cpystr (d);
242         else if (!strcmp (tmp+1,"ENDER"))
243           rfc822_parse_adrlist (&env->sender,d,host);
244         break;
245       case 'T':                 /* possible To: */
246         if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
247         break;
248       default:
249         break;
250       }
251     }
252   }
253   fs_give ((void **) &tmp);     /* done with scratch buffer */
254                                 /* default Sender: and Reply-To: to From: */
255   if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
256   if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
257                                 /* now parse the body */
258   if (body) rfc822_parse_content (body,bs,host,depth,flags);
259 }
260 \f
261 /* Parse a message body content
262  * Accepts: pointer to body structure
263  *          body string
264  *          pointer to local host name
265  *          recursion depth
266  *          source driver flags
267  */
268
269 void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth,
270                            unsigned long flags)
271 {
272   char c,c1,*s,*s1;
273   int f;
274   unsigned long i,j,k,m;
275   PARAMETER *param;
276   PART *part = NIL;
277   if (depth > MAXMIMEDEPTH) {   /* excessively deep recursion? */
278     body->type = TYPETEXT;      /* yes, probably a malicious MIMEgram */
279     MM_LOG ("Ignoring excessively deep MIME recursion",PARSE);
280   }
281   if (!body->subtype)           /* default subtype if still unknown */
282     body->subtype = cpystr (rfc822_default_subtype (body->type));
283                                 /* note offset and sizes */
284   body->contents.offset = GETPOS (bs);
285                                 /* note internal body size in all cases */
286   body->size.bytes = body->contents.text.size = i = SIZE (bs);
287   if (!(flags & DR_CRLF)) body->size.bytes = strcrlflen (bs);
288   switch (body->type) {         /* see if anything else special to do */
289   case TYPETEXT:                /* text content */
290     if (!body->parameter) {     /* no parameters set */
291       body->parameter = mail_newbody_parameter ();
292       body->parameter->attribute = cpystr ("CHARSET");
293       while (i--) {             /* count lines and guess charset */
294         c = SNX (bs);           /* get current character */
295                                 /* charset still unknown? */
296         if (!body->parameter->value) {
297           if ((c == I2C_ESC) && (i && i--) && ((c = SNX (bs)) == I2C_MULTI) &&
298               (i && i--) && (((c = SNX (bs)) == I2CS_94x94_JIS_NEW) ||
299                              (c == I2CS_94x94_JIS_OLD)))
300             body->parameter->value = cpystr ("ISO-2022-JP");
301           else if (c & 0x80) body->parameter->value = cpystr ("X-UNKNOWN");
302         }
303         if (c == '\n') body->size.lines++;
304       }
305                                 /* 7-bit content */
306       if (!body->parameter->value) switch (body->encoding) {
307       case ENC7BIT:             /* unadorned 7-bit */
308       case ENC8BIT:             /* unadorned 8-bit (but 7-bit content) */
309       case ENCBINARY:           /* binary (but 7-bit content( */
310         body->parameter->value = cpystr ("US-ASCII");
311         break;
312       default:                  /* QUOTED-PRINTABLE, BASE64, etc. */
313         body->parameter->value = cpystr ("X-UNKNOWN");
314         break;
315       }
316     }
317                                 /* just count lines */
318     else while (i--) if ((SNX (bs)) == '\n') body->size.lines++;
319     break;
320 \f
321   case TYPEMESSAGE:             /* encapsulated message */
322                                 /* encapsulated RFC-822 message? */
323     if (!strcmp (body->subtype,"RFC822")) {
324       body->nested.msg = mail_newmsg ();
325       switch (body->encoding) { /* make sure valid encoding */
326       case ENC7BIT:             /* these are valid nested encodings */
327       case ENC8BIT:
328       case ENCBINARY:
329         break;
330       default:
331         MM_LOG ("Ignoring nested encoding of message contents",PARSE);
332       }
333                                 /* hunt for blank line */
334       for (c = '\012',j = 0; (i > j) && ((c != '\012') || (CHR(bs) != '\012'));
335            j++) if ((c1 = SNX (bs)) != '\015') c = c1;
336       if (i > j) {              /* unless no more text */
337         c1 = SNX (bs);          /* body starts here */
338         j++;                    /* advance count */
339       }
340                                 /* note body text offset and header size */
341       body->nested.msg->header.text.size = j;
342       body->nested.msg->text.text.size = body->contents.text.size - j;
343       body->nested.msg->text.offset = GETPOS (bs);
344       body->nested.msg->full.offset = body->nested.msg->header.offset =
345         body->contents.offset;
346       body->nested.msg->full.text.size = body->contents.text.size;
347                                 /* copy header string */
348       SETPOS (bs,body->contents.offset);
349       s = (char *) fs_get ((size_t) j + 1);
350       for (s1 = s,k = j; k--; *s1++ = SNX (bs));
351       s[j] = '\0';              /* tie off string (not really necessary) */
352                                 /* now parse the body */
353       rfc822_parse_msg_full (&body->nested.msg->env,&body->nested.msg->body,s,
354                              j,bs,h,depth+1,flags);
355       fs_give ((void **) &s);   /* free header string */
356                                 /* restore position */
357       SETPOS (bs,body->contents.offset);
358     }
359                                 /* count number of lines */
360     while (i--) if (SNX (bs) == '\n') body->size.lines++;
361     break;
362   case TYPEMULTIPART:           /* multiple parts */
363     switch (body->encoding) {   /* make sure valid encoding */
364     case ENC7BIT:               /* these are valid nested encodings */
365     case ENC8BIT:
366     case ENCBINARY:
367       break;
368     default:
369       MM_LOG ("Ignoring nested encoding of multipart contents",PARSE);
370     }
371                                 /* remember if digest */
372     f = !strcmp (body->subtype,"DIGEST");
373                                 /* find cookie */
374     for (s1 = NIL,param = body->parameter; param && !s1; param = param->next)
375       if (!strcmp (param->attribute,"BOUNDARY")) s1 = param->value;
376     if (!s1) s1 = "-";          /* yucky default */
377     j = strlen (s1) + 2;        /* length of cookie and header */
378     c = '\012';                 /* initially at beginning of line */
379 \f
380     while (i > j) {             /* examine data */
381       if (m = GETPOS (bs)) m--; /* get position in front of character */
382       switch (c) {              /* examine each line */
383       case '\015':              /* handle CRLF form */
384         if (CHR (bs) == '\012'){/* following LF? */
385           c = SNX (bs); i--;    /* yes, slurp it */
386         }
387       case '\012':              /* at start of a line, start with -- ? */
388         if (!(i && i-- && ((c = SNX (bs)) == '-') && i-- &&
389               ((c = SNX (bs)) == '-'))) break;
390                                 /* see if cookie matches */
391         if (k = j - 2) for (s = s1; i-- && *s++ == (c = SNX (bs)) && --k;);
392         if (k) break;           /* strings didn't match if non-zero */
393                                 /* terminating delimiter? */
394         if ((c = ((i && i--) ? (SNX (bs)) : '\012')) == '-') {
395           if ((i && i--) && ((c = SNX (bs)) == '-') &&
396               ((i && i--) ? (((c = SNX (bs)) == '\015') || (c=='\012')):T)) {
397                                 /* if have a final part calculate its size */
398             if (part) part->body.mime.text.size =
399               (m > part->body.mime.offset) ? (m - part->body.mime.offset) :0;
400             part = NIL; i = 1;  /* terminate scan */
401           }
402           break;
403         }
404                                 /* swallow trailing whitespace */
405         while ((c == ' ') || (c == '\t'))
406           c = ((i && i--) ? (SNX (bs)) : '\012');
407         switch (c) {            /* need newline after all of it */
408         case '\015':            /* handle CRLF form */
409           if (i && CHR (bs) == '\012') {
410             c = SNX (bs); i--;/* yes, slurp it */
411           }
412         case '\012':            /* new line */
413           if (part) {           /* calculate size of previous */
414             part->body.mime.text.size =
415               (m > part->body.mime.offset) ? (m-part->body.mime.offset) : 0;
416             /* instantiate next */
417             part = part->next = mail_newbody_part ();
418           }                     /* otherwise start new list */
419           else part = body->nested.part = mail_newbody_part ();
420                                 /* digest has a different default */
421           if (f) part->body.type = TYPEMESSAGE;
422                                 /* note offset from main body */
423           part->body.mime.offset = GETPOS (bs);
424           break;
425         default:                /* whatever it was it wasn't valid */
426           break;
427         }
428         break;
429       default:                  /* not at a line */
430         c = SNX (bs); i--;      /* get next character */
431         break;
432       }
433     }
434 \f
435                                 /* calculate size of any final part */
436     if (part) part->body.mime.text.size = i +
437       ((GETPOS(bs) > part->body.mime.offset) ?
438        (GETPOS(bs) - part->body.mime.offset) : 0);
439                                 /* make a scratch buffer */
440     s1 = (char *) fs_get ((size_t) (k = MAILTMPLEN));
441                                 /* in case empty multipart */
442     if (!body->nested.part) body->nested.part = mail_newbody_part ();
443                                 /* parse non-empty body parts */
444     for (part = body->nested.part; part; part = part->next) {
445                                 /* part non-empty (header and/or content)? */
446       if (i = part->body.mime.text.size) {
447                                 /* move to that part of the body */
448         SETPOS (bs,part->body.mime.offset);
449                                 /* until end of header */
450         while (i && ((c = CHR (bs)) != '\015') && (c != '\012')) {
451                                 /* collect text until logical end of line */
452           for (j = 0,c = ' '; c; ) {
453                                 /* make sure buffer big enough */
454             if (j > (k - 10)) fs_resize ((void **) &s1,k += MAILTMPLEN);
455             switch (c1 = SNX (bs)) {
456             case '\015':        /* return */
457               if (i && (CHR (bs) == '\012')) {
458                 c1 = SNX (bs);  /* eat any LF following */
459                 i--;
460               }
461             case '\012':        /* newline, possible end of logical line */
462                                 /* tie off unless continuation */
463               if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t')))
464                 s1[j] = c = '\0';
465               break;
466             case '\t':          /* tab */
467             case ' ':           /* insert whitespace if not already there */
468               if (c != ' ') s1[j++] = c = ' ';
469               break;
470             default:            /* all other characters */
471               s1[j++] = c = c1; /* insert the character into the line */
472               break;
473             }
474                                 /* end of data ties off the header */
475             if (!i || !--i) s1[j++] = c = '\0';
476           }
477 \f
478                                 /* find header item type */
479           if (((s1[0] == 'C') || (s1[0] == 'c')) &&
480               ((s1[1] == 'O') || (s1[1] == 'o')) &&
481               ((s1[2] == 'N') || (s1[2] == 'n')) &&
482               ((s1[3] == 'T') || (s1[3] == 't')) &&
483               ((s1[4] == 'E') || (s1[4] == 'e')) &&
484               ((s1[5] == 'N') || (s1[5] == 'n')) &&
485               ((s1[6] == 'T') || (s1[6] == 't')) &&
486               (s1[7] == '-') && (s = strchr (s1+8,':'))) {
487                                 /* tie off and flush whitespace */
488             for (*s++ = '\0'; *s == ' '; s++);
489                                 /* parse the header */
490             rfc822_parse_content_header (&part->body,ucase (s1+8),s);
491           }
492         }
493                                 /* skip header trailing (CR)LF */
494         if (i && (CHR (bs) =='\015')) {i--; c1 = SNX (bs);}
495         if (i && (CHR (bs) =='\012')) {i--; c1 = SNX (bs);}
496         j = bs->size;           /* save upper level size */
497                                 /* set offset for next level, fake size to i */
498         bs->size = GETPOS (bs) + i;
499         part->body.mime.text.size -= i;
500                                 /* now parse it */
501         rfc822_parse_content (&part->body,bs,h,depth+1,flags);
502         bs->size = j;           /* restore current level size */
503       }
504       else {                    /* zero-length part, use default subtype */
505         part->body.subtype = cpystr (rfc822_default_subtype (part->body.type));
506                                 /* see if anything else special to do */
507         switch (part->body.type) {
508         case TYPETEXT:          /* text content */
509                                 /* default parameters */
510           if (!part->body.parameter) {
511             part->body.parameter = mail_newbody_parameter ();
512             part->body.parameter->attribute = cpystr ("CHARSET");
513                                 /* only assume US-ASCII if 7BIT */
514             part->body.parameter->value =
515               cpystr ((part->body.encoding == ENC7BIT) ?
516                       "US-ASCII" : "X-UNKNOWN");
517           }
518           break;
519         case TYPEMESSAGE:       /* encapsulated message in digest */
520           part->body.nested.msg = mail_newmsg ();
521           break;
522         default:
523           break;
524         }
525       }
526     }
527     fs_give ((void **) &s1);    /* finished with scratch buffer */
528     break;
529   default:                      /* nothing special to do in any other case */
530     break;
531   }
532 }
533 \f
534 /* Parse RFC 2822 body content header
535  * Accepts: body to write to
536  *          possible content name
537  *          remainder of header
538  */
539
540 void rfc822_parse_content_header (BODY *body,char *name,char *s)
541 {
542   char c,*t,tmp[MAILTMPLEN];
543   long i;
544   STRINGLIST *stl;
545   rfc822_skipws (&s);           /* skip leading comments */
546                                 /* flush whitespace */
547   if (t = strchr (name,' ')) *t = '\0';
548   switch (*name) {              /* see what kind of content */
549   case 'I':                     /* possible Content-ID */
550     if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
551     break;
552   case 'D':                     /* possible Content-Description */
553     if (!(strcmp (name+1,"ESCRIPTION") || body->description))
554       body->description = cpystr (s);
555     if (!(strcmp (name+1,"ISPOSITION") || body->disposition.type)) {
556                                 /* get type word */
557       if (!(name = rfc822_parse_word (s,tspecials))) break;
558       c = *name;                /* remember delimiter */
559       *name = '\0';             /* tie off type */
560       body->disposition.type = ucase (cpystr (s));
561       *name = c;                /* restore delimiter */
562       rfc822_skipws (&name);    /* skip whitespace */
563       rfc822_parse_parameter (&body->disposition.parameter,name);
564     }
565     break;
566   case 'L':                     /* possible Content-Language */
567     if (!(strcmp (name+1,"ANGUAGE") || body->language)) {
568       stl = NIL;                /* process languages */
569       while (s && (name = rfc822_parse_word (s,tspecials))) {
570         c = *name;              /* save delimiter */
571         *name = '\0';           /* tie off subtype */
572         if (stl) stl = stl->next = mail_newstringlist ();
573         else stl = body->language = mail_newstringlist ();
574         stl->text.data = (unsigned char *) ucase (cpystr (s));
575         stl->text.size = strlen ((char *) stl->text.data);
576         *name = c;              /* restore delimiter */
577         rfc822_skipws (&name);  /* skip whitespace */
578         if (*name == ',') {     /* any more languages? */
579           s = ++name;           /* advance to it them */
580           rfc822_skipws (&s);
581         }
582         else s = NIL;           /* bogus or end of list */
583       }
584     }
585     else if (!(strcmp (name+1,"OCATION") || body->location))
586       body->location = cpystr (s);
587     break;
588   case 'M':                     /* possible Content-MD5 */
589     if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
590     break;
591 \f
592   case 'T':                     /* possible Content-Type/Transfer-Encoding */
593     if (!(strcmp (name+1,"YPE") || body->subtype || body->parameter)) {
594                                 /* get type word */
595       if (!(name = rfc822_parse_word (s,tspecials))) break;
596       c = *name;                /* remember delimiter */
597       *name = '\0';             /* tie off type */
598                                 /* search for body type */
599       for (i = 0,s = rfc822_cpy (s);
600            (i <= TYPEMAX) && body_types[i] &&
601              compare_cstring (s,body_types[i]); i++);
602       if (i > TYPEMAX) {        /* fell off end of loop? */
603         body->type = TYPEOTHER; /* coerce to X-UNKNOWN */
604         sprintf (tmp,"MIME type table overflow: %.100s",s);
605         MM_LOG (tmp,PARSE);
606       }
607       else {                    /* record body type index */
608         body->type = (unsigned short) i;
609                                 /* and name if new type */
610         if (body_types[body->type]) fs_give ((void **) &s);
611         else {                  /* major MIME body type unknown to us */
612           body_types[body->type] = ucase (s);
613           sprintf (tmp,"Unknown MIME type: %.100s",s);
614           MM_LOG (tmp,PARSE);
615         }
616       }
617       *name = c;                /* restore delimiter */
618       rfc822_skipws (&name);    /* skip whitespace */
619       if ((*name == '/') &&     /* subtype? */
620           (name = rfc822_parse_word ((s = ++name),tspecials))) {
621         c = *name;              /* save delimiter */
622         *name = '\0';           /* tie off subtype */
623         rfc822_skipws (&s);     /* copy subtype */
624         if (s) body->subtype = ucase (rfc822_cpy (s));
625         *name = c;              /* restore delimiter */
626         rfc822_skipws (&name);  /* skip whitespace */
627       }
628       else if (!name) {         /* no subtype, was a subtype delimiter? */
629         name = s;               /* barf, restore pointer */
630         rfc822_skipws (&name);  /* skip leading whitespace */
631       }
632       rfc822_parse_parameter (&body->parameter,name);
633     }
634 \f
635     else if (!strcmp (name+1,"RANSFER-ENCODING")) {
636       if (!(name = rfc822_parse_word (s,tspecials))) break;
637       c = *name;                /* remember delimiter */
638       *name = '\0';             /* tie off encoding */
639                                 /* search for body encoding */      
640       for (i = 0,s = rfc822_cpy (s);
641            (i <= ENCMAX) && body_encodings[i] &&
642              compare_cstring (s,body_encodings[i]); i++);
643       if (i > ENCMAX) {         /* fell off end of loop? */
644         body->encoding = ENCOTHER;
645         sprintf (tmp,"MIME encoding table overflow: %.100s",s);
646         MM_LOG (tmp,PARSE);
647       }
648       else {                    /* record body encoding index */
649         body->encoding = (unsigned short) i;
650                                 /* and name if new encoding */
651         if (body_encodings[body->encoding]) fs_give ((void **) &s);
652         else {
653           body_encodings[body->encoding] = ucase (s);
654           sprintf (tmp,"Unknown MIME transfer encoding: %.100s",s);
655           MM_LOG (tmp,PARSE);
656         }
657       }
658       *name = c;                /* restore delimiter */
659       /* ??check for cruft here?? */
660     }
661     break;
662   default:                      /* otherwise unknown */
663     break;
664   }
665 }
666 \f
667 /* Parse RFC 2822 body parameter list
668  * Accepts: parameter list to write to
669  *          text of list
670  */
671
672 void rfc822_parse_parameter (PARAMETER **par,char *text)
673 {
674   char c,*s,tmp[MAILTMPLEN];
675   PARAMETER *param = NIL;
676                                 /* parameter list? */
677   while (text && (*text == ';') &&
678          (text = rfc822_parse_word ((s = ++text),tspecials))) {
679     c = *text;                  /* remember delimiter */
680     *text = '\0';               /* tie off attribute name */
681     rfc822_skipws (&s);         /* skip leading attribute whitespace */
682     if (!*s) *text = c;         /* must have an attribute name */
683     else {                      /* instantiate a new parameter */
684       if (*par) param = param->next = mail_newbody_parameter ();
685       else param = *par = mail_newbody_parameter ();
686       param->attribute = ucase (cpystr (s));
687       *text = c;                /* restore delimiter */
688       rfc822_skipws (&text);    /* skip whitespace before equal sign */
689       if ((*text == '=') &&     /* make sure have value */
690           (text = rfc822_parse_word ((s = ++text),tspecials))) {
691         c = *text;              /* remember delimiter */
692         *text = '\0';           /* tie off value */
693         rfc822_skipws (&s);     /* skip leading value whitespace */
694         if (*s) param->value = rfc822_cpy (s);
695         *text = c;              /* restore delimiter */
696         rfc822_skipws (&text);
697       }
698       if (!param->value) {      /* value not found? */
699         param->value = cpystr ("MISSING_PARAMETER_VALUE");
700         sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
701         MM_LOG (tmp,PARSE);
702       }
703     }
704   }
705                                 /* string not present */
706   if (!text) MM_LOG ("Missing parameter",PARSE);
707   else if (*text) {             /* must be end of poop */
708     sprintf (tmp,"Unexpected characters at end of parameters: %.80s",text);
709     MM_LOG (tmp,PARSE);
710   }
711 }
712 \f
713 /* Parse RFC 2822 address list
714  * Accepts: address list to write to
715  *          input string
716  *          default host name
717  */
718
719 void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
720 {
721   int c;
722   char *s,tmp[MAILTMPLEN];
723   ADDRESS *last = *lst;
724   ADDRESS *adr;
725   if (!string) return;          /* no string */
726   rfc822_skipws (&string);      /* skip leading WS */
727   if (!*string) return;         /* empty string */
728                                 /* run to tail of list */
729   if (last) while (last->next) last = last->next;
730   while (string) {              /* loop until string exhausted */
731     while (*string == ',') {    /* RFC 822 allowed null addresses!! */
732       ++string;                 /* skip the comma */
733       rfc822_skipws (&string);  /* and any leading WS */
734     }
735     if (!*string) string = NIL; /* punt if ran out of string */
736                                 /* got an address? */
737     else if (adr = rfc822_parse_address (lst,last,&string,host,0)) {
738       last = adr;               /* new tail address */
739       if (string) {             /* analyze what follows */
740         rfc822_skipws (&string);
741
742         /* Recovery from failure on parsing */
743         if ( string != NULL) {
744                 while (*string != ',' && *string != '\0')
745                         string++;
746     }
747
748         switch (c = *(unsigned char *) string) {
749         case ',':               /* comma? */
750           ++string;             /* then another address follows */
751           break;
752         default:
753           s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
754             "Unexpected characters at end of address: %.80s";
755           sprintf (tmp,s,string);
756           MM_LOG (tmp,PARSE);
757           last = last->next = mail_newaddr ();
758           last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
759           last->host = cpystr (errhst);
760                                 /* falls through */
761         case '\0':              /* null-specified address? */
762           string = NIL;         /* punt remainder of parse */
763           break;
764         }
765       }
766     }
767     else if (string) {          /* bad mailbox */
768       rfc822_skipws (&string);  /* skip WS */
769       if (!*string) strcpy (tmp,"Missing address after comma");
770       else sprintf (tmp,"Invalid mailbox list: %.80s",string);
771       MM_LOG (tmp,PARSE);
772       string = NIL;
773       (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS");
774       adr->host = cpystr (errhst);
775       if (last) last = last->next = adr;
776       else *lst = last = adr;
777       break;
778     }
779   }
780 }
781 \f
782 /* Parse RFC 2822 address
783  * Accepts: address list to write to
784  *          tail of address list
785  *          pointer to input string
786  *          default host name
787  *          group nesting depth
788  * Returns: new list tail
789  */
790
791 ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,
792                                char *defaulthost,unsigned long depth)
793 {
794   ADDRESS *adr;
795   if (!*string) return NIL;     /* no string */
796   rfc822_skipws (string);       /* skip leading WS */
797   if (!**string) return NIL;    /* empty string */
798   if (adr = rfc822_parse_group (lst,last,string,defaulthost,depth)) last = adr;
799                                 /* got an address? */
800   else if (adr = rfc822_parse_mailbox (string,defaulthost)) {
801     if (!*lst) *lst = adr;      /* yes, first time through? */
802     else last->next = adr;      /* no, append to the list */
803                                 /* set for subsequent linking */
804     for (last = adr; last->next; last = last->next);
805   }
806   else if (*string) return NIL;
807   return last;
808 }
809 \f
810 /* Parse RFC 2822 group
811  * Accepts: address list to write to
812  *          pointer to tail of address list
813  *          pointer to input string
814  *          default host name
815  *          group nesting depth
816  */
817
818 ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,
819                              char *defaulthost,unsigned long depth)
820 {
821   char tmp[MAILTMPLEN];
822   char *p,*s;
823   ADDRESS *adr;
824   if (depth > MAXGROUPDEPTH) {  /* excessively deep recursion? */
825     MM_LOG ("Ignoring excessively deep group recursion",PARSE);
826     return NIL;                 /* probably abusive */
827   }
828   if (!*string) return NIL;     /* no string */
829   rfc822_skipws (string);       /* skip leading WS */
830   if (!**string ||              /* trailing whitespace or not group */
831       ((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))
832     return NIL;
833   s = p;                        /* end of candidate phrase */
834   rfc822_skipws (&s);           /* find delimiter */
835   if (*s != ':') return NIL;    /* not really a group */
836   *p = '\0';                    /* tie off group name */
837   p = ++s;                      /* continue after the delimiter */
838   rfc822_skipws (&p);           /* skip subsequent whitespace */
839                                 /* write as address */
840   (adr = mail_newaddr ())->mailbox = rfc822_cpy (*string);
841   if (!*lst) *lst = adr;        /* first time through? */
842   else last->next = adr;        /* no, append to the list */
843   last = adr;                   /* set for subsequent linking */
844   *string = p;                  /* continue after this point */
845   while (*string && **string && (**string != ';')) {
846     if (adr = rfc822_parse_address (lst,last,string,defaulthost,depth+1)) {
847       last = adr;               /* new tail address */
848       if (*string) {            /* anything more? */
849         rfc822_skipws (string); /* skip whitespace */
850         switch (**string) {     /* see what follows */
851         case ',':               /* another address? */
852           ++*string;            /* yes, skip past the comma */
853         case ';':               /* end of group? */
854         case '\0':              /* end of string */
855           break;
856         default:
857           sprintf (tmp,"Unexpected characters after address in group: %.80s",
858                    *string);
859           MM_LOG (tmp,PARSE);
860           *string = NIL;        /* cancel remainder of parse */
861           last = last->next = mail_newaddr ();
862           last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP");
863           last->host = cpystr (errhst);
864         }
865       }
866     }
867     else {                      /* bogon */
868       sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
869       MM_LOG (tmp,PARSE);
870       *string = NIL;            /* cancel remainder of parse */
871       (adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP");
872       adr->host = cpystr (errhst);
873       last = last->next = adr;
874     }
875   }
876   if (*string) {                /* skip close delimiter */
877     if (**string == ';') ++*string;
878     rfc822_skipws (string);
879   }
880                                 /* append end of address mark to the list */
881   last->next = (adr = mail_newaddr ());
882   last = adr;                   /* set for subsequent linking */
883   return last;                  /* return the tail */
884 }
885 \f
886 /* Parse RFC 2822 mailbox
887  * Accepts: pointer to string pointer
888  *          default host
889  * Returns: address list
890  *
891  * Updates string pointer
892  */
893
894 ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
895 {
896   ADDRESS *adr = NIL;
897   char *s,*end;
898   parsephrase_t pp = (parsephrase_t) mail_parameters (NIL,GET_PARSEPHRASE,NIL);
899   if (!*string) return NIL;     /* no string */
900   rfc822_skipws (string);       /* flush leading whitespace */
901   if (!**string) return NIL;    /* empty string */
902   if (*(s = *string) == '<')    /* note start, handle case of phraseless RA */
903     adr = rfc822_parse_routeaddr (s,string,defaulthost);
904                                 /* otherwise, expect at least one word */
905   else if (end = rfc822_parse_phrase (s)) {
906     if ((adr = rfc822_parse_routeaddr (end,string,defaulthost))) {
907                                 /* phrase is a personal name */
908       if (adr->personal) fs_give ((void **) &adr->personal);
909       *end = '\0';              /* tie off phrase */
910       adr->personal = rfc822_cpy (s);
911     }
912                                 /* call external phraseparser if phrase only */
913     else if (pp && rfc822_phraseonly (end) &&
914              (adr = (*pp) (s,end,defaulthost))) {
915       *string = end;            /* update parse pointer */
916       rfc822_skipws (string);   /* skip WS in the normal way */
917     }
918     else adr = rfc822_parse_addrspec (s,string,defaulthost);
919   }
920   return adr;                   /* return the address */
921 }
922
923
924 /* Check if address is a phrase only
925  * Accepts: pointer to end of phrase
926  * Returns: T if phrase only, else NIL;
927  */
928
929 long rfc822_phraseonly (char *end)
930 {
931   while (*end == ' ') ++end;    /* call rfc822_skipws() instead?? */
932   switch (*end) {
933   case '\0': case ',': case ';':
934     return LONGT;               /* is a phrase only */
935   }
936   return NIL;                   /* something other than phase is here */
937 }
938 \f
939 /* Parse RFC 2822 route-address
940  * Accepts: string pointer
941  *          pointer to string pointer to update
942  * Returns: address
943  *
944  * Updates string pointer
945  */
946
947 ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost)
948 {
949   char tmp[MAILTMPLEN];
950   ADDRESS *adr;
951   char *s,*t,*adl;
952   size_t adllen,i;
953   if (!string) return NIL;
954   rfc822_skipws (&string);      /* flush leading whitespace */
955                                 /* must start with open broket */
956   if (*string != '<') return NIL;
957   t = ++string;                 /* see if A-D-L there */
958   rfc822_skipws (&t);           /* flush leading whitespace */
959   for (adl = NIL,adllen = 0;    /* parse possible A-D-L */
960        (*t == '@') && (s = rfc822_parse_domain (t+1,&t));) {
961     i = strlen (s) + 2;         /* @ plus domain plus delimiter or NUL */
962     if (adl) {                  /* have existing A-D-L? */
963       fs_resize ((void **) &adl,adllen + i);
964       sprintf (adl + adllen - 1,",@%s",s);
965     }
966                                 /* write initial A-D-L */
967     else sprintf (adl = (char *) fs_get (i),"@%s",s);
968     adllen += i;                /* new A-D-L length */
969     fs_give ((void **) &s);     /* don't need domain any more */
970     rfc822_skipws (&t);         /* skip WS */
971     if (*t != ',') break;       /* put if not comma */
972     t++;                        /* skip the comma */
973     rfc822_skipws (&t);         /* skip WS */
974   }
975   if (adl) {                    /* got an A-D-L? */
976     if (*t != ':') {            /* make sure syntax good */
977       sprintf (tmp,"Unterminated at-domain-list: %.80s%.80s",adl,t);
978       MM_LOG (tmp,PARSE);
979     }
980     else string = ++t;          /* continue parse from this point */
981   }
982 \f
983                                 /* parse address spec */
984   if (!(adr = rfc822_parse_addrspec (string,ret,defaulthost))) {
985     if (adl) fs_give ((void **) &adl);
986     return NIL;
987   }
988   if (adl) adr->adl = adl;      /* have an A-D-L? */
989   if (*ret) if (**ret == '>') { /* make sure terminated OK */
990     ++*ret;                     /* skip past the broket */
991     rfc822_skipws (ret);        /* flush trailing WS */
992     if (!**ret) *ret = NIL;     /* wipe pointer if at end of string */
993     return adr;                 /* return the address */
994   }
995   if (adr) {
996           sprintf (tmp,"Unterminated mailbox: %.80s@%.80s", (adr->mailbox == NULL) ? "<null>" : adr->mailbox,
997                           (adr->host == NULL || *adr->host == '@') ? "<null>" : adr->host);
998           MM_LOG (tmp,PARSE);
999   }
1000   adr->next = mail_newaddr ();
1001   adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR");
1002   adr->next->host = cpystr (errhst);
1003   return adr;                   /* return the address */
1004 }
1005 \f
1006 /* Parse RFC 2822 address-spec
1007  * Accepts: string pointer
1008  *          pointer to string pointer to update
1009  *          default host
1010  * Returns: address
1011  *
1012  * Updates string pointer
1013  */
1014
1015 ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost)
1016 {
1017   ADDRESS *adr;
1018   char c,*s,*t,*v,*end;
1019   if (!string) return NIL;      /* no string */
1020   rfc822_skipws (&string);      /* flush leading whitespace */
1021   if (!*string) return NIL;     /* empty string */
1022                                 /* find end of mailbox */
1023   if (!(t = rfc822_parse_word (string,wspecials))) return NIL;
1024   adr = mail_newaddr ();        /* create address block */
1025   c = *t;                       /* remember delimiter */
1026   *t = '\0';                    /* tie off mailbox */
1027                                 /* copy mailbox */
1028   adr->mailbox = rfc822_cpy (string);
1029   *t = c;                       /* restore delimiter */
1030   end = t;                      /* remember end of mailbox */
1031   rfc822_skipws (&t);           /* skip whitespace */
1032   while (*t == '.') {           /* some cretin taking RFC 822 too seriously? */
1033     string = ++t;               /* skip past the dot and any WS */
1034     rfc822_skipws (&string);
1035                                 /* get next word of mailbox */
1036     if (t = rfc822_parse_word (string,wspecials)) {
1037       end = t;                  /* remember new end of mailbox */
1038       c = *t;                   /* remember delimiter */
1039       *t = '\0';                /* tie off word */
1040       s = rfc822_cpy (string);  /* copy successor part */
1041       *t = c;                   /* restore delimiter */
1042                                 /* build new mailbox */
1043       sprintf (v = (char *) fs_get (strlen (adr->mailbox) + strlen (s) + 2),
1044                "%s.%s",adr->mailbox,s);
1045       fs_give ((void **) &adr->mailbox);
1046       adr->mailbox = v;         /* new host name */
1047       rfc822_skipws (&t);       /* skip WS after mailbox */
1048     }
1049     else {                      /* barf */
1050       MM_LOG ("Invalid mailbox part after .",PARSE);
1051       break;
1052     }
1053   }
1054   t = end;                      /* remember delimiter in case no host */
1055 \f
1056   rfc822_skipws (&end);         /* sniff ahead at what follows */
1057 #if RFC733                      /* RFC 733 used "at" instead of "@" */
1058   if (((*end == 'a') || (*end == 'A')) &&
1059       ((end[1] == 't') || (end[1] == 'T')) &&
1060       ((end[2] == ' ') || (end[2] == '\t') || (end[2] == '\015') ||
1061        (end[2] == '\012') || (end[2] == '(')))
1062     *++end = '@';
1063 #endif
1064   if (*end != '@') end = t;     /* host name missing */
1065                                 /* otherwise parse host name */
1066   else if (!(adr->host = rfc822_parse_domain (++end,&end)))
1067     adr->host = cpystr (errhst);
1068                                 /* default host if missing */
1069   if (!adr->host) adr->host = cpystr (defaulthost);
1070                                 /* try person name in comments if missing */
1071   if (end && !(adr->personal && *adr->personal)) {
1072     while (*end == ' ') ++end;  /* see if we can find a person name here */
1073     if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s))
1074       adr->personal = rfc822_cpy (s);
1075     rfc822_skipws (&end);       /* skip any other WS in the normal way */
1076   }
1077                                 /* set return to end pointer */
1078   *ret = (end && *end) ? end : NIL;
1079   return adr;                   /* return the address we got */
1080 }
1081 \f
1082 /* Parse RFC 2822 domain
1083  * Accepts: string pointer
1084  *          pointer to return end of domain
1085  * Returns: domain name or NIL if failure
1086  */
1087
1088 char *rfc822_parse_domain (char *string,char **end)
1089 {
1090   char *ret = NIL;
1091   char c,*s,*t,*v;
1092   rfc822_skipws (&string);      /* skip whitespace */
1093   if (*string == '[') {         /* domain literal? */
1094     if (!(*end = rfc822_parse_word (string + 1,"]\\")))
1095       MM_LOG ("Empty domain literal",PARSE);
1096     else if (**end != ']') MM_LOG ("Unterminated domain literal",PARSE);
1097     else {
1098       size_t len = ++*end - string;
1099       strncpy (ret = (char *) fs_get (len + 1),string,len);
1100       ret[len] = '\0';          /* tie off literal */
1101     }
1102   }
1103                                 /* search for end of host */
1104   else if (t = rfc822_parse_word (string,wspecials)) {
1105     c = *t;                     /* remember delimiter */
1106     *t = '\0';                  /* tie off host */
1107     ret = rfc822_cpy (string);  /* copy host */
1108     *t = c;                     /* restore delimiter */
1109     *end = t;                   /* remember end of domain */
1110     rfc822_skipws (&t);         /* skip WS after host */
1111     while (*t == '.') {         /* some cretin taking RFC 822 too seriously? */
1112       string = ++t;             /* skip past the dot and any WS */
1113       rfc822_skipws (&string);
1114       if (string = rfc822_parse_domain (string,&t)) {
1115         *end = t;               /* remember new end of domain */
1116         c = *t;                 /* remember delimiter */
1117         *t = '\0';              /* tie off host */
1118         s = rfc822_cpy (string);/* copy successor part */
1119         *t = c;                 /* restore delimiter */
1120                                 /* build new domain */
1121         sprintf (v = (char *) fs_get (strlen (ret) + strlen (s) + 2),
1122                  "%s.%s",ret,s);
1123         fs_give ((void **) &ret);
1124         ret = v;                /* new host name */
1125         rfc822_skipws (&t);     /* skip WS after domain */
1126       }
1127       else {                    /* barf */
1128         MM_LOG ("Invalid domain part after .",PARSE);
1129         break;
1130       }
1131     }
1132   }
1133   else MM_LOG ("Missing or invalid host name after @",PARSE);
1134   return ret;
1135 }
1136 \f
1137 /* Parse RFC 2822 phrase
1138  * Accepts: string pointer
1139  * Returns: pointer to end of phrase
1140  */
1141
1142 char *rfc822_parse_phrase (char *s)
1143 {
1144   char *curpos;
1145   if (!s) return NIL;           /* no-op if no string */
1146                                 /* find first word of phrase */
1147   curpos = rfc822_parse_word (s,NIL);
1148   if (!curpos) return NIL;      /* no words means no phrase */
1149   if (!*curpos) return curpos;  /* check if string ends with word */
1150   s = curpos;                   /* sniff past the end of this word and WS */
1151   rfc822_skipws (&s);           /* skip whitespace */
1152                                 /* recurse to see if any more */
1153   return (s = rfc822_parse_phrase (s)) ? s : curpos;
1154 }
1155 \f
1156 /* Parse RFC 2822 word
1157  * Accepts: string pointer
1158  *          delimiter (or NIL for phrase word parsing)
1159  * Returns: pointer to end of word
1160  */
1161
1162 char *rfc822_parse_word (char *s,const char *delimiters)
1163 {
1164   char *st,*str;
1165   if (!s) return NIL;           /* no string */
1166   rfc822_skipws (&s);           /* flush leading whitespace */
1167   if (!*s) return NIL;          /* empty string */
1168   str = s;                      /* hunt pointer for strpbrk */
1169   while (T) {                   /* look for delimiter, return if none */
1170     if (!(st = strpbrk (str,delimiters ? delimiters : wspecials)))
1171       return str + strlen (str);
1172                                 /* ESC in phrase */
1173     if (!delimiters && (*st == I2C_ESC)) {
1174       str = ++st;               /* always skip past ESC */
1175       switch (*st) {            /* special hack for RFC 1468 (ISO-2022-JP) */
1176       case I2C_MULTI:           /* multi byte sequence */
1177         switch (*++st) {
1178         case I2CS_94x94_JIS_OLD:/* old JIS (1978) */
1179         case I2CS_94x94_JIS_NEW:/* new JIS (1983) */
1180           str = ++st;           /* skip past the shift to JIS */
1181           while (st = strchr (st,I2C_ESC))
1182             if ((*++st == I2C_G0_94) && ((st[1] == I2CS_94_ASCII) ||
1183                                          (st[1] == I2CS_94_JIS_ROMAN) ||
1184                                          (st[1] == I2CS_94_JIS_BUGROM))) {
1185               str = st += 2;    /* skip past the shift back to ASCII */
1186               break;
1187             }
1188                                 /* eats entire text if no shift back */
1189           if (!st || !*st) return str + strlen (str);
1190         }
1191         break;
1192       case I2C_G0_94:           /* single byte sequence */
1193         switch (st[1]) {
1194         case I2CS_94_ASCII:     /* shift to ASCII */
1195         case I2CS_94_JIS_ROMAN: /* shift to JIS-Roman */
1196         case I2CS_94_JIS_BUGROM:/* old buggy definition of JIS-Roman */
1197           str = st + 2;         /* skip past the shift */
1198           break;
1199         }
1200       }
1201     }
1202 \f
1203     else switch (*st) {         /* dispatch based on delimiter */
1204     case '"':                   /* quoted string */
1205                                 /* look for close quote */
1206       while (*++st != '"') switch (*st) {
1207       case '\0':                /* unbalanced quoted string */
1208         return NIL;             /* sick sick sick */
1209       case '\\':                /* quoted character */
1210         if (!*++st) return NIL; /* skip the next character */
1211       default:                  /* ordinary character */
1212         break;                  /* no special action */
1213       }
1214       str = ++st;               /* continue parse */
1215       break;
1216     case '\\':                  /* quoted character */
1217       /* This is wrong; a quoted-pair can not be part of a word.  However,
1218        * domain-literal is parsed as a word and quoted-pairs can be used
1219        * *there*.  Either way, it's pretty pathological.
1220        */
1221       if (st[1]) {              /* not on NUL though... */
1222         str = st + 2;           /* skip quoted character and go on */
1223         break;
1224       }
1225     default:                    /* found a word delimiter */
1226       return (st == s) ? NIL : st;
1227     }
1228   }
1229 }
1230 \f
1231 /* Copy an RFC 2822 format string
1232  * Accepts: string
1233  * Returns: copy of string
1234  */
1235
1236 char *rfc822_cpy (char *src)
1237 {
1238                                 /* copy and unquote */
1239   return rfc822_quote (cpystr (src));
1240 }
1241
1242
1243 /* Unquote an RFC 2822 format string
1244  * Accepts: string
1245  * Returns: string
1246  */
1247
1248 char *rfc822_quote (char *src)
1249 {
1250   char *ret = src;
1251   if (strpbrk (src,"\\\"")) {   /* any quoting in string? */
1252     char *dst = ret;
1253     while (*src) {              /* copy string */
1254       if (*src == '\"') src++;  /* skip double quote entirely */
1255       else {
1256         if (*src == '\\') src++;/* skip over single quote, copy next always */
1257         *dst++ = *src++;        /* copy character */
1258       }
1259     }
1260     *dst = '\0';                /* tie off string */
1261   }
1262   return ret;                   /* return our string */
1263 }
1264
1265
1266 /* Copy address list
1267  * Accepts: address list
1268  * Returns: address list
1269  */
1270
1271 ADDRESS *rfc822_cpy_adr (ADDRESS *adr)
1272 {
1273   ADDRESS *dadr;
1274   ADDRESS *ret = NIL;
1275   ADDRESS *prev = NIL;
1276   while (adr) {                 /* loop while there's still an MAP adr */
1277     dadr = mail_newaddr ();     /* instantiate a new address */
1278     if (!ret) ret = dadr;       /* note return */
1279     if (prev) prev->next = dadr;/* tie on to the end of any previous */
1280     dadr->personal = cpystr (adr->personal);
1281     dadr->adl = cpystr (adr->adl);
1282     dadr->mailbox = cpystr (adr->mailbox);
1283     dadr->host = cpystr (adr->host);
1284     prev = dadr;                /* this is now the previous */
1285     adr = adr->next;            /* go to next address in list */
1286   }
1287   return (ret);                 /* return the MTP address list */
1288 }
1289 \f
1290 /* Skips RFC 2822 whitespace
1291  * Accepts: pointer to string pointer
1292  */
1293
1294 void rfc822_skipws (char **s)
1295 {
1296   while (T) switch (**s) {
1297   case ' ': case '\t': case '\015': case '\012':
1298     ++*s;                       /* skip all forms of LWSP */
1299     break;
1300   case '(':                     /* start of comment */
1301     if (rfc822_skip_comment (s,(long) NIL)) break;
1302   default:
1303     return;                     /* end of whitespace */
1304   }
1305 }
1306
1307
1308 /* Skips RFC 2822 comment
1309  * Accepts: pointer to string pointer
1310  *          trim flag
1311  * Returns: pointer to first non-blank character of comment
1312  */
1313
1314 char *rfc822_skip_comment (char **s,long trim)
1315 {
1316   char *ret,tmp[MAILTMPLEN];
1317   char *s1 = *s;
1318   char *t = NIL;
1319                                 /* skip past whitespace */
1320   for (ret = ++s1; *ret == ' '; ret++);
1321   do switch (*s1) {             /* get character of comment */
1322   case '(':                     /* nested comment? */
1323     if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL;
1324     t = --s1;                   /* last significant char at end of comment */
1325     break;
1326   case ')':                     /* end of comment? */
1327     *s = ++s1;                  /* skip past end of comment */
1328     if (trim) {                 /* if level 0, must trim */
1329       if (t) t[1] = '\0';       /* tie off comment string */
1330       else *ret = '\0';         /* empty comment */
1331     }
1332     return ret;
1333   case '\\':                    /* quote next character? */
1334     if (*++s1) {                /* next character non-null? */
1335       t = s1;                   /* update last significant character pointer */
1336       break;                    /* all OK */
1337     }
1338   case '\0':                    /* end of string */
1339     sprintf (tmp,"Unterminated comment: %.80s",*s);
1340     MM_LOG (tmp,PARSE);
1341     **s = '\0';                 /* nuke duplicate messages in case reparse */
1342     return NIL;                 /* this is wierd if it happens */
1343   case ' ':                     /* whitespace isn't significant */
1344     break;
1345   default:                      /* random character */
1346     t = s1;                     /* update last significant character pointer */
1347     break;
1348   } while (s1++);
1349   return NIL;                   /* impossible, but pacify lint et al */
1350 }
1351 \f
1352 /* Buffered output routines */
1353
1354
1355 /* Output character to buffer
1356  * Accepts: buffer
1357  *          character to write
1358  * Returns: T if success, NIL if error
1359  */
1360
1361 static long rfc822_output_char (RFC822BUFFER *buf,int c)
1362 {
1363   if ((buf->cur == buf->end) && !rfc822_output_flush (buf)) return NIL;
1364   *buf->cur++ = c;              /* add character, soutr buffer if full */
1365   return (buf->cur == buf->end) ? rfc822_output_flush (buf) : LONGT;
1366 }
1367
1368
1369 /* Output data to buffer
1370  * Accepts: buffer
1371  *          data to write
1372  *          size of data
1373  * Returns: T if success, NIL if error
1374  */
1375
1376 static long rfc822_output_data (RFC822BUFFER *buf,char *string,long len)
1377 {
1378   while (len) {                 /* until request satified */
1379     long i;
1380     if (i = min (len,buf->end - buf->cur)) {
1381       memcpy (buf->cur,string,i);
1382       buf->cur += i;            /* blat data */
1383       string += i;
1384       len -= i;
1385     }
1386                                 /* soutr buffer now if full */
1387     if ((len || (buf->cur == buf->end)) && !rfc822_output_flush (buf))
1388       return NIL;
1389   }
1390   return LONGT;
1391 }
1392 \f
1393 /* Output string to buffer
1394  * Accepts: buffer
1395  *          string to write
1396  * Returns: T if success, NIL if error
1397  */
1398
1399 static long rfc822_output_string (RFC822BUFFER *buf,char *string)
1400 {
1401   return rfc822_output_data (buf,string,strlen (string));
1402 }
1403
1404
1405 /* Flush buffer
1406  * Accepts: buffer
1407  *          I/O routine
1408  *          stream for I/O routine
1409  * Returns: T if success, NIL if error
1410  */
1411
1412 long rfc822_output_flush (RFC822BUFFER *buf)
1413 {
1414   *buf->cur = '\0';             /* tie off buffer at this point */
1415   return (*buf->f) (buf->s,buf->cur = buf->beg);
1416 }
1417 \f
1418 /* Message writing routines */
1419
1420
1421 /* Output RFC 822 message
1422  * Accepts: temporary buffer as a SIZEDTEXT
1423  *          envelope
1424  *          body
1425  *          I/O routine
1426  *          stream for I/O routine
1427  *          non-zero if 8-bit output desired
1428  * Returns: T if successful, NIL if failure
1429  *
1430  * This routine always uses standard specials for phrases and does not write
1431  * bcc entries, since it is called from the SMTP and NNTP routines.  If you
1432  * need to do something different you need to arm an rfc822outfull_t and/or
1433  * rfc822out_t function.
1434  */
1435
1436 long rfc822_output_full (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,long ok8)
1437 {
1438   rfc822outfull_t r822of =
1439     (rfc822outfull_t) mail_parameters (NIL,GET_RFC822OUTPUTFULL,NIL);
1440   rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
1441                                 /* call external RFC 2822 output generator */
1442   if (r822of) return (*r822of) (buf,env,body,ok8);
1443   else if (r822o) return (*r822o) (buf->cur,env,body,buf->f,buf->s,ok8);
1444                                 /* encode body as necessary */
1445   if (ok8) rfc822_encode_body_8bit (env,body);
1446   else rfc822_encode_body_7bit (env,body);
1447                                 /* output header and body */
1448   return rfc822_output_header (buf,env,body,NIL,NIL) &&
1449     rfc822_output_text (buf,body) && rfc822_output_flush (buf);
1450 }
1451 \f
1452 /* Output RFC 822 header
1453  * Accepts: buffer
1454  *          envelope
1455  *          body
1456  *          non-standard specials to be used for phrases if non-NIL
1457  *          flags (non-zero to include bcc
1458  * Returns: T if success, NIL if failure
1459  */
1460
1461 long rfc822_output_header (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,
1462                            const char *specials,long flags)
1463 {
1464   long i = env->remail ? strlen (env->remail) : 0;
1465   return                        /* write header */
1466     (!i ||                    /* snip extra CRLF from remail header */
1467      rfc822_output_data (buf,env->remail,
1468                          ((i > 4) && (env->remail[i-4] == '\015')) ?
1469                          i - 2 : i)) &&
1470     rfc822_output_header_line (buf,"Newsgroups",i,env->newsgroups) &&
1471     rfc822_output_header_line (buf,"Date",i,env->date) &&
1472     rfc822_output_address_line (buf,"From",i,env->from,specials) &&
1473     rfc822_output_address_line (buf,"Sender",i,env->sender,specials) &&
1474     rfc822_output_address_line (buf,"Reply-To",i,env->reply_to,specials) &&
1475     rfc822_output_header_line (buf,"Subject",i,env->subject) &&
1476     ((env->bcc && !(env->to || env->cc)) ?
1477      rfc822_output_string (buf,"To: undisclosed recipients: ;\015\012") :
1478      LONGT) &&
1479     rfc822_output_address_line (buf,"To",i,env->to,specials) &&
1480     rfc822_output_address_line (buf,"cc",i,env->cc,specials) &&
1481     (flags ? rfc822_output_address_line (buf,"bcc",i,env->bcc,specials) : T) &&
1482     rfc822_output_header_line (buf,"In-Reply-To",i,env->in_reply_to) &&
1483     rfc822_output_header_line (buf,"Message-ID",i,env->message_id) &&
1484     rfc822_output_header_line (buf,"Followup-to",i,env->followup_to) &&
1485     rfc822_output_header_line (buf,"References",i,env->references) &&
1486     (env->remail || !body ||
1487      (rfc822_output_string (buf,"MIME-Version: 1.0\015\012") &&
1488       rfc822_output_body_header (buf,body))) &&
1489                                 /* write terminating blank line */
1490     rfc822_output_string (buf,"\015\012");
1491 }
1492 \f
1493 /* Output RFC 2822 header text line
1494  * Accepts: buffer
1495  *          pointer to header type
1496  *          non-NIL if resending
1497  *          pointer to text
1498  * Returns: T if success, NIL if failure
1499  */
1500
1501 long rfc822_output_header_line (RFC822BUFFER *buf,char *type,long resent,
1502                                 char *text)
1503 {
1504   return !text ||
1505     ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
1506      rfc822_output_string (buf,type) && rfc822_output_string (buf,": ") &&
1507      rfc822_output_string (buf,text) && rfc822_output_string (buf,"\015\012"));
1508 }
1509
1510
1511 /* Output RFC 2822 header address line
1512  * Accepts: buffer
1513  *          pointer to header type
1514  *          non-NIL if resending
1515  *          address(s) to interpret
1516  *          non-standard specials to be used for phrases if non-NIL
1517  * Returns: T if success, NIL if failure
1518  */
1519
1520 long rfc822_output_address_line (RFC822BUFFER *buf,char *type,long resent,
1521                                  ADDRESS *adr,const char *specials)
1522 {
1523   long pretty = strlen (type);
1524   return !adr ||
1525     ((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
1526      rfc822_output_data (buf,type,pretty) && rfc822_output_string (buf,": ") &&
1527      rfc822_output_address_list (buf,adr,
1528                                  resent ? pretty + sizeof (RESENTPREFIX) - 1 :
1529                                  pretty,specials) &&
1530      rfc822_output_string (buf,"\015\012"));
1531 }
1532 \f
1533 /* Output RFC 2822 address list
1534  * Accepts: buffer
1535  *          pointer to address list
1536  *          non-zero if pretty-printing
1537  *          non-standard specials to be used for phrases if non-NIL
1538  * Returns: T if success, NIL if failure
1539  */
1540
1541 long rfc822_output_address_list (RFC822BUFFER *buf,ADDRESS *adr,long pretty,
1542                                  const char *specials)
1543 {
1544   long n;
1545                                 /* default to rspecials */
1546   if (!specials) specials = rspecials;
1547   for (n = 0; adr; adr = adr->next) {
1548     char *base = buf->cur;
1549     if (adr->host) {            /* ordinary address? */
1550       if (!(pretty && n)) {     /* suppress if pretty and in group */
1551         if (                    /* use phrase <route-addr> if phrase */
1552 #if RFC822
1553             adr->adl ||         /* or A-D-L */
1554 #endif
1555             (adr->personal && *adr->personal)) {
1556           if (!((adr->personal ? rfc822_output_cat (buf,adr->personal,
1557                                                     rspecials) : LONGT) &&
1558                 rfc822_output_string (buf," <") &&
1559                 rfc822_output_address (buf,adr) &&
1560                 rfc822_output_string (buf,">"))) return NIL;
1561         }
1562         else if (!rfc822_output_address (buf,adr)) return NIL;
1563         if (adr->next && adr->next->mailbox &&
1564             !rfc822_output_string (buf,", ")) return NIL;
1565       }
1566     }
1567     else if (adr->mailbox) {    /* start of group? */
1568                                 /* yes, write group */
1569       if (!(rfc822_output_cat (buf,adr->mailbox,rspecials) &&
1570             rfc822_output_string (buf,": "))) return NIL;
1571       ++n;                      /* in a group now */
1572     }
1573     else if (n) {               /* must be end of group (but be paranoid) */
1574       if (!rfc822_output_char (buf,';') ||
1575           ((!--n && adr->next && adr->next->mailbox) &&
1576            !rfc822_output_string (buf,", "))) return NIL;
1577     }
1578     if (pretty && adr->next &&  /* pretty printing? */
1579         ((pretty += ((buf->cur > base) ? buf->cur - base :
1580                      (buf->end - base) + (buf->cur - buf->beg))) >= 78)) {
1581       if (!(rfc822_output_string (buf,"\015\012") &&
1582             rfc822_output_string (buf,RFC822CONT))) return NIL;
1583       base = buf->cur;  /* update base for pretty printing */
1584       pretty = sizeof (RFC822CONT) - 1;
1585     }
1586   }
1587   return LONGT;
1588 }
1589 \f
1590 /* Write RFC 2822 route-address to string
1591  * Accepts: buffer
1592  *          pointer to single address
1593  * Returns: T if success, NIL if failure
1594  */
1595
1596 long rfc822_output_address (RFC822BUFFER *buf,ADDRESS *adr)
1597 {
1598   return !adr || !adr->host ||
1599     (
1600 #if RFC822                      /* old code with A-D-L support */
1601      (!adr->adl || (rfc822_output_string (buf,adr->adl) &&
1602                     rfc822_output_char (buf,':'))) &&
1603 #endif
1604      rfc822_output_cat (buf,adr->mailbox,NIL) &&
1605      ((*adr->host == '@') ||    /* unless null host (HIGHLY discouraged!) */
1606       (rfc822_output_char (buf,'@') &&
1607        rfc822_output_cat (buf,adr->host,NIL))));
1608 }
1609
1610
1611 /* Output RFC 2822 string with concatenation
1612  * Accepts: buffer
1613  *          string to concatenate
1614  *          list of special characters or NIL for dot-atom format
1615  * Returns: T if success, NIL if failure
1616  */
1617
1618 long rfc822_output_cat (RFC822BUFFER *buf,char *src,const char *specials)
1619 {
1620   char *s;
1621   if (!*src ||                  /* empty string or any specials present? */
1622       (specials ? (T && strpbrk (src,specials)) :
1623        (strpbrk (src,wspecials) || (*src == '.') || strstr (src,"..") ||
1624         (src[strlen (src) - 1] == '.')))) {
1625                                 /* yes, write as quoted string*/
1626     if (!rfc822_output_char (buf,'"')) return NIL;
1627                                 /* embedded quote characters? */
1628     for (; s = strpbrk (src,"\\\""); src = s + 1) {
1629                                 /* yes, insert quoting */
1630       if (!(rfc822_output_data (buf,src,s-src) &&
1631             rfc822_output_char (buf,'\\') &&
1632             rfc822_output_char (buf,*s))) return NIL;
1633     }
1634                                 /* return string and trailing quote*/
1635     return rfc822_output_string (buf,src) && rfc822_output_char (buf,'"');
1636   }
1637                                 /* easy case */
1638   return rfc822_output_string (buf,src);
1639 }
1640 \f
1641 /* Output MIME parameter list
1642  * Accepts: buffer
1643  *          parameter list
1644  * Returns: T if success, NIL if failure
1645  */
1646
1647 long rfc822_output_parameter (RFC822BUFFER *buf,PARAMETER *param)
1648 {
1649   while (param) {
1650     if (rfc822_output_string (buf,"; ") &&
1651         rfc822_output_string (buf,param->attribute) &&
1652         rfc822_output_char (buf,'=') &&
1653         rfc822_output_cat (buf,param->value,tspecials)) param = param->next;
1654     else return NIL;
1655   }
1656   return LONGT;
1657 }
1658
1659
1660 /* Output RFC 2822 stringlist
1661  * Accepts: buffer
1662  *          stringlist
1663  * Returns: T if success, NIL if failure
1664  */
1665
1666 long rfc822_output_stringlist (RFC822BUFFER *buf,STRINGLIST *stl)
1667 {
1668   while (stl)
1669     if (!rfc822_output_cat (buf,(char *) stl->text.data,tspecials) ||
1670         ((stl = stl->next) && !rfc822_output_string (buf,", ")))
1671       return NIL;
1672   return LONGT;
1673 }
1674 \f
1675 /* Output body content header
1676  * Accepts: buffer
1677  *          body to interpret
1678  * Returns: T if success, NIL if failure
1679  */
1680
1681 long rfc822_output_body_header (RFC822BUFFER *buf,BODY *body)
1682 {
1683   return                        /* type and subtype*/
1684     rfc822_output_string (buf,"Content-Type: ") &&
1685     rfc822_output_string (buf,body_types[body->type]) &&
1686     rfc822_output_char (buf,'/') &&
1687     rfc822_output_string (buf,body->subtype ? body->subtype :
1688                           rfc822_default_subtype (body->type)) &&
1689                                 /* parameters (w/ US-ASCII default */
1690     (body->parameter ? rfc822_output_parameter (buf,body->parameter) :
1691      ((body->type != TYPETEXT) ||
1692       (rfc822_output_string (buf,"; CHARSET=") &&
1693        rfc822_output_string (buf,(body->encoding == ENC7BIT) ?
1694                              "US-ASCII" : "X-UNKNOWN")))) &&
1695     (!body->encoding ||     /* note: 7BIT never output as encoding! */
1696      (rfc822_output_string (buf,"\015\012Content-Transfer-Encoding: ") &&
1697       rfc822_output_string (buf,body_encodings[body->encoding]))) &&
1698     (!body->id ||               /* identification */
1699      (rfc822_output_string (buf,"\015\012Content-ID: ") &&
1700       rfc822_output_string (buf,body->id))) &&
1701     (!body->description ||      /* description */
1702      (rfc822_output_string (buf,"\015\012Content-Description: ") &&
1703       rfc822_output_string (buf,body->description))) &&
1704     (!body->md5 ||              /* MD5 checksum */
1705      (rfc822_output_string (buf,"\015\012Content-MD5: ") &&
1706       rfc822_output_string (buf,body->md5))) &&
1707     (!body->language ||         /* language */
1708      (rfc822_output_string (buf,"\015\012Content-Language: ") &&
1709       rfc822_output_stringlist (buf,body->language))) &&
1710     (!body->location ||         /* location */
1711      (rfc822_output_string (buf,"\015\012Content-Location: ") &&
1712       rfc822_output_string (buf,body->location))) &&
1713     (!body->disposition.type || /* disposition */
1714      (rfc822_output_string (buf,"\015\012Content-Disposition: ") &&
1715       rfc822_output_string (buf,body->disposition.type) &&
1716       rfc822_output_parameter (buf,body->disposition.parameter))) &&
1717     rfc822_output_string (buf,"\015\012");
1718 }
1719 \f
1720 /* Encode a body for 7BIT transmittal
1721  * Accepts: envelope
1722  *          body
1723  */
1724
1725 void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body)
1726 {
1727   void *f;
1728   PART *part;
1729   PARAMETER **param;
1730   if (body) switch (body->type) {
1731   case TYPEMULTIPART:           /* multi-part */
1732     for (param = &body->parameter;
1733          *param && strcmp ((*param)->attribute,"BOUNDARY");
1734          param = &(*param)->next);
1735     if (!*param) {              /* cookie not set up yet? */
1736       char tmp[MAILTMPLEN];     /* make cookie not in BASE64 or QUOTEPRINT*/
1737       sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1738                (unsigned long) random (),(unsigned long) time (0),
1739                (unsigned long) getpid ());
1740       (*param) = mail_newbody_parameter ();
1741       (*param)->attribute = cpystr ("BOUNDARY");
1742       (*param)->value = cpystr (tmp);
1743     }
1744     part = body->nested.part;   /* encode body parts */
1745     do rfc822_encode_body_7bit (env,&part->body);
1746     while (part = part->next);  /* until done */
1747     break;
1748   case TYPEMESSAGE:             /* encapsulated message */
1749     switch (body->encoding) {
1750     case ENC7BIT:
1751       break;
1752     case ENC8BIT:
1753       MM_LOG ("8-bit included message in 7-bit message body",PARSE);
1754       break;
1755     case ENCBINARY:
1756       MM_LOG ("Binary included message in 7-bit message body",PARSE);
1757       break;
1758     default:
1759       fatal ("Invalid rfc822_encode_body_7bit message encoding");
1760     }
1761     break;                      /* can't change encoding */
1762   default:                      /* all else has some encoding */
1763     switch (body->encoding) {
1764     case ENC8BIT:               /* encode 8BIT into QUOTED-PRINTABLE */
1765                                 /* remember old 8-bit contents */
1766       f = (void *) body->contents.text.data;
1767       body->contents.text.data =
1768         rfc822_8bit (body->contents.text.data,
1769                      body->contents.text.size,&body->contents.text.size);
1770       body->encoding = ENCQUOTEDPRINTABLE;
1771       fs_give (&f);             /* flush old binary contents */
1772       break;
1773     case ENCBINARY:             /* encode binary into BASE64 */
1774                                 /* remember old binary contents */
1775       f = (void *) body->contents.text.data;
1776       body->contents.text.data =
1777         rfc822_binary ((void *) body->contents.text.data,
1778                        body->contents.text.size,&body->contents.text.size);
1779       body->encoding = ENCBASE64;
1780       fs_give (&f);             /* flush old binary contents */
1781     default:                    /* otherwise OK */
1782       break;
1783     }
1784     break;
1785   }
1786 }
1787 \f
1788 /* Encode a body for 8BIT transmittal
1789  * Accepts: envelope
1790  *          body
1791  */
1792
1793 void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body)
1794 {
1795   void *f;
1796   PART *part;
1797   PARAMETER **param;
1798   if (body) switch (body->type) {
1799   case TYPEMULTIPART:           /* multi-part */
1800     for (param = &body->parameter;
1801          *param && strcmp ((*param)->attribute,"BOUNDARY");
1802          param = &(*param)->next);
1803     if (!*param) {              /* cookie not set up yet? */
1804       char tmp[MAILTMPLEN];     /* make cookie not in BASE64 or QUOTEPRINT*/
1805       sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1806                (unsigned long) random (),(unsigned long) time (0),
1807                (unsigned long) getpid ());
1808       (*param) = mail_newbody_parameter ();
1809       (*param)->attribute = cpystr ("BOUNDARY");
1810       (*param)->value = cpystr (tmp);
1811     }
1812     part = body->nested.part;   /* encode body parts */
1813     do rfc822_encode_body_8bit (env,&part->body);
1814     while (part = part->next);  /* until done */
1815     break;
1816   case TYPEMESSAGE:             /* encapsulated message */
1817     switch (body->encoding) {
1818     case ENC7BIT:
1819     case ENC8BIT:
1820       break;
1821     case ENCBINARY:
1822       MM_LOG ("Binary included message in 8-bit message body",PARSE);
1823       break;
1824     default:
1825       fatal ("Invalid rfc822_encode_body_7bit message encoding");
1826     }
1827     break;                      /* can't change encoding */
1828   default:                      /* other type, encode binary into BASE64 */
1829     if (body->encoding == ENCBINARY) {
1830                                 /* remember old binary contents */
1831       f = (void *) body->contents.text.data;
1832       body->contents.text.data =
1833         rfc822_binary ((void *) body->contents.text.data,
1834                        body->contents.text.size,&body->contents.text.size);
1835       body->encoding = ENCBASE64;
1836       fs_give (&f);             /* flush old binary contents */
1837     }
1838     break;
1839   }
1840 }
1841 \f
1842 /* Output RFC 822 text
1843  * Accepts: buffer
1844  *          body
1845  * Returns: T if successful, NIL if failure
1846  */
1847
1848 long rfc822_output_text (RFC822BUFFER *buf,BODY *body)
1849 {
1850                                 /* MULTIPART gets special handling */
1851   if (body->type == TYPEMULTIPART) {
1852     char *cookie,tmp[MAILTMPLEN];
1853     PARAMETER *param;
1854     PART *part;
1855                                 /* find cookie */
1856     for (param = body->parameter; param && strcmp (param->attribute,"BOUNDARY");
1857          param = param->next);
1858     if (param) cookie = param->value;
1859     else {                /* make cookie not in BASE64 or QUOTEPRINT*/
1860       sprintf (cookie = tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1861                (unsigned long) random (),(unsigned long) time (0),
1862                (unsigned long) getpid ());
1863       (param = mail_newbody_parameter ())->attribute = cpystr ("BOUNDARY");
1864       param->value = cpystr (tmp);
1865       param->next = body->parameter;
1866       body->parameter = param;
1867     }
1868                                 /* output each part */
1869     for (part = body->nested.part; part; part = part->next)
1870       if (!(rfc822_output_string (buf,"--") &&
1871             rfc822_output_string (buf,cookie) &&
1872             rfc822_output_string (buf,"\015\012") &&
1873             rfc822_output_body_header (buf,&part->body) &&
1874             rfc822_output_string (buf,"\015\012") &&
1875             rfc822_output_text (buf,&part->body))) return NIL;
1876                                 /* output trailing cookie */
1877     return rfc822_output_string (buf,"--") &&
1878       rfc822_output_string (buf,cookie) &&
1879       rfc822_output_string (buf,"--\015\012");
1880   }
1881                                 /* output segment and trailing CRLF */
1882   return (!body->contents.text.data ||
1883           rfc822_output_string (buf,(char *) body->contents.text.data)) &&
1884     rfc822_output_string (buf,"\015\012");
1885 }
1886 \f
1887 /* Body contents encoding/decoding routines */
1888
1889
1890 /* Convert BASE64 contents to binary
1891  * Accepts: source
1892  *          length of source
1893  *          pointer to return destination length
1894  * Returns: destination as binary or NIL if error
1895  */
1896
1897 #define WSP 0176                /* NUL, TAB, LF, FF, CR, SPC */
1898 #define JNK 0177
1899 #define PAD 0100
1900
1901 void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
1902 {
1903   char c,*s,tmp[MAILTMPLEN];
1904   void *ret = fs_get ((size_t) ((*len = 4 + ((srcl * 3) / 4))) + 1);
1905   char *d = (char *) ret;
1906   int e;
1907   static char decode[256] = {
1908    WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,WSP,WSP,JNK,WSP,WSP,JNK,JNK,
1909    JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1910    WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,076,JNK,JNK,JNK,077,
1911    064,065,066,067,070,071,072,073,074,075,JNK,JNK,JNK,PAD,JNK,JNK,
1912    JNK,000,001,002,003,004,005,006,007,010,011,012,013,014,015,016,
1913    017,020,021,022,023,024,025,026,027,030,031,JNK,JNK,JNK,JNK,JNK,
1914    JNK,032,033,034,035,036,037,040,041,042,043,044,045,046,047,050,
1915    051,052,053,054,055,056,057,060,061,062,063,JNK,JNK,JNK,JNK,JNK,
1916    JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1917    JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1918    JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1919    JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1920    JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1921    JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1922    JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1923    JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK
1924   };
1925                                 /* initialize block */
1926   memset (ret,0,((size_t) *len) + 1);
1927   *len = 0;                     /* in case we return an error */
1928 \f
1929                                 /* simple-minded decode */
1930   for (e = 0; srcl--; ) switch (c = decode[*src++]) {
1931   default:                      /* valid BASE64 data character */
1932     switch (e++) {              /* install based on quantum position */
1933     case 0:
1934       *d = c << 2;              /* byte 1: high 6 bits */
1935       break;
1936     case 1:
1937       *d++ |= c >> 4;           /* byte 1: low 2 bits */
1938       *d = c << 4;              /* byte 2: high 4 bits */
1939       break;
1940     case 2:
1941       *d++ |= c >> 2;           /* byte 2: low 4 bits */
1942       *d = c << 6;              /* byte 3: high 2 bits */
1943       break;
1944     case 3:
1945       *d++ |= c;                /* byte 3: low 6 bits */
1946       e = 0;                    /* reinitialize mechanism */
1947       break;
1948     }
1949     break;
1950   case WSP:                     /* whitespace */
1951     break;
1952   case PAD:                     /* padding */
1953     switch (e++) {              /* check quantum position */
1954     case 3:                     /* one = is good enough in quantum 3 */
1955                                 /* make sure no data characters in remainder */
1956       for (; srcl; --srcl) switch (decode[*src++]) {
1957                                 /* ignore space, junk and extraneous padding */
1958       case WSP: case JNK: case PAD:
1959         break;
1960       default:                  /* valid BASE64 data character */
1961         /* This indicates bad MIME.  One way that it can be caused is if
1962            a single-section message was BASE64 encoded and then something
1963            (e.g. a mailing list processor) appended text.  The problem is
1964            that in 1 out of 3 cases, there is no padding and hence no way
1965            to detect the end of the data.  Consequently, prudent software
1966            will always encapsulate a BASE64 segment inside a MULTIPART.
1967            */
1968         sprintf (tmp,"Possible data truncation in rfc822_base64(): %.80s",
1969                  (char *) src - 1);
1970         if (s = strpbrk (tmp,"\015\012")) *s = NIL;
1971         mm_log (tmp,PARSE);
1972         srcl = 1;               /* don't issue any more messages */
1973         break;
1974       }
1975       break;
1976     case 2:                     /* expect a second = in quantum 2 */
1977       if (srcl && (*src == '=')) break;
1978     default:                    /* impossible quantum position */
1979       fs_give (&ret);
1980       return NIL;
1981     }
1982     break;
1983   case JNK:                     /* junk character */
1984     fs_give (&ret);
1985     return NIL;
1986   }
1987   *len = d - (char *) ret;      /* calculate data length */
1988   *d = '\0';                    /* NUL terminate just in case */
1989   return ret;                   /* return the string */
1990 }
1991 \f
1992 /* Convert binary contents to BASE64
1993  * Accepts: source
1994  *          length of source
1995  *          pointer to return destination length
1996  * Returns: destination as BASE64
1997  */
1998
1999 unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
2000 {
2001   unsigned char *ret,*d;
2002   unsigned char *s = (unsigned char *) src;
2003   char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2004   unsigned long i = ((srcl + 2) / 3) * 4;
2005   *len = i += 2 * ((i / 60) + 1);
2006   d = ret = (unsigned char *) fs_get ((size_t) ++i);
2007                                 /* process tuplets */
2008   for (i = 0; srcl >= 3; s += 3, srcl -= 3) {
2009     *d++ = v[s[0] >> 2];        /* byte 1: high 6 bits (1) */
2010                                 /* byte 2: low 2 bits (1), high 4 bits (2) */
2011     *d++ = v[((s[0] << 4) + (s[1] >> 4)) & 0x3f];
2012                                 /* byte 3: low 4 bits (2), high 2 bits (3) */
2013     *d++ = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f];
2014     *d++ = v[s[2] & 0x3f];      /* byte 4: low 6 bits (3) */
2015     if ((++i) == 15) {          /* output 60 characters? */
2016       i = 0;                    /* restart line break count, insert CRLF */
2017       *d++ = '\015'; *d++ = '\012';
2018     }
2019   }
2020   if (srcl) {
2021     *d++ = v[s[0] >> 2];        /* byte 1: high 6 bits (1) */
2022                                 /* byte 2: low 2 bits (1), high 4 bits (2) */
2023     *d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
2024                                 /* byte 3: low 4 bits (2), high 2 bits (3) */
2025     *d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
2026                                 /* byte 4: low 6 bits (3) */
2027     *d++ = srcl ? v[s[2] & 0x3f] : '=';
2028     if (srcl) srcl--;           /* count third character if processed */
2029     if ((++i) == 15) {          /* output 60 characters? */
2030       i = 0;                    /* restart line break count, insert CRLF */
2031       *d++ = '\015'; *d++ = '\012';
2032     }
2033   }
2034   *d++ = '\015'; *d++ = '\012'; /* insert final CRLF */
2035   *d = '\0';                    /* tie off string */
2036   if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
2037   return ret;                   /* return the resulting string */
2038 }
2039 \f
2040 /* Convert QUOTED-PRINTABLE contents to 8BIT
2041  * Accepts: source
2042  *          length of source
2043  *          pointer to return destination length
2044  * Returns: destination as 8-bit text or NIL if error
2045  */
2046
2047 unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,
2048                               unsigned long *len)
2049 {
2050   char tmp[MAILTMPLEN];
2051   unsigned int bogon = NIL;
2052   unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
2053   unsigned char *d = ret;
2054   unsigned char *t = d;
2055   unsigned char *s = src;
2056   unsigned char c,e;
2057   *len = 0;                     /* in case we return an error */
2058                                 /* until run out of characters */
2059   while (((unsigned long) (s - src)) < srcl) {
2060     switch (c = *s++) {         /* what type of character is it? */
2061     case '=':                   /* quoting character */
2062       if (((unsigned long) (s - src)) < srcl) switch (c = *s++) {
2063       case '\0':                /* end of data */
2064         s--;                    /* back up pointer */
2065         break;
2066       case '\015':              /* non-significant line break */
2067         if ((((unsigned long) (s - src)) < srcl) && (*s == '\012')) s++;
2068       case '\012':              /* bare LF */
2069         t = d;                  /* accept any leading spaces */
2070         break;
2071       default:                  /* two hex digits then */
2072         if (!(isxdigit (c) && (((unsigned long) (s - src)) < srcl) &&
2073               (e = *s++) && isxdigit (e))) {
2074           /* This indicates bad MIME.  One way that it can be caused is if
2075              a single-section message was QUOTED-PRINTABLE encoded and then
2076              something (e.g. a mailing list processor) appended text.  The
2077              problem is that there is no way to determine where the encoded
2078              data ended and the appended crud began.  Consequently, prudent
2079              software will always encapsulate a QUOTED-PRINTABLE segment
2080              inside a MULTIPART.
2081            */
2082           if (!bogon++) {       /* only do this once */
2083             sprintf (tmp,"Invalid quoted-printable sequence: =%.80s",
2084                    (char *) s - 1);
2085             mm_log (tmp,PARSE);
2086           }
2087           *d++ = '=';           /* treat = as ordinary character */
2088           *d++ = c;             /* and the character following */
2089           t = d;                /* note point of non-space */
2090           break;
2091         }
2092         *d++ = hex2byte (c,e);  /* merge the two hex digits */
2093         t = d;                  /* note point of non-space */
2094         break;
2095       }
2096       break;
2097     case ' ':                   /* space, possibly bogus */
2098       *d++ = c;                 /* stash the space but don't update s */
2099       break;
2100     case '\015':                /* end of line */
2101     case '\012':                /* bare LF */
2102       d = t;                    /* slide back to last non-space, drop in */
2103     default:
2104       *d++ = c;                 /* stash the character */
2105       t = d;                    /* note point of non-space */
2106     }      
2107   }
2108   *d = '\0';                    /* tie off results */
2109   *len = d - ret;               /* calculate length */
2110   return ret;                   /* return the string */
2111 }
2112 \f
2113 /* Convert 8BIT contents to QUOTED-PRINTABLE
2114  * Accepts: source
2115  *          length of source
2116  *          pointer to return destination length
2117  * Returns: destination as quoted-printable text
2118  */
2119
2120 #define MAXL (size_t) 75        /* 76th position only used by continuation = */
2121
2122 unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
2123                             unsigned long *len)
2124 {
2125   unsigned long lp = 0;
2126   unsigned char *ret = (unsigned char *)
2127     fs_get ((size_t) (3*srcl + 3*(((3*srcl)/MAXL) + 1)));
2128   unsigned char *d = ret;
2129   char *hex = "0123456789ABCDEF";
2130   unsigned char c;
2131   while (srcl--) {              /* for each character */
2132                                 /* true line break? */
2133     if (((c = *src++) == '\015') && (*src == '\012') && srcl) {
2134       *d++ = '\015'; *d++ = *src++; srcl--;
2135       lp = 0;                   /* reset line count */
2136     }
2137     else {                      /* not a line break */
2138                                 /* quoting required? */
2139       if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
2140           ((c == ' ') && (*src == '\015'))) {
2141         if ((lp += 3) > MAXL) { /* yes, would line overflow? */
2142           *d++ = '='; *d++ = '\015'; *d++ = '\012';
2143           lp = 3;               /* set line count */
2144         }
2145         *d++ = '=';             /* quote character */
2146         *d++ = hex[c >> 4];     /* high order 4 bits */
2147         *d++ = hex[c & 0xf];    /* low order 4 bits */
2148       }
2149       else {                    /* ordinary character */
2150         if ((++lp) > MAXL) {    /* would line overflow? */
2151           *d++ = '='; *d++ = '\015'; *d++ = '\012';
2152           lp = 1;               /* set line count */
2153         }
2154         *d++ = c;               /* ordinary character */
2155       }
2156     }
2157   }
2158   *d = '\0';                    /* tie off destination */
2159   *len = d - ret;               /* calculate true size */
2160                                 /* try to give some space back */
2161   fs_resize ((void **) &ret,(size_t) *len + 1);
2162   return ret;
2163 }
2164 \f
2165 /* Legacy Routines */
2166
2167 /*
2168  * WARNING: These routines are for compatibility with old software only.
2169  *
2170  * Their use in new software is to be avoided.
2171  *
2172  * These interfaces do not provide satisfactory buffer checking.  In
2173  * versions of c-client prior to imap-2005, they did not provide any
2174  * buffer checking at all.
2175  *
2176  * As a half-hearted attempt, these new compatability functions for the
2177  * legacy interfaces limit what they write to size SENDBUFLEN and will
2178  * fatal() if more than that is written.  However, that isn't good enough
2179  * since several of these functions *append* to the buffer, and return an
2180  * updated pointer.  Consequently, there is no way of knowing what the
2181  * actual available space is in the buffer, yet the function will still
2182  * write up to SENDBUFLEN bytes even if there is much less space actually
2183  * available.  The result is a buffer overflow.
2184  *
2185  * You won't get a buffer overflow if you never attempt to append using
2186  * these interfaces, but you can get the fatal() if it tries to write
2187  * more than SENDBUFLEN bytes.
2188  *
2189  * To avoid this problem, use the corresponding rfc822_output_???()
2190  * functions instead, e.g., rfc822_output_address() instead of
2191  * rfc822_address().
2192  *
2193  */
2194
2195 /* Flush routine, only called if overflow
2196  * Accepts: stream
2197  *          string to output
2198  * Returns: never
2199  */
2200
2201 static long rfc822_legacy_soutr (void *stream,char *string)
2202 {
2203   fatal ("rfc822.c legacy routine buffer overflow");
2204   return NIL;
2205 }
2206 \f
2207 /* Legacy write RFC 2822 header from message structure
2208  * Accepts: scratch buffer to write into
2209  *          message envelope
2210  *          message body
2211  */
2212
2213 void rfc822_header (char *header,ENVELOPE *env,BODY *body)
2214 {
2215   RFC822BUFFER buf;
2216                                 /* write at start of buffer */
2217   buf.end = (buf.beg = buf.cur = header) + SENDBUFLEN - 1;
2218   buf.f = rfc822_legacy_soutr;
2219   buf.s = NIL;
2220   rfc822_output_header (&buf,env,body,NIL,NIL);
2221   *buf.cur = '\0';              /* tie off buffer */
2222 }
2223
2224
2225 /* Legacy write RFC 2822 text from header line
2226  * Accepts: pointer to destination string pointer
2227  *          pointer to header type
2228  *          message to interpret
2229  *          pointer to text
2230  */
2231
2232 void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text)
2233 {
2234   RFC822BUFFER buf;
2235                                 /* append to buffer */
2236   buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2237   buf.f = rfc822_legacy_soutr;
2238   buf.s = NIL;
2239   rfc822_output_header_line (&buf,type,env->remail ? LONGT : NIL,text);
2240   *(*header = buf.cur) = '\0';  /* tie off buffer */
2241 }
2242 \f
2243 /* Legacy write RFC 2822 address from header line
2244  * Accepts: pointer to destination string pointer
2245  *          pointer to header type
2246  *          message to interpret
2247  *          address to interpret
2248  */
2249
2250 void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr)
2251 {
2252   RFC822BUFFER buf;
2253                                 /* append to buffer */
2254   buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2255   buf.f = rfc822_legacy_soutr;
2256   buf.s = NIL;
2257   rfc822_output_address_line (&buf,type,env->remail ? LONGT : NIL,adr,NIL);
2258   *(*header = buf.cur) = '\0';  /* tie off buffer */
2259 }
2260
2261
2262 /* Legacy write RFC 2822 address list
2263  * Accepts: pointer to destination string
2264  *          address to interpret
2265  *          header base if pretty-printing
2266  * Returns: end of destination string
2267  */
2268
2269 char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base)
2270 {
2271   RFC822BUFFER buf;
2272                                 /* append to buffer */
2273   buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2274   buf.f = rfc822_legacy_soutr;
2275   buf.s = NIL;
2276   rfc822_output_address_list (&buf,adr,base ? dest - base : 0,NIL);
2277   *buf.cur = '\0';              /* tie off buffer */
2278   return buf.cur;
2279 }
2280
2281
2282 /* Legacy write RFC 2822 route-address to string
2283  * Accepts: pointer to destination string
2284  *          address to interpret
2285  */
2286
2287 void rfc822_address (char *dest,ADDRESS *adr)
2288 {
2289   RFC822BUFFER buf;
2290                                 /* append to buffer */
2291   buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2292   buf.f = rfc822_legacy_soutr;
2293   buf.s = NIL;
2294   rfc822_output_address (&buf,adr);
2295   *buf.cur = '\0';              /* tie off buffer */
2296 }
2297 \f
2298 /* Concatenate RFC 2822 string
2299  * Accepts: pointer to destination string
2300  *          pointer to string to concatenate
2301  *          list of special characters or NIL for dot-atom format
2302  */
2303
2304 void rfc822_cat (char *dest,char *src,const char *specials)
2305 {
2306   RFC822BUFFER buf;
2307                                 /* append to buffer */
2308   buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2309   buf.f = rfc822_legacy_soutr;
2310   buf.s = NIL;
2311   rfc822_output_cat (&buf,src,specials);
2312   *buf.cur = '\0';              /* tie off buffer */
2313 }
2314
2315
2316 /* Legacy write body content header
2317  * Accepts: pointer to destination string pointer
2318  *          pointer to body to interpret
2319  */
2320
2321 void rfc822_write_body_header (char **dst,BODY *body)
2322 {
2323   RFC822BUFFER buf;
2324                                 /* append to buffer */
2325   buf.end = (buf.beg = buf.cur = *dst + strlen (*dst)) + SENDBUFLEN - 1;
2326   buf.f = rfc822_legacy_soutr;
2327   buf.s = NIL;
2328   rfc822_output_body_header (&buf,body);
2329   *(*dst = buf.cur) = '\0';     /* tie off buffer */
2330 }
2331 \f
2332 /* Legacy output RFC 822 message
2333  * Accepts: temporary buffer
2334  *          envelope
2335  *          body
2336  *          I/O routine
2337  *          stream for I/O routine
2338  *          non-zero if 8-bit output desired
2339  * Returns: T if successful, NIL if failure
2340  */
2341
2342 long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,void *s,
2343                     long ok8bit)
2344 {
2345   long ret;
2346   rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
2347                                 /* call external RFC 2822 output generator */
2348   if (r822o) ret = (*r822o) (t,env,body,f,s,ok8bit);
2349   else {                        /* output generator not armed */
2350     RFC822BUFFER buf;           /* use our own buffer rather than trust */
2351     char tmp[SENDBUFLEN+1];     /*  client to give us a big enough one */
2352     buf.f = f;
2353     buf.s = s;
2354     buf.end = (buf.beg = buf.cur = t) + SENDBUFLEN - 1;
2355     tmp[SENDBUFLEN] = '\0';     /* must have additional guard byte */
2356     ret = rfc822_output_full (&buf,env,body,ok8bit);
2357   }
2358   return ret;
2359 }
2360
2361
2362 /* Legacy output RFC 822 body
2363  * Accepts: body
2364  *          I/O routine
2365  *          stream for I/O routine
2366  * Returns: T if successful, NIL if failure
2367  */
2368
2369 long rfc822_output_body (BODY *body,soutr_t f,void *s)
2370 {
2371   RFC822BUFFER buf;
2372   char tmp[SENDBUFLEN+1];
2373   buf.f = f;
2374   buf.s = s;
2375   buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN;
2376   tmp[SENDBUFLEN] = '\0';       /* must have additional guard byte */
2377   return rfc822_output_text (&buf,body) && rfc822_output_flush (&buf);
2378 }