1
/* ========================================================================
2
* Copyright 1988-2006 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
18
* Networks and Distributed Computing
19
* Computing & Communications
20
* University of Washington
21
* Administration Building, AG-44
23
* Internet: MRC@CAC.Washington.EDU
26
* Last Edited: 6 December 2006
28
* This original version of this file is
29
* Copyright 1988 Stanford University
30
* and was developed in the Symbolic Systems Resources Group of the Knowledge
31
* Systems Laboratory at Stanford University in 1987-88, and was funded by the
32
* Biomedical Research Technology Program of the NationalInstitutes of Health
33
* under grant number RR-00785.
43
/* Support for deprecated features in earlier specifications. Note that this
44
* module follows RFC 2822, and all use of "rfc822" in function names is
45
* for compatibility. Only the code identified by the conditionals below
46
* follows the earlier documents.
49
#define RFC733 1 /* parse "at" */
50
#define RFC822 0 /* generate A-D-L (MUST be 0 for 2822) */
52
/* RFC-822 static data */
54
#define RFC822CONT " " /* RFC 2822 continuation */
56
/* should have been "Remailed-" */
57
#define RESENTPREFIX "ReSent-"
58
static char *resentprefix = RESENTPREFIX;
59
/* syntax error host string */
60
static const char *errhst = ERRHOST;
63
/* Body formats constant strings, must match definitions in mail.h */
65
char *body_types[TYPEMAX+1] = {
66
"TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO",
71
char *body_encodings[ENCMAX+1] = {
72
"7BIT", "8BIT", "BINARY", "BASE64", "QUOTED-PRINTABLE", "X-UNKNOWN"
76
/* Token delimiting special characters */
78
/* RFC 2822 specials */
79
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";
80
/* RFC 2822 phrase specials (no space) */
81
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";
82
/* RFC 2822 dot-atom specials (no dot) */
83
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";
84
/* RFC 2045 MIME body token specials */
85
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";
87
/* Subtype defaulting (a no-no, but regretably necessary...)
89
* Returns: default subtype name
92
char *rfc822_default_subtype (unsigned short type)
95
case TYPETEXT: /* default is TEXT/PLAIN */
97
case TYPEMULTIPART: /* default is MULTIPART/MIXED */
99
case TYPEMESSAGE: /* default is MESSAGE/RFC822 */
101
case TYPEAPPLICATION: /* default is APPLICATION/OCTET-STREAM */
102
return "OCTET-STREAM";
103
case TYPEAUDIO: /* default is AUDIO/BASIC */
105
default: /* others have no default subtype */
110
/* RFC 2822 parsing routines */
113
/* Parse an RFC 2822 message
114
* Accepts: pointer to return envelope
115
* pointer to return body
118
* pointer to body stringstruct
119
* pointer to local host name
121
* source driver flags
124
void rfc822_parse_msg_full (ENVELOPE **en,BODY **bdy,char *s,unsigned long i,
125
STRING *bs,char *host,unsigned long depth,
129
char *tmp = (char *) fs_get ((size_t) i + 100);
130
ENVELOPE *env = (*en = mail_newenvelope ());
131
BODY *body = bdy ? (*bdy = mail_newbody ()) : NIL;
132
long MIMEp = -1; /* flag that MIME semantics are in effect */
133
parseline_t pl = (parseline_t) mail_parameters (NIL,GET_PARSELINE,NIL);
134
if (!host) host = BADHOST; /* make sure that host is non-null */
135
while (i && *s != '\n') { /* until end of header */
136
t = tmp; /* initialize buffer pointer */
137
c = ' '; /* and previous character */
138
while (i && c) { /* collect text until logical end of line */
139
switch (c = *s++) { /* slurp a character */
140
case '\015': /* return, possible end of logical line */
141
if (*s == '\n') break; /* ignore if LF follows */
142
case '\012': /* LF, possible end of logical line */
143
/* tie off unless next line starts with WS */
144
if (*s != ' ' && *s != '\t') *t++ = c = '\0';
147
*t++ = ' '; /* coerce to space */
149
default: /* all other characters */
150
*t++ = c; /* insert the character into the line */
153
if (!--i) *t++ = '\0'; /* see if end of header */
156
/* find header item type */
157
if (t = d = strchr (tmp,':')) {
158
*d++ = '\0'; /* tie off header item, point at its data */
159
while (*d == ' ') d++; /* flush whitespace */
160
while ((tmp < t--) && (*t == ' ')) *t = '\0';
161
ucase (tmp); /* coerce to uppercase */
162
/* external callback */
163
if (pl) (*pl) (env,tmp,d,host);
164
switch (*tmp) { /* dispatch based on first character */
165
case '>': /* possible >From: */
166
if (!strcmp (tmp+1,"FROM")) rfc822_parse_adrlist (&env->from,d,host);
168
case 'B': /* possible bcc: */
169
if (!strcmp (tmp+1,"CC")) rfc822_parse_adrlist (&env->bcc,d,host);
171
case 'C': /* possible cc: or Content-<mumble>*/
172
if (!strcmp (tmp+1,"C")) rfc822_parse_adrlist (&env->cc,d,host);
173
else if ((tmp[1] == 'O') && (tmp[2] == 'N') && (tmp[3] == 'T') &&
174
(tmp[4] == 'E') && (tmp[5] == 'N') && (tmp[6] == 'T') &&
175
(tmp[7] == '-') && body)
177
case -1: /* unknown if MIME or not */
178
if (!(MIMEp = /* see if MIME-Version header exists */
179
search ((unsigned char *) s-1,i,
180
(unsigned char *)"\012MIME-Version",(long) 13))) {
182
/* This is a disgusting kludge, and most of the messages which
183
* benefit from it are spam.
185
if (!strcmp (tmp+8,"TRANSFER-ENCODING") ||
186
(!strcmp (tmp+8,"TYPE") && strchr (d,'/'))) {
187
MM_LOG ("Warning: MIME header encountered in non-MIME message",
189
MIMEp = 1; /* declare MIME now */
193
break; /* non-MIME message */
195
case T: /* definitely MIME */
196
rfc822_parse_content_header (body,tmp+8,d);
199
case 'D': /* possible Date: */
200
if (!env->date && !strcmp (tmp+1,"ATE")) env->date = cpystr (d);
202
case 'F': /* possible From: */
203
if (!strcmp (tmp+1,"ROM")) rfc822_parse_adrlist (&env->from,d,host);
204
else if (!strcmp (tmp+1,"OLLOWUP-TO")) {
205
t = env->followup_to = (char *) fs_get (1 + strlen (d));
206
while (c = *d++) if (c != ' ') *t++ = c;
210
case 'I': /* possible In-Reply-To: */
211
if (!env->in_reply_to && !strcmp (tmp+1,"N-REPLY-TO"))
212
env->in_reply_to = cpystr (d);
215
case 'M': /* possible Message-ID: or MIME-Version: */
216
if (!env->message_id && !strcmp (tmp+1,"ESSAGE-ID"))
217
env->message_id = cpystr (d);
218
else if (!strcmp (tmp+1,"IME-VERSION")) {
219
/* tie off at end of phrase */
220
if (t = rfc822_parse_phrase (d)) *t = '\0';
221
rfc822_skipws (&d); /* skip whitespace */
223
if (strcmp (d,"1.0") && strcmp (d,"RFC-XXXX"))
224
MM_LOG ("Warning: message has unknown MIME version",PARSE);
225
MIMEp = T; /* note that we are MIME */
228
case 'N': /* possible Newsgroups: */
229
if (!env->newsgroups && !strcmp (tmp+1,"EWSGROUPS")) {
230
t = env->newsgroups = (char *) fs_get (1 + strlen (d));
231
while (c = *d++) if (c != ' ') *t++ = c;
235
case 'R': /* possible Reply-To: */
236
if (!strcmp (tmp+1,"EPLY-TO"))
237
rfc822_parse_adrlist (&env->reply_to,d,host);
238
else if (!env->references && !strcmp (tmp+1,"EFERENCES"))
239
env->references = cpystr (d);
241
case 'S': /* possible Subject: or Sender: */
242
if (!env->subject && !strcmp (tmp+1,"UBJECT"))
243
env->subject = cpystr (d);
244
else if (!strcmp (tmp+1,"ENDER"))
245
rfc822_parse_adrlist (&env->sender,d,host);
247
case 'T': /* possible To: */
248
if (!strcmp (tmp+1,"O")) rfc822_parse_adrlist (&env->to,d,host);
255
fs_give ((void **) &tmp); /* done with scratch buffer */
256
/* default Sender: and Reply-To: to From: */
257
if (!env->sender) env->sender = rfc822_cpy_adr (env->from);
258
if (!env->reply_to) env->reply_to = rfc822_cpy_adr (env->from);
259
/* now parse the body */
260
if (body) rfc822_parse_content (body,bs,host,depth,flags);
263
/* Parse a message body content
264
* Accepts: pointer to body structure
266
* pointer to local host name
268
* source driver flags
271
void rfc822_parse_content (BODY *body,STRING *bs,char *h,unsigned long depth,
276
unsigned long i,j,k,m;
279
if (depth > MAXMIMEDEPTH) { /* excessively deep recursion? */
280
body->type = TYPETEXT; /* yes, probably a malicious MIMEgram */
281
MM_LOG ("Ignoring excessively deep MIME recursion",PARSE);
283
if (!body->subtype) /* default subtype if still unknown */
284
body->subtype = cpystr (rfc822_default_subtype (body->type));
285
/* note offset and sizes */
286
body->contents.offset = GETPOS (bs);
287
/* note internal body size in all cases */
288
body->size.bytes = body->contents.text.size = i = SIZE (bs);
289
if (!(flags & DR_CRLF)) body->size.bytes = strcrlflen (bs);
290
switch (body->type) { /* see if anything else special to do */
291
case TYPETEXT: /* text content */
292
if (!body->parameter) { /* no parameters set */
293
body->parameter = mail_newbody_parameter ();
294
body->parameter->attribute = cpystr ("CHARSET");
295
while (i--) { /* count lines and guess charset */
296
c = SNX (bs); /* get current character */
297
/* charset still unknown? */
298
if (!body->parameter->value) {
299
if ((c == I2C_ESC) && (i && i--) && ((c = SNX (bs)) == I2C_MULTI) &&
300
(i && i--) && (((c = SNX (bs)) == I2CS_94x94_JIS_NEW) ||
301
(c == I2CS_94x94_JIS_OLD)))
302
body->parameter->value = cpystr ("ISO-2022-JP");
303
else if (c & 0x80) body->parameter->value = cpystr ("X-UNKNOWN");
305
if (c == '\n') body->size.lines++;
307
/* otherwise assume US-ASCII */
308
if (!body->parameter->value) body->parameter->value = cpystr("US-ASCII");
310
/* just count lines */
311
else while (i--) if ((SNX (bs)) == '\n') body->size.lines++;
314
case TYPEMESSAGE: /* encapsulated message */
315
/* encapsulated RFC-822 message? */
316
if (!strcmp (body->subtype,"RFC822")) {
317
body->nested.msg = mail_newmsg ();
318
switch (body->encoding) { /* make sure valid encoding */
319
case ENC7BIT: /* these are valid nested encodings */
324
MM_LOG ("Ignoring nested encoding of message contents",PARSE);
326
/* hunt for blank line */
327
for (c = '\012',j = 0; (i > j) && ((c != '\012') || (CHR(bs) != '\012'));
328
j++) if ((c1 = SNX (bs)) != '\015') c = c1;
329
if (i > j) { /* unless no more text */
330
c1 = SNX (bs); /* body starts here */
331
j++; /* advance count */
333
/* note body text offset and header size */
334
body->nested.msg->header.text.size = j;
335
body->nested.msg->text.text.size = body->contents.text.size - j;
336
body->nested.msg->text.offset = GETPOS (bs);
337
body->nested.msg->full.offset = body->nested.msg->header.offset =
338
body->contents.offset;
339
body->nested.msg->full.text.size = body->contents.text.size;
340
/* copy header string */
341
SETPOS (bs,body->contents.offset);
342
s = (char *) fs_get ((size_t) j + 1);
343
for (s1 = s,k = j; k--; *s1++ = SNX (bs));
344
s[j] = '\0'; /* tie off string (not really necessary) */
345
/* now parse the body */
346
rfc822_parse_msg_full (&body->nested.msg->env,&body->nested.msg->body,s,
347
j,bs,h,depth+1,flags);
348
fs_give ((void **) &s); /* free header string */
349
/* restore position */
350
SETPOS (bs,body->contents.offset);
352
/* count number of lines */
353
while (i--) if (SNX (bs) == '\n') body->size.lines++;
355
case TYPEMULTIPART: /* multiple parts */
356
switch (body->encoding) { /* make sure valid encoding */
357
case ENC7BIT: /* these are valid nested encodings */
362
MM_LOG ("Ignoring nested encoding of multipart contents",PARSE);
364
/* remember if digest */
365
f = !strcmp (body->subtype,"DIGEST");
367
for (s1 = NIL,param = body->parameter; param && !s1; param = param->next)
368
if (!strcmp (param->attribute,"BOUNDARY")) s1 = param->value;
369
if (!s1) s1 = "-"; /* yucky default */
370
j = strlen (s1) + 2; /* length of cookie and header */
371
c = '\012'; /* initially at beginning of line */
373
while (i > j) { /* examine data */
374
if (m = GETPOS (bs)) m--; /* get position in front of character */
375
switch (c) { /* examine each line */
376
case '\015': /* handle CRLF form */
377
if (CHR (bs) == '\012'){/* following LF? */
378
c = SNX (bs); i--; /* yes, slurp it */
380
case '\012': /* at start of a line, start with -- ? */
381
if (!(i && i-- && ((c = SNX (bs)) == '-') && i-- &&
382
((c = SNX (bs)) == '-'))) break;
383
/* see if cookie matches */
384
if (k = j - 2) for (s = s1; i-- && *s++ == (c = SNX (bs)) && --k;);
385
if (k) break; /* strings didn't match if non-zero */
386
/* terminating delimiter? */
387
if ((c = ((i && i--) ? (SNX (bs)) : '\012')) == '-') {
388
if ((i && i--) && ((c = SNX (bs)) == '-') &&
389
((i && i--) ? (((c = SNX (bs)) == '\015') || (c=='\012')):T)) {
390
/* if have a final part calculate its size */
391
if (part) part->body.mime.text.size =
392
(m > part->body.mime.offset) ? (m - part->body.mime.offset) :0;
393
part = NIL; i = 1; /* terminate scan */
397
/* swallow trailing whitespace */
398
while ((c == ' ') || (c == '\t'))
399
c = ((i && i--) ? (SNX (bs)) : '\012');
400
switch (c) { /* need newline after all of it */
401
case '\015': /* handle CRLF form */
402
if (i && CHR (bs) == '\012') {
403
c = SNX (bs); i--;/* yes, slurp it */
405
case '\012': /* new line */
406
if (part) { /* calculate size of previous */
407
part->body.mime.text.size =
408
(m > part->body.mime.offset) ? (m-part->body.mime.offset) : 0;
409
/* instantiate next */
410
part = part->next = mail_newbody_part ();
411
} /* otherwise start new list */
412
else part = body->nested.part = mail_newbody_part ();
413
/* digest has a different default */
414
if (f) part->body.type = TYPEMESSAGE;
415
/* note offset from main body */
416
part->body.mime.offset = GETPOS (bs);
418
default: /* whatever it was it wasn't valid */
422
default: /* not at a line */
423
c = SNX (bs); i--; /* get next character */
428
/* calculate size of any final part */
429
if (part) part->body.mime.text.size = i +
430
((GETPOS(bs) > part->body.mime.offset) ?
431
(GETPOS(bs) - part->body.mime.offset) : 0);
432
/* make a scratch buffer */
433
s1 = (char *) fs_get ((size_t) (k = MAILTMPLEN));
434
/* in case empty multipart */
435
if (!body->nested.part) body->nested.part = mail_newbody_part ();
436
/* parse non-empty body parts */
437
for (part = body->nested.part; part; part = part->next) {
438
/* part non-empty (header and/or content)? */
439
if (i = part->body.mime.text.size) {
440
/* move to that part of the body */
441
SETPOS (bs,part->body.mime.offset);
442
/* until end of header */
443
while (i && ((c = CHR (bs)) != '\015') && (c != '\012')) {
444
/* collect text until logical end of line */
445
for (j = 0,c = ' '; c; ) {
446
/* make sure buffer big enough */
447
if (j > (k - 10)) fs_resize ((void **) &s1,k += MAILTMPLEN);
448
switch (c1 = SNX (bs)) {
449
case '\015': /* return */
450
if (i && (CHR (bs) == '\012')) {
451
c1 = SNX (bs); /* eat any LF following */
454
case '\012': /* newline, possible end of logical line */
455
/* tie off unless continuation */
456
if (!i || ((CHR (bs) != ' ') && (CHR (bs) != '\t')))
460
case ' ': /* insert whitespace if not already there */
461
if (c != ' ') s1[j++] = c = ' ';
463
default: /* all other characters */
464
s1[j++] = c = c1; /* insert the character into the line */
467
/* end of data ties off the header */
468
if (!i || !--i) s1[j++] = c = '\0';
471
/* find header item type */
472
if (((s1[0] == 'C') || (s1[0] == 'c')) &&
473
((s1[1] == 'O') || (s1[1] == 'o')) &&
474
((s1[2] == 'N') || (s1[2] == 'n')) &&
475
((s1[3] == 'T') || (s1[3] == 't')) &&
476
((s1[4] == 'E') || (s1[4] == 'e')) &&
477
((s1[5] == 'N') || (s1[5] == 'n')) &&
478
((s1[6] == 'T') || (s1[6] == 't')) &&
479
(s1[7] == '-') && (s = strchr (s1+8,':'))) {
480
/* tie off and flush whitespace */
481
for (*s++ = '\0'; *s == ' '; s++);
482
/* parse the header */
483
rfc822_parse_content_header (&part->body,ucase (s1+8),s);
486
/* skip header trailing (CR)LF */
487
if (i && (CHR (bs) =='\015')) {i--; c1 = SNX (bs);}
488
if (i && (CHR (bs) =='\012')) {i--; c1 = SNX (bs);}
489
j = bs->size; /* save upper level size */
490
/* set offset for next level, fake size to i */
491
bs->size = GETPOS (bs) + i;
492
part->body.mime.text.size -= i;
494
rfc822_parse_content (&part->body,bs,h,depth+1,flags);
495
bs->size = j; /* restore current level size */
497
else { /* zero-length part, use default subtype */
498
part->body.subtype = cpystr (rfc822_default_subtype (part->body.type));
499
/* see if anything else special to do */
500
switch (part->body.type) {
501
case TYPETEXT: /* text content */
502
/* default parameters */
503
if (!part->body.parameter) {
504
part->body.parameter = mail_newbody_parameter ();
505
part->body.parameter->attribute = cpystr ("CHARSET");
506
part->body.parameter->value = cpystr ("US-ASCII");
509
case TYPEMESSAGE: /* encapsulated message in digest */
510
part->body.nested.msg = mail_newmsg ();
517
fs_give ((void **) &s1); /* finished with scratch buffer */
519
default: /* nothing special to do in any other case */
524
/* Parse RFC 2822 body content header
525
* Accepts: body to write to
526
* possible content name
527
* remainder of header
530
void rfc822_parse_content_header (BODY *body,char *name,char *s)
535
rfc822_skipws (&s); /* skip leading comments */
536
/* flush whitespace */
537
if (t = strchr (name,' ')) *t = '\0';
538
switch (*name) { /* see what kind of content */
539
case 'I': /* possible Content-ID */
540
if (!(strcmp (name+1,"D") || body->id)) body->id = cpystr (s);
542
case 'D': /* possible Content-Description */
543
if (!(strcmp (name+1,"ESCRIPTION") || body->description))
544
body->description = cpystr (s);
545
if (!(strcmp (name+1,"ISPOSITION") || body->disposition.type)) {
547
if (!(name = rfc822_parse_word (s,tspecials))) break;
548
c = *name; /* remember delimiter */
549
*name = '\0'; /* tie off type */
550
body->disposition.type = ucase (cpystr (s));
551
*name = c; /* restore delimiter */
552
rfc822_skipws (&name); /* skip whitespace */
553
rfc822_parse_parameter (&body->disposition.parameter,name);
556
case 'L': /* possible Content-Language */
557
if (!(strcmp (name+1,"ANGUAGE") || body->language)) {
558
stl = NIL; /* process languages */
559
while (s && (name = rfc822_parse_word (s,tspecials))) {
560
c = *name; /* save delimiter */
561
*name = '\0'; /* tie off subtype */
562
if (stl) stl = stl->next = mail_newstringlist ();
563
else stl = body->language = mail_newstringlist ();
564
stl->text.data = (unsigned char *) ucase (cpystr (s));
565
stl->text.size = strlen ((char *) stl->text.data);
566
*name = c; /* restore delimiter */
567
rfc822_skipws (&name); /* skip whitespace */
568
if (*name == ',') { /* any more languages? */
569
s = ++name; /* advance to it them */
572
else s = NIL; /* bogus or end of list */
575
else if (!(strcmp (name+1,"OCATION") || body->location))
576
body->location = cpystr (s);
578
case 'M': /* possible Content-MD5 */
579
if (!(strcmp (name+1,"D5") || body->md5)) body->md5 = cpystr (s);
582
case 'T': /* possible Content-Type/Transfer-Encoding */
583
if (!(strcmp (name+1,"YPE") || body->subtype || body->parameter)) {
585
if (!(name = rfc822_parse_word (s,tspecials))) break;
586
c = *name; /* remember delimiter */
587
*name = '\0'; /* tie off type */
588
s = ucase (rfc822_cpy(s));/* search for body type */
589
for (i = 0; (i <= TYPEMAX) && body_types[i] &&
590
strcmp (s,body_types[i]); i++);
591
/* record body type index */
592
body->type = (i <= TYPEMAX) ? (unsigned short) i : TYPEOTHER;
593
/* and name if new type */
594
if (body_types[body->type]) fs_give ((void **) &s);
595
else body_types[body->type] = s;
596
*name = c; /* restore delimiter */
597
rfc822_skipws (&name); /* skip whitespace */
598
if ((*name == '/') && /* subtype? */
599
(name = rfc822_parse_word ((s = ++name),tspecials))) {
600
c = *name; /* save delimiter */
601
*name = '\0'; /* tie off subtype */
602
rfc822_skipws (&s); /* copy subtype */
603
if (s) body->subtype = ucase (rfc822_cpy (s));
604
*name = c; /* restore delimiter */
605
rfc822_skipws (&name); /* skip whitespace */
607
else if (!name) { /* no subtype, was a subtype delimiter? */
608
name = s; /* barf, restore pointer */
609
rfc822_skipws (&name); /* skip leading whitespace */
611
rfc822_parse_parameter (&body->parameter,name);
613
else if (!strcmp (name+1,"RANSFER-ENCODING")) {
614
if (!(name = rfc822_parse_word (s,tspecials))) break;
615
*name = '\0'; /* tie off encoding */
616
s = ucase (rfc822_cpy(s));/* search for body encoding */
617
for (i = 0; (i <= ENCMAX) && body_encodings[i] &&
618
strcmp (s,body_encodings[i]); i++);
619
/* record body encoding index */
620
body->encoding = (i <= ENCMAX) ? (unsigned short) i : ENCOTHER;
621
/* and name if new encoding */
622
if (body_encodings[body->encoding]) fs_give ((void **) &s);
623
else body_encodings[body->encoding] = ucase (cpystr (s));
626
default: /* otherwise unknown */
631
/* Parse RFC 2822 body parameter list
632
* Accepts: parameter list to write to
636
void rfc822_parse_parameter (PARAMETER **par,char *text)
638
char c,*s,tmp[MAILTMPLEN];
639
PARAMETER *param = NIL;
640
/* parameter list? */
641
while (text && (*text == ';') &&
642
(text = rfc822_parse_word ((s = ++text),tspecials))) {
643
c = *text; /* remember delimiter */
644
*text = '\0'; /* tie off attribute name */
645
rfc822_skipws (&s); /* skip leading attribute whitespace */
646
if (!*s) *text = c; /* must have an attribute name */
647
else { /* instantiate a new parameter */
648
if (*par) param = param->next = mail_newbody_parameter ();
649
else param = *par = mail_newbody_parameter ();
650
param->attribute = ucase (cpystr (s));
651
*text = c; /* restore delimiter */
652
rfc822_skipws (&text); /* skip whitespace before equal sign */
653
if ((*text == '=') && /* make sure have value */
654
(text = rfc822_parse_word ((s = ++text),tspecials))) {
655
c = *text; /* remember delimiter */
656
*text = '\0'; /* tie off value */
657
rfc822_skipws (&s); /* skip leading value whitespace */
658
if (*s) param->value = rfc822_cpy (s);
659
*text = c; /* restore delimiter */
660
rfc822_skipws (&text);
662
if (!param->value) { /* value not found? */
663
param->value = cpystr ("MISSING_PARAMETER_VALUE");
664
sprintf (tmp,"Missing parameter value: %.80s",param->attribute);
669
/* string not present */
670
if (!text) MM_LOG ("Missing parameter",PARSE);
671
else if (*text) { /* must be end of poop */
672
sprintf (tmp,"Unexpected characters at end of parameters: %.80s",text);
677
/* Parse RFC 2822 address list
678
* Accepts: address list to write to
683
void rfc822_parse_adrlist (ADDRESS **lst,char *string,char *host)
686
char *s,tmp[MAILTMPLEN];
687
ADDRESS *last = *lst;
689
if (!string) return; /* no string */
690
rfc822_skipws (&string); /* skip leading WS */
691
if (!*string) return; /* empty string */
692
/* run to tail of list */
693
if (last) while (last->next) last = last->next;
694
while (string) { /* loop until string exhausted */
695
while (*string == ',') { /* RFC 822 allowed null addresses!! */
696
++string; /* skip the comma */
697
rfc822_skipws (&string); /* and any leading WS */
699
if (!*string) string = NIL; /* punt if ran out of string */
700
/* got an address? */
701
else if (adr = rfc822_parse_address (lst,last,&string,host,0)) {
702
last = adr; /* new tail address */
703
if (string) { /* analyze what follows */
704
rfc822_skipws (&string);
705
switch (c = *(unsigned char *) string) {
706
case ',': /* comma? */
707
++string; /* then another address follows */
710
s = isalnum (c) ? "Must use comma to separate addresses: %.80s" :
711
"Unexpected characters at end of address: %.80s";
712
sprintf (tmp,s,string);
714
last = last->next = mail_newaddr ();
715
last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS");
716
last->host = cpystr (errhst);
718
case '\0': /* null-specified address? */
719
string = NIL; /* punt remainder of parse */
724
else if (string) { /* bad mailbox */
725
rfc822_skipws (&string); /* skip WS */
726
if (!*string) strcpy (tmp,"Missing address after comma");
727
else sprintf (tmp,"Invalid mailbox list: %.80s",string);
730
(adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS");
731
adr->host = cpystr (errhst);
732
if (last) last = last->next = adr;
733
else *lst = last = adr;
739
/* Parse RFC 2822 address
740
* Accepts: address list to write to
741
* tail of address list
742
* pointer to input string
744
* group nesting depth
745
* Returns: new list tail
748
ADDRESS *rfc822_parse_address (ADDRESS **lst,ADDRESS *last,char **string,
749
char *defaulthost,unsigned long depth)
752
if (!*string) return NIL; /* no string */
753
rfc822_skipws (string); /* skip leading WS */
754
if (!**string) return NIL; /* empty string */
755
if (adr = rfc822_parse_group (lst,last,string,defaulthost,depth)) last = adr;
756
/* got an address? */
757
else if (adr = rfc822_parse_mailbox (string,defaulthost)) {
758
if (!*lst) *lst = adr; /* yes, first time through? */
759
else last->next = adr; /* no, append to the list */
760
/* set for subsequent linking */
761
for (last = adr; last->next; last = last->next);
763
else if (*string) return NIL;
767
/* Parse RFC 2822 group
768
* Accepts: address list to write to
769
* pointer to tail of address list
770
* pointer to input string
772
* group nesting depth
775
ADDRESS *rfc822_parse_group (ADDRESS **lst,ADDRESS *last,char **string,
776
char *defaulthost,unsigned long depth)
778
char tmp[MAILTMPLEN];
781
if (depth > MAXGROUPDEPTH) { /* excessively deep recursion? */
782
MM_LOG ("Ignoring excessively deep group recursion",PARSE);
783
return NIL; /* probably abusive */
785
if (!*string) return NIL; /* no string */
786
rfc822_skipws (string); /* skip leading WS */
787
if (!**string || /* trailing whitespace or not group */
788
((*(p = *string) != ':') && !(p = rfc822_parse_phrase (*string))))
790
s = p; /* end of candidate phrase */
791
rfc822_skipws (&s); /* find delimiter */
792
if (*s != ':') return NIL; /* not really a group */
793
*p = '\0'; /* tie off group name */
794
p = ++s; /* continue after the delimiter */
795
rfc822_skipws (&p); /* skip subsequent whitespace */
796
/* write as address */
797
(adr = mail_newaddr ())->mailbox = rfc822_cpy (*string);
798
if (!*lst) *lst = adr; /* first time through? */
799
else last->next = adr; /* no, append to the list */
800
last = adr; /* set for subsequent linking */
801
*string = p; /* continue after this point */
802
while (*string && **string && (**string != ';')) {
803
if (adr = rfc822_parse_address (lst,last,string,defaulthost,depth+1)) {
804
last = adr; /* new tail address */
805
if (*string) { /* anything more? */
806
rfc822_skipws (string); /* skip whitespace */
807
switch (**string) { /* see what follows */
808
case ',': /* another address? */
809
++*string; /* yes, skip past the comma */
810
case ';': /* end of group? */
811
case '\0': /* end of string */
814
sprintf (tmp,"Unexpected characters after address in group: %.80s",
817
*string = NIL; /* cancel remainder of parse */
818
last = last->next = mail_newaddr ();
819
last->mailbox = cpystr ("UNEXPECTED_DATA_AFTER_ADDRESS_IN_GROUP");
820
last->host = cpystr (errhst);
825
sprintf (tmp,"Invalid group mailbox list: %.80s",*string);
827
*string = NIL; /* cancel remainder of parse */
828
(adr = mail_newaddr ())->mailbox = cpystr ("INVALID_ADDRESS_IN_GROUP");
829
adr->host = cpystr (errhst);
830
last = last->next = adr;
833
if (*string) { /* skip close delimiter */
834
if (**string == ';') ++*string;
835
rfc822_skipws (string);
837
/* append end of address mark to the list */
838
last->next = (adr = mail_newaddr ());
839
last = adr; /* set for subsequent linking */
840
return last; /* return the tail */
843
/* Parse RFC 2822 mailbox
844
* Accepts: pointer to string pointer
846
* Returns: address list
848
* Updates string pointer
851
ADDRESS *rfc822_parse_mailbox (char **string,char *defaulthost)
855
parsephrase_t pp = (parsephrase_t) mail_parameters (NIL,GET_PARSEPHRASE,NIL);
856
if (!*string) return NIL; /* no string */
857
rfc822_skipws (string); /* flush leading whitespace */
858
if (!**string) return NIL; /* empty string */
859
if (*(s = *string) == '<') /* note start, handle case of phraseless RA */
860
adr = rfc822_parse_routeaddr (s,string,defaulthost);
861
/* otherwise, expect at least one word */
862
else if (end = rfc822_parse_phrase (s)) {
863
if ((adr = rfc822_parse_routeaddr (end,string,defaulthost))) {
864
/* phrase is a personal name */
865
if (adr->personal) fs_give ((void **) &adr->personal);
866
*end = '\0'; /* tie off phrase */
867
adr->personal = rfc822_cpy (s);
869
/* call external phraseparser if phrase only */
870
else if (pp && rfc822_phraseonly (end) &&
871
(adr = (*pp) (s,end,defaulthost))) {
872
*string = end; /* update parse pointer */
873
rfc822_skipws (string); /* skip WS in the normal way */
875
else adr = rfc822_parse_addrspec (s,string,defaulthost);
877
return adr; /* return the address */
881
/* Check if address is a phrase only
882
* Accepts: pointer to end of phrase
883
* Returns: T if phrase only, else NIL;
886
long rfc822_phraseonly (char *end)
888
while (*end == ' ') ++end; /* call rfc822_skipws() instead?? */
890
case '\0': case ',': case ';':
891
return LONGT; /* is a phrase only */
893
return NIL; /* something other than phase is here */
896
/* Parse RFC 2822 route-address
897
* Accepts: string pointer
898
* pointer to string pointer to update
901
* Updates string pointer
904
ADDRESS *rfc822_parse_routeaddr (char *string,char **ret,char *defaulthost)
906
char tmp[MAILTMPLEN];
910
if (!string) return NIL;
911
rfc822_skipws (&string); /* flush leading whitespace */
912
/* must start with open broket */
913
if (*string != '<') return NIL;
914
t = ++string; /* see if A-D-L there */
915
rfc822_skipws (&t); /* flush leading whitespace */
916
for (adl = NIL,adllen = 0; /* parse possible A-D-L */
917
(*t == '@') && (s = rfc822_parse_domain (t+1,&t));) {
918
i = strlen (s) + 2; /* @ plus domain plus delimiter or NUL */
919
if (adl) { /* have existing A-D-L? */
920
fs_resize ((void **) &adl,adllen + i);
921
sprintf (adl + adllen - 1,",@%s",s);
923
/* write initial A-D-L */
924
else sprintf (adl = (char *) fs_get (i),"@%s",s);
925
adllen += i; /* new A-D-L length */
926
fs_give ((void **) &s); /* don't need domain any more */
927
rfc822_skipws (&t); /* skip WS */
928
if (*t != ',') break; /* put if not comma */
929
t++; /* skip the comma */
930
rfc822_skipws (&t); /* skip WS */
932
if (adl) { /* got an A-D-L? */
933
if (*t != ':') { /* make sure syntax good */
934
sprintf (tmp,"Unterminated at-domain-list: %.80s%.80s",adl,t);
937
else string = ++t; /* continue parse from this point */
940
/* parse address spec */
941
if (!(adr = rfc822_parse_addrspec (string,ret,defaulthost))) {
942
if (adl) fs_give ((void **) &adl);
945
if (adl) adr->adl = adl; /* have an A-D-L? */
946
if (*ret) if (**ret == '>') { /* make sure terminated OK */
947
++*ret; /* skip past the broket */
948
rfc822_skipws (ret); /* flush trailing WS */
949
if (!**ret) *ret = NIL; /* wipe pointer if at end of string */
950
return adr; /* return the address */
952
sprintf (tmp,"Unterminated mailbox: %.80s@%.80s",adr->mailbox,
953
*adr->host == '@' ? "<null>" : adr->host);
955
adr->next = mail_newaddr ();
956
adr->next->mailbox = cpystr ("MISSING_MAILBOX_TERMINATOR");
957
adr->next->host = cpystr (errhst);
958
return adr; /* return the address */
961
/* Parse RFC 2822 address-spec
962
* Accepts: string pointer
963
* pointer to string pointer to update
967
* Updates string pointer
970
ADDRESS *rfc822_parse_addrspec (char *string,char **ret,char *defaulthost)
973
char c,*s,*t,*v,*end;
974
if (!string) return NIL; /* no string */
975
rfc822_skipws (&string); /* flush leading whitespace */
976
if (!*string) return NIL; /* empty string */
977
/* find end of mailbox */
978
if (!(t = rfc822_parse_word (string,wspecials))) return NIL;
979
adr = mail_newaddr (); /* create address block */
980
c = *t; /* remember delimiter */
981
*t = '\0'; /* tie off mailbox */
983
adr->mailbox = rfc822_cpy (string);
984
*t = c; /* restore delimiter */
985
end = t; /* remember end of mailbox */
986
rfc822_skipws (&t); /* skip whitespace */
987
while (*t == '.') { /* some cretin taking RFC 822 too seriously? */
988
string = ++t; /* skip past the dot and any WS */
989
rfc822_skipws (&string);
990
/* get next word of mailbox */
991
if (t = rfc822_parse_word (string,wspecials)) {
992
end = t; /* remember new end of mailbox */
993
c = *t; /* remember delimiter */
994
*t = '\0'; /* tie off word */
995
s = rfc822_cpy (string); /* copy successor part */
996
*t = c; /* restore delimiter */
997
/* build new mailbox */
998
sprintf (v = (char *) fs_get (strlen (adr->mailbox) + strlen (s) + 2),
999
"%s.%s",adr->mailbox,s);
1000
fs_give ((void **) &adr->mailbox);
1001
adr->mailbox = v; /* new host name */
1002
rfc822_skipws (&t); /* skip WS after mailbox */
1005
MM_LOG ("Invalid mailbox part after .",PARSE);
1009
t = end; /* remember delimiter in case no host */
1011
rfc822_skipws (&end); /* sniff ahead at what follows */
1012
#if RFC733 /* RFC 733 used "at" instead of "@" */
1013
if (((*end == 'a') || (*end == 'A')) &&
1014
((end[1] == 't') || (end[1] == 'T')) &&
1015
((end[2] == ' ') || (end[2] == '\t') || (end[2] == '\015') ||
1016
(end[2] == '\012') || (end[2] == '(')))
1019
if (*end != '@') end = t; /* host name missing */
1020
/* otherwise parse host name */
1021
else if (!(adr->host = rfc822_parse_domain (++end,&end)))
1022
adr->host = cpystr (errhst);
1023
/* default host if missing */
1024
if (!adr->host) adr->host = cpystr (defaulthost);
1025
/* try person name in comments if missing */
1026
if (end && !(adr->personal && *adr->personal)) {
1027
while (*end == ' ') ++end; /* see if we can find a person name here */
1028
if ((*end == '(') && (s = rfc822_skip_comment (&end,LONGT)) && strlen (s))
1029
adr->personal = rfc822_cpy (s);
1030
rfc822_skipws (&end); /* skip any other WS in the normal way */
1032
/* set return to end pointer */
1033
*ret = (end && *end) ? end : NIL;
1034
return adr; /* return the address we got */
1037
/* Parse RFC 2822 domain
1038
* Accepts: string pointer
1039
* pointer to return end of domain
1040
* Returns: domain name or NIL if failure
1043
char *rfc822_parse_domain (char *string,char **end)
1047
rfc822_skipws (&string); /* skip whitespace */
1048
if (*string == '[') { /* domain literal? */
1049
if (!(*end = rfc822_parse_word (string + 1,"]\\")))
1050
MM_LOG ("Empty domain literal",PARSE);
1051
else if (**end != ']') MM_LOG ("Unterminated domain literal",PARSE);
1053
size_t len = ++*end - string;
1054
strncpy (ret = (char *) fs_get (len + 1),string,len);
1055
ret[len] = '\0'; /* tie off literal */
1058
/* search for end of host */
1059
else if (t = rfc822_parse_word (string,wspecials)) {
1060
c = *t; /* remember delimiter */
1061
*t = '\0'; /* tie off host */
1062
ret = rfc822_cpy (string); /* copy host */
1063
*t = c; /* restore delimiter */
1064
*end = t; /* remember end of domain */
1065
rfc822_skipws (&t); /* skip WS after host */
1066
while (*t == '.') { /* some cretin taking RFC 822 too seriously? */
1067
string = ++t; /* skip past the dot and any WS */
1068
rfc822_skipws (&string);
1069
if (string = rfc822_parse_domain (string,&t)) {
1070
*end = t; /* remember new end of domain */
1071
c = *t; /* remember delimiter */
1072
*t = '\0'; /* tie off host */
1073
s = rfc822_cpy (string);/* copy successor part */
1074
*t = c; /* restore delimiter */
1075
/* build new domain */
1076
sprintf (v = (char *) fs_get (strlen (ret) + strlen (s) + 2),
1078
fs_give ((void **) &ret);
1079
ret = v; /* new host name */
1080
rfc822_skipws (&t); /* skip WS after domain */
1083
MM_LOG ("Invalid domain part after .",PARSE);
1088
else MM_LOG ("Missing or invalid host name after @",PARSE);
1092
/* Parse RFC 2822 phrase
1093
* Accepts: string pointer
1094
* Returns: pointer to end of phrase
1097
char *rfc822_parse_phrase (char *s)
1100
if (!s) return NIL; /* no-op if no string */
1101
/* find first word of phrase */
1102
curpos = rfc822_parse_word (s,NIL);
1103
if (!curpos) return NIL; /* no words means no phrase */
1104
if (!*curpos) return curpos; /* check if string ends with word */
1105
s = curpos; /* sniff past the end of this word and WS */
1106
rfc822_skipws (&s); /* skip whitespace */
1107
/* recurse to see if any more */
1108
return (s = rfc822_parse_phrase (s)) ? s : curpos;
1111
/* Parse RFC 2822 word
1112
* Accepts: string pointer
1113
* delimiter (or NIL for phrase word parsing)
1114
* Returns: pointer to end of word
1117
char *rfc822_parse_word (char *s,const char *delimiters)
1120
if (!s) return NIL; /* no string */
1121
rfc822_skipws (&s); /* flush leading whitespace */
1122
if (!*s) return NIL; /* empty string */
1123
str = s; /* hunt pointer for strpbrk */
1124
while (T) { /* look for delimiter, return if none */
1125
if (!(st = strpbrk (str,delimiters ? delimiters : wspecials)))
1126
return str + strlen (str);
1128
if (!delimiters && (*st == I2C_ESC)) {
1129
str = ++st; /* always skip past ESC */
1130
switch (*st) { /* special hack for RFC 1468 (ISO-2022-JP) */
1131
case I2C_MULTI: /* multi byte sequence */
1133
case I2CS_94x94_JIS_OLD:/* old JIS (1978) */
1134
case I2CS_94x94_JIS_NEW:/* new JIS (1983) */
1135
str = ++st; /* skip past the shift to JIS */
1136
while (st = strchr (st,I2C_ESC))
1137
if ((*++st == I2C_G0_94) && ((st[1] == I2CS_94_ASCII) ||
1138
(st[1] == I2CS_94_JIS_ROMAN) ||
1139
(st[1] == I2CS_94_JIS_BUGROM))) {
1140
str = st += 2; /* skip past the shift back to ASCII */
1143
/* eats entire text if no shift back */
1144
if (!st || !*st) return str + strlen (str);
1147
case I2C_G0_94: /* single byte sequence */
1149
case I2CS_94_ASCII: /* shift to ASCII */
1150
case I2CS_94_JIS_ROMAN: /* shift to JIS-Roman */
1151
case I2CS_94_JIS_BUGROM:/* old buggy definition of JIS-Roman */
1152
str = st + 2; /* skip past the shift */
1158
else switch (*st) { /* dispatch based on delimiter */
1159
case '"': /* quoted string */
1160
/* look for close quote */
1161
while (*++st != '"') switch (*st) {
1162
case '\0': /* unbalanced quoted string */
1163
return NIL; /* sick sick sick */
1164
case '\\': /* quoted character */
1165
if (!*++st) return NIL; /* skip the next character */
1166
default: /* ordinary character */
1167
break; /* no special action */
1169
str = ++st; /* continue parse */
1171
case '\\': /* quoted character */
1172
/* This is wrong; a quoted-pair can not be part of a word. However,
1173
* domain-literal is parsed as a word and quoted-pairs can be used
1174
* *there*. Either way, it's pretty pathological.
1176
if (st[1]) { /* not on NUL though... */
1177
str = st + 2; /* skip quoted character and go on */
1180
default: /* found a word delimiter */
1181
return (st == s) ? NIL : st;
1186
/* Copy an RFC 2822 format string
1188
* Returns: copy of string
1191
char *rfc822_cpy (char *src)
1193
/* copy and unquote */
1194
return rfc822_quote (cpystr (src));
1198
/* Unquote an RFC 2822 format string
1203
char *rfc822_quote (char *src)
1206
if (strpbrk (src,"\\\"")) { /* any quoting in string? */
1208
while (*src) { /* copy string */
1209
if (*src == '\"') src++; /* skip double quote entirely */
1211
if (*src == '\\') src++;/* skip over single quote, copy next always */
1212
*dst++ = *src++; /* copy character */
1215
*dst = '\0'; /* tie off string */
1217
return ret; /* return our string */
1221
/* Copy address list
1222
* Accepts: address list
1223
* Returns: address list
1226
ADDRESS *rfc822_cpy_adr (ADDRESS *adr)
1230
ADDRESS *prev = NIL;
1231
while (adr) { /* loop while there's still an MAP adr */
1232
dadr = mail_newaddr (); /* instantiate a new address */
1233
if (!ret) ret = dadr; /* note return */
1234
if (prev) prev->next = dadr;/* tie on to the end of any previous */
1235
dadr->personal = cpystr (adr->personal);
1236
dadr->adl = cpystr (adr->adl);
1237
dadr->mailbox = cpystr (adr->mailbox);
1238
dadr->host = cpystr (adr->host);
1239
prev = dadr; /* this is now the previous */
1240
adr = adr->next; /* go to next address in list */
1242
return (ret); /* return the MTP address list */
1245
/* Skips RFC 2822 whitespace
1246
* Accepts: pointer to string pointer
1249
void rfc822_skipws (char **s)
1251
while (T) switch (**s) {
1252
case ' ': case '\t': case '\015': case '\012':
1253
++*s; /* skip all forms of LWSP */
1255
case '(': /* start of comment */
1256
if (rfc822_skip_comment (s,(long) NIL)) break;
1258
return; /* end of whitespace */
1263
/* Skips RFC 2822 comment
1264
* Accepts: pointer to string pointer
1266
* Returns: pointer to first non-blank character of comment
1269
char *rfc822_skip_comment (char **s,long trim)
1271
char *ret,tmp[MAILTMPLEN];
1274
/* skip past whitespace */
1275
for (ret = ++s1; *ret == ' '; ret++);
1276
do switch (*s1) { /* get character of comment */
1277
case '(': /* nested comment? */
1278
if (!rfc822_skip_comment (&s1,(long) NIL)) return NIL;
1279
t = --s1; /* last significant char at end of comment */
1281
case ')': /* end of comment? */
1282
*s = ++s1; /* skip past end of comment */
1283
if (trim) { /* if level 0, must trim */
1284
if (t) t[1] = '\0'; /* tie off comment string */
1285
else *ret = '\0'; /* empty comment */
1288
case '\\': /* quote next character? */
1289
if (*++s1) { /* next character non-null? */
1290
t = s1; /* update last significant character pointer */
1293
case '\0': /* end of string */
1294
sprintf (tmp,"Unterminated comment: %.80s",*s);
1296
**s = '\0'; /* nuke duplicate messages in case reparse */
1297
return NIL; /* this is wierd if it happens */
1298
case ' ': /* whitespace isn't significant */
1300
default: /* random character */
1301
t = s1; /* update last significant character pointer */
1304
return NIL; /* impossible, but pacify lint et al */
1307
/* Buffered output routines */
1310
/* Output character to buffer
1312
* character to write
1313
* Returns: T if success, NIL if error
1316
static long rfc822_output_char (RFC822BUFFER *buf,int c)
1318
*buf->cur++ = c; /* add character, soutr buffer if full */
1319
return (buf->cur == buf->end) ? rfc822_output_flush (buf) : LONGT;
1323
/* Output data to buffer
1327
* Returns: T if success, NIL if error
1330
static long rfc822_output_data (RFC822BUFFER *buf,char *string,long len)
1332
while (len) { /* until request satified */
1334
if (i = min (len,buf->end - buf->cur)) {
1335
memcpy (buf->cur,string,i);
1336
buf->cur += i; /* blat data */
1340
/* soutr buffer now if full */
1341
if (len && !rfc822_output_flush (buf)) return NIL;
1346
/* Output string to buffer
1349
* Returns: T if success, NIL if error
1352
static long rfc822_output_string (RFC822BUFFER *buf,char *string)
1354
return rfc822_output_data (buf,string,strlen (string));
1361
* stream for I/O routine
1362
* Returns: T if success, NIL if error
1365
long rfc822_output_flush (RFC822BUFFER *buf)
1367
*buf->cur = '\0'; /* tie off buffer at this point */
1368
return (*buf->f) (buf->s,buf->cur = buf->beg);
1371
/* Message writing routines */
1374
/* Output RFC 822 message
1375
* Accepts: temporary buffer as a SIZEDTEXT
1379
* stream for I/O routine
1380
* non-zero if 8-bit output desired
1381
* Returns: T if successful, NIL if failure
1383
* This routine always uses standard specials for phrases and does not write
1384
* bcc entries, since it is called from the SMTP and NNTP routines. If you
1385
* need to do something different you need to arm an rfc822outfull_t and/or
1386
* rfc822out_t function.
1389
long rfc822_output_full (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,long ok8)
1391
rfc822outfull_t r822of =
1392
(rfc822outfull_t) mail_parameters (NIL,GET_RFC822OUTPUTFULL,NIL);
1393
rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
1394
/* call external RFC 2822 output generator */
1395
if (r822of) return (*r822of) (buf,env,body,ok8);
1396
else if (r822o) return (*r822o) (buf->cur,env,body,buf->f,buf->s,ok8);
1397
/* encode body as necessary */
1398
if (ok8) rfc822_encode_body_8bit (env,body);
1399
else rfc822_encode_body_7bit (env,body);
1400
/* output header and body */
1401
return rfc822_output_header (buf,env,body,NIL,NIL) &&
1402
rfc822_output_text (buf,body) && rfc822_output_flush (buf);
1405
/* Output RFC 822 header
1409
* non-standard specials to be used for phrases if non-NIL
1410
* flags (non-zero to include bcc
1411
* Returns: T if success, NIL if failure
1414
long rfc822_output_header (RFC822BUFFER *buf,ENVELOPE *env,BODY *body,
1415
const char *specials,long flags)
1417
long i = env->remail ? strlen (env->remail) : 0;
1418
return /* write header */
1419
(!i || /* snip extra CRLF from remail header */
1420
rfc822_output_data (buf,env->remail,
1421
((i > 4) && (env->remail[i-4] == '\015')) ?
1423
rfc822_output_header_line (buf,"Newsgroups",i,env->newsgroups) &&
1424
rfc822_output_header_line (buf,"Date",i,env->date) &&
1425
rfc822_output_address_line (buf,"From",i,env->from,specials) &&
1426
rfc822_output_address_line (buf,"Sender",i,env->sender,specials) &&
1427
rfc822_output_address_line (buf,"Reply-To",i,env->reply_to,specials) &&
1428
rfc822_output_header_line (buf,"Subject",i,env->subject) &&
1429
((env->bcc && !(env->to || env->cc)) ?
1430
rfc822_output_string (buf,"To: undisclosed recipients: ;\015\012") :
1432
rfc822_output_address_line (buf,"To",i,env->to,specials) &&
1433
rfc822_output_address_line (buf,"cc",i,env->cc,specials) &&
1434
(flags ? rfc822_output_address_line (buf,"bcc",i,env->bcc,specials) : T) &&
1435
rfc822_output_header_line (buf,"In-Reply-To",i,env->in_reply_to) &&
1436
rfc822_output_header_line (buf,"Message-ID",i,env->message_id) &&
1437
rfc822_output_header_line (buf,"Followup-to",i,env->followup_to) &&
1438
rfc822_output_header_line (buf,"References",i,env->references) &&
1439
(env->remail || !body ||
1440
(rfc822_output_string (buf,"MIME-Version: 1.0\015\012") &&
1441
rfc822_output_body_header (buf,body))) &&
1442
/* write terminating blank line */
1443
rfc822_output_string (buf,"\015\012");
1446
/* Output RFC 2822 header text line
1448
* pointer to header type
1449
* non-NIL if resending
1451
* Returns: T if success, NIL if failure
1454
long rfc822_output_header_line (RFC822BUFFER *buf,char *type,long resent,
1458
((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
1459
rfc822_output_string (buf,type) && rfc822_output_string (buf,": ") &&
1460
rfc822_output_string (buf,text) && rfc822_output_string (buf,"\015\012"));
1464
/* Output RFC 2822 header address line
1466
* pointer to header type
1467
* non-NIL if resending
1468
* address(s) to interpret
1469
* non-standard specials to be used for phrases if non-NIL
1470
* Returns: T if success, NIL if failure
1473
long rfc822_output_address_line (RFC822BUFFER *buf,char *type,long resent,
1474
ADDRESS *adr,const char *specials)
1476
long pretty = strlen (type);
1478
((resent ? rfc822_output_string (buf,resentprefix) : LONGT) &&
1479
rfc822_output_data (buf,type,pretty) && rfc822_output_string (buf,": ") &&
1480
rfc822_output_address_list (buf,adr,
1481
resent ? pretty + sizeof (RESENTPREFIX) - 1 :
1483
rfc822_output_string (buf,"\015\012"));
1486
/* Output RFC 2822 address list
1488
* pointer to address list
1489
* non-zero if pretty-printing
1490
* non-standard specials to be used for phrases if non-NIL
1491
* Returns: T if success, NIL if failure
1494
long rfc822_output_address_list (RFC822BUFFER *buf,ADDRESS *adr,long pretty,
1495
const char *specials)
1498
/* default to rspecials */
1499
if (!specials) specials = rspecials;
1500
for (n = 0; adr; adr = adr->next) {
1501
char *base = buf->cur;
1502
if (adr->host) { /* ordinary address? */
1503
if (!(pretty && n)) { /* suppress if pretty and in group */
1504
if ( /* use phrase <route-addr> if phrase */
1506
adr->adl || /* or A-D-L */
1508
(adr->personal && *adr->personal)) {
1509
if (!((adr->personal ? rfc822_output_cat (buf,adr->personal,
1510
rspecials) : LONGT) &&
1511
rfc822_output_string (buf," <") &&
1512
rfc822_output_address (buf,adr) &&
1513
rfc822_output_string (buf,">"))) return NIL;
1515
else if (!rfc822_output_address (buf,adr)) return NIL;
1516
if (adr->next && adr->next->mailbox &&
1517
!rfc822_output_string (buf,", ")) return NIL;
1520
else if (adr->mailbox) { /* start of group? */
1521
/* yes, write group */
1522
if (!(rfc822_output_cat (buf,adr->mailbox,rspecials) &&
1523
rfc822_output_string (buf,": "))) return NIL;
1524
++n; /* in a group now */
1526
else if (n) { /* must be end of group (but be paranoid) */
1527
if (!rfc822_output_char (buf,';') ||
1528
((!--n && adr->next && adr->next->mailbox) &&
1529
!rfc822_output_string (buf,", "))) return NIL;
1531
if (pretty && /* pretty printing? */
1532
((pretty += ((buf->cur > base) ? buf->cur - base :
1533
(buf->end - base) + (buf->cur - buf->beg))) >= 78)) {
1534
if (!(rfc822_output_string (buf,"\015\012") &&
1535
rfc822_output_string (buf,RFC822CONT))) return NIL;
1536
base = buf->cur; /* update base for pretty printing */
1537
pretty = sizeof (RFC822CONT) - 1;
1543
/* Write RFC 2822 route-address to string
1545
* pointer to single address
1546
* Returns: T if success, NIL if failure
1549
long rfc822_output_address (RFC822BUFFER *buf,ADDRESS *adr)
1551
return !adr || !adr->host ||
1553
#if RFC822 /* old code with A-D-L support */
1554
(!adr->adl || (rfc822_output_string (buf,adr->adl) &&
1555
rfc822_output_char (buf,':'))) &&
1557
rfc822_output_cat (buf,adr->mailbox,NIL) &&
1558
((*adr->host == '@') || /* unless null host (HIGHLY discouraged!) */
1559
(rfc822_output_char (buf,'@') &&
1560
rfc822_output_cat (buf,adr->host,NIL))));
1564
/* Output RFC 2822 string with concatenation
1566
* string to concatenate
1567
* list of special characters or NIL for dot-atom format
1568
* Returns: T if success, NIL if failure
1571
long rfc822_output_cat (RFC822BUFFER *buf,char *src,const char *specials)
1574
if (!*src || /* empty string or any specials present? */
1575
(specials ? (T && strpbrk (src,specials)) :
1576
(strpbrk (src,wspecials) || (*src == '.') || strstr (src,"..") ||
1577
(src[strlen (src) - 1] == '.')))) {
1578
/* yes, write as quoted string*/
1579
if (!rfc822_output_char (buf,'"')) return NIL;
1580
/* embedded quote characters? */
1581
for (; s = strpbrk (src,"\\\""); src = s + 1) {
1582
/* yes, insert quoting */
1583
if (!(rfc822_output_data (buf,src,s-src) &&
1584
rfc822_output_char (buf,'\\') &&
1585
rfc822_output_char (buf,*s))) return NIL;
1587
/* return string and trailing quote*/
1588
return rfc822_output_string (buf,src) && rfc822_output_char (buf,'"');
1591
return rfc822_output_string (buf,src);
1594
/* Output MIME parameter list
1597
* Returns: T if success, NIL if failure
1600
long rfc822_output_parameter (RFC822BUFFER *buf,PARAMETER *param)
1603
if (rfc822_output_string (buf,"; ") &&
1604
rfc822_output_string (buf,param->attribute) &&
1605
rfc822_output_char (buf,'=') &&
1606
rfc822_output_cat (buf,param->value,tspecials)) param = param->next;
1613
/* Output RFC 2822 stringlist
1616
* Returns: T if success, NIL if failure
1619
long rfc822_output_stringlist (RFC822BUFFER *buf,STRINGLIST *stl)
1622
if (!rfc822_output_cat (buf,(char *) stl->text.data,tspecials) ||
1623
((stl = stl->next) && !rfc822_output_string (buf,", ")))
1628
/* Output body content header
1631
* Returns: T if success, NIL if failure
1634
long rfc822_output_body_header (RFC822BUFFER *buf,BODY *body)
1636
return /* type and subtype*/
1637
rfc822_output_string (buf,"Content-Type: ") &&
1638
rfc822_output_string (buf,body_types[body->type]) &&
1639
rfc822_output_char (buf,'/') &&
1640
rfc822_output_string (buf,body->subtype ? body->subtype :
1641
rfc822_default_subtype (body->type)) &&
1642
/* parameters (w/ US-ASCII default */
1643
(body->parameter ? rfc822_output_parameter (buf,body->parameter) :
1644
((body->type != TYPETEXT) ||
1645
(rfc822_output_string (buf,"; CHARSET=") &&
1646
rfc822_output_string (buf,(body->encoding == ENC7BIT) ?
1647
"US-ASCII" : "X-UNKNOWN")))) &&
1648
(!body->encoding || /* note: 7BIT never output as encoding! */
1649
(rfc822_output_string (buf,"\015\012Content-Transfer-Encoding: ") &&
1650
rfc822_output_string (buf,body_encodings[body->encoding]))) &&
1651
(!body->id || /* identification */
1652
(rfc822_output_string (buf,"\015\012Content-ID: ") &&
1653
rfc822_output_string (buf,body->id))) &&
1654
(!body->description || /* description */
1655
(rfc822_output_string (buf,"\015\012Content-Description: ") &&
1656
rfc822_output_string (buf,body->description))) &&
1657
(!body->md5 || /* MD5 checksum */
1658
(rfc822_output_string (buf,"\015\012Content-MD5: ") &&
1659
rfc822_output_string (buf,body->md5))) &&
1660
(!body->language || /* language */
1661
(rfc822_output_string (buf,"\015\012Content-Language: ") &&
1662
rfc822_output_stringlist (buf,body->language))) &&
1663
(!body->location || /* location */
1664
(rfc822_output_string (buf,"\015\012Content-Location: ") &&
1665
rfc822_output_string (buf,body->location))) &&
1666
(!body->disposition.type || /* disposition */
1667
(rfc822_output_string (buf,"\015\012Content-Disposition: ") &&
1668
rfc822_output_string (buf,body->disposition.type) &&
1669
rfc822_output_parameter (buf,body->disposition.parameter))) &&
1670
rfc822_output_string (buf,"\015\012");
1673
/* Encode a body for 7BIT transmittal
1678
void rfc822_encode_body_7bit (ENVELOPE *env,BODY *body)
1683
if (body) switch (body->type) {
1684
case TYPEMULTIPART: /* multi-part */
1685
for (param = &body->parameter;
1686
*param && strcmp ((*param)->attribute,"BOUNDARY");
1687
param = &(*param)->next);
1688
if (!*param) { /* cookie not set up yet? */
1689
char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
1690
sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1691
(unsigned long) random (),(unsigned long) time (0),
1692
(unsigned long) getpid ());
1693
(*param) = mail_newbody_parameter ();
1694
(*param)->attribute = cpystr ("BOUNDARY");
1695
(*param)->value = cpystr (tmp);
1697
part = body->nested.part; /* encode body parts */
1698
do rfc822_encode_body_7bit (env,&part->body);
1699
while (part = part->next); /* until done */
1701
case TYPEMESSAGE: /* encapsulated message */
1702
switch (body->encoding) {
1706
MM_LOG ("8-bit included message in 7-bit message body",PARSE);
1709
MM_LOG ("Binary included message in 7-bit message body",PARSE);
1712
fatal ("Invalid rfc822_encode_body_7bit message encoding");
1714
break; /* can't change encoding */
1715
default: /* all else has some encoding */
1716
switch (body->encoding) {
1717
case ENC8BIT: /* encode 8BIT into QUOTED-PRINTABLE */
1718
/* remember old 8-bit contents */
1719
f = (void *) body->contents.text.data;
1720
body->contents.text.data =
1721
rfc822_8bit (body->contents.text.data,
1722
body->contents.text.size,&body->contents.text.size);
1723
body->encoding = ENCQUOTEDPRINTABLE;
1724
fs_give (&f); /* flush old binary contents */
1726
case ENCBINARY: /* encode binary into BASE64 */
1727
/* remember old binary contents */
1728
f = (void *) body->contents.text.data;
1729
body->contents.text.data =
1730
rfc822_binary ((void *) body->contents.text.data,
1731
body->contents.text.size,&body->contents.text.size);
1732
body->encoding = ENCBASE64;
1733
fs_give (&f); /* flush old binary contents */
1734
default: /* otherwise OK */
1741
/* Encode a body for 8BIT transmittal
1746
void rfc822_encode_body_8bit (ENVELOPE *env,BODY *body)
1751
if (body) switch (body->type) {
1752
case TYPEMULTIPART: /* multi-part */
1753
for (param = &body->parameter;
1754
*param && strcmp ((*param)->attribute,"BOUNDARY");
1755
param = &(*param)->next);
1756
if (!*param) { /* cookie not set up yet? */
1757
char tmp[MAILTMPLEN]; /* make cookie not in BASE64 or QUOTEPRINT*/
1758
sprintf (tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1759
(unsigned long) random (),(unsigned long) time (0),
1760
(unsigned long) getpid ());
1761
(*param) = mail_newbody_parameter ();
1762
(*param)->attribute = cpystr ("BOUNDARY");
1763
(*param)->value = cpystr (tmp);
1765
part = body->nested.part; /* encode body parts */
1766
do rfc822_encode_body_8bit (env,&part->body);
1767
while (part = part->next); /* until done */
1769
case TYPEMESSAGE: /* encapsulated message */
1770
switch (body->encoding) {
1775
MM_LOG ("Binary included message in 8-bit message body",PARSE);
1778
fatal ("Invalid rfc822_encode_body_7bit message encoding");
1780
break; /* can't change encoding */
1781
default: /* other type, encode binary into BASE64 */
1782
if (body->encoding == ENCBINARY) {
1783
/* remember old binary contents */
1784
f = (void *) body->contents.text.data;
1785
body->contents.text.data =
1786
rfc822_binary ((void *) body->contents.text.data,
1787
body->contents.text.size,&body->contents.text.size);
1788
body->encoding = ENCBASE64;
1789
fs_give (&f); /* flush old binary contents */
1795
/* Output RFC 822 text
1798
* Returns: T if successful, NIL if failure
1801
long rfc822_output_text (RFC822BUFFER *buf,BODY *body)
1803
/* MULTIPART gets special handling */
1804
if (body->type == TYPEMULTIPART) {
1805
char *cookie,tmp[MAILTMPLEN];
1809
for (param = body->parameter; param && strcmp (param->attribute,"BOUNDARY");
1810
param = param->next);
1811
if (param) cookie = param->value;
1812
else { /* make cookie not in BASE64 or QUOTEPRINT*/
1813
sprintf (cookie = tmp,"%lu-%lu-%lu=:%lu",(unsigned long) gethostid (),
1814
(unsigned long) random (),(unsigned long) time (0),
1815
(unsigned long) getpid ());
1816
(param = mail_newbody_parameter ())->attribute = cpystr ("BOUNDARY");
1817
param->value = cpystr (tmp);
1818
param->next = body->parameter;
1819
body->parameter = param;
1821
/* output each part */
1822
for (part = body->nested.part; part; part = part->next)
1823
if (!(rfc822_output_string (buf,"--") &&
1824
rfc822_output_string (buf,cookie) &&
1825
rfc822_output_string (buf,"\015\012") &&
1826
rfc822_output_body_header (buf,&part->body) &&
1827
rfc822_output_string (buf,"\015\012") &&
1828
rfc822_output_text (buf,&part->body))) return NIL;
1829
/* output trailing cookie */
1830
return rfc822_output_string (buf,"--") &&
1831
rfc822_output_string (buf,cookie) &&
1832
rfc822_output_string (buf,"--\015\012");
1834
/* output segment and trailing CRLF */
1835
return (!body->contents.text.data ||
1836
rfc822_output_string (buf,(char *) body->contents.text.data)) &&
1837
rfc822_output_string (buf,"\015\012");
1840
/* Body contents encoding/decoding routines */
1843
/* Convert BASE64 contents to binary
1846
* pointer to return destination length
1847
* Returns: destination as binary or NIL if error
1850
#define WSP 0176 /* NUL, TAB, LF, FF, CR, SPC */
1854
void *rfc822_base64 (unsigned char *src,unsigned long srcl,unsigned long *len)
1856
char c,*s,tmp[MAILTMPLEN];
1857
void *ret = fs_get ((size_t) ((*len = 4 + ((srcl * 3) / 4))) + 1);
1858
char *d = (char *) ret;
1860
static char decode[256] = {
1861
WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,WSP,WSP,JNK,WSP,WSP,JNK,JNK,
1862
JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1863
WSP,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,076,JNK,JNK,JNK,077,
1864
064,065,066,067,070,071,072,073,074,075,JNK,JNK,JNK,PAD,JNK,JNK,
1865
JNK,000,001,002,003,004,005,006,007,010,011,012,013,014,015,016,
1866
017,020,021,022,023,024,025,026,027,030,031,JNK,JNK,JNK,JNK,JNK,
1867
JNK,032,033,034,035,036,037,040,041,042,043,044,045,046,047,050,
1868
051,052,053,054,055,056,057,060,061,062,063,JNK,JNK,JNK,JNK,JNK,
1869
JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1870
JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1871
JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1872
JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1873
JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1874
JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1875
JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,
1876
JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK,JNK
1878
/* initialize block */
1879
memset (ret,0,((size_t) *len) + 1);
1880
*len = 0; /* in case we return an error */
1882
/* simple-minded decode */
1883
for (e = 0; srcl--; ) switch (c = decode[*src++]) {
1884
default: /* valid BASE64 data character */
1885
switch (e++) { /* install based on quantum position */
1887
*d = c << 2; /* byte 1: high 6 bits */
1890
*d++ |= c >> 4; /* byte 1: low 2 bits */
1891
*d = c << 4; /* byte 2: high 4 bits */
1894
*d++ |= c >> 2; /* byte 2: low 4 bits */
1895
*d = c << 6; /* byte 3: high 2 bits */
1898
*d++ |= c; /* byte 3: low 6 bits */
1899
e = 0; /* reinitialize mechanism */
1903
case WSP: /* whitespace */
1905
case PAD: /* padding */
1906
switch (e++) { /* check quantum position */
1907
case 3: /* one = is good enough in quantum 3 */
1908
/* make sure no data characters in remainder */
1909
for (; srcl; --srcl) switch (decode[*src++]) {
1910
/* ignore space, junk and extraneous padding */
1911
case WSP: case JNK: case PAD:
1913
default: /* valid BASE64 data character */
1914
/* This indicates bad MIME. One way that it can be caused is if
1915
a single-section message was BASE64 encoded and then something
1916
(e.g. a mailing list processor) appended text. The problem is
1917
that in 1 out of 3 cases, there is no padding and hence no way
1918
to detect the end of the data. Consequently, prudent software
1919
will always encapsulate a BASE64 segment inside a MULTIPART.
1921
sprintf (tmp,"Possible data truncation in rfc822_base64(): %.80s",
1923
if (s = strpbrk (tmp,"\015\012")) *s = NIL;
1925
srcl = 1; /* don't issue any more messages */
1929
case 2: /* expect a second = in quantum 2 */
1930
if (srcl && (*src == '=')) break;
1931
default: /* impossible quantum position */
1936
case JNK: /* junk character */
1940
*len = d - (char *) ret; /* calculate data length */
1941
*d = '\0'; /* NUL terminate just in case */
1942
return ret; /* return the string */
1945
/* Convert binary contents to BASE64
1948
* pointer to return destination length
1949
* Returns: destination as BASE64
1952
unsigned char *rfc822_binary (void *src,unsigned long srcl,unsigned long *len)
1954
unsigned char *ret,*d;
1955
unsigned char *s = (unsigned char *) src;
1956
char *v = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1957
unsigned long i = ((srcl + 2) / 3) * 4;
1958
*len = i += 2 * ((i / 60) + 1);
1959
d = ret = (unsigned char *) fs_get ((size_t) ++i);
1960
/* process tuplets */
1961
for (i = 0; srcl >= 3; s += 3, srcl -= 3) {
1962
*d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
1963
/* byte 2: low 2 bits (1), high 4 bits (2) */
1964
*d++ = v[((s[0] << 4) + (s[1] >> 4)) & 0x3f];
1965
/* byte 3: low 4 bits (2), high 2 bits (3) */
1966
*d++ = v[((s[1] << 2) + (s[2] >> 6)) & 0x3f];
1967
*d++ = v[s[2] & 0x3f]; /* byte 4: low 6 bits (3) */
1968
if ((++i) == 15) { /* output 60 characters? */
1969
i = 0; /* restart line break count, insert CRLF */
1970
*d++ = '\015'; *d++ = '\012';
1974
*d++ = v[s[0] >> 2]; /* byte 1: high 6 bits (1) */
1975
/* byte 2: low 2 bits (1), high 4 bits (2) */
1976
*d++ = v[((s[0] << 4) + (--srcl ? (s[1] >> 4) : 0)) & 0x3f];
1977
/* byte 3: low 4 bits (2), high 2 bits (3) */
1978
*d++ = srcl ? v[((s[1] << 2) + (--srcl ? (s[2] >> 6) : 0)) & 0x3f] : '=';
1979
/* byte 4: low 6 bits (3) */
1980
*d++ = srcl ? v[s[2] & 0x3f] : '=';
1981
if (srcl) srcl--; /* count third character if processed */
1982
if ((++i) == 15) { /* output 60 characters? */
1983
i = 0; /* restart line break count, insert CRLF */
1984
*d++ = '\015'; *d++ = '\012';
1987
*d++ = '\015'; *d++ = '\012'; /* insert final CRLF */
1988
*d = '\0'; /* tie off string */
1989
if (((unsigned long) (d - ret)) != *len) fatal ("rfc822_binary logic flaw");
1990
return ret; /* return the resulting string */
1993
/* Convert QUOTED-PRINTABLE contents to 8BIT
1996
* pointer to return destination length
1997
* Returns: destination as 8-bit text or NIL if error
2000
unsigned char *rfc822_qprint (unsigned char *src,unsigned long srcl,
2003
char tmp[MAILTMPLEN];
2004
unsigned int bogon = NIL;
2005
unsigned char *ret = (unsigned char *) fs_get ((size_t) srcl + 1);
2006
unsigned char *d = ret;
2007
unsigned char *t = d;
2008
unsigned char *s = src;
2010
*len = 0; /* in case we return an error */
2011
/* until run out of characters */
2012
while (((unsigned long) (s - src)) < srcl) {
2013
switch (c = *s++) { /* what type of character is it? */
2014
case '=': /* quoting character */
2015
if (((unsigned long) (s - src)) < srcl) switch (c = *s++) {
2016
case '\0': /* end of data */
2017
s--; /* back up pointer */
2019
case '\015': /* non-significant line break */
2020
if ((((unsigned long) (s - src)) < srcl) && (*s == '\012')) s++;
2021
case '\012': /* bare LF */
2022
t = d; /* accept any leading spaces */
2024
default: /* two hex digits then */
2025
if (!(isxdigit (c) && (((unsigned long) (s - src)) < srcl) &&
2026
(e = *s++) && isxdigit (e))) {
2027
/* This indicates bad MIME. One way that it can be caused is if
2028
a single-section message was QUOTED-PRINTABLE encoded and then
2029
something (e.g. a mailing list processor) appended text. The
2030
problem is that there is no way to determine where the encoded
2031
data ended and the appended crud began. Consequently, prudent
2032
software will always encapsulate a QUOTED-PRINTABLE segment
2035
if (!bogon++) { /* only do this once */
2036
sprintf (tmp,"Invalid quoted-printable sequence: =%.80s",
2040
*d++ = '='; /* treat = as ordinary character */
2041
*d++ = c; /* and the character following */
2042
t = d; /* note point of non-space */
2045
*d++ = hex2byte (c,e); /* merge the two hex digits */
2046
t = d; /* note point of non-space */
2050
case ' ': /* space, possibly bogus */
2051
*d++ = c; /* stash the space but don't update s */
2053
case '\015': /* end of line */
2054
case '\012': /* bare LF */
2055
d = t; /* slide back to last non-space, drop in */
2057
*d++ = c; /* stash the character */
2058
t = d; /* note point of non-space */
2061
*d = '\0'; /* tie off results */
2062
*len = d - ret; /* calculate length */
2063
return ret; /* return the string */
2066
/* Convert 8BIT contents to QUOTED-PRINTABLE
2069
* pointer to return destination length
2070
* Returns: destination as quoted-printable text
2073
#define MAXL (size_t) 75 /* 76th position only used by continuation = */
2075
unsigned char *rfc822_8bit (unsigned char *src,unsigned long srcl,
2078
unsigned long lp = 0;
2079
unsigned char *ret = (unsigned char *)
2080
fs_get ((size_t) (3*srcl + 3*(((3*srcl)/MAXL) + 1)));
2081
unsigned char *d = ret;
2082
char *hex = "0123456789ABCDEF";
2084
while (srcl--) { /* for each character */
2085
/* true line break? */
2086
if (((c = *src++) == '\015') && (*src == '\012') && srcl) {
2087
*d++ = '\015'; *d++ = *src++; srcl--;
2088
lp = 0; /* reset line count */
2090
else { /* not a line break */
2091
/* quoting required? */
2092
if (iscntrl (c) || (c == 0x7f) || (c & 0x80) || (c == '=') ||
2093
((c == ' ') && (*src == '\015'))) {
2094
if ((lp += 3) > MAXL) { /* yes, would line overflow? */
2095
*d++ = '='; *d++ = '\015'; *d++ = '\012';
2096
lp = 3; /* set line count */
2098
*d++ = '='; /* quote character */
2099
*d++ = hex[c >> 4]; /* high order 4 bits */
2100
*d++ = hex[c & 0xf]; /* low order 4 bits */
2102
else { /* ordinary character */
2103
if ((++lp) > MAXL) { /* would line overflow? */
2104
*d++ = '='; *d++ = '\015'; *d++ = '\012';
2105
lp = 1; /* set line count */
2107
*d++ = c; /* ordinary character */
2111
*d = '\0'; /* tie off destination */
2112
*len = d - ret; /* calculate true size */
2113
/* try to give some space back */
2114
fs_resize ((void **) &ret,(size_t) *len + 1);
2118
/* Legacy Routines */
2121
* WARNING: These routines are for compatibility with old software only.
2123
* Their use in new software is to be avoided.
2125
* These interfaces do not provide satisfactory buffer checking. In
2126
* versions of c-client prior to imap-2005, they did not provide any
2127
* buffer checking at all.
2129
* As a half-hearted attempt, these new compatability functions for the
2130
* legacy interfaces limit what they write to size SENDBUFLEN and will
2131
* fatal() if more than that is written. However, that isn't good enough
2132
* since several of these functions *append* to the buffer, and return an
2133
* updated pointer. Consequently, there is no way of knowing what the
2134
* actual available space is in the buffer, yet the function will still
2135
* write up to SENDBUFLEN bytes even if there is much less space actually
2136
* available. The result is a buffer overflow.
2138
* You won't get a buffer overflow if you never attempt to append using
2139
* these interfaces, but you can get the fatal() if it tries to write
2140
* more than SENDBUFLEN bytes.
2142
* To avoid this problem, use the corresponding rfc822_output_???()
2143
* functions instead, e.g., rfc822_output_address() instead of
2148
/* Flush routine, only called if overflow
2154
static long rfc822_legacy_soutr (void *stream,char *string)
2156
fatal ("rfc822.c legacy routine buffer overflow");
2160
/* Legacy write RFC 2822 header from message structure
2161
* Accepts: scratch buffer to write into
2166
void rfc822_header (char *header,ENVELOPE *env,BODY *body)
2169
/* write at start of buffer */
2170
buf.end = (buf.beg = buf.cur = header) + SENDBUFLEN - 1;
2171
buf.f = rfc822_legacy_soutr;
2173
rfc822_output_header (&buf,env,body,NIL,NIL);
2174
*buf.cur = '\0'; /* tie off buffer */
2178
/* Legacy write RFC 2822 text from header line
2179
* Accepts: pointer to destination string pointer
2180
* pointer to header type
2181
* message to interpret
2185
void rfc822_header_line (char **header,char *type,ENVELOPE *env,char *text)
2188
/* append to buffer */
2189
buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2190
buf.f = rfc822_legacy_soutr;
2192
rfc822_output_header_line (&buf,type,env->remail ? LONGT : NIL,text);
2193
*(*header = buf.cur) = '\0'; /* tie off buffer */
2196
/* Legacy write RFC 2822 address from header line
2197
* Accepts: pointer to destination string pointer
2198
* pointer to header type
2199
* message to interpret
2200
* address to interpret
2203
void rfc822_address_line (char **header,char *type,ENVELOPE *env,ADDRESS *adr)
2206
/* append to buffer */
2207
buf.end = (buf.beg = buf.cur = *header + strlen (*header)) + SENDBUFLEN - 1;
2208
buf.f = rfc822_legacy_soutr;
2210
rfc822_output_address_line (&buf,type,env->remail ? LONGT : NIL,adr,NIL);
2211
*(*header = buf.cur) = '\0'; /* tie off buffer */
2215
/* Legacy write RFC 2822 address list
2216
* Accepts: pointer to destination string
2217
* address to interpret
2218
* header base if pretty-printing
2219
* Returns: end of destination string
2222
char *rfc822_write_address_full (char *dest,ADDRESS *adr,char *base)
2225
/* append to buffer */
2226
buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2227
buf.f = rfc822_legacy_soutr;
2229
rfc822_output_address_list (&buf,adr,base ? dest - base : 0,NIL);
2230
*buf.cur = '\0'; /* tie off buffer */
2235
/* Legacy write RFC 2822 route-address to string
2236
* Accepts: pointer to destination string
2237
* address to interpret
2240
void rfc822_address (char *dest,ADDRESS *adr)
2243
/* append to buffer */
2244
buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2245
buf.f = rfc822_legacy_soutr;
2247
rfc822_output_address (&buf,adr);
2248
*buf.cur = '\0'; /* tie off buffer */
2251
/* Concatenate RFC 2822 string
2252
* Accepts: pointer to destination string
2253
* pointer to string to concatenate
2254
* list of special characters or NIL for dot-atom format
2257
void rfc822_cat (char *dest,char *src,const char *specials)
2260
/* append to buffer */
2261
buf.end = (buf.beg = buf.cur = dest + strlen (dest)) + SENDBUFLEN - 1;
2262
buf.f = rfc822_legacy_soutr;
2264
rfc822_output_cat (&buf,src,specials);
2265
*buf.cur = '\0'; /* tie off buffer */
2269
/* Legacy write body content header
2270
* Accepts: pointer to destination string pointer
2271
* pointer to body to interpret
2274
void rfc822_write_body_header (char **dst,BODY *body)
2277
/* append to buffer */
2278
buf.end = (buf.beg = buf.cur = *dst + strlen (*dst)) + SENDBUFLEN - 1;
2279
buf.f = rfc822_legacy_soutr;
2281
rfc822_output_body_header (&buf,body);
2282
*(*dst = buf.cur) = '\0'; /* tie off buffer */
2285
/* Legacy output RFC 822 message
2286
* Accepts: temporary buffer
2290
* stream for I/O routine
2291
* non-zero if 8-bit output desired
2292
* Returns: T if successful, NIL if failure
2295
long rfc822_output (char *t,ENVELOPE *env,BODY *body,soutr_t f,void *s,
2299
rfc822out_t r822o = (rfc822out_t) mail_parameters (NIL,GET_RFC822OUTPUT,NIL);
2300
/* call external RFC 2822 output generator */
2301
if (r822o) ret = (*r822o) (t,env,body,f,s,ok8bit);
2302
else { /* output generator not armed */
2303
RFC822BUFFER buf; /* use our own buffer rather than trust */
2304
char tmp[SENDBUFLEN+1]; /* client to give us a big enough one */
2307
buf.end = (buf.beg = buf.cur = t) + SENDBUFLEN - 1;
2308
tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */
2309
ret = rfc822_output_full (&buf,env,body,ok8bit);
2315
/* Legacy output RFC 822 body
2318
* stream for I/O routine
2319
* Returns: T if successful, NIL if failure
2322
long rfc822_output_body (BODY *body,soutr_t f,void *s)
2325
char tmp[SENDBUFLEN+1];
2328
buf.end = (buf.beg = buf.cur = tmp) + SENDBUFLEN;
2329
tmp[SENDBUFLEN] = '\0'; /* must have additional guard byte */
2330
return rfc822_output_text (&buf,body) && rfc822_output_flush (&buf);