xref: /plan9/sys/src/cmd/upas/scanmail/scanmail.c (revision 4d44ba9b9ee4246ddbd96c7fcaf0918ab92ab35a)
1 #include "common.h"
2 #include "spam.h"
3 
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;
12 
13 	/* file names */
14 char	patfile[128];
15 char	linefile[128];
16 char	holdqueue[128];
17 char	copydir[128];
18 
19 char	header[Hdrsize+2];
20 char	cmd[1024];
21 char	**qname;
22 char	**qdir;
23 char	*sender;
24 String	*recips;
25 
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*);
34 
35 void
usage(void)36 usage(void)
37 {
38 	fprint(2, "missing or bad arguments to qer\n");
39 	exits("usage");
40 }
41 
42 void
regerror(char * s)43 regerror(char *s)
44 {
45 	fprint(2, "scanmail: %s\n", s);
46 }
47 
48 void *
Malloc(long n)49 Malloc(long n)
50 {
51 	void *p;
52 
53 	p = malloc(n);
54 	if(p == 0)
55 		exits("malloc");
56 	return p;
57 }
58 
59 void*
Realloc(void * p,ulong n)60 Realloc(void *p, ulong n)
61 {
62 	p = realloc(p, n);
63 	if(p == 0)
64 		exits("realloc");
65 	return p;
66 }
67 
68 void
main(int argc,char * argv[])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;
76 
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);
83 
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;
130 		}
131 	}
132 
133 	if(argc < 3)
134 		usage();
135 
136 	Binit(&bin, 0, OREAD);
137 	bp = Bopen(patfile, OREAD);
138 	if(bp){
139 		parsepats(bp);
140 		Bterm(bp);
141 	}
142 	qdir = a;
143 	sender = argv[2];
144 
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;
154 		}
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;
162 	}
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);
169 
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");
180 
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));
186 	}
187 
188 		/* Turn off line logging, if command line matches */
189 	nolines = matchaction(Lineoff, cmd, match);
190 
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;
207 	}
208 	if(cflag && patterns[i].action == 0)	/* no match found - save msg */
209 		cout = opencopy(sender);
210 
211 	exits(qmail(args, buf, n, cout));
212 }
213 
214 char*
qmail(char ** argv,char * buf,int n,Biobuf * cout)215 qmail(char **argv, char *buf, int n, Biobuf *cout)
216 {
217 	Waitmsg *status;
218 	int i, pid, pipefd[2];
219 	char path[512];
220 	Biobuf *bp;
221 
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");
235 		}
236 		Binit(&bout, pipefd[1], OWRITE);
237 		bp = &bout;
238 	} else
239 		bp = Bopen("/dev/null", OWRITE);
240 
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);
246 	}
247 	Bterm(bp);
248 	if(cout)
249 		Bterm(cout);
250 	if(tflag)
251 		return 0;
252 
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);
260 	}
261 	if(status == nil)
262 		strcpy(buf, "wait failed");
263 	else{
264 		strcpy(buf, status->msg);
265 		free(status);
266 	}
267 	return buf;
268 }
269 
270 char*
canon(Biobuf * bp,char * header,char * body,int * n)271 canon(Biobuf *bp, char *header, char *body, int *n)
272 {
273 	int hsize;
274 	char *raw;
275 
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 */
285 	}
286 	return raw;
287 }
288 
289 int
matchaction(int action,char * message,Resub * m)290 matchaction(int action, char *message, Resub *m)
291 {
292 	char *name;
293 	Pattern *p;
294 
295 	if(message == 0 || *message == 0)
296 		return 0;
297 
298 	name = patterns[action].action;
299 	p = patterns[action].strings;
300 	if(p)
301 		if(matcher(name, p, message, m))
302 			return 1;
303 
304 	for(p = patterns[action].regexps; p; p = p->next)
305 		if(matcher(name, p, message, m))
306 			return 1;
307 	return 0;
308 }
309 
310 int
matcher(char * action,Pattern * p,char * message,Resub * m)311 matcher(char *action, Pattern *p, char *message, Resub *m)
312 {
313 	char *cp;
314 	String *s;
315 
316 	for(cp = message; matchpat(p, cp, m); cp = m->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);
338 			}
339 			return 1;
340 		case Dump:
341 			if(vflag)
342 				xprint(2, action, m);
343 			*(m->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->sp,
349 					s_to_c(s_restart(recips)));
350 				s_free(s);
351 			}
352 			tflag = 1;
353 			if(sflag)
354 				cout = opendump(sender);
355 			return 1;
356 		default:
357 			break;
358 		}
359 	}
360 	return 0;
361 }
362 
363 void
saveline(char * file,char * sender,Resub * rp)364 saveline(char *file, char *sender, Resub *rp)
365 {
366 	char *p, *q;
367 	int i, c;
368 	Biobuf *bp;
369 
370 	if(rp->sp == 0 || rp->ep == 0)
371 		return;
372 		/* back up approx 20 characters to whitespace */
373 	for(p = rp->sp, i = 0; *p && i < 20; i++, p--)
374 			;
375 	while(*p && *p != ' ')
376 		p--;
377 	p++;
378 
379 		/* grab about 20 more chars beyond the end of the match */
380 	for(q = rp->ep, i = 0; *q && i < 20; i++, q++)
381 			;
382 	while(*q && *q != ' ')
383 		q++;
384 
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);
391 	}
392 	else if(debug)
393 		fprint(2, "can't save line: (%s) %s\n", sender, p);
394 	*q = c;
395 }
396 
397 Biobuf*
opendump(char * sender)398 opendump(char *sender)
399 {
400 	int i;
401 	ulong h;
402 	char buf[512];
403 	Biobuf *b;
404 	char *cp;
405 
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;
417 	}
418 
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;
430 		}
431 	}
432 	return 0;
433 }
434 
435 Biobuf*
opencopy(char * sender)436 opencopy(char *sender)
437 {
438 	int i;
439 	ulong h;
440 	char buf[512];
441 	Biobuf *b;
442 
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;
452 	}
453 	return 0;
454 }
455 
456 int
optoutofspamfilter(char * addr)457 optoutofspamfilter(char *addr)
458 {
459 	char *p, *f;
460 	int rv;
461 
462 	p = strchr(addr, '!');
463 	if(p)
464 		p++;
465 	else
466 		p = addr;
467 
468 	rv = 0;
469 	f = smprint("/mail/box/%s/nospamfiltering", p);
470 	if(f != nil){
471 		rv = access(f, 0)==0;
472 		free(f);
473 	}
474 
475 	return rv;
476 }
477