Blob


1 #include "common.h"
2 #include "spam.h"
4 int cflag;
5 int debug;
6 int hflag;
7 int nflag;
8 int sflag;
9 int tflag;
10 int vflag;
11 Biobuf bin, bout, *cout;
13 /* file names */
14 char patfile[128];
15 char linefile[128];
16 char holdqueue[128];
17 char copydir[128];
19 char header[Hdrsize+2];
20 char cmd[1024];
21 char **qname;
22 char **qdir;
23 char *sender;
24 String *recips;
26 char* canon(Biobuf*, char*, char*, int*);
27 int matcher(char*, Pattern*, char*, Resub*);
28 int matchaction(int, char*, Resub*);
29 Biobuf *opencopy(char*);
30 Biobuf *opendump(char*);
31 char *qmail(char**, char*, int, Biobuf*);
32 void saveline(char*, char*, Resub*);
33 int optoutofspamfilter(char*);
35 void
36 usage(void)
37 {
38 fprint(2, "missing or bad arguments to qer\n");
39 exits("usage");
40 }
42 void
43 regerror(char *s)
44 {
45 fprint(2, "scanmail: %s\n", s);
46 }
48 void *
49 Malloc(long n)
50 {
51 void *p;
53 p = malloc(n);
54 if(p == 0)
55 exits("malloc");
56 return p;
57 }
59 void*
60 Realloc(void *p, ulong n)
61 {
62 p = realloc(p, n);
63 if(p == 0)
64 exits("realloc");
65 return p;
66 }
68 void
69 main(int argc, char *argv[])
70 {
71 int i, n, nolines, optout;
72 char **args, **a, *cp, *buf;
73 char body[Bodysize+2];
74 Resub match[1];
75 Biobuf *bp;
77 optout = 1;
78 a = args = Malloc((argc+1)*sizeof(char*));
79 sprint(patfile, "%s/patterns", UPASLIB);
80 sprint(linefile, "%s/lines", UPASLOG);
81 sprint(holdqueue, "%s/queue.hold", SPOOL);
82 sprint(copydir, "%s/copy", SPOOL);
84 *a++ = argv[0];
85 for(argc--, argv++; argv[0] && argv[0][0] == '-'; argc--, argv++){
86 switch(argv[0][1]){
87 case 'c': /* save copy of message */
88 cflag = 1;
89 break;
90 case 'd': /* debug */
91 debug++;
92 *a++ = argv[0];
93 break;
94 case 'h': /* queue held messages by sender domain */
95 hflag = 1; /* -q flag must be set also */
96 break;
97 case 'n': /* NOHOLD mode */
98 nflag = 1;
99 break;
100 case 'p': /* pattern file */
101 if(argv[0][2] || argv[1] == 0)
102 usage();
103 argc--;
104 argv++;
105 strecpy(patfile, patfile+sizeof patfile, *argv);
106 break;
107 case 'q': /* queue name */
108 if(argv[0][2] || argv[1] == 0)
109 usage();
110 *a++ = argv[0];
111 argc--;
112 argv++;
113 qname = a;
114 *a++ = argv[0];
115 break;
116 case 's': /* save copy of dumped message */
117 sflag = 1;
118 break;
119 case 't': /* test mode - don't log match
120 * and write message to /dev/null
121 */
122 tflag = 1;
123 break;
124 case 'v': /* vebose - print matches */
125 vflag = 1;
126 break;
127 default:
128 *a++ = argv[0];
129 break;
133 if(argc < 3)
134 usage();
136 Binit(&bin, 0, OREAD);
137 bp = Bopen(patfile, OREAD);
138 if(bp){
139 parsepats(bp);
140 Bterm(bp);
142 qdir = a;
143 sender = argv[2];
145 /* copy the rest of argv, acummulating the recipients as we go */
146 for(i = 0; argv[i]; i++){
147 *a++ = argv[i];
148 if(i < 4) /* skip queue, 'mail', sender, dest sys */
149 continue;
150 /* recipients and smtp flags - skip the latter*/
151 if(strcmp(argv[i], "-g") == 0){
152 *a++ = argv[++i];
153 continue;
155 if(recips)
156 s_append(recips, ", ");
157 else
158 recips = s_new();
159 s_append(recips, argv[i]);
160 if(optout && !optoutofspamfilter(argv[i]))
161 optout = 0;
163 *a = 0;
164 /* construct a command string for matching */
165 snprint(cmd, sizeof(cmd)-1, "%s %s", sender, s_to_c(recips));
166 cmd[sizeof(cmd)-1] = 0;
167 for(cp = cmd; *cp; cp++)
168 *cp = tolower(*cp);
170 /* canonicalize a copy of the header and body.
171 * buf points to orginal message and n contains
172 * number of bytes of original message read during
173 * canonicalization.
174 */
175 *body = 0;
176 *header = 0;
177 buf = canon(&bin, header+1, body+1, &n);
178 if (buf == 0)
179 exits("read");
181 /* if all users opt out, don't try matches */
182 if(optout){
183 if(cflag)
184 cout = opencopy(sender);
185 exits(qmail(args, buf, n, cout));
188 /* Turn off line logging, if command line matches */
189 nolines = matchaction(Lineoff, cmd, match);
191 for(i = 0; patterns[i].action; i++){
192 /* Lineoff patterns were already done above */
193 if(i == Lineoff)
194 continue;
195 /* don't apply "Line" patterns if excluded above */
196 if(nolines && i == SaveLine)
197 continue;
198 /* apply patterns to the sender/recips, header and body */
199 if(matchaction(i, cmd, match))
200 break;
201 if(matchaction(i, header+1, match))
202 break;
203 if(i == HoldHeader)
204 continue;
205 if(matchaction(i, body+1, match))
206 break;
208 if(cflag && patterns[i].action == 0) /* no match found - save msg */
209 cout = opencopy(sender);
211 exits(qmail(args, buf, n, cout));
214 char*
215 qmail(char **argv, char *buf, int n, Biobuf *cout)
217 Waitmsg *status;
218 int i, pid, pipefd[2];
219 char path[512];
220 Biobuf *bp;
222 pid = 0;
223 if(tflag == 0){
224 if(pipe(pipefd) < 0)
225 exits("pipe");
226 pid = fork();
227 if(pid == 0){
228 dup(pipefd[0], 0);
229 for(i = sysfiles(); i >= 3; i--)
230 close(i);
231 snprint(path, sizeof(path), "%s/qer", UPASBIN);
232 *argv=path;
233 exec(path, argv);
234 exits("exec");
236 Binit(&bout, pipefd[1], OWRITE);
237 bp = &bout;
238 } else
239 bp = Bopen("/dev/null", OWRITE);
241 while(n > 0){
242 Bwrite(bp, buf, n);
243 if(cout)
244 Bwrite(cout, buf, n);
245 n = Bread(&bin, buf, sizeof(buf)-1);
247 Bterm(bp);
248 if(cout)
249 Bterm(cout);
250 if(tflag)
251 return 0;
253 close(pipefd[1]);
254 close(pipefd[0]);
255 for(;;){
256 status = wait();
257 if(status == nil || status->pid == pid)
258 break;
259 free(status);
261 if(status == nil)
262 strcpy(buf, "wait failed");
263 else{
264 strcpy(buf, status->msg);
265 free(status);
267 return buf;
270 char*
271 canon(Biobuf *bp, char *header, char *body, int *n)
273 int hsize;
274 char *raw;
276 hsize = 0;
277 *header = 0;
278 *body = 0;
279 raw = readmsg(bp, &hsize, n);
280 if(raw){
281 if(convert(raw, raw+hsize, header, Hdrsize, 0))
282 conv64(raw+hsize, raw+*n, body, Bodysize); /* base64 */
283 else
284 convert(raw+hsize, raw+*n, body, Bodysize, 1); /* text */
286 return raw;
289 int
290 matchaction(int action, char *message, Resub *m)
292 char *name;
293 Pattern *p;
295 if(message == 0 || *message == 0)
296 return 0;
298 name = patterns[action].action;
299 p = patterns[action].strings;
300 if(p)
301 if(matcher(name, p, message, m))
302 return 1;
304 for(p = patterns[action].regexps; p; p = p->next)
305 if(matcher(name, p, message, m))
306 return 1;
307 return 0;
310 int
311 matcher(char *action, Pattern *p, char *message, Resub *m)
313 char *cp;
314 String *s;
316 for(cp = message; matchpat(p, cp, m); cp = m->e.ep){
317 switch(p->action){
318 case SaveLine:
319 if(vflag)
320 xprint(2, action, m);
321 saveline(linefile, sender, m);
322 break;
323 case HoldHeader:
324 case Hold:
325 if(nflag)
326 continue;
327 if(vflag)
328 xprint(2, action, m);
329 *qdir = holdqueue;
330 if(hflag && qname){
331 cp = strchr(sender, '!');
332 if(cp){
333 *cp = 0;
334 *qname = strdup(sender);
335 *cp = '!';
336 } else
337 *qname = strdup(sender);
339 return 1;
340 case Dump:
341 if(vflag)
342 xprint(2, action, m);
343 *m->e.ep = 0;
344 if(!tflag){
345 s = s_new();
346 s_append(s, sender);
347 s = unescapespecial(s);
348 syslog(0, "smtpd", "Dumped %s [%s] to %s", s_to_c(s), m->s.sp,
349 s_to_c(s_restart(recips)));
350 s_free(s);
352 tflag = 1;
353 if(sflag)
354 cout = opendump(sender);
355 return 1;
356 default:
357 break;
360 return 0;
363 void
364 saveline(char *file, char *sender, Resub *rp)
366 char *p, *q;
367 int i, c;
368 Biobuf *bp;
370 if(rp->s.sp == 0 || rp->e.ep == 0)
371 return;
372 /* back up approx 20 characters to whitespace */
373 for(p = rp->s.sp, i = 0; *p && i < 20; i++, p--)
375 while(*p && *p != ' ')
376 p--;
377 p++;
379 /* grab about 20 more chars beyond the end of the match */
380 for(q = rp->e.ep, i = 0; *q && i < 20; i++, q++)
382 while(*q && *q != ' ')
383 q++;
385 c = *q;
386 *q = 0;
387 bp = sysopen(file, "al", 0644);
388 if(bp){
389 Bprint(bp, "%s-> %s\n", sender, p);
390 Bterm(bp);
392 else if(debug)
393 fprint(2, "can't save line: (%s) %s\n", sender, p);
394 *q = c;
397 Biobuf*
398 opendump(char *sender)
400 int i;
401 ulong h;
402 char buf[512];
403 Biobuf *b;
404 char *cp;
406 cp = ctime(time(0));
407 cp[7] = 0;
408 cp[10] = 0;
409 if(cp[8] == ' ')
410 sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
411 else
412 sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
413 cp = buf+strlen(buf);
414 if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0){
415 syslog(0, "smtpd", "couldn't dump mail from %s: %r", sender);
416 return 0;
419 h = 0;
420 while(*sender)
421 h = h*257 + *sender++;
422 for(i = 0; i < 50; i++){
423 h += lrand();
424 sprint(cp, "/%lud", h);
425 b = sysopen(buf, "wlc", 0644);
426 if(b){
427 if(vflag)
428 fprint(2, "saving in %s\n", buf);
429 return b;
432 return 0;
435 Biobuf*
436 opencopy(char *sender)
438 int i;
439 ulong h;
440 char buf[512];
441 Biobuf *b;
443 h = 0;
444 while(*sender)
445 h = h*257 + *sender++;
446 for(i = 0; i < 50; i++){
447 h += lrand();
448 sprint(buf, "%s/%lud", copydir, h);
449 b = sysopen(buf, "wlc", 0600);
450 if(b)
451 return b;
453 return 0;
456 int
457 optoutofspamfilter(char *addr)
459 char *p, *f;
460 int rv;
462 p = strchr(addr, '!');
463 if(p)
464 p++;
465 else
466 p = addr;
468 rv = 0;
469 f = smprint("/mail/box/%s/nospamfiltering", p);
470 if(f != nil){
471 rv = access(f, 0)==0;
472 free(f);
475 return rv;