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