]> diplodocus.org Git - nmh/blob - sbr/mf.c
00e158224df058fea8bc08e0eb0a87eb51a17ada
[nmh] / sbr / mf.c
1 /*
2 * mf.c -- mail filter subroutines
3 *
4 * This code is Copyright (c) 2002, by the authors of nmh. See the
5 * COPYRIGHT file in the root directory of the nmh distribution for
6 * complete copyright information.
7 */
8
9 #include <h/mf.h>
10 #include <h/utils.h>
11
12 /*
13 * static prototypes
14 */
15 static int isat (const char *);
16 static int parse_address (void);
17 static int phrase (char *);
18 static int route_addr (char *);
19 static int local_part (char *);
20 static int domain (char *);
21 static int route (char *);
22 static int my_lex (char *);
23
24
25 static int
26 isat (const char *p)
27 {
28 return has_prefix(p, " AT ") || has_prefix(p, " At ") ||
29 has_prefix(p, " aT ") || has_prefix(p, " at ");
30 }
31
32
33 /*
34 *
35 * getadrx() implements a partial 822-style address parser. The parser
36 * is neither complete nor correct. It does however recognize nearly all
37 * of the 822 address syntax. In addition it handles the majority of the
38 * 733 syntax as well. Most problems arise from trying to accommodate both.
39 *
40 * In terms of 822, the route-specification in
41 *
42 * "<" [route] local-part "@" domain ">"
43 *
44 * is parsed and returned unchanged. Multiple at-signs are compressed
45 * via source-routing. Recursive groups are not allowed as per the
46 * standard.
47 *
48 * In terms of 733, " at " is recognized as equivalent to "@".
49 *
50 * In terms of both the parser will not complain about missing hosts.
51 *
52 * -----
53 *
54 * We should not allow addresses like
55 *
56 * Marshall T. Rose <MRose@UCI>
57 *
58 * but should insist on
59 *
60 * "Marshall T. Rose" <MRose@UCI>
61 *
62 * Unfortunately, a lot of mailers stupidly let people get away with this.
63 *
64 * -----
65 *
66 * We should not allow addresses like
67 *
68 * <MRose@UCI>
69 *
70 * but should insist on
71 *
72 * MRose@UCI
73 *
74 * Unfortunately, a lot of mailers stupidly let people's UAs get away with
75 * this.
76 *
77 * -----
78 *
79 * We should not allow addresses like
80 *
81 * @UCI:MRose@UCI-750a
82 *
83 * but should insist on
84 *
85 * Marshall Rose <@UCI:MRose@UCI-750a>
86 *
87 * Unfortunately, a lot of mailers stupidly do this.
88 *
89 */
90
91 #define QUOTE '\\'
92
93 #define LX_END 0
94 #define LX_ERR 1
95 #define LX_ATOM 2
96 #define LX_QSTR 3
97 #define LX_DLIT 4
98 #define LX_SEMI 5
99 #define LX_COMA 6
100 #define LX_LBRK 7
101 #define LX_RBRK 8
102 #define LX_COLN 9
103 #define LX_DOT 10
104 #define LX_AT 11
105
106 struct specials {
107 char lx_chr;
108 int lx_val;
109 };
110
111 static struct specials special[] = {
112 { ';', LX_SEMI },
113 { ',', LX_COMA },
114 { '<', LX_LBRK },
115 { '>', LX_RBRK },
116 { ':', LX_COLN },
117 { '.', LX_DOT },
118 { '@', LX_AT },
119 { '(', LX_ERR },
120 { ')', LX_ERR },
121 { QUOTE, LX_ERR },
122 { '"', LX_ERR },
123 { '[', LX_ERR },
124 { ']', LX_ERR },
125 { 0, 0 }
126 };
127
128 static int glevel = 0;
129 static int ingrp = 0;
130 static int last_lex = LX_END;
131
132 static char *dp = NULL;
133 static char *cp = NULL;
134 static char *ap = NULL;
135 static char *pers = NULL;
136 static char *mbox = NULL;
137 static char *host = NULL;
138 static char *path = NULL;
139 static char *grp = NULL;
140 static char *note = NULL;
141 static char err[BUFSIZ];
142 static char adr[BUFSIZ];
143
144 static struct adrx adrxs2;
145
146
147 /* eai = Email Address Internationalization */
148 struct adrx *
149 getadrx (const char *addrs, int eai)
150 {
151 char *bp;
152 struct adrx *adrxp = &adrxs2;
153
154 mh_xfree(pers);
155 mh_xfree(mbox);
156 mh_xfree(host);
157 mh_xfree(path);
158 mh_xfree(grp);
159 mh_xfree(note);
160 pers = mbox = host = path = grp = note = NULL;
161 err[0] = 0;
162
163 if (dp == NULL) {
164 dp = cp = strdup (addrs ? addrs : "");
165 glevel = 0;
166 }
167 else
168 if (cp == NULL) {
169 free (dp);
170 dp = NULL;
171 return NULL;
172 }
173
174 switch (parse_address ()) {
175 case DONE:
176 free (dp);
177 dp = cp = NULL;
178 return NULL;
179
180 case OK:
181 switch (last_lex) {
182 case LX_COMA:
183 case LX_END:
184 break;
185
186 default: /* catch trailing comments */
187 bp = cp;
188 my_lex (adr);
189 cp = bp;
190 break;
191 }
192 break;
193
194 default:
195 break;
196 }
197
198 if (! eai) {
199 /*
200 * Reject the address if key fields contain 8bit characters
201 */
202
203 if (contains8bit(mbox, NULL) || contains8bit(host, NULL) ||
204 contains8bit(path, NULL) || contains8bit(grp, NULL)) {
205 strcpy(err, "Address contains 8-bit characters");
206 }
207 }
208
209 if (err[0])
210 for (;;) {
211 switch (last_lex) {
212 case LX_COMA:
213 case LX_END:
214 break;
215
216 default:
217 my_lex (adr);
218 continue;
219 }
220 break;
221 }
222 while (isspace ((unsigned char) *ap))
223 ap++;
224 if (cp)
225 snprintf(adr, sizeof adr, "%.*s", (int)(cp - ap), ap);
226 else
227 strcpy (adr, ap);
228 bp = adr + strlen (adr) - 1;
229 if (*bp == ',' || *bp == ';' || *bp == '\n')
230 *bp = 0;
231
232 adrxp->text = adr;
233 adrxp->pers = pers;
234 adrxp->mbox = mbox;
235 adrxp->host = host;
236 adrxp->path = path;
237 adrxp->grp = grp;
238 adrxp->ingrp = ingrp;
239 adrxp->note = note;
240 adrxp->err = err[0] ? err : NULL;
241
242 return adrxp;
243 }
244
245
246 static int
247 parse_address (void)
248 {
249 char buffer[BUFSIZ];
250
251 again: ;
252 ap = cp;
253 switch (my_lex (buffer)) {
254 case LX_ATOM:
255 case LX_QSTR:
256 pers = strdup (buffer);
257 break;
258
259 case LX_SEMI:
260 if (glevel-- <= 0) {
261 strcpy (err, "extraneous semi-colon");
262 return NOTOK;
263 }
264 /* FALLTHRU */
265 case LX_COMA:
266 mh_xfree(note);
267 note = NULL;
268 goto again;
269
270 case LX_END:
271 return DONE;
272
273 case LX_LBRK: /* sigh (2) */
274 goto get_addr;
275
276 case LX_AT: /* sigh (3) */
277 cp = ap;
278 if (route_addr (buffer) == NOTOK)
279 return NOTOK;
280 return OK; /* why be choosy? */
281
282 default:
283 snprintf(err, sizeof err, "illegal address construct (%s)", buffer);
284 return NOTOK;
285 }
286
287 switch (my_lex (buffer)) {
288 case LX_ATOM:
289 case LX_QSTR:
290 pers = add (buffer, add (" ", pers));
291 more_phrase: ; /* sigh (1) */
292 if (phrase (buffer) == NOTOK)
293 return NOTOK;
294
295 switch (last_lex) {
296 case LX_LBRK:
297 get_addr: ;
298 if (route_addr (buffer) == NOTOK)
299 return NOTOK;
300 if (last_lex == LX_RBRK)
301 return OK;
302 snprintf(err, sizeof err, "missing right-bracket (%s)", buffer);
303 return NOTOK;
304
305 case LX_COLN:
306 get_group: ;
307 if (glevel++ > 0) {
308 snprintf(err, sizeof err, "nested groups not allowed (%s)", pers);
309 return NOTOK;
310 }
311 grp = add (": ", pers);
312 pers = NULL;
313 {
314 char *pp = cp;
315
316 for (;;)
317 switch (my_lex (buffer)) {
318 case LX_SEMI:
319 case LX_END: /* tsk, tsk */
320 glevel--;
321 return OK;
322
323 case LX_COMA:
324 continue;
325
326 default:
327 cp = pp;
328 return parse_address ();
329 }
330 }
331
332 case LX_DOT: /* sigh (1) */
333 pers = add (".", pers);
334 goto more_phrase;
335
336 default:
337 snprintf(err, sizeof err, "no mailbox in address, only a phrase (%s%s)",
338 pers, buffer);
339 return NOTOK;
340 }
341
342 case LX_LBRK:
343 goto get_addr;
344
345 case LX_COLN:
346 goto get_group;
347
348 case LX_DOT:
349 mbox = add (buffer, pers);
350 pers = NULL;
351 if (route_addr (buffer) == NOTOK)
352 return NOTOK;
353 goto check_end;
354
355 case LX_AT:
356 ingrp = glevel;
357 mbox = pers;
358 pers = NULL;
359 if (domain (buffer) == NOTOK)
360 return NOTOK;
361 check_end: ;
362 switch (last_lex) {
363 case LX_SEMI:
364 if (glevel-- <= 0) {
365 strcpy (err, "extraneous semi-colon");
366 return NOTOK;
367 }
368 /* FALLTHRU */
369 case LX_COMA:
370 case LX_END:
371 return OK;
372
373 default:
374 snprintf(err, sizeof err, "junk after local@domain (%s)", buffer);
375 return NOTOK;
376 }
377
378 case LX_SEMI: /* no host */
379 case LX_COMA:
380 case LX_END:
381 ingrp = glevel;
382 if (last_lex == LX_SEMI && glevel-- <= 0) {
383 strcpy (err, "extraneous semi-colon");
384 return NOTOK;
385 }
386 mbox = pers;
387 pers = NULL;
388 return OK;
389
390 default:
391 snprintf(err, sizeof err, "missing mailbox (%s)", buffer);
392 return NOTOK;
393 }
394 }
395
396
397 static int
398 phrase (char *buffer)
399 {
400 for (;;)
401 switch (my_lex (buffer)) {
402 case LX_ATOM:
403 case LX_QSTR:
404 pers = add (buffer, add (" ", pers));
405 continue;
406
407 default:
408 return OK;
409 }
410 }
411
412
413 static int
414 route_addr (char *buffer)
415 {
416 char *pp = cp;
417
418 if (my_lex (buffer) == LX_AT) {
419 if (route (buffer) == NOTOK)
420 return NOTOK;
421 }
422 else
423 cp = pp;
424
425 if (local_part (buffer) == NOTOK)
426 return NOTOK;
427
428 switch (last_lex) {
429 case LX_AT:
430 return domain (buffer);
431
432 case LX_SEMI: /* if in group */
433 case LX_RBRK: /* no host */
434 case LX_COMA:
435 case LX_END:
436 return OK;
437
438 default:
439 snprintf(err, sizeof err, "no at-sign after local-part (%s)", buffer);
440 return NOTOK;
441 }
442 }
443
444
445 static int
446 local_part (char *buffer)
447 {
448 ingrp = glevel;
449
450 for (;;) {
451 switch (my_lex (buffer)) {
452 case LX_ATOM:
453 case LX_QSTR:
454 mbox = add (buffer, mbox);
455 break;
456
457 default:
458 snprintf(err, sizeof err, "no mailbox in local-part (%s)", buffer);
459 return NOTOK;
460 }
461
462 switch (my_lex (buffer)) {
463 case LX_DOT:
464 mbox = add (buffer, mbox);
465 continue;
466
467 default:
468 return OK;
469 }
470 }
471 }
472
473
474 static int
475 domain (char *buffer)
476 {
477 for (;;) {
478 switch (my_lex (buffer)) {
479 case LX_ATOM:
480 case LX_DLIT:
481 host = add (buffer, host);
482 break;
483
484 default:
485 snprintf(err, sizeof err, "no sub-domain in domain-part of address (%s)", buffer);
486 return NOTOK;
487 }
488
489 switch (my_lex (buffer)) {
490 case LX_DOT:
491 host = add (buffer, host);
492 continue;
493
494 case LX_AT: /* sigh (0) */
495 mbox = add (host, add ("%", mbox));
496 free (host);
497 host = NULL;
498 continue;
499
500 default:
501 return OK;
502 }
503 }
504 }
505
506
507 static int
508 route (char *buffer)
509 {
510 path = mh_xstrdup ("@");
511
512 for (;;) {
513 switch (my_lex (buffer)) {
514 case LX_ATOM:
515 case LX_DLIT:
516 path = add (buffer, path);
517 break;
518
519 default:
520 snprintf(err, sizeof err, "no sub-domain in domain-part of address (%s)", buffer);
521 return NOTOK;
522 }
523 switch (my_lex (buffer)) {
524 case LX_COMA:
525 path = add (buffer, path);
526 for (;;) {
527 switch (my_lex (buffer)) {
528 case LX_COMA:
529 continue;
530
531 case LX_AT:
532 path = add (buffer, path);
533 break;
534
535 default:
536 snprintf(err, sizeof err, "no at-sign found for next domain in route (%s)",
537 buffer);
538 }
539 break;
540 }
541 continue;
542
543 case LX_AT: /* XXX */
544 case LX_DOT:
545 path = add (buffer, path);
546 continue;
547
548 case LX_COLN:
549 path = add (buffer, path);
550 return OK;
551
552 default:
553 snprintf(err, sizeof err, "no colon found to terminate route (%s)", buffer);
554 return NOTOK;
555 }
556 }
557 }
558
559
560 static int
561 my_lex (char *buffer)
562 {
563 /* buffer should be at least BUFSIZ bytes long */
564 int i, gotat = 0;
565 char c, *bp;
566
567 /* Add C to the buffer bp. After use of this macro *bp is guaranteed to be within the buffer. */
568 #define ADDCHR(C) do { *bp++ = (C); if ((bp - buffer) == (BUFSIZ-1)) goto my_lex_buffull; } while (0)
569
570 bp = buffer;
571 *bp = 0;
572 if (!cp)
573 return (last_lex = LX_END);
574
575 gotat = isat (cp);
576 c = *cp++;
577 while (isspace ((unsigned char) c))
578 c = *cp++;
579 if (c == 0) {
580 cp = NULL;
581 return (last_lex = LX_END);
582 }
583
584 if (c == '(') {
585 ADDCHR(c);
586 for (i = 0;;)
587 switch (c = *cp++) {
588 case 0:
589 cp = NULL;
590 return (last_lex = LX_ERR);
591 case QUOTE:
592 ADDCHR(c);
593 if ((c = *cp++) == 0) {
594 cp = NULL;
595 return (last_lex = LX_ERR);
596 }
597 ADDCHR(c);
598 continue;
599 case '(':
600 i++;
601 /* FALLTHRU */
602 default:
603 ADDCHR(c);
604 continue;
605 case ')':
606 ADDCHR(c);
607 if (--i < 0) {
608 *bp = 0;
609 note = note ? add (buffer, add (" ", note))
610 : strdup (buffer);
611 return my_lex (buffer);
612 }
613 }
614 }
615
616 if (c == '"') {
617 ADDCHR(c);
618 for (;;)
619 switch (c = *cp++) {
620 case 0:
621 cp = NULL;
622 return (last_lex = LX_ERR);
623 case QUOTE:
624 ADDCHR(c);
625 if ((c = *cp++) == 0) {
626 cp = NULL;
627 return (last_lex = LX_ERR);
628 }
629 /* FALLTHRU */
630 default:
631 ADDCHR(c);
632 continue;
633 case '"':
634 ADDCHR(c);
635 *bp = 0;
636 return (last_lex = LX_QSTR);
637 }
638 }
639
640 if (c == '[') {
641 ADDCHR(c);
642 for (;;)
643 switch (c = *cp++) {
644 case 0:
645 cp = NULL;
646 return (last_lex = LX_ERR);
647 case QUOTE:
648 ADDCHR(c);
649 if ((c = *cp++) == 0) {
650 cp = NULL;
651 return (last_lex = LX_ERR);
652 }
653 /* FALLTHRU */
654 default:
655 ADDCHR(c);
656 continue;
657 case ']':
658 ADDCHR(c);
659 *bp = 0;
660 return (last_lex = LX_DLIT);
661 }
662 }
663
664 ADDCHR(c);
665 *bp = 0;
666 for (i = 0; special[i].lx_chr != 0; i++)
667 if (c == special[i].lx_chr)
668 return (last_lex = special[i].lx_val);
669
670 if (iscntrl ((unsigned char) c))
671 return (last_lex = LX_ERR);
672
673 for (;;) {
674 if ((c = *cp++) == 0)
675 break;
676 for (i = 0; special[i].lx_chr != 0; i++)
677 if (c == special[i].lx_chr)
678 goto got_atom;
679 if (iscntrl ((unsigned char) c) || isspace ((unsigned char) c))
680 break;
681 ADDCHR(c);
682 }
683 got_atom: ;
684 if (c == 0)
685 cp = NULL;
686 else
687 cp--;
688 *bp = 0;
689 last_lex = !gotat || cp == NULL || strchr(cp, '<') != NULL
690 ? LX_ATOM : LX_AT;
691 return last_lex;
692
693 my_lex_buffull:
694 /* Out of buffer space. *bp is the last byte in the buffer */
695 *bp = 0;
696 return (last_lex = LX_ERR);
697 }
698
699
700 char *
701 legal_person (const char *p)
702 {
703 int i;
704 const char *cp;
705 static char buffer[BUFSIZ];
706
707 if (*p == '"')
708 return (char *) p;
709 for (cp = p; *cp; cp++)
710 for (i = 0; special[i].lx_chr; i++)
711 if (*cp == special[i].lx_chr) {
712 snprintf(buffer, sizeof buffer, "\"%s\"", p);
713 return buffer;
714 }
715
716 return (char *) p;
717 }