plan9port

fork of plan9port with libvec, libstr and libsdb
Log | Files | Refs | README | LICENSE

scanmail.c (8754B)


      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 regerror(char *s)
     44 {
     45 	fprint(2, "scanmail: %s\n", s);
     46 }
     47 
     48 void *
     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*
     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
     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*
    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*
    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
    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
    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->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);
    338 			}
    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);
    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
    364 saveline(char *file, char *sender, Resub *rp)
    365 {
    366 	char *p, *q;
    367 	int i, c;
    368 	Biobuf *bp;
    369 
    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--)
    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->e.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*
    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*
    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
    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 }