plan9port

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

spam.c (10367B)


      1 #include "common.h"
      2 #include "smtpd.h"
      3 #include <ip.h>
      4 
      5 enum {
      6 	NORELAY = 0,
      7 	DNSVERIFY,
      8 	SAVEBLOCK,
      9 	DOMNAME,
     10 	OURNETS,
     11 	OURDOMS,
     12 
     13 	IP = 0,
     14 	STRING
     15 };
     16 
     17 
     18 typedef struct Keyword Keyword;
     19 
     20 struct Keyword {
     21 	char	*name;
     22 	int	code;
     23 };
     24 
     25 static Keyword options[] = {
     26 	"norelay",		NORELAY,
     27 	"verifysenderdom",	DNSVERIFY,
     28 	"saveblockedmsg",	SAVEBLOCK,
     29 	"defaultdomain",	DOMNAME,
     30 	"ournets",		OURNETS,
     31 	"ourdomains",		OURDOMS,
     32 	0,			NONE
     33 };
     34 
     35 static Keyword actions[] = {
     36 	"allow",		ACCEPT,
     37 	"block",		BLOCKED,
     38 	"deny",			DENIED,
     39 	"dial",			DIALUP,
     40 	"delay",		DELAY,
     41 	0,			NONE
     42 };
     43 
     44 static	int	hisaction;
     45 static	List	ourdoms;
     46 static	List 	badguys;
     47 static	ulong	v4peerip;
     48 
     49 static	char*	getline(Biobuf*);
     50 static	int	cidrcheck(char*);
     51 
     52 static int
     53 findkey(char *val, Keyword *p)
     54 {
     55 
     56 	for(; p->name; p++)
     57 		if(strcmp(val, p->name) == 0)
     58 				break;
     59 	return p->code;
     60 }
     61 
     62 char*
     63 actstr(int a)
     64 {
     65 	static char buf[32];
     66 	Keyword *p;
     67 
     68 	for(p=actions; p->name; p++)
     69 		if(p->code == a)
     70 			return p->name;
     71 	if(a==NONE)
     72 		return "none";
     73 	sprint(buf, "%d", a);
     74 	return buf;
     75 }
     76 
     77 int
     78 getaction(char *s, char *type)
     79 {
     80 	char buf[1024];
     81 	Keyword *k;
     82 
     83 	if(s == nil || *s == 0)
     84 		return ACCEPT;
     85 
     86 	for(k = actions; k->name != 0; k++){
     87 		snprint(buf, sizeof buf, "%s/mail/ratify/%s/%s/%s",
     88 			get9root(), k->name, type, s);
     89 		if(access(buf,0) >= 0)
     90 			return k->code;
     91 	}
     92 	return ACCEPT;
     93 }
     94 
     95 int
     96 istrusted(char *s)
     97 {
     98 	char buf[1024];
     99 
    100 	if(s == nil || *s == 0)
    101 		return 0;
    102 
    103 	snprint(buf, sizeof buf, "%s/mail/ratify/trusted/%s", get9root(), s);
    104 	return access(buf,0) >= 0;
    105 }
    106 
    107 void
    108 getconf(void)
    109 {
    110 	Biobuf *bp;
    111 	char *cp, *p;
    112 	String *s;
    113 	char buf[512];
    114 	uchar addr[4];
    115 
    116 	v4parseip(addr, nci->rsys);
    117 	v4peerip = nhgetl(addr);
    118 
    119 	trusted = istrusted(nci->rsys);
    120 	hisaction = getaction(nci->rsys, "ip");
    121 	if(debug){
    122 		fprint(2, "istrusted(%s)=%d\n", nci->rsys, trusted);
    123 		fprint(2, "getaction(%s, ip)=%s\n", nci->rsys, actstr(hisaction));
    124 	}
    125 	snprint(buf, sizeof(buf), "%s/smtpd.conf", UPASLIB);
    126 	bp = sysopen(buf, "r", 0);
    127 	if(bp == 0)
    128 		return;
    129 
    130 	for(;;){
    131 		cp = getline(bp);
    132 		if(cp == 0)
    133 			break;
    134 		p = cp+strlen(cp)+1;
    135 		switch(findkey(cp, options)){
    136 		case NORELAY:
    137 			if(fflag == 0 && strcmp(p, "on") == 0)
    138 				fflag++;
    139 			break;
    140 		case DNSVERIFY:
    141 			if(rflag == 0 && strcmp(p, "on") == 0)
    142 				rflag++;
    143 			break;
    144 		case SAVEBLOCK:
    145 			if(sflag == 0 && strcmp(p, "on") == 0)
    146 				sflag++;
    147 			break;
    148 		case DOMNAME:
    149 			if(dom == 0)
    150 				dom = strdup(p);
    151 			break;
    152 		case OURNETS:
    153 			if (trusted == 0)
    154 				trusted = cidrcheck(p);
    155 			break;
    156 		case OURDOMS:
    157 			while(*p){
    158 				s = s_new();
    159 				s_append(s, p);
    160 				listadd(&ourdoms, s);
    161 				p += strlen(p)+1;
    162 			}
    163 			break;
    164 		default:
    165 			break;
    166 		}
    167 	}
    168 	sysclose(bp);
    169 }
    170 
    171 #if 0
    172 /*
    173  *	match a user name.  the only meta-char is '*' which matches all
    174  *	characters.  we only allow it as "*", which matches anything or
    175  *	an * at the end of the name (e.g., "username*") which matches
    176  *	trailing characters.
    177  */
    178 static int
    179 usermatch(char *pathuser, char *specuser)
    180 {
    181 	int n;
    182 
    183 	n = strlen(specuser)-1;
    184 	if(specuser[n] == '*'){
    185 		if(n == 0)		/* match everything */
    186 			return 0;
    187 		return strncmp(pathuser, specuser, n);
    188 	}
    189 	return strcmp(pathuser, specuser);
    190 }
    191 #endif
    192 
    193 static int
    194 dommatch(char *pathdom, char *specdom)
    195 {
    196 	int n;
    197 
    198 	if (*specdom == '*'){
    199 		if (specdom[1] == '.' && specdom[2]){
    200 			specdom += 2;
    201 			n = strlen(pathdom)-strlen(specdom);
    202 			if(n == 0 || (n > 0 && pathdom[n-1] == '.'))
    203 				return strcmp(pathdom+n, specdom);
    204 			return n;
    205 		}
    206 	}
    207 	return strcmp(pathdom, specdom);
    208 }
    209 
    210 /*
    211  *  figure out action for this sender
    212  */
    213 int
    214 blocked(String *path)
    215 {
    216 	String *lpath;
    217 	int action;
    218 
    219 	if(debug)
    220 		fprint(2, "blocked(%s)\n", s_to_c(path));
    221 
    222 	/* if the sender's IP address is blessed, ignore sender email address */
    223 	if(trusted){
    224 		if(debug)
    225 			fprint(2, "\ttrusted => trusted\n");
    226 		return TRUSTED;
    227 	}
    228 
    229 	/* if sender's IP address is blocked, ignore sender email address */
    230 	if(hisaction != ACCEPT){
    231 		if(debug)
    232 			fprint(2, "\thisaction=%s => %s\n", actstr(hisaction), actstr(hisaction));
    233 		return hisaction;
    234 	}
    235 
    236 	/* convert to lower case */
    237 	lpath = s_copy(s_to_c(path));
    238 	s_tolower(lpath);
    239 
    240 	/* classify */
    241 	action = getaction(s_to_c(lpath), "account");
    242 	if(debug)
    243 		fprint(2, "\tgetaction account %s => %s\n", s_to_c(lpath), actstr(action));
    244 	s_free(lpath);
    245 	return action;
    246 }
    247 
    248 /*
    249  * get a canonicalized line: a string of null-terminated lower-case
    250  * tokens with a two null bytes at the end.
    251  */
    252 static char*
    253 getline(Biobuf *bp)
    254 {
    255 	char c, *cp, *p, *q;
    256 	int n;
    257 
    258 	static char *buf;
    259 	static int bufsize;
    260 
    261 	for(;;){
    262 		cp = Brdline(bp, '\n');
    263 		if(cp == 0)
    264 			return 0;
    265 		n = Blinelen(bp);
    266 		cp[n-1] = 0;
    267 		if(buf == 0 || bufsize < n+1){
    268 			bufsize += 512;
    269 			if(bufsize < n+1)
    270 				bufsize = n+1;
    271 			buf = realloc(buf, bufsize);
    272 			if(buf == 0)
    273 				break;
    274 		}
    275 		q = buf;
    276 		for (p = cp; *p; p++){
    277 			c = *p;
    278 			if(c == '\\' && p[1])	/* we don't allow \<newline> */
    279 				c = *++p;
    280 			else
    281 			if(c == '#')
    282 				break;
    283 			else
    284 			if(c == ' ' || c == '\t' || c == ',')
    285 				if(q == buf || q[-1] == 0)
    286 					continue;
    287 				else
    288 					c = 0;
    289 			*q++ = tolower(c);
    290 		}
    291 		if(q != buf){
    292 			if(q[-1])
    293 				*q++ = 0;
    294 			*q = 0;
    295 			break;
    296 		}
    297 	}
    298 	return buf;
    299 }
    300 
    301 static int
    302 isourdom(char *s)
    303 {
    304 	Link *l;
    305 
    306 	if(strchr(s, '.') == nil)
    307 		return 1;
    308 
    309 	for(l = ourdoms.first; l; l = l->next){
    310 		if(dommatch(s, s_to_c(l->p)) == 0)
    311 			return 1;
    312 	}
    313 	return 0;
    314 }
    315 
    316 int
    317 forwarding(String *path)
    318 {
    319 	char *cp, *s;
    320 	String *lpath;
    321 
    322 	if(debug)
    323 		fprint(2, "forwarding(%s)\n", s_to_c(path));
    324 
    325 	/* first check if they want loopback */
    326 	lpath = s_copy(s_to_c(s_restart(path)));
    327 	if(nci->rsys && *nci->rsys){
    328 		cp = s_to_c(lpath);
    329 		if(strncmp(cp, "[]!", 3) == 0){
    330 found:
    331 			s_append(path, "[");
    332 			s_append(path, nci->rsys);
    333 			s_append(path, "]!");
    334 			s_append(path, cp+3);
    335 			s_terminate(path);
    336 			s_free(lpath);
    337 			return 0;
    338 		}
    339 		cp = strchr(cp,'!');			/* skip our domain and check next */
    340 		if(cp++ && strncmp(cp, "[]!", 3) == 0)
    341 			goto found;
    342 	}
    343 
    344 	/* if mail is from a trusted IP addr, allow it to forward */
    345 	if(trusted) {
    346 		s_free(lpath);
    347 		return 0;
    348 	}
    349 
    350 	/* sender is untrusted; ensure receiver is in one of our domains */
    351 	for(cp = s_to_c(lpath); *cp; cp++)		/* convert receiver lc */
    352 		*cp = tolower(*cp);
    353 
    354 	for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){
    355 		*cp = 0;
    356 		if(!isourdom(s)){
    357 			s_free(lpath);
    358 			return 1;
    359 		}
    360 	}
    361 	s_free(lpath);
    362 	return 0;
    363 }
    364 
    365 int
    366 masquerade(String *path, char *him)
    367 {
    368 	char *cp, *s;
    369 	String *lpath;
    370 	int rv = 0;
    371 
    372 	if(debug)
    373 		fprint(2, "masquerade(%s)\n", s_to_c(path));
    374 
    375 	if(trusted)
    376 		return 0;
    377 	if(path == nil)
    378 		return 0;
    379 
    380 	lpath = s_copy(s_to_c(path));
    381 
    382 	/* sender is untrusted; ensure receiver is in one of our domains */
    383 	for(cp = s_to_c(lpath); *cp; cp++)		/* convert receiver lc */
    384 		*cp = tolower(*cp);
    385 	s = s_to_c(lpath);
    386 
    387 	/* scan first element of ! or last element of @ paths */
    388 	if((cp = strchr(s, '!')) != nil){
    389 		*cp = 0;
    390 		if(isourdom(s))
    391 			rv = 1;
    392 	} else if((cp = strrchr(s, '@')) != nil){
    393 		if(isourdom(cp+1))
    394 			rv = 1;
    395 	} else {
    396 		if(isourdom(him))
    397 			rv = 1;
    398 	}
    399 
    400 	s_free(lpath);
    401 	return rv;
    402 }
    403 
    404 /* this is a v4 only check */
    405 static int
    406 cidrcheck(char *cp)
    407 {
    408 	char *p;
    409 	ulong a, m;
    410 	uchar addr[IPv4addrlen];
    411 	uchar mask[IPv4addrlen];
    412 
    413 	if(v4peerip == 0)
    414 		return 0;
    415 
    416 	/* parse a list of CIDR addresses comparing each to the peer IP addr */
    417 	while(cp && *cp){
    418 		v4parsecidr(addr, mask, cp);
    419 		a = nhgetl(addr);
    420 		m = nhgetl(mask);
    421 		/*
    422 		 * if a mask isn't specified, we build a minimal mask
    423 		 * instead of using the default mask for that net.  in this
    424 		 * case we never allow a class A mask (0xff000000).
    425 		 */
    426 		if(strchr(cp, '/') == 0){
    427 			m = 0xff000000;
    428 			p = cp;
    429 			for(p = strchr(p, '.'); p && p[1]; p = strchr(p+1, '.'))
    430 					m = (m>>8)|0xff000000;
    431 
    432 			/* force at least a class B */
    433 			m |= 0xffff0000;
    434 		}
    435 		if((v4peerip&m) == a)
    436 			return 1;
    437 		cp += strlen(cp)+1;
    438 	}
    439 	return 0;
    440 }
    441 
    442 int
    443 isbadguy(void)
    444 {
    445 	Link *l;
    446 
    447 	/* check if this IP address is banned */
    448 	for(l = badguys.first; l; l = l->next)
    449 		if(cidrcheck(s_to_c(l->p)))
    450 			return 1;
    451 
    452 	return 0;
    453 }
    454 
    455 void
    456 addbadguy(char *p)
    457 {
    458 	listadd(&badguys, s_copy(p));
    459 };
    460 
    461 char*
    462 dumpfile(char *sender)
    463 {
    464 	int i, fd;
    465 	ulong h;
    466 	static char buf[512];
    467 	char *cp;
    468 
    469 	if (sflag == 1){
    470 		cp = ctime(time(0));
    471 		cp[7] = 0;
    472 		if(cp[8] == ' ')
    473 			sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
    474 		else
    475 			sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
    476 		cp = buf+strlen(buf);
    477 		if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0)
    478 			return "/dev/null";
    479 		h = 0;
    480 		while(*sender)
    481 			h = h*257 + *sender++;
    482 		for(i = 0; i < 50; i++){
    483 			h += lrand();
    484 			sprint(cp, "/%lud", h);
    485 			if(access(buf, 0) >= 0)
    486 				continue;
    487 			fd = syscreate(buf, ORDWR, 0666);
    488 			if(fd >= 0){
    489 				if(debug)
    490 					fprint(2, "saving in %s\n", buf);
    491 				close(fd);
    492 				return buf;
    493 			}
    494 		}
    495 	}
    496 	return "/dev/null";
    497 }
    498 
    499 char *validator = "#9/mail/lib/validateaddress";
    500 
    501 int
    502 recipok(char *user)
    503 {
    504 	char *cp, *p, c;
    505 	char buf[512];
    506 	int n;
    507 	Biobuf *bp;
    508 	int pid;
    509 	Waitmsg *w;
    510 	static int beenhere;
    511 
    512 	if(!beenhere){
    513 		beenhere++;
    514 		validator = unsharp(validator);
    515 	}
    516 	if(shellchars(user)){
    517 		syslog(0, "smtpd", "shellchars in user name");
    518 		return 0;
    519 	}
    520 
    521 	if(access(validator, AEXEC) == 0)
    522 	switch(pid = fork()) {
    523 	case -1:
    524 		break;
    525 	case 0:
    526 		execl(validator, "validateaddress", user, nil);
    527 		exits(0);
    528 	default:
    529 		while(w = wait()) {
    530 			if(w->pid != pid)
    531 				continue;
    532 			if(w->msg[0] != 0){
    533 				syslog(0, "smtpd", "validateaddress %s: %s", user, w->msg);
    534 				return 0;
    535 			}
    536 			break;
    537 		}
    538 	}
    539 
    540 	snprint(buf, sizeof(buf), "%s/names.blocked", UPASLIB);
    541 	bp = sysopen(buf, "r", 0);
    542 	if(bp == 0)
    543 		return 1;
    544 	for(;;){
    545 		cp = Brdline(bp, '\n');
    546 		if(cp == 0)
    547 			break;
    548 		n = Blinelen(bp);
    549 		cp[n-1] = 0;
    550 
    551 		while(*cp == ' ' || *cp == '\t')
    552 			cp++;
    553 		for(p = cp; c = *p; p++){
    554 			if(c == '#')
    555 				break;
    556 			if(c == ' ' || c == '\t')
    557 				break;
    558 		}
    559 		if(p > cp){
    560 			*p = 0;
    561 			if(cistrcmp(user, cp) == 0){
    562 				syslog(0, "smtpd", "names.blocked blocks %s", user);
    563 				Bterm(bp);
    564 				return 0;
    565 			}
    566 		}
    567 	}
    568 	Bterm(bp);
    569 	return 1;
    570 }
    571 
    572 /*
    573  *  a user can opt out of spam filtering by creating
    574  *  a file in his mail directory named 'nospamfiltering'.
    575  */
    576 int
    577 optoutofspamfilter(char *addr)
    578 {
    579 	char *p, *f;
    580 	int rv;
    581 
    582 	p = strchr(addr, '!');
    583 	if(p)
    584 		p++;
    585 	else
    586 		p = addr;
    587 
    588 
    589 	rv = 0;
    590 	f = smprint("%s/mail/box/%s/nospamfiltering", get9root(), p);
    591 	if(f != nil){
    592 		rv = access(f, 0)==0;
    593 		free(f);
    594 	}
    595 
    596 	return rv;
    597 }