| #include "common.h" | 
 | #include "spam.h" | 
 |  | 
 | int	cflag; | 
 | int	debug; | 
 | int	hflag; | 
 | int	nflag; | 
 | int	sflag; | 
 | int	tflag; | 
 | int	vflag; | 
 | Biobuf	bin, bout, *cout; | 
 |  | 
 | 	/* file names */ | 
 | char	patfile[128]; | 
 | char	linefile[128]; | 
 | char	holdqueue[128]; | 
 | char	copydir[128]; | 
 |  | 
 | char	header[Hdrsize+2]; | 
 | char	cmd[1024]; | 
 | char	**qname; | 
 | char	**qdir; | 
 | char	*sender; | 
 | String	*recips; | 
 |  | 
 | char*	canon(Biobuf*, char*, char*, int*); | 
 | int	matcher(char*, Pattern*, char*, Resub*); | 
 | int	matchaction(int, char*, Resub*); | 
 | Biobuf	*opencopy(char*); | 
 | Biobuf	*opendump(char*); | 
 | char	*qmail(char**, char*, int, Biobuf*); | 
 | void	saveline(char*, char*, Resub*); | 
 | int	optoutofspamfilter(char*); | 
 |  | 
 | void | 
 | usage(void) | 
 | { | 
 | 	fprint(2, "missing or bad arguments to qer\n"); | 
 | 	exits("usage"); | 
 | } | 
 |  | 
 | void | 
 | regerror(char *s) | 
 | { | 
 | 	fprint(2, "scanmail: %s\n", s); | 
 | } | 
 |  | 
 | void * | 
 | Malloc(long n) | 
 | { | 
 | 	void *p; | 
 |  | 
 | 	p = malloc(n); | 
 | 	if(p == 0) | 
 | 		exits("malloc"); | 
 | 	return p; | 
 | } | 
 |  | 
 | void* | 
 | Realloc(void *p, ulong n) | 
 | { | 
 | 	p = realloc(p, n); | 
 | 	if(p == 0) | 
 | 		exits("realloc"); | 
 | 	return p; | 
 | } | 
 |  | 
 | void | 
 | main(int argc, char *argv[]) | 
 | { | 
 | 	int i, n, nolines, optout; | 
 | 	char **args, **a, *cp, *buf; | 
 | 	char body[Bodysize+2]; | 
 | 	Resub match[1]; | 
 | 	Biobuf *bp; | 
 |  | 
 | 	optout = 1; | 
 | 	a = args = Malloc((argc+1)*sizeof(char*)); | 
 | 	sprint(patfile, "%s/patterns", UPASLIB); | 
 | 	sprint(linefile, "%s/lines", UPASLOG); | 
 | 	sprint(holdqueue, "%s/queue.hold", SPOOL); | 
 | 	sprint(copydir, "%s/copy", SPOOL); | 
 |  | 
 | 	*a++ = argv[0]; | 
 | 	for(argc--, argv++; argv[0] && argv[0][0] == '-'; argc--, argv++){ | 
 | 		switch(argv[0][1]){ | 
 | 		case 'c':			/* save copy of message */ | 
 | 			cflag = 1; | 
 | 			break; | 
 | 		case 'd':			/* debug */ | 
 | 			debug++; | 
 | 			*a++ = argv[0]; | 
 | 			break; | 
 | 		case 'h':			/* queue held messages by sender domain */ | 
 | 			hflag = 1;		/* -q flag must be set also */ | 
 | 			break; | 
 | 		case 'n':			/* NOHOLD mode */ | 
 | 			nflag = 1; | 
 | 			break; | 
 | 		case 'p':			/* pattern file */ | 
 | 			if(argv[0][2] || argv[1] == 0) | 
 | 				usage(); | 
 | 			argc--; | 
 | 			argv++; | 
 | 			strecpy(patfile, patfile+sizeof patfile, *argv); | 
 | 			break; | 
 | 		case 'q':			/* queue name */ | 
 | 			if(argv[0][2] ||  argv[1] == 0) | 
 | 				usage(); | 
 | 			*a++ = argv[0]; | 
 | 			argc--; | 
 | 			argv++; | 
 | 			qname = a; | 
 | 			*a++ = argv[0]; | 
 | 			break; | 
 | 		case 's':			/* save copy of dumped message */ | 
 | 			sflag = 1; | 
 | 			break; | 
 | 		case 't':			/* test mode - don't log match | 
 | 						 * and write message to /dev/null | 
 | 						 */ | 
 | 			tflag = 1; | 
 | 			break; | 
 | 		case 'v':			/* vebose - print matches */ | 
 | 			vflag = 1; | 
 | 			break; | 
 | 		default: | 
 | 			*a++ = argv[0]; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if(argc < 3) | 
 | 		usage(); | 
 |  | 
 | 	Binit(&bin, 0, OREAD); | 
 | 	bp = Bopen(patfile, OREAD); | 
 | 	if(bp){ | 
 | 		parsepats(bp); | 
 | 		Bterm(bp); | 
 | 	} | 
 | 	qdir = a; | 
 | 	sender = argv[2]; | 
 |  | 
 | 		/* copy the rest of argv, acummulating the recipients as we go */ | 
 | 	for(i = 0; argv[i]; i++){ | 
 | 		*a++ = argv[i]; | 
 | 		if(i < 4)	/* skip queue, 'mail', sender, dest sys */ | 
 | 			continue; | 
 | 			/* recipients and smtp flags - skip the latter*/ | 
 | 		if(strcmp(argv[i], "-g") == 0){ | 
 | 			*a++ = argv[++i]; | 
 | 			continue; | 
 | 		} | 
 | 		if(recips) | 
 | 			s_append(recips, ", "); | 
 | 		else | 
 | 			recips = s_new(); | 
 | 		s_append(recips, argv[i]); | 
 | 		if(optout && !optoutofspamfilter(argv[i])) | 
 | 			optout = 0; | 
 | 	} | 
 | 	*a = 0; | 
 | 		/* construct a command string for matching */ | 
 | 	snprint(cmd, sizeof(cmd)-1, "%s %s", sender, s_to_c(recips)); | 
 | 	cmd[sizeof(cmd)-1] = 0; | 
 | 	for(cp = cmd; *cp; cp++) | 
 | 		*cp = tolower(*cp); | 
 |  | 
 | 		/* canonicalize a copy of the header and body. | 
 | 		 * buf points to orginal message and n contains | 
 | 		 * number of bytes of original message read during | 
 | 		 * canonicalization. | 
 | 		 */ | 
 | 	*body = 0; | 
 | 	*header = 0; | 
 | 	buf = canon(&bin, header+1, body+1, &n); | 
 | 	if (buf == 0) | 
 | 		exits("read"); | 
 |  | 
 | 		/* if all users opt out, don't try matches */ | 
 | 	if(optout){ | 
 | 		if(cflag) | 
 | 			cout = opencopy(sender); | 
 | 		exits(qmail(args, buf, n, cout)); | 
 | 	} | 
 |  | 
 | 		/* Turn off line logging, if command line matches */ | 
 | 	nolines = matchaction(Lineoff, cmd, match); | 
 |  | 
 | 	for(i = 0; patterns[i].action; i++){ | 
 | 			/* Lineoff patterns were already done above */ | 
 | 		if(i == Lineoff) | 
 | 			continue; | 
 | 			/* don't apply "Line" patterns if excluded above */ | 
 | 		if(nolines && i == SaveLine) | 
 | 			continue; | 
 | 			/* apply patterns to the sender/recips, header and body */ | 
 | 		if(matchaction(i, cmd, match)) | 
 | 			break; | 
 | 		if(matchaction(i, header+1, match)) | 
 | 			break; | 
 | 		if(i == HoldHeader) | 
 | 			continue; | 
 | 		if(matchaction(i, body+1, match)) | 
 | 			break; | 
 | 	} | 
 | 	if(cflag && patterns[i].action == 0)	/* no match found - save msg */ | 
 | 		cout = opencopy(sender); | 
 |  | 
 | 	exits(qmail(args, buf, n, cout)); | 
 | } | 
 |  | 
 | char* | 
 | qmail(char **argv, char *buf, int n, Biobuf *cout) | 
 | { | 
 | 	Waitmsg *status; | 
 | 	int i, pid, pipefd[2]; | 
 | 	char path[512]; | 
 | 	Biobuf *bp; | 
 |  | 
 | 	pid = 0; | 
 | 	if(tflag == 0){ | 
 | 		if(pipe(pipefd) < 0) | 
 | 			exits("pipe"); | 
 | 		pid = fork(); | 
 | 		if(pid == 0){ | 
 | 			dup(pipefd[0], 0); | 
 | 			for(i = sysfiles(); i >= 3; i--) | 
 | 				close(i); | 
 | 			snprint(path, sizeof(path), "%s/qer", UPASBIN); | 
 | 			*argv=path; | 
 | 			exec(path, argv); | 
 | 			exits("exec"); | 
 | 		} | 
 | 		Binit(&bout, pipefd[1], OWRITE); | 
 | 		bp = &bout; | 
 | 	} else | 
 | 		bp = Bopen("/dev/null", OWRITE); | 
 |  | 
 | 	while(n > 0){ | 
 | 		Bwrite(bp, buf, n); | 
 | 		if(cout) | 
 | 			Bwrite(cout, buf, n); | 
 | 		n = Bread(&bin, buf, sizeof(buf)-1); | 
 | 	} | 
 | 	Bterm(bp); | 
 | 	if(cout) | 
 | 		Bterm(cout); | 
 | 	if(tflag) | 
 | 		return 0; | 
 |  | 
 | 	close(pipefd[1]); | 
 | 	close(pipefd[0]); | 
 | 	for(;;){ | 
 | 		status = wait(); | 
 | 		if(status == nil || status->pid == pid) | 
 | 			break; | 
 | 		free(status); | 
 | 	} | 
 | 	if(status == nil) | 
 | 		strcpy(buf, "wait failed"); | 
 | 	else{ | 
 | 		strcpy(buf, status->msg); | 
 | 		free(status); | 
 | 	} | 
 | 	return buf; | 
 | } | 
 |  | 
 | char* | 
 | canon(Biobuf *bp, char *header, char *body, int *n) | 
 | { | 
 | 	int hsize; | 
 | 	char *raw; | 
 |  | 
 | 	hsize = 0; | 
 | 	*header = 0; | 
 | 	*body = 0; | 
 | 	raw = readmsg(bp, &hsize, n); | 
 | 	if(raw){ | 
 | 		if(convert(raw, raw+hsize, header, Hdrsize, 0)) | 
 | 			conv64(raw+hsize, raw+*n, body, Bodysize);	/* base64 */ | 
 | 		else | 
 | 			convert(raw+hsize, raw+*n, body, Bodysize, 1);	/* text */ | 
 | 	} | 
 | 	return raw; | 
 | } | 
 |  | 
 | int | 
 | matchaction(int action, char *message, Resub *m) | 
 | { | 
 | 	char *name; | 
 | 	Pattern *p; | 
 |  | 
 | 	if(message == 0 || *message == 0) | 
 | 		return 0; | 
 |  | 
 | 	name = patterns[action].action; | 
 | 	p = patterns[action].strings; | 
 | 	if(p) | 
 | 		if(matcher(name, p, message, m)) | 
 | 			return 1; | 
 |  | 
 | 	for(p = patterns[action].regexps; p; p = p->next) | 
 | 		if(matcher(name, p, message, m)) | 
 | 			return 1; | 
 | 	return 0; | 
 | } | 
 |  | 
 | int | 
 | matcher(char *action, Pattern *p, char *message, Resub *m) | 
 | { | 
 | 	char *cp; | 
 | 	String *s; | 
 |  | 
 | 	for(cp = message; matchpat(p, cp, m); cp = m->e.ep){ | 
 | 		switch(p->action){ | 
 | 		case SaveLine: | 
 | 			if(vflag) | 
 | 				xprint(2, action, m); | 
 | 			saveline(linefile, sender, m); | 
 | 			break; | 
 | 		case HoldHeader: | 
 | 		case Hold: | 
 | 			if(nflag) | 
 | 				continue; | 
 | 			if(vflag) | 
 | 				xprint(2, action, m); | 
 | 			*qdir = holdqueue; | 
 | 			if(hflag && qname){ | 
 | 				cp = strchr(sender, '!'); | 
 | 				if(cp){ | 
 | 					*cp = 0; | 
 | 					*qname = strdup(sender); | 
 | 					*cp = '!'; | 
 | 				} else | 
 | 					*qname = strdup(sender); | 
 | 			} | 
 | 			return 1; | 
 | 		case Dump: | 
 | 			if(vflag) | 
 | 				xprint(2, action, m); | 
 | 			*m->e.ep = 0; | 
 | 			if(!tflag){ | 
 | 				s = s_new(); | 
 | 				s_append(s, sender); | 
 | 				s = unescapespecial(s); | 
 | 				syslog(0, "smtpd", "Dumped %s [%s] to %s", s_to_c(s), m->s.sp, | 
 | 					s_to_c(s_restart(recips))); | 
 | 				s_free(s); | 
 | 			} | 
 | 			tflag = 1; | 
 | 			if(sflag) | 
 | 				cout = opendump(sender); | 
 | 			return 1; | 
 | 		default: | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | void | 
 | saveline(char *file, char *sender, Resub *rp) | 
 | { | 
 | 	char *p, *q; | 
 | 	int i, c; | 
 | 	Biobuf *bp; | 
 |  | 
 | 	if(rp->s.sp == 0 || rp->e.ep == 0) | 
 | 		return; | 
 | 		/* back up approx 20 characters to whitespace */ | 
 | 	for(p = rp->s.sp, i = 0; *p && i < 20; i++, p--) | 
 | 			; | 
 | 	while(*p && *p != ' ') | 
 | 		p--; | 
 | 	p++; | 
 |  | 
 | 		/* grab about 20 more chars beyond the end of the match */ | 
 | 	for(q = rp->e.ep, i = 0; *q && i < 20; i++, q++) | 
 | 			; | 
 | 	while(*q && *q != ' ') | 
 | 		q++; | 
 |  | 
 | 	c = *q; | 
 | 	*q = 0; | 
 | 	bp = sysopen(file, "al", 0644); | 
 | 	if(bp){ | 
 | 		Bprint(bp, "%s-> %s\n", sender, p); | 
 | 		Bterm(bp); | 
 | 	} | 
 | 	else if(debug) | 
 | 		fprint(2, "can't save line: (%s) %s\n", sender, p); | 
 | 	*q = c; | 
 | } | 
 |  | 
 | Biobuf* | 
 | opendump(char *sender) | 
 | { | 
 | 	int i; | 
 | 	ulong h; | 
 | 	char buf[512]; | 
 | 	Biobuf *b; | 
 | 	char *cp; | 
 |  | 
 | 	cp = ctime(time(0)); | 
 | 	cp[7] = 0; | 
 | 	cp[10] = 0; | 
 | 	if(cp[8] == ' ') | 
 | 		sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]); | 
 | 	else | 
 | 		sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]); | 
 | 	cp = buf+strlen(buf); | 
 | 	if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0){ | 
 | 		syslog(0, "smtpd", "couldn't dump mail from %s: %r", sender); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	h = 0; | 
 | 	while(*sender) | 
 | 		h = h*257 + *sender++; | 
 | 	for(i = 0; i < 50; i++){ | 
 | 		h += lrand(); | 
 | 		sprint(cp, "/%lud", h); | 
 | 		b = sysopen(buf, "wlc", 0644); | 
 | 		if(b){ | 
 | 			if(vflag) | 
 | 				fprint(2, "saving in %s\n", buf); | 
 | 			return b; | 
 | 		} | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | Biobuf* | 
 | opencopy(char *sender) | 
 | { | 
 | 	int i; | 
 | 	ulong h; | 
 | 	char buf[512]; | 
 | 	Biobuf *b; | 
 |  | 
 | 	h = 0; | 
 | 	while(*sender) | 
 | 		h = h*257 + *sender++; | 
 | 	for(i = 0; i < 50; i++){ | 
 | 		h += lrand(); | 
 | 		sprint(buf, "%s/%lud", copydir, h); | 
 | 		b = sysopen(buf, "wlc", 0600); | 
 | 		if(b) | 
 | 			return b; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | int | 
 | optoutofspamfilter(char *addr) | 
 | { | 
 | 	char *p, *f; | 
 | 	int rv; | 
 |  | 
 | 	p = strchr(addr, '!'); | 
 | 	if(p) | 
 | 		p++; | 
 | 	else | 
 | 		p = addr; | 
 |  | 
 | 	rv = 0; | 
 | 	f = smprint("/mail/box/%s/nospamfiltering", p); | 
 | 	if(f != nil){ | 
 | 		rv = access(f, 0)==0; | 
 | 		free(f); | 
 | 	} | 
 |  | 
 | 	return rv; | 
 | } |