| #include "common.h" | 
 | #include "smtpd.h" | 
 | #include <ip.h> | 
 |  | 
 | enum { | 
 | 	NORELAY = 0, | 
 | 	DNSVERIFY, | 
 | 	SAVEBLOCK, | 
 | 	DOMNAME, | 
 | 	OURNETS, | 
 | 	OURDOMS, | 
 |  | 
 | 	IP = 0, | 
 | 	STRING | 
 | }; | 
 |  | 
 |  | 
 | typedef struct Keyword Keyword; | 
 |  | 
 | struct Keyword { | 
 | 	char	*name; | 
 | 	int	code; | 
 | }; | 
 |  | 
 | static Keyword options[] = { | 
 | 	"norelay",		NORELAY, | 
 | 	"verifysenderdom",	DNSVERIFY, | 
 | 	"saveblockedmsg",	SAVEBLOCK, | 
 | 	"defaultdomain",	DOMNAME,	 | 
 | 	"ournets",		OURNETS, | 
 | 	"ourdomains",		OURDOMS, | 
 | 	0,			NONE | 
 | }; | 
 |  | 
 | static Keyword actions[] = { | 
 | 	"allow",		ACCEPT, | 
 | 	"block",		BLOCKED, | 
 | 	"deny",			DENIED, | 
 | 	"dial",			DIALUP, | 
 | 	"delay",		DELAY, | 
 | 	0,			NONE | 
 | }; | 
 |  | 
 | static	int	hisaction; | 
 | static	List	ourdoms; | 
 | static	List 	badguys; | 
 | static	ulong	v4peerip; | 
 |  | 
 | static	char*	getline(Biobuf*); | 
 | static	int	cidrcheck(char*); | 
 |  | 
 | static int | 
 | findkey(char *val, Keyword *p) | 
 | { | 
 |  | 
 | 	for(; p->name; p++) | 
 | 		if(strcmp(val, p->name) == 0) | 
 | 				break; | 
 | 	return p->code; | 
 | } | 
 |  | 
 | char* | 
 | actstr(int a) | 
 | { | 
 | 	static char buf[32]; | 
 | 	Keyword *p; | 
 |  | 
 | 	for(p=actions; p->name; p++) | 
 | 		if(p->code == a) | 
 | 			return p->name; | 
 | 	if(a==NONE) | 
 | 		return "none"; | 
 | 	sprint(buf, "%d", a); | 
 | 	return buf; | 
 | } | 
 |  | 
 | int | 
 | getaction(char *s, char *type) | 
 | { | 
 | 	char buf[1024]; | 
 | 	Keyword *k; | 
 |  | 
 | 	if(s == nil || *s == 0) | 
 | 		return ACCEPT; | 
 |  | 
 | 	for(k = actions; k->name != 0; k++){ | 
 | 		snprint(buf, sizeof buf, "%s/mail/ratify/%s/%s/%s",  | 
 | 			get9root(), k->name, type, s); | 
 | 		if(access(buf,0) >= 0) | 
 | 			return k->code; | 
 | 	} | 
 | 	return ACCEPT; | 
 | } | 
 |  | 
 | int | 
 | istrusted(char *s) | 
 | { | 
 | 	char buf[1024]; | 
 |  | 
 | 	if(s == nil || *s == 0) | 
 | 		return 0; | 
 |  | 
 | 	snprint(buf, sizeof buf, "%s/mail/ratify/trusted/%s", get9root(), s); | 
 | 	return access(buf,0) >= 0; | 
 | } | 
 |  | 
 | void | 
 | getconf(void) | 
 | { | 
 | 	Biobuf *bp; | 
 | 	char *cp, *p; | 
 | 	String *s; | 
 | 	char buf[512]; | 
 | 	uchar addr[4]; | 
 |  | 
 | 	v4parseip(addr, nci->rsys); | 
 | 	v4peerip = nhgetl(addr); | 
 |  | 
 | 	trusted = istrusted(nci->rsys); | 
 | 	hisaction = getaction(nci->rsys, "ip"); | 
 | 	if(debug){ | 
 | 		fprint(2, "istrusted(%s)=%d\n", nci->rsys, trusted); | 
 | 		fprint(2, "getaction(%s, ip)=%s\n", nci->rsys, actstr(hisaction)); | 
 | 	} | 
 | 	snprint(buf, sizeof(buf), "%s/smtpd.conf", UPASLIB); | 
 | 	bp = sysopen(buf, "r", 0); | 
 | 	if(bp == 0) | 
 | 		return; | 
 |  | 
 | 	for(;;){ | 
 | 		cp = getline(bp); | 
 | 		if(cp == 0) | 
 | 			break; | 
 | 		p = cp+strlen(cp)+1; | 
 | 		switch(findkey(cp, options)){ | 
 | 		case NORELAY: | 
 | 			if(fflag == 0 && strcmp(p, "on") == 0) | 
 | 				fflag++; | 
 | 			break; | 
 | 		case DNSVERIFY: | 
 | 			if(rflag == 0 && strcmp(p, "on") == 0) | 
 | 				rflag++; | 
 | 			break; | 
 | 		case SAVEBLOCK: | 
 | 			if(sflag == 0 && strcmp(p, "on") == 0) | 
 | 				sflag++; | 
 | 			break; | 
 | 		case DOMNAME: | 
 | 			if(dom == 0) | 
 | 				dom = strdup(p); | 
 | 			break; | 
 | 		case OURNETS: | 
 | 			if (trusted == 0) | 
 | 				trusted = cidrcheck(p); | 
 | 			break; | 
 | 		case OURDOMS: | 
 | 			while(*p){ | 
 | 				s = s_new(); | 
 | 				s_append(s, p); | 
 | 				listadd(&ourdoms, s); | 
 | 				p += strlen(p)+1; | 
 | 			} | 
 | 			break; | 
 | 		default: | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | 	sysclose(bp); | 
 | } | 
 |  | 
 | #if 0 | 
 | /* | 
 |  *	match a user name.  the only meta-char is '*' which matches all | 
 |  *	characters.  we only allow it as "*", which matches anything or | 
 |  *	an * at the end of the name (e.g., "username*") which matches | 
 |  *	trailing characters. | 
 |  */ | 
 | static int | 
 | usermatch(char *pathuser, char *specuser) | 
 | { | 
 | 	int n; | 
 |  | 
 | 	n = strlen(specuser)-1; | 
 | 	if(specuser[n] == '*'){ | 
 | 		if(n == 0)		/* match everything */ | 
 | 			return 0; | 
 | 		return strncmp(pathuser, specuser, n); | 
 | 	} | 
 | 	return strcmp(pathuser, specuser); | 
 | } | 
 | #endif | 
 |  | 
 | static int | 
 | dommatch(char *pathdom, char *specdom) | 
 | { | 
 | 	int n; | 
 |  | 
 | 	if (*specdom == '*'){ | 
 | 		if (specdom[1] == '.' && specdom[2]){ | 
 | 			specdom += 2; | 
 | 			n = strlen(pathdom)-strlen(specdom); | 
 | 			if(n == 0 || (n > 0 && pathdom[n-1] == '.')) | 
 | 				return strcmp(pathdom+n, specdom); | 
 | 			return n; | 
 | 		} | 
 | 	} | 
 | 	return strcmp(pathdom, specdom); | 
 | } | 
 |  | 
 | /* | 
 |  *  figure out action for this sender | 
 |  */ | 
 | int | 
 | blocked(String *path) | 
 | { | 
 | 	String *lpath; | 
 | 	int action; | 
 |  | 
 | 	if(debug) | 
 | 		fprint(2, "blocked(%s)\n", s_to_c(path)); | 
 |  | 
 | 	/* if the sender's IP address is blessed, ignore sender email address */ | 
 | 	if(trusted){ | 
 | 		if(debug) | 
 | 			fprint(2, "\ttrusted => trusted\n"); | 
 | 		return TRUSTED; | 
 | 	} | 
 |  | 
 | 	/* if sender's IP address is blocked, ignore sender email address */ | 
 | 	if(hisaction != ACCEPT){ | 
 | 		if(debug) | 
 | 			fprint(2, "\thisaction=%s => %s\n", actstr(hisaction), actstr(hisaction)); | 
 | 		return hisaction; | 
 | 	} | 
 |  | 
 | 	/* convert to lower case */ | 
 | 	lpath = s_copy(s_to_c(path)); | 
 | 	s_tolower(lpath); | 
 |  | 
 | 	/* classify */ | 
 | 	action = getaction(s_to_c(lpath), "account"); | 
 | 	if(debug) | 
 | 		fprint(2, "\tgetaction account %s => %s\n", s_to_c(lpath), actstr(action)); | 
 | 	s_free(lpath); | 
 | 	return action; | 
 | } | 
 |  | 
 | /* | 
 |  * get a canonicalized line: a string of null-terminated lower-case | 
 |  * tokens with a two null bytes at the end. | 
 |  */ | 
 | static char* | 
 | getline(Biobuf *bp) | 
 | { | 
 | 	char c, *cp, *p, *q; | 
 | 	int n; | 
 |  | 
 | 	static char *buf; | 
 | 	static int bufsize; | 
 |  | 
 | 	for(;;){ | 
 | 		cp = Brdline(bp, '\n'); | 
 | 		if(cp == 0) | 
 | 			return 0; | 
 | 		n = Blinelen(bp); | 
 | 		cp[n-1] = 0; | 
 | 		if(buf == 0 || bufsize < n+1){ | 
 | 			bufsize += 512; | 
 | 			if(bufsize < n+1) | 
 | 				bufsize = n+1; | 
 | 			buf = realloc(buf, bufsize); | 
 | 			if(buf == 0) | 
 | 				break; | 
 | 		} | 
 | 		q = buf; | 
 | 		for (p = cp; *p; p++){ | 
 | 			c = *p; | 
 | 			if(c == '\\' && p[1])	/* we don't allow \<newline> */ | 
 | 				c = *++p; | 
 | 			else | 
 | 			if(c == '#') | 
 | 				break; | 
 | 			else | 
 | 			if(c == ' ' || c == '\t' || c == ',') | 
 | 				if(q == buf || q[-1] == 0) | 
 | 					continue; | 
 | 				else | 
 | 					c = 0; | 
 | 			*q++ = tolower(c); | 
 | 		} | 
 | 		if(q != buf){ | 
 | 			if(q[-1]) | 
 | 				*q++ = 0; | 
 | 			*q = 0; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | 	return buf; | 
 | } | 
 |  | 
 | static int | 
 | isourdom(char *s) | 
 | { | 
 | 	Link *l; | 
 |  | 
 | 	if(strchr(s, '.') == nil) | 
 | 		return 1; | 
 |  | 
 | 	for(l = ourdoms.first; l; l = l->next){ | 
 | 		if(dommatch(s, s_to_c(l->p)) == 0) | 
 | 			return 1; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | int | 
 | forwarding(String *path) | 
 | { | 
 | 	char *cp, *s; | 
 | 	String *lpath; | 
 |  | 
 | 	if(debug) | 
 | 		fprint(2, "forwarding(%s)\n", s_to_c(path)); | 
 |  | 
 | 	/* first check if they want loopback */ | 
 | 	lpath = s_copy(s_to_c(s_restart(path))); | 
 | 	if(nci->rsys && *nci->rsys){ | 
 | 		cp = s_to_c(lpath); | 
 | 		if(strncmp(cp, "[]!", 3) == 0){ | 
 | found: | 
 | 			s_append(path, "["); | 
 | 			s_append(path, nci->rsys); | 
 | 			s_append(path, "]!"); | 
 | 			s_append(path, cp+3); | 
 | 			s_terminate(path); | 
 | 			s_free(lpath); | 
 | 			return 0; | 
 | 		} | 
 | 		cp = strchr(cp,'!');			/* skip our domain and check next */ | 
 | 		if(cp++ && strncmp(cp, "[]!", 3) == 0) | 
 | 			goto found; | 
 | 	} | 
 |  | 
 | 	/* if mail is from a trusted IP addr, allow it to forward */ | 
 | 	if(trusted) { | 
 | 		s_free(lpath); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	/* sender is untrusted; ensure receiver is in one of our domains */ | 
 | 	for(cp = s_to_c(lpath); *cp; cp++)		/* convert receiver lc */ | 
 | 		*cp = tolower(*cp); | 
 |  | 
 | 	for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){ | 
 | 		*cp = 0; | 
 | 		if(!isourdom(s)){ | 
 | 			s_free(lpath); | 
 | 			return 1; | 
 | 		} | 
 | 	} | 
 | 	s_free(lpath); | 
 | 	return 0; | 
 | } | 
 |  | 
 | int | 
 | masquerade(String *path, char *him) | 
 | { | 
 | 	char *cp, *s; | 
 | 	String *lpath; | 
 | 	int rv = 0; | 
 |  | 
 | 	if(debug) | 
 | 		fprint(2, "masquerade(%s)\n", s_to_c(path)); | 
 |  | 
 | 	if(trusted) | 
 | 		return 0; | 
 | 	if(path == nil) | 
 | 		return 0; | 
 |  | 
 | 	lpath = s_copy(s_to_c(path)); | 
 |  | 
 | 	/* sender is untrusted; ensure receiver is in one of our domains */ | 
 | 	for(cp = s_to_c(lpath); *cp; cp++)		/* convert receiver lc */ | 
 | 		*cp = tolower(*cp); | 
 | 	s = s_to_c(lpath); | 
 |  | 
 | 	/* scan first element of ! or last element of @ paths */ | 
 | 	if((cp = strchr(s, '!')) != nil){ | 
 | 		*cp = 0; | 
 | 		if(isourdom(s)) | 
 | 			rv = 1; | 
 | 	} else if((cp = strrchr(s, '@')) != nil){ | 
 | 		if(isourdom(cp+1)) | 
 | 			rv = 1; | 
 | 	} else { | 
 | 		if(isourdom(him)) | 
 | 			rv = 1; | 
 | 	} | 
 |  | 
 | 	s_free(lpath); | 
 | 	return rv; | 
 | } | 
 |  | 
 | /* this is a v4 only check */ | 
 | static int | 
 | cidrcheck(char *cp) | 
 | { | 
 | 	char *p; | 
 | 	ulong a, m; | 
 | 	uchar addr[IPv4addrlen]; | 
 | 	uchar mask[IPv4addrlen]; | 
 |  | 
 | 	if(v4peerip == 0) | 
 | 		return 0; | 
 |  | 
 | 	/* parse a list of CIDR addresses comparing each to the peer IP addr */ | 
 | 	while(cp && *cp){ | 
 | 		v4parsecidr(addr, mask, cp); | 
 | 		a = nhgetl(addr); | 
 | 		m = nhgetl(mask); | 
 | 		/* | 
 | 		 * if a mask isn't specified, we build a minimal mask | 
 | 		 * instead of using the default mask for that net.  in this | 
 | 		 * case we never allow a class A mask (0xff000000). | 
 | 		 */ | 
 | 		if(strchr(cp, '/') == 0){ | 
 | 			m = 0xff000000; | 
 | 			p = cp; | 
 | 			for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.')) | 
 | 					m = (m>>8)|0xff000000; | 
 |  | 
 | 			/* force at least a class B */ | 
 | 			m |= 0xffff0000; | 
 | 		} | 
 | 		if((v4peerip&m) == a) | 
 | 			return 1; | 
 | 		cp += strlen(cp)+1; | 
 | 	}		 | 
 | 	return 0; | 
 | } | 
 |  | 
 | int | 
 | isbadguy(void) | 
 | { | 
 | 	Link *l; | 
 |  | 
 | 	/* check if this IP address is banned */ | 
 | 	for(l = badguys.first; l; l = l->next) | 
 | 		if(cidrcheck(s_to_c(l->p))) | 
 | 			return 1; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | void | 
 | addbadguy(char *p) | 
 | { | 
 | 	listadd(&badguys, s_copy(p)); | 
 | }; | 
 |  | 
 | char* | 
 | dumpfile(char *sender) | 
 | { | 
 | 	int i, fd; | 
 | 	ulong h; | 
 | 	static char buf[512]; | 
 | 	char *cp; | 
 |  | 
 | 	if (sflag == 1){ | 
 | 		cp = ctime(time(0)); | 
 | 		cp[7] = 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) | 
 | 			return "/dev/null"; | 
 | 		h = 0; | 
 | 		while(*sender) | 
 | 			h = h*257 + *sender++; | 
 | 		for(i = 0; i < 50; i++){ | 
 | 			h += lrand(); | 
 | 			sprint(cp, "/%lud", h); | 
 | 			if(access(buf, 0) >= 0) | 
 | 				continue; | 
 | 			fd = syscreate(buf, ORDWR, 0666); | 
 | 			if(fd >= 0){ | 
 | 				if(debug) | 
 | 					fprint(2, "saving in %s\n", buf); | 
 | 				close(fd); | 
 | 				return buf; | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return "/dev/null"; | 
 | } | 
 |  | 
 | char *validator = "#9/mail/lib/validateaddress"; | 
 |  | 
 | int | 
 | recipok(char *user) | 
 | { | 
 | 	char *cp, *p, c; | 
 | 	char buf[512]; | 
 | 	int n; | 
 | 	Biobuf *bp; | 
 | 	int pid; | 
 | 	Waitmsg *w; | 
 | 	static int beenhere; | 
 | 	 | 
 | 	if(!beenhere){ | 
 | 		beenhere++; | 
 | 		validator = unsharp(validator); | 
 | 	} | 
 | 	if(shellchars(user)){ | 
 | 		syslog(0, "smtpd", "shellchars in user name"); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	if(access(validator, AEXEC) == 0) | 
 | 	switch(pid = fork()) { | 
 | 	case -1: | 
 | 		break; | 
 | 	case 0: | 
 | 		execl(validator, "validateaddress", user, nil); | 
 | 		exits(0); | 
 | 	default: | 
 | 		while(w = wait()) { | 
 | 			if(w->pid != pid) | 
 | 				continue; | 
 | 			if(w->msg[0] != 0){ | 
 | 				syslog(0, "smtpd", "validateaddress %s: %s", user, w->msg); | 
 | 				return 0; | 
 | 			} | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	snprint(buf, sizeof(buf), "%s/names.blocked", UPASLIB); | 
 | 	bp = sysopen(buf, "r", 0); | 
 | 	if(bp == 0) | 
 | 		return 1; | 
 | 	for(;;){ | 
 | 		cp = Brdline(bp, '\n'); | 
 | 		if(cp == 0) | 
 | 			break; | 
 | 		n = Blinelen(bp); | 
 | 		cp[n-1] = 0; | 
 |  | 
 | 		while(*cp == ' ' || *cp == '\t') | 
 | 			cp++; | 
 | 		for(p = cp; c = *p; p++){ | 
 | 			if(c == '#') | 
 | 				break; | 
 | 			if(c == ' ' || c == '\t') | 
 | 				break; | 
 | 		} | 
 | 		if(p > cp){ | 
 | 			*p = 0; | 
 | 			if(cistrcmp(user, cp) == 0){ | 
 | 				syslog(0, "smtpd", "names.blocked blocks %s", user); | 
 | 				Bterm(bp); | 
 | 				return 0; | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	Bterm(bp); | 
 | 	return 1; | 
 | } | 
 |  | 
 | /* | 
 |  *  a user can opt out of spam filtering by creating | 
 |  *  a file in his mail directory named 'nospamfiltering'. | 
 |  */ | 
 | int | 
 | optoutofspamfilter(char *addr) | 
 | { | 
 | 	char *p, *f; | 
 | 	int rv; | 
 |  | 
 | 	p = strchr(addr, '!'); | 
 | 	if(p) | 
 | 		p++; | 
 | 	else | 
 | 		p = addr; | 
 |  | 
 |  | 
 | 	rv = 0; | 
 | 	f = smprint("%s/mail/box/%s/nospamfiltering", get9root(), p); | 
 | 	if(f != nil){ | 
 | 		rv = access(f, 0)==0; | 
 | 		free(f); | 
 | 	} | 
 |  | 
 | 	return rv; | 
 | } |