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