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