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