d1db19fc11a93024156c114d7cd21320fbfb19c5
[mmh] / zotnet / mf / mf.c
1
2 /*
3  * mf.c -- mail filter subroutines
4  *
5  * $Id$
6  */
7
8 #include <mf.h>
9 #include <ctype.h>
10 #include <stdio.h>
11
12 /*
13  * static prototypes
14  */
15 static char *getcpy (char *);
16 static char *add (char *, char *);
17 static void compress (char *, char *);
18 static int isat (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 static char *
29 getcpy (char *s)
30 {
31     register char *p;
32
33     if (!s) {
34         _cleanup();
35         abort();
36         for(;;)
37             pause();
38     }
39     if ((p = malloc ((size_t) (strlen (s) + 2))))
40         strcpy (p, s);
41     return p;
42 }
43
44
45 static char *
46 add (char *s1, char *s2)
47 {
48     register char *p;
49
50     if (!s2)
51         return getcpy (s1);
52
53     if ((p = malloc ((size_t) (strlen (s1) + strlen (s2) + 2))))
54         sprintf (p, "%s%s", s2, s1);
55     free (s2);
56     return p;
57 }
58
59 int
60 isfrom(char *string)
61 {
62     return (strncmp (string, "From ", 5) == 0
63             || strncmp (string, ">From ", 6) == 0);
64 }
65
66
67 int
68 lequal (char *a, char *b)
69 {
70     for (; *a; a++, b++)
71         if (*b == 0)
72             return FALSE;
73         else {
74             char c1 = islower (*a) ? toupper (*a) : *a;
75             char c2 = islower (*b) ? toupper (*b) : *b;
76             if (c1 != c2)
77                 return FALSE;
78         }
79
80     return (*b == 0);
81 }
82
83
84 /* 
85  * seekadrx() is tricky.  We want to cover both UUCP-style and ARPA-style
86  * addresses, so for each list of addresses we see if we can find some
87  * character to give us a hint.
88  */
89
90
91 #define CHKADR  0               /* undertermined address style */
92 #define UNIXDR  1               /* UNIX-style address */
93 #define ARPADR  2               /* ARPAnet-style address */
94
95
96 static char *punctuators = ";<>.()[]";
97 static char *vp = NULL;
98 static char *tp = NULL;
99
100 static struct adrx adrxs1;
101
102
103 struct adrx *
104 seekadrx (char *addrs)
105 {
106     static int state = CHKADR;
107     register char *cp;
108     register struct adrx *adrxp;
109
110     if (state == CHKADR)
111         for (state = UNIXDR, cp = addrs; *cp; cp++)
112             if (strchr(punctuators, *cp)) {
113                 state = ARPADR;
114                 break;
115             }
116
117     switch (state) {
118         case UNIXDR: 
119             adrxp = uucpadrx (addrs);
120             break;
121
122         case ARPADR: 
123         default:
124             adrxp = getadrx (addrs);
125             break;
126     }
127
128     if (adrxp == NULL)
129         state = CHKADR;
130
131     return adrxp;
132 }
133
134
135 /*
136  * uucpadrx() implements a partial UUCP-style address parser.  It's based
137  * on the UUCP notion that addresses are separated by spaces or commas.
138  */
139
140
141 struct adrx *
142 uucpadrx (char *addrs)
143 {
144     register char *cp, *wp, *xp, *yp, *zp;
145     register struct adrx *adrxp = &adrxs1;
146
147     if (vp == NULL) {
148         vp = tp = getcpy (addrs);
149         compress (addrs, vp);
150     }
151     else
152         if (tp == NULL) {
153             free (vp);
154             vp = NULL;
155             return NULL;
156         }
157
158     for (cp = tp; isspace (*cp); cp++)
159         continue;
160     if (*cp == 0) {
161         free (vp);
162         vp = tp = NULL;
163         return NULL;
164     }
165
166     if ((wp = strchr(cp, ',')) == NULL) {
167         if ((wp = strchr(cp, ' ')) != NULL) {
168             xp = wp;
169             while (isspace (*xp))
170                 xp++;
171             if (*xp != 0 && isat (--xp)) {
172                 yp = xp + 4;
173                 while (isspace (*yp))
174                     yp++;
175                 if (*yp != 0) {
176                     if ((zp = strchr(yp, ' ')) != NULL)
177                         *zp = 0, tp = ++zp;
178                     else
179                         tp = NULL;
180                 }
181                 else
182                     *wp = 0, tp = ++wp;
183             }
184             else
185                 *wp = 0, tp = ++wp;
186         }
187         else
188             tp = NULL;
189     }
190     else
191         *wp = 0, tp = ++wp;
192
193     if (adrxp->text)
194         free (adrxp->text);
195     adrxp->text = getcpy (cp);
196     adrxp->mbox = cp;
197     adrxp->host = adrxp->path = NULL;
198     if ((wp = strrchr(cp, '@')) != NULL) {
199         *wp++ = 0;
200         adrxp->host = *wp ? wp : NULL;
201     }
202     else
203         for (wp = cp + strlen (cp) - 4; wp >= cp; wp--)
204             if (isat (wp)) {
205                 *wp++ = 0;
206                 adrxp->host = wp + 3;
207             }
208
209     adrxp->pers = adrxp->grp = adrxp->note = adrxp->err = NULL;
210     adrxp->ingrp = 0;
211
212     return adrxp;
213 }
214
215
216 static void
217 compress (char *fp, char *tp)
218 {
219     register char c, *cp;
220
221     for (c = ' ', cp = tp; (*tp = *fp++) != 0;)
222         if (isspace (*tp)) {
223             if (c != ' ')
224                 *tp++ = c = ' ';
225         }
226         else
227             c = *tp++;
228
229     if (c == ' ' && cp < tp)
230         *--tp = 0;
231 }
232
233
234 static int
235 isat (char *p)
236 {
237     return (strncmp (p, " AT ", 4)
238             && strncmp (p, " At ", 4)
239             && strncmp (p, " aT ", 4)
240             && strncmp (p, " at ", 4) ? FALSE : TRUE);
241 }
242
243
244 /*
245  *
246  * getadrx() implements a partial 822-style address parser.  The parser
247  * is neither complete nor correct.  It does however recognize nearly all
248  * of the 822 address syntax.  In addition it handles the majority of the
249  * 733 syntax as well.  Most problems arise from trying to accomodate both.
250  *
251  * In terms of 822, the route-specification in 
252  *
253  *               "<" [route] local-part "@" domain ">"
254  *
255  * is parsed and returned unchanged.  Multiple at-signs are compressed
256  * via source-routing.  Recursive groups are not allowed as per the 
257  * standard.
258  *
259  * In terms of 733, " at " is recognized as equivalent to "@".
260  *
261  * In terms of both the parser will not complain about missing hosts.
262  *
263  * -----
264  *
265  * We should not allow addresses like   
266  *
267  *              Marshall T. Rose <MRose@UCI>
268  *
269  * but should insist on
270  *
271  *              "Marshall T. Rose" <MRose@UCI>
272  *
273  * Unfortunately, a lot of mailers stupidly let people get away with this.
274  *
275  * -----
276  *
277  * We should not allow addresses like
278  *
279  *              <MRose@UCI>
280  *
281  * but should insist on
282  *
283  *              MRose@UCI
284  *
285  * Unfortunately, a lot of mailers stupidly let people's UAs get away with
286  * this.
287  *
288  * -----
289  *
290  * We should not allow addresses like
291  *
292  *              @UCI:MRose@UCI-750a
293  *
294  * but should insist on
295  *
296  *              Marshall Rose <@UCI:MRose@UCI-750a>
297  *
298  * Unfortunately, a lot of mailers stupidly do this.
299  *
300  */
301
302 #define QUOTE   '\\'
303
304 #define LX_END   0
305 #define LX_ERR   1
306 #define LX_ATOM  2
307 #define LX_QSTR  3
308 #define LX_DLIT  4
309 #define LX_SEMI  5
310 #define LX_COMA  6
311 #define LX_LBRK  7
312 #define LX_RBRK  8
313 #define LX_COLN  9
314 #define LX_DOT  10
315 #define LX_AT   11
316
317 struct specials {
318     char lx_chr;
319     int  lx_val;
320 };
321
322 static struct specials special[] = {
323     { ';',   LX_SEMI },
324     { ',',   LX_COMA },
325     { '<',   LX_LBRK },
326     { '>',   LX_RBRK },
327     { ':',   LX_COLN },
328     { '.',   LX_DOT },
329     { '@',   LX_AT },
330     { '(',   LX_ERR },
331     { ')',   LX_ERR },
332     { QUOTE, LX_ERR },
333     { '"',   LX_ERR },
334     { '[',   LX_ERR },
335     { ']',   LX_ERR },
336     { 0,     0 }
337 };
338
339 static int glevel = 0;
340 static int ingrp = 0;
341 static int last_lex = LX_END;
342
343 static char *dp = NULL;
344 static char *cp = NULL;
345 static char *ap = NULL;
346 static char *pers = NULL;
347 static char *mbox = NULL;
348 static char *host = NULL;
349 static char *path = NULL;
350 static char *grp = NULL;
351 static char *note = NULL;
352 static char err[BUFSIZ];
353 static char adr[BUFSIZ];
354
355 static struct adrx  adrxs2;
356
357
358 struct adrx *
359 getadrx (char *addrs)
360 {
361     register char *bp;
362     register struct adrx *adrxp = &adrxs2;
363
364     if (pers)
365         free (pers);
366     if (mbox)
367         free (mbox);
368     if (host)
369         free (host);
370     if (path)
371         free (path);
372     if (grp)
373         free (grp);
374     if (note)
375         free (note);
376     pers = mbox = host = path = grp = note = NULL;
377     err[0] = 0;
378
379     if (dp == NULL) {
380         dp = cp = getcpy (addrs ? addrs : "");
381         glevel = 0;
382     }
383     else
384         if (cp == NULL) {
385             free (dp);
386             dp = NULL;
387             return NULL;
388         }
389
390     switch (parse_address ()) {
391         case DONE:
392             free (dp);
393             dp = cp = NULL;
394             return NULL;
395
396         case OK:
397             switch (last_lex) {
398                 case LX_COMA:
399                 case LX_END:
400                     break;
401
402                 default:        /* catch trailing comments */
403                     bp = cp;
404                     my_lex (adr);
405                     cp = bp;
406                     break;
407             }
408             break;
409
410         default:
411             break;
412         }
413
414     if (err[0])
415         for (;;) {
416             switch (last_lex) {
417                 case LX_COMA: 
418                 case LX_END: 
419                     break;
420
421                 default: 
422                     my_lex (adr);
423                     continue;
424             }
425             break;
426         }
427     while (isspace (*ap))
428         ap++;
429     if (cp)
430         sprintf (adr, "%.*s", cp - ap, ap);
431     else
432         strcpy (adr, ap);
433     bp = adr + strlen (adr) - 1;
434     if (*bp == ',' || *bp == ';' || *bp == '\n')
435         *bp = 0;
436
437     adrxp->text = adr;
438     adrxp->pers = pers;
439     adrxp->mbox = mbox;
440     adrxp->host = host;
441     adrxp->path = path;
442     adrxp->grp = grp;
443     adrxp->ingrp = ingrp;
444     adrxp->note = note;
445     adrxp->err = err[0] ? err : NULL;
446
447     return adrxp;
448 }
449
450
451 static int
452 parse_address (void)
453 {
454     char buffer[BUFSIZ];
455
456 again: ;
457     ap = cp;
458     switch (my_lex (buffer)) {
459         case LX_ATOM: 
460         case LX_QSTR: 
461             pers = getcpy (buffer);
462             break;
463
464         case LX_SEMI: 
465             if (glevel-- <= 0) {
466                 strcpy (err, "extraneous semi-colon");
467                 return NOTOK;
468             }
469         case LX_COMA: 
470             if (note) {
471                 free (note);
472                 note = NULL;
473             }
474             goto again;
475
476         case LX_END: 
477             return DONE;
478
479         case LX_LBRK:           /* sigh (2) */
480             goto get_addr;
481
482         case LX_AT:             /* sigh (3) */
483             cp = ap;
484             if (route_addr (buffer) == NOTOK)
485                 return NOTOK;
486             return OK;          /* why be choosy? */
487
488         default: 
489             sprintf (err, "illegal address construct (%s)", buffer);
490             return NOTOK;
491     }
492
493     switch (my_lex (buffer)) {
494         case LX_ATOM: 
495         case LX_QSTR: 
496             pers = add (buffer, add (" ", pers));
497     more_phrase: ;              /* sigh (1) */
498             if (phrase (buffer) == NOTOK)
499                 return NOTOK;
500
501             switch (last_lex) {
502                 case LX_LBRK: 
503             get_addr: ;
504                     if (route_addr (buffer) == NOTOK)
505                         return NOTOK;
506                     if (last_lex == LX_RBRK)
507                         return OK;
508                     sprintf (err, "missing right-bracket (%s)", buffer);
509                     return NOTOK;
510
511                 case LX_COLN: 
512             get_group: ;
513                     if (glevel++ > 0) {
514                         sprintf (err, "nested groups not allowed (%s)", pers);
515                         return NOTOK;
516                     }
517                     grp = add (": ", pers);
518                     pers = NULL;
519                     {
520                         char   *pp = cp;
521
522                         for (;;)
523                             switch (my_lex (buffer)) {
524                                 case LX_SEMI: 
525                                 case LX_END: /* tsk, tsk */
526                                     glevel--;
527                                     return OK;
528
529                                 case LX_COMA: 
530                                     continue;
531
532                                 default: 
533                                     cp = pp;
534                                     return parse_address ();
535                             }
536                     }
537
538                 case LX_DOT:    /* sigh (1) */
539                     pers = add (".", pers);
540                     goto more_phrase;
541
542                 default: 
543                     sprintf (err, "no mailbox in address, only a phrase (%s%s)",
544                             pers, buffer);
545                     return NOTOK;
546             }
547
548         case LX_LBRK: 
549             goto get_addr;
550
551         case LX_COLN: 
552             goto get_group;
553
554         case LX_DOT: 
555             mbox = add (buffer, pers);
556             pers = NULL;
557             if (route_addr (buffer) == NOTOK)
558                 return NOTOK;
559             goto check_end;
560
561         case LX_AT: 
562             ingrp = glevel;
563             mbox = pers;
564             pers = NULL;
565             if (domain (buffer) == NOTOK)
566                 return NOTOK;
567     check_end: ;
568             switch (last_lex) {
569                 case LX_SEMI: 
570                     if (glevel-- <= 0) {
571                         strcpy (err, "extraneous semi-colon");
572                         return NOTOK;
573                     }
574                 case LX_COMA: 
575                 case LX_END: 
576                     return OK;
577
578                 default: 
579                     sprintf (err, "junk after local@domain (%s)", buffer);
580                     return NOTOK;
581             }
582
583         case LX_SEMI:           /* no host */
584         case LX_COMA: 
585         case LX_END: 
586             ingrp = glevel;
587             if (last_lex == LX_SEMI && glevel-- <= 0) {
588                 strcpy (err, "extraneous semi-colon");
589                 return NOTOK;
590             }
591             mbox = pers;
592             pers = NULL;
593             return OK;
594
595         default: 
596             sprintf (err, "missing mailbox (%s)", buffer);
597             return NOTOK;
598     }
599 }
600
601
602 static int
603 phrase (char *buffer)
604 {
605     for (;;)
606         switch (my_lex (buffer)) {
607             case LX_ATOM: 
608             case LX_QSTR: 
609                 pers = add (buffer, add (" ", pers));
610                 continue;
611
612             default: 
613                 return OK;
614         }
615 }
616
617
618 static int
619 route_addr (char *buffer)
620 {
621     register char *pp = cp;
622
623     if (my_lex (buffer) == LX_AT) {
624         if (route (buffer) == NOTOK)
625             return NOTOK;
626     }
627     else
628         cp = pp;
629
630     if (local_part (buffer) == NOTOK)
631         return NOTOK;
632
633     switch (last_lex) {
634         case LX_AT: 
635             return domain (buffer);
636
637         case LX_SEMI:   /* if in group */
638         case LX_RBRK:           /* no host */
639         case LX_COMA:
640         case LX_END: 
641             return OK;
642
643         default: 
644             sprintf (err, "no at-sign after local-part (%s)", buffer);
645             return NOTOK;
646     }
647 }
648
649
650 static int
651 local_part (char *buffer)
652 {
653     ingrp = glevel;
654
655     for (;;) {
656         switch (my_lex (buffer)) {
657             case LX_ATOM: 
658             case LX_QSTR: 
659                 mbox = add (buffer, mbox);
660                 break;
661
662             default: 
663                 sprintf (err, "no mailbox in local-part (%s)", buffer);
664                 return NOTOK;
665         }
666
667         switch (my_lex (buffer)) {
668             case LX_DOT: 
669                 mbox = add (buffer, mbox);
670                 continue;
671
672             default: 
673                 return OK;
674         }
675     }
676 }
677
678
679 static int
680 domain (char *buffer)
681 {
682     for (;;) {
683         switch (my_lex (buffer)) {
684             case LX_ATOM: 
685             case LX_DLIT: 
686                 host = add (buffer, host);
687                 break;
688
689             default: 
690                 sprintf (err, "no sub-domain in domain-part of address (%s)", buffer);
691                 return NOTOK;
692         }
693
694         switch (my_lex (buffer)) {
695             case LX_DOT: 
696                 host = add (buffer, host);
697                 continue;
698
699             case LX_AT:         /* sigh (0) */
700                 mbox = add (host, add ("%", mbox));
701                 free (host);
702                 host = NULL;
703                 continue;
704
705             default: 
706                 return OK;
707         }
708     }
709 }
710
711
712 static int
713 route (char *buffer)
714 {
715     path = getcpy ("@");
716
717     for (;;) {
718         switch (my_lex (buffer)) {
719             case LX_ATOM: 
720             case LX_DLIT: 
721                 path = add (buffer, path);
722                 break;
723
724             default: 
725                 sprintf (err, "no sub-domain in domain-part of address (%s)", buffer);
726                 return NOTOK;
727         }
728         switch (my_lex (buffer)) {
729             case LX_COMA: 
730                 path = add (buffer, path);
731                 for (;;) {
732                     switch (my_lex (buffer)) {
733                         case LX_COMA: 
734                             continue;
735
736                         case LX_AT: 
737                             path = add (buffer, path);
738                             break;
739
740                         default: 
741                             sprintf (err, "no at-sign found for next domain in route (%s)",
742                                      buffer);
743                     }
744                     break;
745                 }
746                 continue;
747
748             case LX_AT:         /* XXX */
749             case LX_DOT: 
750                 path = add (buffer, path);
751                 continue;
752
753             case LX_COLN: 
754                 path = add (buffer, path);
755                 return OK;
756
757             default: 
758                 sprintf (err, "no colon found to terminate route (%s)", buffer);
759                 return NOTOK;
760         }
761     }
762 }
763
764
765 static int
766 my_lex (char *buffer)
767 {
768     int i, gotat = 0;
769     register char c, *bp;
770
771     bp = buffer;
772     *bp = 0;
773     if (!cp)
774         return (last_lex = LX_END);
775
776     gotat = isat (cp);
777     c = *cp++;
778     while (isspace (c))
779         c = *cp++;
780     if (c == 0) {
781         cp = NULL;
782         return (last_lex = LX_END);
783     }
784
785     if (c == '(')
786         for (*bp++ = c, i = 0;;)
787             switch (c = *cp++) {
788                 case 0: 
789                     cp = NULL;
790                     return (last_lex = LX_ERR);
791                 case QUOTE: 
792                     *bp++ = c;
793                     if ((c = *cp++) == 0) {
794                         cp = NULL;
795                         return (last_lex = LX_ERR);
796                     }
797                     *bp++ = c;
798                     continue;
799                 case '(': 
800                     i++;
801                 default: 
802                     *bp++ = c;
803                     continue;
804                 case ')': 
805                     *bp++ = c;
806                     if (--i < 0) {
807                         *bp = 0;
808                         note = note ? add (buffer, add (" ", note))
809                             : getcpy (buffer);
810                         return my_lex (buffer);
811                     }
812             }
813
814     if (c == '"')
815         for (*bp++ = c;;)
816             switch (c = *cp++) {
817                 case 0: 
818                     cp = NULL;
819                     return (last_lex = LX_ERR);
820                 case QUOTE: 
821                     *bp++ = c;
822                     if ((c = *cp++) == 0) {
823                         cp = NULL;
824                         return (last_lex = LX_ERR);
825                     }
826                 default: 
827                     *bp++ = c;
828                     continue;
829                 case '"': 
830                     *bp++ = c;
831                     *bp = 0;
832                     return (last_lex = LX_QSTR);
833             }
834
835     if (c == '[')
836         for (*bp++ = c;;)
837             switch (c = *cp++) {
838                 case 0: 
839                     cp = NULL;
840                     return (last_lex = LX_ERR);
841                 case QUOTE: 
842                     *bp++ = c;
843                     if ((c = *cp++) == 0) {
844                         cp = NULL;
845                         return (last_lex = LX_ERR);
846                     }
847                 default: 
848                     *bp++ = c;
849                     continue;
850                 case ']': 
851                     *bp++ = c;
852                     *bp = 0;
853                     return (last_lex = LX_DLIT);
854             }
855
856     *bp++ = c;
857     *bp = 0;
858     for (i = 0; special[i].lx_chr != 0; i++)
859         if (c == special[i].lx_chr)
860             return (last_lex = special[i].lx_val);
861
862     if (iscntrl (c))
863         return (last_lex = LX_ERR);
864
865     for (;;) {
866         if ((c = *cp++) == 0)
867             break;
868         for (i = 0; special[i].lx_chr != 0; i++)
869             if (c == special[i].lx_chr)
870                 goto got_atom;
871         if (iscntrl (c) || isspace (c))
872             break;
873         *bp++ = c;
874     }
875 got_atom: ;
876     if (c == 0)
877         cp = NULL;
878     else
879         cp--;
880     *bp = 0;
881     last_lex = !gotat || cp == NULL || strchr(cp, '<') != NULL
882         ? LX_ATOM : LX_AT;
883     return last_lex;
884 }
885
886
887 char *
888 legal_person (char *p)
889 {
890     int i;
891     register char *cp;
892     static char buffer[BUFSIZ];
893
894     if (*p == '"')
895         return p;
896     for (cp = p; *cp; cp++)
897         for (i = 0; special[i].lx_chr; i++)
898             if (*cp == special[i].lx_chr) {
899                 sprintf (buffer, "\"%s\"", p);
900                 return buffer;
901             }
902
903     return p;
904 }
905
906
907 int
908 mfgets (FILE *in, char **bp)
909 {
910     int i;
911     register char *cp, *dp, *ep;
912     static int len = 0;
913     static char *pp = NULL;
914
915     if (pp == NULL)
916         if (!(pp = malloc ((size_t) (len = BUFSIZ))))
917             return NOTOK;
918
919     for (ep = (cp = pp) + len - 2;;) {
920         switch (i = getc (in)) {
921             case EOF: 
922         eol:    ;
923                 if (cp != pp) {
924                     *cp = 0;
925                     *bp = pp;
926                     return OK;
927                 }
928         eoh:    ;
929                 *bp = NULL;
930                 free (pp);
931                 pp = NULL;
932                 return DONE;
933
934             case 0: 
935                 continue;
936
937             case '\n': 
938                 if (cp == pp)   /* end of headers, gobble it */
939                     goto eoh;
940                 switch (i = getc (in)) {
941                     default:    /* end of line */
942                     case '\n':  /* end of headers, save for next call */
943                         ungetc (i, in);
944                         goto eol;
945
946                     case ' ':   /* continue headers */
947                     case '\t': 
948                         *cp++ = '\n';
949                         break;
950                 }               /* fall into default case */
951
952             default: 
953                 *cp++ = i;
954                 break;
955         }
956         if (cp >= ep) {
957             if (!(dp = realloc (pp, (size_t) (len += BUFSIZ)))) {
958                 free (pp);
959                 pp = NULL;
960                 return NOTOK;
961             }
962             else
963                 cp += dp - pp, ep = (pp = cp) + len - 2;
964         }
965     }
966 }