1 /* ========================================================================
2 * Copyright 1988-2008 University of Washington
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
8 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
15 * Program: RFC 2822 and MIME routines
17 * Author: Mark Crispin
19 * University of Washington
21 * Internet: MRC@Washington.EDU
24 * Last Edited: 14 May 2008
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.
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.
47 #define RFC733 1 /* parse "at" */
48 #define RFC822 0 /* generate A-D-L (MUST be 0 for 2822) */
50 /* RFC-822 static data */
52 #define RFC822CONT " " /* RFC 2822 continuation */
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;
61 /* Body formats constant strings, must match definitions in mail.h */
63 char *body_types[TYPEMAX+1] = {
64 "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
69 char *body_encodings[ENCMAX+1] = {
70 "7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
74 /* Token delimiting special characters */
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";
85 /* Subtype defaulting (a no-no, but regretably necessary...)
87 * Returns: default subtype name
90 char *rfc822_default_subtype (unsigned short type)
93 case TYPETEXT: /* default is TEXT/PLAIN */
95 case TYPEMULTIPART: /* default is MULTIPART/MIXED */
97 case TYPEMESSAGE: /* default is MESSAGE/RFC822 */
99 case TYPEAPPLICATION: /* default is APPLICATION/OCTET-STREAM */
100 return "OCTET-STREAM";
101 case TYPEAUDIO: /* default is AUDIO/BASIC */
103 default: /* others have no default subtype */
108 /* RFC 2822 parsing routines */
111 /* Parse an RFC 2822 message
112 * Accepts: pointer to return envelope
113 * pointer to return body
116 * pointer to body stringstruct
117 * pointer to local host name
119 * source driver flags
122 void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i,
123 STRING *bs,char *host,unsigned long depth,
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';
145 *t++ = ' '; /* coerce to space */
147 default: /* all other characters */
148 *t++ = c; /* insert the character into the line */
151 if (!--i) *t++ = '\0'; /* see if end of header */
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);
166 case 'B': /* possible bcc: */
167 if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
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)
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))) {
180 /* This is a disgusting kludge, and most of the messages which
181 * benefit from it are spam.
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",
187 MIMEp = 1; /* declare MIME now */
191 break; /* non-MIME message */
193 case T: /* definitely MIME */
194 rfc822_parse_content_header (body,tmp+8,d);
197 case 'D': /* possible Date: */
198 if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
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;
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);
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 */
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 */
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;
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);
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);
245 case 'T': /* possible To: */
246 if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
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);
261 /* Parse a message body content
262 * Accepts: pointer to body structure
264 * pointer to local host name
266 * source driver flags
269 void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth,
274 unsigned long i,j,k,m;
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);
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");
303 if (c == '\n') body->size.lines++;
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");
312 default: /* QUOTED-PRINTABLE, BASE64, etc. */
313 body->parameter->value = cpystr ("X-UNKNOWN");
317 /* just count lines */
318 else while (i--) if ((SNX (bs)) == '\n') body->size.lines++;
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 */
331 MM_LOG ("Ignoring nested encoding of message contents",PARSE);
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 */
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);
359 /* count number of lines */
360 while (i--) if (SNX (bs) == '\n') body->size.lines++;
362 case TYPEMULTIPART: /* multiple parts */
363 switch (body->encoding) { /* make sure valid encoding */
364 case ENC7BIT: /* these are valid nested encodings */
369 MM_LOG ("Ignoring nested encoding of multipart contents",PARSE);
371 /* remember if digest */
372 f = !strcmp (body->subtype,"DIGEST");
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 */
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 */
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 */
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 */
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);
425 default: /* whatever it was it wasn't valid */
429 default: /* not at a line */
430 c = SNX (bs); i--; /* get next character */
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 */
461 case '\012': /* newline, possible end of logical line */
462 /* tie off unless continuation */
463 if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t')))
467 case ' ': /* insert whitespace if not already there */
468 if (c != ' ') s1[j++] = c = ' ';
470 default: /* all other characters */
471 s1[j++] = c = c1; /* insert the character into the line */
474 /* end of data ties off the header */
475 if (!i || !--i) s1[j++] = c = '\0';
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);
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;
501 rfc822_parse_content (&part->body,bs,h,depth+1,flags);
502 bs->size = j; /* restore current level size */
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");
519 case TYPEMESSAGE: /* encapsulated message in digest */
520 part->body.nested.msg = mail_newmsg ();
527 fs_give ((void **) &s1); /* finished with scratch buffer */
529 default: /* nothing special to do in any other case */
534 /* Parse RFC 2822 body content header
535 * Accepts: body to write to
536 * possible content name
537 * remainder of header
540 void rfc822_parse_content_header (BODY *body,char *name,char *s)
542 char c,*t,tmp[MAILTMPLEN];
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);
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)) {
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);
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 */
582 else s = NIL; /* bogus or end of list */
585 else if (!(strcmp (name+1,"OCATION") || body->location))
586 body->location = cpystr (s);
588 case 'M': /* possible Content-MD5 */
589 if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
592 case 'T': /* possible Content-Type/Transfer-Encoding */
593 if (!(strcmp (name+1,"YPE") || body->subtype || body->parameter)) {
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);
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);
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 */
628 else if (!name) { /* no subtype, was a subtype delimiter? */
629 name = s; /* barf, restore pointer */
630 rfc822_skipws (&name); /* skip leading whitespace */
632 rfc822_parse_parameter (&body->parameter,name);
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);
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);
653 body_encodings[body->encoding] = ucase (s);
654 sprintf (tmp,"Unknown MIME transfer encoding: %.100s",s);
658 *name = c; /* restore delimiter */
659 /* ??check for cruft here?? */
662 default: /* otherwise unknown */
667 /* Parse RFC 2822 body parameter list
668 * Accepts: parameter list to write to
672 void rfc822_parse_parameter (PARAMETER **par,char *text)
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);
698 if (!param->value) { /* value not found? */
699 param->value = cpystr ("MISSING_PARAMETER_VALUE");
700 sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
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);
713 /* Parse RFC 2822 address list
714 * Accepts: address list to write to
719 void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
722 char *s,tmp[MAILTMPLEN];
723 ADDRESS *last = *lst;
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 */
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);
742 /* Recovery from failure on parsing */
743 if ( string != NULL) {
744 while (*string != ',' && *string != '\0')
748 switch (c = *(unsigned char *) string) {
749 case ',': /* comma? */
750 ++string; /* then another address follows */
753 s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
754 "Unexpected characters at end of address: %.80s";
755 sprintf (tmp,s,string);
757 last = last->next = mail_newaddr ();
758 last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
759 last->host = cpystr (errhst);
761 case '\0': /* null-specified address? */
762 string = NIL; /* punt remainder of parse */
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);
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;
782 /* Parse RFC 2822 address
783 * Accepts: address list to write to
784 * tail of address list
785 * pointer to input string
787 * group nesting depth
788 * Returns: new list tail
791 ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,
792 char *defaulthost,unsigned long depth)
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);
806 else if (*string) return NIL;
810 /* Parse RFC 2822 group
811 * Accepts: address list to write to
812 * pointer to tail of address list
813 * pointer to input string
815 * group nesting depth
818 ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,
819 char *defaulthost,unsigned long depth)
821 char tmp[MAILTMPLEN];
824 if (depth > MAXGROUPDEPTH) { /* excessively deep recursion? */
825 MM_LOG ("Ignoring excessively deep group recursion",PARSE);
826 return NIL; /* probably abusive */
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))))
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 */
857 sprintf (tmp,"Unexpected characters after address in group: %.80s",
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);
868 sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
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;
876 if (*string) { /* skip close delimiter */
877 if (**string == ';') ++*string;
878 rfc822_skipws (string);
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 */
886 /* Parse RFC 2822 mailbox
887 * Accepts: pointer to string pointer
889 * Returns: address list
891 * Updates string pointer
894 ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
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);
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 */
918 else adr = rfc822_parse_addrspec (s,string,defaulthost);
920 return adr; /* return the address */
924 /* Check if address is a phrase only
925 * Accepts: pointer to end of phrase
926 * Returns: T if phrase only, else NIL;
929 long rfc822_phraseonly (char *end)
931 while (*end == ' ') ++end; /* call rfc822_skipws() instead?? */
933 case '\0': case ',': case ';':
934 return LONGT; /* is a phrase only */
936 return NIL; /* something other than phase is here */
939 /* Parse RFC 2822 route-address
940 * Accepts: string pointer
941 * pointer to string pointer to update
944 * Updates string pointer
947 ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost)
949 char tmp[MAILTMPLEN];
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);
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 */
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);
980 else string = ++t; /* continue parse from this point */
983 /* parse address spec */
984 if (!(adr = rfc822_parse_addrspec (string,ret,defaulthost))) {
985 if (adl) fs_give ((void **) &adl);
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 */
996 sprintf (tmp,"Unterminated mailbox: %.80s@%.80s", (adr->mailbox == NULL) ? "<null>" : adr->mailbox,
997 (adr->host == NULL || *adr->host == '@') ? "<null>" : adr->host);
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 */
1006 /* Parse RFC 2822 address-spec
1007 * Accepts: string pointer
1008 * pointer to string pointer to update
1012 * Updates string pointer
1015 ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost)
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 */
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 */
1050 MM_LOG ("Invalid mailbox part after .",PARSE);
1054 t = end; /* remember delimiter in case no host */
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] == '(')))
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 */
1077 /* set return to end pointer */
1078 *ret = (end && *end) ? end : NIL;
1079 return adr; /* return the address we got */
1082 /* Parse RFC 2822 domain
1083 * Accepts: string pointer
1084 * pointer to return end of domain
1085 * Returns: domain name or NIL if failure
1088 char *rfc822_parse_domain (char *string,char **end)
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);
1098 size_t len = ++*end - string;
1099 strncpy (ret = (char *) fs_get (len + 1),string,len);
1100 ret[len] = '\0'; /* tie off literal */
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),
1123 fs_give ((void **) &ret);
1124 ret = v; /* new host name */
1125 rfc822_skipws (&t); /* skip WS after domain */
1128 MM_LOG ("Invalid domain part after .",PARSE);
1133 else MM_LOG ("Missing or invalid host name after @",PARSE);
1137 /* Parse RFC 2822 phrase
1138 * Accepts: string pointer
1139 * Returns: pointer to end of phrase
1142 char *rfc822_parse_phrase (char *s)
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;
1156 /* Parse RFC 2822 word
1157 * Accepts: string pointer
1158 * delimiter (or NIL for phrase word parsing)
1159 * Returns: pointer to end of word
1162 char *rfc822_parse_word (char *s,const char *delimiters)
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);
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 */
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 */
1188 /* eats entire text if no shift back */
1189 if (!st || !*st) return str + strlen (str);
1192 case I2C_G0_94: /* single byte sequence */
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 */
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 */
1214 str = ++st; /* continue parse */
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.
1221 if (st[1]) { /* not on NUL though... */
1222 str = st + 2; /* skip quoted character and go on */
1225 default: /* found a word delimiter */
1226 return (st == s) ? NIL : st;
1231 /* Copy an RFC 2822 format string
1233 * Returns: copy of string
1236 char *rfc822_cpy (char *src)
1238 /* copy and unquote */
1239 return rfc822_quote (cpystr (src));
1243 /* Unquote an RFC 2822 format string
1248 char *rfc822_quote (char *src)
1251 if (strpbrk (src,"\\\"")) { /* any quoting in string? */
1253 while (*src) { /* copy string */
1254 if (*src == '\"') src++; /* skip double quote entirely */
1256 if (*src == '\\') src++;/* skip over single quote, copy next always */
1257 *dst++ = *src++; /* copy character */
1260 *dst = '\0'; /* tie off string */
1262 return ret; /* return our string */
1266 /* Copy address list
1267 * Accepts: address list
1268 * Returns: address list
1271 ADDRESS *rfc822_cpy_adr (ADDRESS *adr)
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 */
1287 return (ret); /* return the MTP address list */
1290 /* Skips RFC 2822 whitespace
1291 * Accepts: pointer to string pointer
1294 void rfc822_skipws (char **s)
1296 while (T) switch (**s) {
1297 case ' ': case '\t': case '\015': case '\012':
1298 ++*s; /* skip all forms of LWSP */
1300 case '(': /* start of comment */
1301 if (rfc822_skip_comment (s,(long) NIL)) break;
1303 return; /* end of whitespace */
1308 /* Skips RFC 2822 comment
1309 * Accepts: pointer to string pointer
1311 * Returns: pointer to first non-blank character of comment
1314 char *rfc822_skip_comment (char **s,long trim)
1316 char *ret,tmp[MAILTMPLEN];
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 */
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 */
1333 case '\\': /* quote next character? */
1334 if (*++s1) { /* next character non-null? */
1335 t = s1; /* update last significant character pointer */
1338 case '\0': /* end of string */
1339 sprintf (tmp,"Unterminated comment: %.80s",*s);
1341 **s = '\0'; /* nuke duplicate messages in case reparse */
1342 return NIL; /* this is wierd if it happens */
1343 case ' ': /* whitespace isn't significant */
1345 default: /* random character */
1346 t = s1; /* update last significant character pointer */
1349 return NIL; /* impossible, but pacify lint et al */
1352 /* Buffered output routines */
1355 /* Output character to buffer
1357 * character to write
1358 * Returns: T if success, NIL if error
1361 static long rfc822_output_char (RFC822BUFFER *buf,int c)
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;
1369 /* Output data to buffer
1373 * Returns: T if success, NIL if error
1376 static long rfc822_output_data (RFC822BUFFER *buf,char *string,long len)
1378 while (len) { /* until request satified */
1380 if (i = min (len,buf->end - buf->cur)) {
1381 memcpy (buf->cur,string,i);
1382 buf->cur += i; /* blat data */
1386 /* soutr buffer now if full */
1387 if ((len || (buf->cur == buf->end)) && !rfc822_output_flush (buf))
1393 /* Output string to buffer
1396 * Returns: T if success, NIL if error
1399 static long rfc822_output_string (RFC822BUFFER *buf,char *string)
1401 return rfc822_output_data (buf,string,strlen (string));
1408 * stream for I/O routine
1409 * Returns: T if success, NIL if error
1412 long rfc822_output_flush (RFC822BUFFER *buf)
1414 *buf->cur = '\0'; /* tie off buffer at this point */
1415 return (*buf->f) (buf->s,buf->cur = buf->beg);
1418 /* Message writing routines */
1421 /* Output RFC 822 message
1422 * Accepts: temporary buffer as a SIZEDTEXT
1426 * stream for I/O routine
1427 * non-zero if 8-bit output desired
1428 * Returns: T if successful, NIL if failure
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.
1436 long rfc822_output_full (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,long ok8)
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);
1452 /* Output RFC 822 header
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
1461 long rfc822_output_header (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,
1462 const char *specials,long flags)
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')) ?
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") :
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");
1493 /* Output RFC 2822 header text line
1495 * pointer to header type
1496 * non-NIL if resending
1498 * Returns: T if success, NIL if failure
1501 long rfc822_output_header_line (RFC822BUFFER *buf,char *type,long resent,
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"));
1511 /* Output RFC 2822 header address line
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
1520 long rfc822_output_address_line (RFC822BUFFER *buf,char *type,long resent,
1521 ADDRESS *adr,const char *specials)
1523 long pretty = strlen (type);
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 :
1530 rfc822_output_string (buf,"\015\012"));
1533 /* Output RFC 2822 address list
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
1541 long rfc822_output_address_list (RFC822BUFFER *buf,ADDRESS *adr,long pretty,
1542 const char *specials)
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 */
1553 adr->adl || /* or A-D-L */
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;
1562 else if (!rfc822_output_address (buf,adr)) return NIL;
1563 if (adr->next && adr->next->mailbox &&
1564 !rfc822_output_string (buf,", ")) return NIL;
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 */
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;
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;
1590 /* Write RFC 2822 route-address to string
1592 * pointer to single address
1593 * Returns: T if success, NIL if failure
1596 long rfc822_output_address (RFC822BUFFER *buf,ADDRESS *adr)
1598 return !adr || !adr->host ||
1600 #if RFC822 /* old code with A-D-L support */
1601 (!adr->adl || (rfc822_output_string (buf,adr->adl) &&
1602 rfc822_output_char (buf,':'))) &&
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))));
1611 /* Output RFC 2822 string with concatenation
1613 * string to concatenate
1614 * list of special characters or NIL for dot-atom format
1615 * Returns: T if success, NIL if failure
1618 long rfc822_output_cat (RFC822BUFFER *buf,char *src,const char *specials)
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;
1634 /* return string and trailing quote*/
1635 return rfc822_output_string (buf,src) && rfc822_output_char (buf,'"');
1638 return rfc822_output_string (buf,src);
1641 /* Output MIME parameter list
1644 * Returns: T if success, NIL if failure
1647 long rfc822_output_parameter (RFC822BUFFER *buf,PARAMETER *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;
1660 /* Output RFC 2822 stringlist
1663 * Returns: T if success, NIL if failure
1666 long rfc822_output_stringlist (RFC822BUFFER *buf,STRINGLIST *stl)
1669 if (!rfc822_output_cat (buf,(char *) stl->text.data,tspecials) ||
1670 ((stl = stl->next) && !rfc822_output_string (buf,", ")))
1675 /* Output body content header
1678 * Returns: T if success, NIL if failure
1681 long rfc822_output_body_header (RFC822BUFFER *buf,BODY *body)
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");
1720 /* Encode a body for 7BIT transmittal
1725 void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body)
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);
1744 part = body->nested.part; /* encode body parts */
1745 do rfc822_encode_body_7bit (env,&part->body);
1746 while (part = part->next); /* until done */
1748 case TYPEMESSAGE: /* encapsulated message */
1749 switch (body->encoding) {
1753 MM_LOG ("8-bit included message in 7-bit message body",PARSE);
1756 MM_LOG ("Binary included message in 7-bit message body",PARSE);
1759 fatal ("Invalid rfc822_encode_body_7bit message encoding");
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 */
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 */
1788 /* Encode a body for 8BIT transmittal
1793 void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body)
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);
1812 part = body->nested.part; /* encode body parts */
1813 do rfc822_encode_body_8bit (env,&part->body);
1814 while (part = part->next); /* until done */
1816 case TYPEMESSAGE: /* encapsulated message */
1817 switch (body->encoding) {
1822 MM_LOG ("Binary included message in 8-bit message body",PARSE);
1825 fatal ("Invalid rfc822_encode_body_7bit message encoding");
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 */
1842 /* Output RFC 822 text
1845 * Returns: T if successful, NIL if failure
1848 long rfc822_output_text (RFC822BUFFER *buf,BODY *body)
1850 /* MULTIPART gets special handling */
1851 if (body->type == TYPEMULTIPART) {
1852 char *cookie,tmp[MAILTMPLEN];
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;
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");
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");
1887 /* Body contents encoding/decoding routines */
1890 /* Convert BASE64 contents to binary
1893 * pointer to return destination length
1894 * Returns: destination as binary or NIL if error
1897 #define WSP 0176 /* NUL, TAB, LF, FF, CR, SPC */
1901 void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
1903 char c,*s,tmp[MAILTMPLEN];
1904 void *ret = fs_get ((size_t) ((*len = 4 + ((srcl * 3) / 4))) + 1);
1905 char *d = (char *) ret;
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
1925 /* initialize block */
1926 memset (ret,0,((size_t) *len) + 1);
1927 *len = 0; /* in case we return an error */
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 */
1934 *d = c << 2; /* byte 1: high 6 bits */
1937 *d++ |= c >> 4; /* byte 1: low 2 bits */
1938 *d = c << 4; /* byte 2: high 4 bits */
1941 *d++ |= c >> 2; /* byte 2: low 4 bits */
1942 *d = c << 6; /* byte 3: high 2 bits */
1945 *d++ |= c; /* byte 3: low 6 bits */
1946 e = 0; /* reinitialize mechanism */
1950 case WSP: /* whitespace */
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:
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.
1968 sprintf (tmp,"Possible data truncation in rfc822_base64(): %.80s",
1970 if (s = strpbrk (tmp,"\015\012")) *s = NIL;
1972 srcl = 1; /* don't issue any more messages */
1976 case 2: /* expect a second = in quantum 2 */
1977 if (srcl && (*src == '=')) break;
1978 default: /* impossible quantum position */
1983 case JNK: /* junk character */
1987 *len = d - (char *) ret; /* calculate data length */
1988 *d = '\0'; /* NUL terminate just in case */
1989 return ret; /* return the string */
1992 /* Convert binary contents to BASE64
1995 * pointer to return destination length
1996 * Returns: destination as BASE64
1999 unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
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';
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';
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 */
2040 /* Convert QUOTED-PRINTABLE contents to 8BIT
2043 * pointer to return destination length
2044 * Returns: destination as 8-bit text or NIL if error
2047 unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,
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;
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 */
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 */
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
2082 if (!bogon++) { /* only do this once */
2083 sprintf (tmp,"Invalid quoted-printable sequence: =%.80s",
2087 *d++ = '='; /* treat = as ordinary character */
2088 *d++ = c; /* and the character following */
2089 t = d; /* note point of non-space */
2092 *d++ = hex2byte (c,e); /* merge the two hex digits */
2093 t = d; /* note point of non-space */
2097 case ' ': /* space, possibly bogus */
2098 *d++ = c; /* stash the space but don't update s */
2100 case '\015': /* end of line */
2101 case '\012': /* bare LF */
2102 d = t; /* slide back to last non-space, drop in */
2104 *d++ = c; /* stash the character */
2105 t = d; /* note point of non-space */
2108 *d = '\0'; /* tie off results */
2109 *len = d - ret; /* calculate length */
2110 return ret; /* return the string */
2113 /* Convert 8BIT contents to QUOTED-PRINTABLE
2116 * pointer to return destination length
2117 * Returns: destination as quoted-printable text
2120 #define MAXL (size_t) 75 /* 76th position only used by continuation = */
2122 unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
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";
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 */
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 */
2145 *d++ = '='; /* quote character */
2146 *d++ = hex[c >> 4]; /* high order 4 bits */
2147 *d++ = hex[c & 0xf]; /* low order 4 bits */
2149 else { /* ordinary character */
2150 if ((++lp) > MAXL) { /* would line overflow? */
2151 *d++ = '='; *d++ = '\015'; *d++ = '\012';
2152 lp = 1; /* set line count */
2154 *d++ = c; /* ordinary character */
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);
2165 /* Legacy Routines */
2168 * WARNING: These routines are for compatibility with old software only.
2170 * Their use in new software is to be avoided.
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.
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.
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.
2189 * To avoid this problem, use the corresponding rfc822_output_???()
2190 * functions instead, e.g., rfc822_output_address() instead of
2195 /* Flush routine, only called if overflow
2201 static long rfc822_legacy_soutr (void *stream,char *string)
2203 fatal ("rfc822.c legacy routine buffer overflow");
2207 /* Legacy write RFC 2822 header from message structure
2208 * Accepts: scratch buffer to write into
2213 void rfc822_header (char *header,ENVELOPE *env,BODY *body)
2216 /* write at start of buffer */
2217 buf.end = (buf.beg = buf.cur = header) + SENDBUFLEN - 1;
2218 buf.f = rfc822_legacy_soutr;
2220 rfc822_output_header (&buf,env,body,NIL,NIL);
2221 *buf.cur = '\0'; /* tie off buffer */
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
2232 void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text)
2235 /* append to buffer */
2236 buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2237 buf.f = rfc822_legacy_soutr;
2239 rfc822_output_header_line (&buf,type,env->remail ? LONGT : NIL,text);
2240 *(*header = buf.cur) = '\0'; /* tie off buffer */
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
2250 void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr)
2253 /* append to buffer */
2254 buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2255 buf.f = rfc822_legacy_soutr;
2257 rfc822_output_address_line (&buf,type,env->remail ? LONGT : NIL,adr,NIL);
2258 *(*header = buf.cur) = '\0'; /* tie off buffer */
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
2269 char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base)
2272 /* append to buffer */
2273 buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2274 buf.f = rfc822_legacy_soutr;
2276 rfc822_output_address_list (&buf,adr,base ? dest - base : 0,NIL);
2277 *buf.cur = '\0'; /* tie off buffer */
2282 /* Legacy write RFC 2822 route-address to string
2283 * Accepts: pointer to destination string
2284 * address to interpret
2287 void rfc822_address (char *dest,ADDRESS *adr)
2290 /* append to buffer */
2291 buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2292 buf.f = rfc822_legacy_soutr;
2294 rfc822_output_address (&buf,adr);
2295 *buf.cur = '\0'; /* tie off buffer */
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
2304 void rfc822_cat (char *dest,char *src,const char *specials)
2307 /* append to buffer */
2308 buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2309 buf.f = rfc822_legacy_soutr;
2311 rfc822_output_cat (&buf,src,specials);
2312 *buf.cur = '\0'; /* tie off buffer */
2316 /* Legacy write body content header
2317 * Accepts: pointer to destination string pointer
2318 * pointer to body to interpret
2321 void rfc822_write_body_header (char **dst,BODY *body)
2324 /* append to buffer */
2325 buf.end = (buf.beg = buf.cur = *dst + strlen (*dst)) + SENDBUFLEN - 1;
2326 buf.f = rfc822_legacy_soutr;
2328 rfc822_output_body_header (&buf,body);
2329 *(*dst = buf.cur) = '\0'; /* tie off buffer */
2332 /* Legacy output RFC 822 message
2333 * Accepts: temporary buffer
2337 * stream for I/O routine
2338 * non-zero if 8-bit output desired
2339 * Returns: T if successful, NIL if failure
2342 long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,void *s,
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 */
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);
2362 /* Legacy output RFC 822 body
2365 * stream for I/O routine
2366 * Returns: T if successful, NIL if failure
2369 long rfc822_output_body (BODY *body,soutr_t f,void *s)
2372 char tmp[SENDBUFLEN+1];
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);