xref: /plan9/sys/src/cmd/upas/smtp/spam.c (revision c6569576083e48cef148efbb162a33bacec4ce98)
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
findkey(char * val,Keyword * p)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*
actstr(int a)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
getaction(char * s,char * type)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, "/mail/ratify/%s/%s/%s", k->name, type, s);
88 		if(access(buf,0) >= 0)
89 			return k->code;
90 	}
91 	return ACCEPT;
92 }
93 
94 int
istrusted(char * s)95 istrusted(char *s)
96 {
97 	char buf[1024];
98 
99 	if(s == nil || *s == 0)
100 		return 0;
101 
102 	snprint(buf, sizeof buf, "/mail/ratify/trusted/%s", s);
103 	return access(buf,0) >= 0;
104 }
105 
106 void
getconf(void)107 getconf(void)
108 {
109 	Biobuf *bp;
110 	char *cp, *p;
111 	String *s;
112 	char buf[512];
113 	uchar addr[4];
114 
115 	v4parseip(addr, nci->rsys);
116 	v4peerip = nhgetl(addr);
117 
118 	trusted = istrusted(nci->rsys);
119 	hisaction = getaction(nci->rsys, "ip");
120 	if(debug){
121 		fprint(2, "istrusted(%s)=%d\n", nci->rsys, trusted);
122 		fprint(2, "getaction(%s, ip)=%s\n", nci->rsys, actstr(hisaction));
123 	}
124 	snprint(buf, sizeof(buf), "%s/smtpd.conf", UPASLIB);
125 	bp = sysopen(buf, "r", 0);
126 	if(bp == 0)
127 		return;
128 
129 	for(;;){
130 		cp = getline(bp);
131 		if(cp == 0)
132 			break;
133 		p = cp+strlen(cp)+1;
134 		switch(findkey(cp, options)){
135 		case NORELAY:
136 			if(fflag == 0 && strcmp(p, "on") == 0)
137 				fflag++;
138 			break;
139 		case DNSVERIFY:
140 			if(rflag == 0 && strcmp(p, "on") == 0)
141 				rflag++;
142 			break;
143 		case SAVEBLOCK:
144 			if(sflag == 0 && strcmp(p, "on") == 0)
145 				sflag++;
146 			break;
147 		case DOMNAME:
148 			if(dom == 0)
149 				dom = strdup(p);
150 			break;
151 		case OURNETS:
152 			if (trusted == 0)
153 				trusted = cidrcheck(p);
154 			break;
155 		case OURDOMS:
156 			while(*p){
157 				s = s_new();
158 				s_append(s, p);
159 				listadd(&ourdoms, s);
160 				p += strlen(p)+1;
161 			}
162 			break;
163 		default:
164 			break;
165 		}
166 	}
167 	sysclose(bp);
168 }
169 
170 /*
171  *	match a user name.  the only meta-char is '*' which matches all
172  *	characters.  we only allow it as "*", which matches anything or
173  *	an * at the end of the name (e.g., "username*") which matches
174  *	trailing characters.
175  */
176 static int
usermatch(char * pathuser,char * specuser)177 usermatch(char *pathuser, char *specuser)
178 {
179 	int n;
180 
181 	n = strlen(specuser)-1;
182 	if(specuser[n] == '*'){
183 		if(n == 0)		/* match everything */
184 			return 0;
185 		return strncmp(pathuser, specuser, n);
186 	}
187 	return strcmp(pathuser, specuser);
188 }
189 
190 static int
dommatch(char * pathdom,char * specdom)191 dommatch(char *pathdom, char *specdom)
192 {
193 	int n;
194 
195 	if (*specdom == '*'){
196 		if (specdom[1] == '.' && specdom[2]){
197 			specdom += 2;
198 			n = strlen(pathdom)-strlen(specdom);
199 			if(n == 0 || (n > 0 && pathdom[n-1] == '.'))
200 				return strcmp(pathdom+n, specdom);
201 			return n;
202 		}
203 	}
204 	return strcmp(pathdom, specdom);
205 }
206 
207 /*
208  *  figure out action for this sender
209  */
210 int
blocked(String * path)211 blocked(String *path)
212 {
213 	String *lpath;
214 	int action;
215 
216 	if(debug)
217 		fprint(2, "blocked(%s)\n", s_to_c(path));
218 
219 	/* if the sender's IP address is blessed, ignore sender email address */
220 	if(trusted){
221 		if(debug)
222 			fprint(2, "\ttrusted => trusted\n");
223 		return TRUSTED;
224 	}
225 
226 	/* if sender's IP address is blocked, ignore sender email address */
227 	if(hisaction != ACCEPT){
228 		if(debug)
229 			fprint(2, "\thisaction=%s => %s\n", actstr(hisaction), actstr(hisaction));
230 		return hisaction;
231 	}
232 
233 	/* convert to lower case */
234 	lpath = s_copy(s_to_c(path));
235 	s_tolower(lpath);
236 
237 	/* classify */
238 	action = getaction(s_to_c(lpath), "account");
239 	if(debug)
240 		fprint(2, "\tgetaction account %s => %s\n", s_to_c(lpath), actstr(action));
241 	s_free(lpath);
242 	return action;
243 }
244 
245 /*
246  * get a canonicalized line: a string of null-terminated lower-case
247  * tokens with a two null bytes at the end.
248  */
249 static char*
getline(Biobuf * bp)250 getline(Biobuf *bp)
251 {
252 	char c, *cp, *p, *q;
253 	int n;
254 
255 	static char *buf;
256 	static int bufsize;
257 
258 	for(;;){
259 		cp = Brdline(bp, '\n');
260 		if(cp == 0)
261 			return 0;
262 		n = Blinelen(bp);
263 		cp[n-1] = 0;
264 		if(buf == 0 || bufsize < n+1){
265 			bufsize += 512;
266 			if(bufsize < n+1)
267 				bufsize = n+1;
268 			buf = realloc(buf, bufsize);
269 			if(buf == 0)
270 				break;
271 		}
272 		q = buf;
273 		for (p = cp; *p; p++){
274 			c = *p;
275 			if(c == '\\' && p[1])	/* we don't allow \<newline> */
276 				c = *++p;
277 			else
278 			if(c == '#')
279 				break;
280 			else
281 			if(c == ' ' || c == '\t' || c == ',')
282 				if(q == buf || q[-1] == 0)
283 					continue;
284 				else
285 					c = 0;
286 			*q++ = tolower(c);
287 		}
288 		if(q != buf){
289 			if(q[-1])
290 				*q++ = 0;
291 			*q = 0;
292 			break;
293 		}
294 	}
295 	return buf;
296 }
297 
298 static int
isourdom(char * s)299 isourdom(char *s)
300 {
301 	Link *l;
302 
303 	if(strchr(s, '.') == nil)
304 		return 1;
305 
306 	for(l = ourdoms.first; l; l = l->next){
307 		if(dommatch(s, s_to_c(l->p)) == 0)
308 			return 1;
309 	}
310 	return 0;
311 }
312 
313 int
forwarding(String * path)314 forwarding(String *path)
315 {
316 	char *cp, *s;
317 	String *lpath;
318 
319 	if(debug)
320 		fprint(2, "forwarding(%s)\n", s_to_c(path));
321 
322 	/* first check if they want loopback */
323 	lpath = s_copy(s_to_c(s_restart(path)));
324 	if(nci->rsys && *nci->rsys){
325 		cp = s_to_c(lpath);
326 		if(strncmp(cp, "[]!", 3) == 0){
327 found:
328 			s_append(path, "[");
329 			s_append(path, nci->rsys);
330 			s_append(path, "]!");
331 			s_append(path, cp+3);
332 			s_terminate(path);
333 			s_free(lpath);
334 			return 0;
335 		}
336 		cp = strchr(cp,'!');			/* skip our domain and check next */
337 		if(cp++ && strncmp(cp, "[]!", 3) == 0)
338 			goto found;
339 	}
340 
341 	/* if mail is from a trusted IP addr, allow it to forward */
342 	if(trusted) {
343 		s_free(lpath);
344 		return 0;
345 	}
346 
347 	/* sender is untrusted; ensure receiver is in one of our domains */
348 	for(cp = s_to_c(lpath); *cp; cp++)		/* convert receiver lc */
349 		*cp = tolower(*cp);
350 
351 	for(s = s_to_c(lpath); cp = strchr(s, '!'); s = cp+1){
352 		*cp = 0;
353 		if(!isourdom(s)){
354 			s_free(lpath);
355 			return 1;
356 		}
357 	}
358 	s_free(lpath);
359 	return 0;
360 }
361 
362 int
masquerade(String * path,char * him)363 masquerade(String *path, char *him)
364 {
365 	char *cp, *s;
366 	String *lpath;
367 	int rv = 0;
368 
369 	if(debug)
370 		fprint(2, "masquerade(%s) ", s_to_c(path));
371 
372 	if(trusted || path == nil) {
373 		if(debug)
374 			fprint(2, "0\n");
375 		return 0;
376 	}
377 
378 	lpath = s_copy(s_to_c(path));
379 
380 	/* sender is untrusted; ensure receiver is in one of our domains */
381 	for(cp = s_to_c(lpath); *cp; cp++)		/* convert receiver lc */
382 		*cp = tolower(*cp);
383 	s = s_to_c(lpath);
384 
385 	/* scan first element of ! or last element of @ paths */
386 	if((cp = strchr(s, '!')) != nil){
387 		*cp = 0;
388 		if(isourdom(s))
389 			rv = 1;
390 	} else if((cp = strrchr(s, '@')) != nil){
391 		if(isourdom(cp+1))
392 			rv = 1;
393 	} else {
394 		if(isourdom(him))
395 			rv = 1;
396 	}
397 
398 	s_free(lpath);
399 	if (debug)
400 		fprint(2, "%d\n", rv);
401 	return rv;
402 }
403 
404 /* this is a v4 only check */
405 static int
cidrcheck(char * cp)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
isbadguy(void)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
addbadguy(char * p)456 addbadguy(char *p)
457 {
458 	listadd(&badguys, s_copy(p));
459 };
460 
461 char*
dumpfile(char * sender)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 = "/mail/lib/validateaddress";
500 
501 int
recipok(char * user)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 
511 	if(shellchars(user)){
512 		syslog(0, "smtpd", "shellchars in user name");
513 		return 0;
514 	}
515 
516 	if(access(validator, AEXEC) == 0)
517 	switch(pid = fork()) {
518 	case -1:
519 		break;
520 	case 0:
521 		execl(validator, "validateaddress", user, nil);
522 		exits(0);
523 	default:
524 		while(w = wait()) {
525 			if(w->pid != pid)
526 				continue;
527 			if(w->msg[0] != 0){
528 				/*
529 				syslog(0, "smtpd", "validateaddress %s: %s", user, w->msg);
530 				*/
531 				return 0;
532 			}
533 			break;
534 		}
535 	}
536 
537 	snprint(buf, sizeof(buf), "%s/names.blocked", UPASLIB);
538 	bp = sysopen(buf, "r", 0);
539 	if(bp == 0)
540 		return 1;
541 	for(;;){
542 		cp = Brdline(bp, '\n');
543 		if(cp == 0)
544 			break;
545 		n = Blinelen(bp);
546 		cp[n-1] = 0;
547 
548 		while(*cp == ' ' || *cp == '\t')
549 			cp++;
550 		for(p = cp; c = *p; p++){
551 			if(c == '#')
552 				break;
553 			if(c == ' ' || c == '\t')
554 				break;
555 		}
556 		if(p > cp){
557 			*p = 0;
558 			if(cistrcmp(user, cp) == 0){
559 				syslog(0, "smtpd", "names.blocked blocks %s", user);
560 				Bterm(bp);
561 				return 0;
562 			}
563 		}
564 	}
565 	Bterm(bp);
566 	return 1;
567 }
568 
569 /*
570  *  a user can opt out of spam filtering by creating
571  *  a file in his mail directory named 'nospamfiltering'.
572  */
573 int
optoutofspamfilter(char * addr)574 optoutofspamfilter(char *addr)
575 {
576 	char *p, *f;
577 	int rv;
578 
579 	p = strchr(addr, '!');
580 	if(p)
581 		p++;
582 	else
583 		p = addr;
584 
585 
586 	rv = 0;
587 	f = smprint("/mail/box/%s/nospamfiltering", p);
588 	if(f != nil){
589 		rv = access(f, 0)==0;
590 		free(f);
591 	}
592 
593 	return rv;
594 }
595