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