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 }