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