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