|  | #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; | 
|  | } |