qmail-smtpd.c (13654B)
1 #include "sig.h" 2 #include "readwrite.h" 3 #include "stralloc.h" 4 #include "substdio.h" 5 #include "alloc.h" 6 #include "auto_qmail.h" 7 #include "control.h" 8 #include "datetime.h" 9 #include "received.h" 10 #include "constmap.h" 11 #include "error.h" 12 #include "ipme.h" 13 #include "ip.h" 14 #include "qmail.h" 15 #include "str.h" 16 #include "fmt.h" 17 #include "scan.h" 18 #include "byte.h" 19 #include "case.h" 20 #include "env.h" 21 #include "now.h" 22 #include "exit.h" 23 #include "rcpthosts.h" 24 #include "realrcptto.h" 25 #include "timeoutread.h" 26 #include "timeoutwrite.h" 27 #include "commands.h" 28 #include "dns.h" 29 30 #define MAXHOPS 100 31 unsigned int databytes = 0; 32 int timeout = 1200; 33 34 GEN_SAFE_TIMEOUTWRITE(safewrite,timeout,fd,_exit(1)) 35 36 char ssoutbuf[512]; 37 substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof(ssoutbuf)); 38 39 void flush() { substdio_flush(&ssout); } 40 void out(s) char *s; { substdio_puts(&ssout,s); } 41 42 void die_read() { _exit(1); } 43 void die_alarm() { out("451 4.4.2 Timeout\r\n"); flush(); _exit(1); } 44 void die_nomem() { out("421 4.3.0 Out of memory\r\n"); flush(); _exit(1); } 45 void die_control() { out("421 4.3.0 Unable to read controls\r\n"); flush(); _exit(1); } 46 void die_ipme() { out("421 4.3.0 Unable to figure out my IP addresses\r\n"); flush(); _exit(1); } 47 void die_ip6me() { out("421 4.3.0 Unable to figure out my IPv6 addresses\r\n"); flush(); _exit(1); } 48 void straynewline() { out("451 You sent a stray newline. See https://cr.yp.to/docs/smtplf.html.\r\n"); flush(); _exit(1); } 49 50 void err_bmf() { out("553 5.7.1 Sorry, your envelope sender is in my badmailfrom list.\r\n"); } 51 void err_nogateway() { out("553 5.7.1 Sorry, that domain isn't in my list of allowed rcpthosts\r\n"); } 52 void err_unimpl(char *arg) { out("502 5.5.1 Unimplemented\r\n"); } 53 void err_syntax() { out("555 5.5.4 Syntax error\r\n"); } 54 void err_wantmail() { out("503 5.5.1 You must execute MAIL first\r\n"); } 55 void err_wantrcpt() { out("503 5.5.1 You must execute RCPT first\r\n"); } 56 void die_cdb() { out("421 4.3.0 Unable to read cdb user database\r\n"); flush(); _exit(1); } 57 void die_sys() { out("421 4.3.0 Unable to read system user database\r\n"); flush(); _exit(1); } 58 void err_noop(char *arg) { out("250 As requested, no operation performed.\r\n"); } 59 void err_vrfy(char *arg) { out("252 Send something and we'll give it a shot.\r\n"); } 60 void err_qqt() { out("451 4.3.0 qqt failure\r\n"); } 61 void die_dnsbl(char *arg) 62 { 63 out("421 your ip is currently blacklisted, try to auth first ("); out(arg); out(")\r\n"); 64 flush(); 65 _exit(1); 66 } 67 68 stralloc greeting = {0}; 69 70 void smtp_greet(code) char *code; 71 { 72 substdio_puts(&ssout,code); 73 substdio_put(&ssout,greeting.s,greeting.len); 74 } 75 void smtp_help(char *arg) 76 { 77 out("214 NightmareMail home page: <https://github.com./SystemLeningrad/mxf> MXF is based on notqmail. notqmail home page: <https://notqmail.org>\r\n"); 78 } 79 void smtp_quit(char *arg) 80 { 81 smtp_greet("221 "); out("\r\n"); flush(); _exit(0); 82 } 83 84 char *remoteip; 85 char *remotehost; 86 char *remoteinfo; 87 char *local; 88 char *relayclient; 89 char *dnsblskip; 90 91 stralloc helohost = {0}; 92 char *fakehelo; /* pointer into helohost, or 0 */ 93 94 void dohelo(char *arg) { 95 if (!stralloc_copys(&helohost,arg)) die_nomem(); 96 if (!stralloc_0(&helohost)) die_nomem(); 97 fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0; 98 } 99 100 int liphostok = 0; 101 stralloc liphost = {0}; 102 int bmfok = 0; 103 stralloc bmf = {0}; 104 struct constmap mapbmf; 105 106 void setup() 107 { 108 char *x; 109 unsigned long u; 110 111 if (control_init() == -1) die_control(); 112 if (control_rldef(&greeting,"control/smtpgreeting",1,NULL) != 1) 113 die_control(); 114 liphostok = control_rldef(&liphost,"control/localiphost",1,NULL); 115 if (liphostok == -1) die_control(); 116 if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control(); 117 if (timeout <= 0) timeout = 1; 118 119 if (rcpthosts_init() == -1) die_control(); 120 121 bmfok = control_readfile(&bmf,"control/badmailfrom",0); 122 if (bmfok == -1) die_control(); 123 if (bmfok) 124 if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem(); 125 126 // manually merged from prj's realrcptto patch 127 realrcptto_init(); 128 129 if (control_readint(&databytes,"control/databytes") == -1) die_control(); 130 x = env_get("DATABYTES"); 131 if (x) { scan_ulong(x,&u); databytes = u; } 132 if (!(databytes + 1)) --databytes; 133 134 remoteip = env_get("TCPREMOTEIP"); 135 if (!remoteip) remoteip = "unknown"; 136 local = env_get("TCPLOCALHOST"); 137 if (!local) local = env_get("TCPLOCALIP"); 138 if (!local) local = "unknown"; 139 remotehost = env_get("TCPREMOTEHOST"); 140 if (!remotehost) remotehost = "unknown"; 141 remoteinfo = env_get("TCPREMOTEINFO"); 142 relayclient = env_get("RELAYCLIENT"); 143 dnsblskip = env_get("DNSBLSKIP"); 144 dohelo(remotehost); 145 } 146 147 extern void realrcptto_init(); 148 extern void realrcptto_start(); 149 extern int realrcptto(); 150 extern int realrcptto_deny(); 151 152 stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */ 153 154 int addrparse(char *arg) 155 { 156 int i; 157 char ch; 158 char terminator; 159 struct ip_address ip; 160 int flagesc; 161 int flagquoted; 162 163 terminator = '>'; 164 i = str_chr(arg,'<'); 165 if (arg[i]) 166 arg += i + 1; 167 else { /* partner should go read rfc 821 */ 168 terminator = ' '; 169 arg += str_chr(arg,':'); 170 if (*arg == ':') ++arg; 171 while (*arg == ' ') ++arg; 172 } 173 174 /* strip source route */ 175 if (*arg == '@') while (*arg) if (*arg++ == ':') break; 176 177 if (!stralloc_copys(&addr,"")) die_nomem(); 178 flagesc = 0; 179 flagquoted = 0; 180 for (i = 0;(ch = arg[i]);++i) { /* copy arg to addr, stripping quotes */ 181 if (flagesc) { 182 if (!stralloc_append(&addr,&ch)) die_nomem(); 183 flagesc = 0; 184 } 185 else { 186 if (!flagquoted && (ch == terminator)) break; 187 switch(ch) { 188 case '\\': flagesc = 1; break; 189 case '"': flagquoted = !flagquoted; break; 190 default: if (!stralloc_append(&addr,&ch)) die_nomem(); 191 } 192 } 193 } 194 /* could check for termination failure here, but why bother? */ 195 if (!stralloc_append(&addr,"")) die_nomem(); 196 197 if (liphostok) { 198 i = byte_rchr(addr.s,addr.len,'@'); 199 if (i < addr.len) /* if not, partner should go read rfc 821 */ 200 if (addr.s[i + 1] == '[') 201 if (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,&ip)]) 202 if (ipme_is(&ip)) { 203 addr.len = i + 1; 204 if (!stralloc_cat(&addr,&liphost)) die_nomem(); 205 if (!stralloc_0(&addr)) die_nomem(); 206 } 207 } 208 209 if (addr.len > 900) return 0; 210 return 1; 211 } 212 213 int bmfcheck() 214 { 215 int j; 216 if (!bmfok) return 0; 217 if (constmap(&mapbmf,addr.s,addr.len - 1)) return 1; 218 j = byte_rchr(addr.s,addr.len,'@'); 219 if (j < addr.len) 220 if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1; 221 return 0; 222 } 223 224 int addrallowed() 225 { 226 int r; 227 r = rcpthosts(addr.s,str_len(addr.s)); 228 if (r == -1) die_control(); 229 return r; 230 } 231 232 int flagdnsbl = 0; 233 stralloc dnsblhost = {0}; 234 235 int dnsblcheck() 236 { 237 char *ch; 238 static stralloc dnsblbyte = {0}; 239 static stralloc dnsblrev = {0}; 240 static ipalloc dnsblip = {0}; 241 static ip6alloc dnsblip6 = {0}; 242 static stralloc dnsbllist = {0}; 243 244 ch = remoteip; 245 if(control_readfile(&dnsbllist,"control/dnsbllist",0) != 1) return 0; 246 247 if (!stralloc_copys(&dnsblrev,"")) return 0; 248 for (;;) { 249 if (!stralloc_copys(&dnsblbyte,"")) return 0; 250 while (ch[0] && (ch[0] != '.')) { 251 if (!stralloc_append(&dnsblbyte,ch)) return 0; 252 ch++; 253 } 254 if (!stralloc_append(&dnsblbyte,".")) return 0; 255 if (!stralloc_cat(&dnsblbyte,&dnsblrev)) return 0; 256 if (!stralloc_copy(&dnsblrev,&dnsblbyte)) return 0; 257 258 if (!ch[0]) break; 259 ch++; 260 } 261 262 flagdnsbl = 1; 263 ch = dnsbllist.s; 264 while (ch < (dnsbllist.s + dnsbllist.len)) { 265 if (!stralloc_copy(&dnsblhost,&dnsblrev)) return 0; 266 if (!stralloc_cats(&dnsblhost,ch)) return 0; 267 if (!stralloc_0(&dnsblhost)) return 0; 268 269 if (!dns_ip(&dnsblip,&dnsblhost)) return 1; 270 while (*ch++); 271 } 272 273 return 0; 274 } 275 276 int seenmail = 0; 277 int flagbarf; /* defined if seenmail */ 278 stralloc mailfrom = {0}; 279 stralloc rcptto = {0}; 280 281 void smtp_helo(char *arg) 282 { 283 smtp_greet("250 "); out("\r\n"); 284 seenmail = 0; dohelo(arg); 285 } 286 void smtp_ehlo(char *arg) 287 { 288 smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250-ENHANCEDSTATUSCODES\r\n"); 289 // TODO: add startTLS cutover support 290 out("250 8BITMIME\r\n"); 291 seenmail = 0; dohelo(arg); 292 } 293 void smtp_rset(char *arg) 294 { 295 seenmail = 0; 296 out("250 flushed\r\n"); 297 } 298 void smtp_mail(char *arg) 299 { 300 if (!addrparse(arg)) { err_syntax(); return; } 301 flagbarf = bmfcheck(); 302 seenmail = 1; 303 if (!stralloc_copys(&rcptto,"")) die_nomem(); 304 if (!stralloc_copys(&mailfrom,addr.s)) die_nomem(); 305 if (!stralloc_0(&mailfrom)) die_nomem(); 306 realrcptto_start(); 307 out("250 ok\r\n"); 308 } 309 void smtp_rcpt(char *arg) { 310 if (!seenmail) { err_wantmail(); return; } 311 if (!addrparse(arg)) { err_syntax(); return; } 312 if (flagbarf) { err_bmf(); return; } 313 if (relayclient) { 314 --addr.len; 315 if (!stralloc_cats(&addr,relayclient)) die_nomem(); 316 if (!stralloc_0(&addr)) die_nomem(); 317 } 318 else 319 if (!addrallowed()) { err_nogateway(); return; } 320 if (!realrcptto(addr.s)) { 321 out("550 5.1.1 That user has elected not to receive emails.\r\n"); 322 return; 323 } 324 if (!(relayclient || dnsblskip || flagdnsbl)) 325 if (dnsblcheck()) die_dnsbl(dnsblhost.s); 326 if (!stralloc_cats(&rcptto,"T")) die_nomem(); 327 if (!stralloc_cats(&rcptto,addr.s)) die_nomem(); 328 if (!stralloc_0(&rcptto)) die_nomem(); 329 out("250 ok\r\n"); 330 } 331 332 ssize_t saferead(int fd, void *buf, size_t len) 333 { 334 ssize_t r; 335 flush(); 336 r = timeoutread(timeout,fd,buf,len); 337 if (r == -1) if (errno == error_timeout) die_alarm(); 338 if (r == 0 || r == -1) die_read(); 339 return r; 340 } 341 342 char ssinbuf[1024]; 343 substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof(ssinbuf)); 344 345 struct qmail qqt; 346 unsigned int bytestooverflow = 0; 347 348 void put(ch) 349 char *ch; 350 { 351 if (bytestooverflow) 352 if (!--bytestooverflow) 353 qmail_fail(&qqt); 354 qmail_put(&qqt,ch,1); 355 } 356 357 void blast(hops) 358 int *hops; 359 { 360 char ch; 361 int state; 362 int flaginheader; 363 int pos; /* number of bytes since most recent \n, if fih */ 364 int flagmaybex; /* 1 if this line might match RECEIVED, if fih */ 365 int flagmaybey; /* 1 if this line might match \r\n, if fih */ 366 int flagmaybez; /* 1 if this line might match DELIVERED, if fih */ 367 368 state = 1; 369 *hops = 0; 370 flaginheader = 1; 371 pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; 372 for (;;) { 373 /* This isn't very optimized -- Amelia B */ 374 substdio_get(&ssin,&ch,1); 375 if (flaginheader) { 376 if (pos < 9) { 377 if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0; 378 if (flagmaybez) if (pos == 8) ++*hops; 379 if (pos < 8) 380 if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0; 381 if (flagmaybex) if (pos == 7) ++*hops; 382 if (pos < 2) if (ch != "\r\n"[pos]) flagmaybey = 0; 383 if (flagmaybey) if (pos == 1) flaginheader = 0; 384 ++pos; 385 } 386 if (ch == '\n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; } 387 } 388 switch(state) { 389 case 0: 390 if (ch == '\n') straynewline(); 391 if (ch == '\r') { state = 4; continue; } 392 break; 393 case 1: /* \r\n */ 394 if (ch == '\n') straynewline(); 395 if (ch == '.') { state = 2; continue; } 396 if (ch == '\r') { state = 4; continue; } 397 state = 0; 398 break; 399 case 2: /* \r\n + . */ 400 if (ch == '\n') straynewline(); 401 if (ch == '\r') { state = 3; continue; } 402 state = 0; 403 break; 404 case 3: /* \r\n + .\r */ 405 if (ch == '\n') return; 406 put("."); 407 put("\r"); 408 if (ch == '\r') { state = 4; continue; } 409 state = 0; 410 break; 411 case 4: /* + \r */ 412 if (ch == '\n') { state = 1; break; } 413 if (ch != '\r') { put("\r"); state = 0; } 414 } 415 put(&ch); 416 } 417 } 418 419 char accept_buf[FMT_ULONG]; 420 void acceptmessage(qp) unsigned long qp; 421 { 422 datetime_sec when; 423 when = now(); 424 out("250 ok "); 425 accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0; 426 out(accept_buf); 427 out(" qp "); 428 accept_buf[fmt_ulong(accept_buf,qp)] = 0; 429 out(accept_buf); 430 out("\r\n"); 431 } 432 433 void smtp_data(char *arg) { 434 int hops; 435 unsigned long qp; 436 char *qqx; 437 438 if (!seenmail) { err_wantmail(); return; } 439 if (!rcptto.len) { err_wantrcpt(); return; } 440 if (realrcptto_deny()) { out("554 5.1.1 sorry, no mailbox here by that name.\r\n"); return; } 441 seenmail = 0; 442 if (databytes) bytestooverflow = databytes + 1; 443 if (qmail_open(&qqt) == -1) { err_qqt(); return; } 444 qp = qmail_qp(&qqt); 445 out("354 go ahead\r\n"); 446 447 received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo); 448 blast(&hops); 449 hops = (hops >= MAXHOPS); 450 if (hops) qmail_fail(&qqt); 451 qmail_from(&qqt,mailfrom.s); 452 qmail_put(&qqt,rcptto.s,rcptto.len); 453 454 qqx = qmail_close(&qqt); 455 if (!*qqx) { acceptmessage(qp); return; } 456 if (hops) { out("554 5.4.6 too many hops, this message is looping\r\n"); return; } 457 if (databytes) if (!bytestooverflow) { out("552 5.3.4 sorry, that message size exceeds my databytes limit\r\n"); return; } 458 if (*qqx == 'D') out("554 "); else out("451 "); 459 out(qqx + 1); 460 out("\r\n"); 461 } 462 463 struct commands smtpcommands[] = { 464 { "rcpt", smtp_rcpt, 0 } 465 , { "mail", smtp_mail, 0 } 466 , { "data", smtp_data, flush } 467 , { "quit", smtp_quit, flush } 468 , { "helo", smtp_helo, flush } 469 , { "ehlo", smtp_ehlo, flush } 470 , { "rset", smtp_rset, 0 } 471 , { "help", smtp_help, flush } 472 , { "noop", err_noop, flush } 473 , { "vrfy", err_vrfy, flush } 474 , { 0, err_unimpl, flush } 475 } ; 476 477 int main(void) 478 { 479 sig_pipeignore(); 480 if (chdir(auto_qmail) == -1) die_control(); 481 setup(); 482 if (ipme_init() != 1) die_ipme(); 483 if (ip6me_init() != 1) die_ip6me(); 484 smtp_greet("220 "); 485 out(" ESMTP\r\n"); 486 if (commands(&ssin,&smtpcommands) == 0) die_read(); 487 die_nomem(); 488 }