nmail-remote-smtpc.c (15104B)
1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 #include "sig.h" 6 #include "stralloc.h" 7 #include "substdio.h" 8 #include "bufmissing.h" /* to reset ringbuffers */ 9 #include "subfd.h" 10 #include "scan.h" 11 #include "case.h" 12 #include "error.h" 13 #include "auto_qmail.h" 14 #include "control.h" 15 #include "dns.h" 16 #include "quote.h" 17 #include "ip.h" 18 #include "ipalloc.h" 19 #include "ipme.h" 20 #include "gen_alloc.h" 21 #include "gen_allocdefs.h" /* should use typeallocs de suitcase */ 22 #include "str.h" 23 #include "now.h" 24 #include "exit.h" 25 #include "constmap.h" 26 #include "noreturn.h" 27 #include "tcpto.h" 28 #include "readwrite.h" 29 #include "timeoutconn.h" 30 #include "timeoutread.h" 31 #include "timeoutwrite.h" 32 33 #define HUGESMTPTEXT 5000 34 /* ? Sort errors into netw and application */ 35 #define PORT_SMTP 25 /* silly rabbit, /etc/services is for users */ 36 unsigned long port = PORT_SMTP; 37 38 GEN_ALLOC_typedef(saa, stralloc, sa, len, a) 39 GEN_ALLOC_readyplus(saa, stralloc, sa, len, a, 10, saa_readyplus) 40 static stralloc sauninit = STRALLOC_ZERO; 41 42 stralloc helohost = {0}; 43 stralloc routes = {0}; 44 struct constmap maproutes; 45 stralloc host = {0}; 46 stralloc sender = {0}; 47 int pedanticsmtp = 1; 48 49 saa reciplist = {0}; 50 /* These defines make it easier to sort for future wreckage into app and net subprograms. */ 51 #define _err_app 52 #define _err_net 53 #define _call_app 54 #define _call_net 55 56 /* XXX: should be an ip46full-like object 57 * XXX: the future is here! ip_address IS an ip46full-like object! */ 58 _call_app struct ip_address partner; 59 60 void out(char *s) { 61 if (substdio_puts(subfdoutsmall, s) == -1) 62 _exit(0); 63 } 64 void zero() { 65 if (substdio_put(subfdoutsmall, "\0", 1) == -1) 66 _exit(0); 67 } 68 void _noreturn_ zerodie() { 69 zero(); 70 substdio_flush(subfdoutsmall); 71 _exit(0); 72 } 73 void 74 outsafe(stralloc * sa) 75 { 76 int i; 77 char ch; 78 for (i = 0; i < sa->len; ++i) { 79 ch = sa->s[i]; 80 if (ch < 33) 81 ch = '?'; 82 if (ch > 126) 83 ch = '?'; 84 if (substdio_put(subfdoutsmall, &ch, 1) == -1) 85 _exit(0); 86 } 87 } 88 89 void _noreturn_ _err_app temp_nomem() { 90 out("Z4.3.0 Out of memory.\n"); 91 zerodie(); 92 } 93 void _noreturn_ _err_net _err_app temp_oserr() 94 { 95 out("Z4.3.0 System resources temporarily unavailable.\n"); 96 zerodie(); 97 } 98 void _noreturn_ _err_net temp_noconn() 99 { 100 out("Z4.4.1 Sorry, I wasn't able to establish an SMTP connection.\n"); 101 zerodie(); 102 } 103 void _noreturn_ _err_app temp_read() { 104 out("Z4.3.0 Unable to read message.\n"); 105 zerodie(); 106 } 107 void _noreturn_ _err_net temp_dnscanon() 108 { 109 out("Z4.4.3 CNAME lookup failed temporarily.\n"); 110 zerodie(); 111 } 112 void _noreturn_ _err_net temp_dns() 113 { 114 out("Z4.1.2 Sorry, I couldn't find any host by that name.\n"); 115 zerodie(); 116 } 117 void _noreturn_ _err_app temp_chdir() 118 { 119 out("Z4.3.0 Unable to switch to home directory.\n"); 120 zerodie(); 121 } 122 void _noreturn_ _err_app temp_control() 123 { 124 out("Z4.3.0 Unable to read control files.\n"); 125 zerodie(); 126 } 127 void _noreturn_ _err_app perm_partialline() 128 { 129 out("D5.6.2 SMTP cannot transfer messages with partial final lines.\n"); 130 zerodie(); 131 } 132 void _noreturn_ _err_app _err_net perm_usage() 133 { 134 out("D5.3.5 I (qmail-remote) was invoked improperly.\n"); 135 zerodie(); 136 } 137 void _noreturn_ _err_net perm_dns() 138 { 139 out("D5.1.2 Sorry, I couldn't find any host named "); 140 outsafe(&host); 141 out(".\n"); 142 zerodie(); 143 } 144 void _noreturn_ _err_net perm_nomx() 145 { 146 out("D5.4.4 Sorry, I couldn't find a mail exchanger or IP address.\n"); 147 zerodie(); 148 } 149 void _noreturn_ _err_net perm_ambigmx() 150 { 151 out("D5.4.6 Sorry. Although I'm listed as a best-preference MX, A or AAAA for that host,\n" 152 "it isn't in my control/locals file, so I don't treat it as local.\n"); 153 zerodie(); 154 } 155 156 int flagcritical = 0; 157 158 void 159 outhost() 160 { 161 char x[IPFMT]; 162 if (substdio_put(subfdoutsmall, x, ip_fmt(x, &partner)) == -1) 163 _exit(0); 164 } 165 166 void _noreturn_ 167 dropped() 168 { 169 out("Z4.4.2 Connected to "); 170 outhost(); 171 out(" but connection died."); 172 if (flagcritical) 173 out(" Possible duplicate!"); 174 out("\n"); 175 zerodie(); 176 } 177 178 int timeoutconnect = 60; 179 int smtpfdin; 180 int smtpfdou; 181 int timeout = 1200; 182 183 /* this is an utter crock. what the fuck is a timeoutread??? */ 184 GEN_SAFE_TIMEOUTREAD(saferead, timeout, smtpfdin, dropped()) 185 GEN_SAFE_TIMEOUTWRITE(safewrite, timeout, smtpfdou, dropped()) 186 187 char inbuf[1024]; 188 substdio ssin = SUBSTDIO_FDBUF(read, 0, inbuf, sizeof(inbuf)); 189 190 // smtpc version is UCSPI-TCP. 191 char smtptobuf[1024]; 192 substdio smtpto = SUBSTDIO_FDBUF(safewrite, 6, smtptobuf, sizeof(smtptobuf)); 193 char smtpfrombuf[128]; 194 substdio smtpfrom = SUBSTDIO_FDBUF(saferead, 7, smtpfrombuf, sizeof(smtpfrombuf)); 195 196 stralloc smtptext = {0}; 197 198 static void 199 get(unsigned char *uc) 200 { 201 char *ch = (char *)uc; 202 substdio_get(&smtpfrom, ch, 1); 203 if (*ch != '\r') 204 if (smtptext.len < HUGESMTPTEXT) 205 if (!stralloc_append(&smtptext, ch)) 206 temp_nomem(); 207 } 208 209 unsigned long 210 smtpcode() 211 { 212 unsigned char ch = 0; 213 unsigned long code = 0; 214 215 if (!stralloc_copys(&smtptext, "")) 216 temp_nomem(); 217 218 get(&ch); 219 if (ch < '0' || ch > '9') return 599; 220 code = ch - '0'; 221 get(&ch); 222 if (ch < '0' || ch > '9') return 599; 223 code = code * 10 + (ch - '0'); 224 get(&ch); 225 if (ch < '0' || ch > '9') return 599; 226 code = code * 10 + (ch - '0'); 227 for (;;) { 228 get(&ch); 229 if (ch != '-') 230 break; 231 while (ch != '\n') 232 get(&ch); 233 get(&ch); 234 get(&ch); 235 get(&ch); 236 } 237 while (ch != '\n') 238 get(&ch); 239 240 return code; 241 } 242 243 /* smtpcodetext 244 * globals: smtptext, both directly and indirectly 245 * inputs: a stralloc to scribble on 246 * outputs: a code 247 * side-effects: reads an SMTP response from the server, 248 */ 249 unsigned long 250 smtpcodetext(stralloc *sa) 251 { 252 /* We assume we get a stralloc we can scribble on. Hildie */ 253 unsigned char ch = 0; 254 unsigned long code = 0; 255 256 if (!stralloc_copys(&smtptext, "")) 257 temp_nomem(); 258 259 if (sa != NULL) if (!stralloc_copys(sa, "")) 260 temp_nomem(); 261 262 if (sa != NULL) stralloc_readyplus(sa, 256); /* It doesn't hurt to have a little extra space eh? Hildie */ 263 get(&ch); 264 if (ch < '0' || ch > '9') return 599; 265 code = ch - '0'; 266 get(&ch); 267 if (ch < '0' || ch > '9') return 599; 268 code = code * 10 + (ch - '0'); 269 get(&ch); 270 if (ch < '0' || ch > '9') return 599; 271 code = code * 10 + (ch - '0'); 272 for (;;) { 273 get(&ch); 274 if (ch != '-') /* If it is a dash, we keep iterating until we get */ 275 break; 276 while (ch != '\n') /* XXX - wouldn't strip \r Hildie */ { 277 if (sa != NULL) stralloc_cat(sa, &ch, 1); 278 get(&ch); 279 } 280 /* .. wouldn't ch be \n at this stage? Hildie */ 281 if (ch == '\n') stralloc_cat(sa, &ch, 1); 282 get(&ch); 283 get(&ch); 284 get(&ch); 285 } 286 while (ch != '\n') { 287 get(&ch); 288 } 289 290 return code; 291 } 292 293 void 294 outsmtptext() 295 { 296 int i; 297 if (smtptext.s) 298 if (smtptext.len) { 299 out("Remote host said: "); 300 for (i = 0; i < smtptext.len; ++i) 301 if (!smtptext.s[i]) 302 smtptext.s[i] = '?'; 303 if (substdio_put(subfdoutsmall, smtptext.s, smtptext.len) == -1) 304 _exit(0); 305 smtptext.len = 0; 306 } 307 } 308 309 void _call_app 310 quit(char *prepend, 311 char *append) 312 { 313 substdio_putsflush(&smtpto, "QUIT\r\n"); 314 /* waiting for remote side is just too ridiculous 315 * do it anyway if we are pedanticsmtp (the default) */ 316 out(prepend); 317 outhost(); 318 out(append); 319 out(".\n"); 320 outsmtptext(); 321 // if (pedanticsmtp) smtpcode(); 322 /* Discard; &smtptext will just be freed when we die */ 323 zerodie(); /* the qmail-remote-net program should cut if the qmail-remote-smtpc program dies zero (success or permfail) */ 324 } 325 326 void _call_app 327 blastsmtp() 328 { 329 int r; 330 char ch; 331 332 for (;;) { 333 r = substdio_get(&ssin, &ch, 1); 334 if (r == 0) 335 break; 336 if (r == -1) 337 temp_read(); 338 if (ch == '.') 339 substdio_put(&smtpto, ".", 1); /* SMTP quoting crock. Only needed for DATA, not BDAT. Hildie */ 340 while (ch != '\n') { 341 if (ch == '\r') { 342 r = substdio_get(&ssin, &ch, 1); 343 if (r == 0) 344 break; 345 if (r == -1) 346 temp_read(); 347 if (ch != '\n') { 348 substdio_put(&smtpto, "\r\n", 2); /* Accepts both \r\n... */ 349 } else 350 break; 351 } 352 substdio_put(&smtpto, &ch, 1); 353 r = substdio_get(&ssin, &ch, 1); 354 if (r == 0) 355 perm_partialline(); /* Daniel, this is a crock. remote should fastforward the file to the end, check it ends appropriately, and if not, fail fast. */ 356 if (r == -1) 357 temp_read(); 358 } 359 substdio_put(&smtpto, "\r\n", 2); /* ... and \n in the queue. Why shouldn't we just store \r\n in the queue, as it's required by SMTP anyway and would allow us to give an accurate size description for SIZE= and BDAT=? */ 360 } 361 362 flagcritical = 1; 363 substdio_put(&smtpto, ".\r\n", 3); 364 substdio_flush(&smtpto); 365 } 366 367 stralloc recip = {0}; 368 int esmtp = 1; 369 370 void 371 smtp() 372 { 373 unsigned long code; 374 int flagbother; 375 int i; 376 377 //XXX commented example(straight BER flow) is RFC non-compliant. 378 // if (smtpcode() != 220) quit("ZConnected to ", " but greeting failed"); 379 //Use theory of codes instead - any 2xx code is fine 380 code = smtpcode(); 381 if (code < 200 || code > 299) { 382 substdio_putsflush(&smtpto, "QUIT\r\n"); 383 close(smtpfdin); 384 close(smtpfdou); /* Already closed? Doesn't matter. Hildie */ 385 BUF_HEAD(&smtpfrom) = BUF_TAIL(&smtpfrom); /* clear the buffer - re qmail-get-141331@list.cr.yp.to */ 386 return; 387 } 388 389 substdio_puts(&smtpto, "EHLO "); 390 substdio_put(&smtpto, helohost.s, helohost.len); 391 substdio_puts(&smtpto, "\r\n"); 392 substdio_flush(&smtpto); 393 code = smtpcode(); 394 esmtp = 1; 395 if (code < 250 || code > 259) { 396 esmtp = 0; 397 /* Old far side doesn't understand EHLO. That's fine, we don't use any ESMTP extensions anyway. */ 398 substdio_puts(&smtpto, "HELO "); 399 substdio_put(&smtpto, helohost.s, helohost.len); 400 substdio_puts(&smtpto, "\r\n"); 401 substdio_flush(&smtpto); 402 code = smtpcode(); 403 if (code < 250 || code > 259) 404 quit("ZConnected to ", " but my name was rejected, both for EHLO and HELO. I do not expect to be able to deliver mail here at this time."); 405 } 406 407 substdio_puts(&smtpto, "MAIL FROM:<"); 408 substdio_put(&smtpto, sender.s, sender.len); 409 substdio_puts(&smtpto, ">\r\n"); 410 substdio_flush(&smtpto); 411 code = smtpcode(); 412 if (code >= 500) 413 quit("DConnected to ", " but sender was rejected"); 414 if (code >= 400) 415 quit("ZConnected to ", " but sender was deferred"); 416 417 flagbother = 0; 418 /* strange: qmail-remote is capable of multiple recipients, but 419 * is only ever fed just one. éh? Lightning */ 420 for (i = 0; i < reciplist.len; ++i) { 421 substdio_puts(&smtpto, "RCPT TO:<"); 422 substdio_put(&smtpto, reciplist.sa[i].s, reciplist.sa[i].len); 423 substdio_puts(&smtpto, ">\r\n"); 424 substdio_flush(&smtpto); 425 code = smtpcode(); 426 if (code >= 500) { 427 out("h"); 428 outhost(); 429 out(" does not like recipient.\n"); 430 outsmtptext(); 431 zero(); 432 } else if (code >= 400) { 433 out("s"); 434 outhost(); 435 out(" does not like recipient.\n"); 436 outsmtptext(); 437 zero(); 438 } else { 439 out("r"); 440 zero(); 441 flagbother = 1; 442 } 443 } 444 if (!flagbother) 445 quit("DGiving up on ", " because remote server "); 446 447 substdio_putsflush(&smtpto, "DATA\r\n"); 448 code = smtpcode(); 449 if (code >= 500) 450 quit("D", " failed on DATA command"); 451 if (code >= 400) 452 quit("Z", " failed on DATA command"); 453 454 blastsmtp(); 455 code = smtpcode(); 456 flagcritical = 0; 457 if (code >= 500) 458 quit("D", " failed after I sent the message"); 459 if (code >= 400) 460 quit("Z", " failed after I sent the message"); 461 if (esmtp) 462 quit("K", " accepted message with ESMTP"); 463 else quit("K", " accepted message with standard SMTP"); 464 } 465 466 stralloc canonhost = {0}; 467 stralloc canonbox = {0}; 468 469 void 470 addrmangle(stralloc * saout, char *s) 471 { 472 int j; 473 474 j = str_rchr(s, '@'); 475 if (!s[j]) { 476 if (!stralloc_copys(saout, s)) 477 temp_nomem(); 478 return; 479 } 480 if (!stralloc_copys(&canonbox, s)) 481 temp_nomem(); 482 canonbox.len = j; 483 /* box has to be quoted */ 484 if (!quote(saout, &canonbox)) 485 temp_nomem(); 486 if (!stralloc_cats(saout, "@")) 487 temp_nomem(); 488 489 if (!stralloc_copys(&canonhost, s + j + 1)) 490 temp_nomem(); 491 492 if (!stralloc_cat(saout, &canonhost)) 493 temp_nomem(); 494 } 495 496 void _call_app 497 getappcontrols() 498 { 499 if (control_readint(&pedanticsmtp, "control/pedanticsmtp") == -1) _call_app 500 pedanticsmtp = 1; // default to pedantic 501 if (control_readint(&timeout, "control/timeoutremote") == -1) _call_app 502 temp_control(); 503 if (control_rldef(&helohost, "control/helohost", 1, NULL) != 1) _call_app 504 temp_control(); 505 } 506 507 void _call_net 508 getnetcontrols() 509 { 510 if (control_readint(&timeoutconnect, "control/timeoutconnect") == -1) _call_net 511 temp_control(); 512 switch (control_readfile(&routes, "control/smtproutes", 0)) { _call_net /* would be looked up by */ 513 case -1: 514 temp_control(); 515 case 0: 516 if (!constmap_init(&maproutes, "", 0, 1)) 517 temp_nomem(); 518 break; 519 case 1: 520 if (!constmap_init(&maproutes, routes.s, routes.len, 1)) 521 temp_nomem(); 522 break; 523 } 524 } 525 526 void _call_app _call_net 527 getcontrols() 528 { 529 if (control_init() == -1) 530 temp_control(); 531 getappcontrols(); 532 //getnetcontrols(); 533 } 534 535 int 536 main(int argc, char **argv) 537 { 538 int i; 539 _call_net static ipalloc ip = {0}; 540 _call_net unsigned long prefme; 541 _call_net char *relayhost; 542 _call_app unsigned long random; 543 _call_app char **recips; 544 545 sig_pipeignore(); 546 547 if (argc < 4) 548 perm_usage(); 549 if (chdir(auto_qmail) == -1) 550 temp_chdir(); 551 552 getcontrols(); 553 554 if (!stralloc_copys(&host, argv[1])) 555 temp_nomem(); 556 557 _call_net // { 558 559 relayhost = 0; 560 for (i = 0; i <= host.len; ++i) 561 if ((i == 0) || (i == host.len) || (host.s[i] == '.')) 562 if ((relayhost = constmap(&maproutes, host.s + i, host.len - i))) 563 break; 564 565 if (relayhost && !*relayhost) 566 relayhost = 0; 567 568 if (relayhost) { 569 i = str_chr(relayhost, ':'); 570 if (relayhost[i]) { 571 scan_ulong(relayhost + i + 1, &port); 572 relayhost[i] = 0; 573 } 574 if (!stralloc_copys(&host, relayhost)) 575 temp_nomem(); 576 } 577 // } 578 579 _call_app addrmangle(&sender, argv[2]); 580 581 _call_app if (!saa_readyplus(&reciplist, 0)) 582 temp_nomem(); 583 584 _call_net if (ipme_init() != 1) 585 temp_oserr(); 586 587 _call_app recips = argv + 3; 588 _call_app while (*recips) { 589 if (!saa_readyplus(&reciplist, 1)) 590 temp_nomem(); 591 reciplist.sa[reciplist.len] = sauninit; 592 addrmangle(reciplist.sa + reciplist.len, *recips); 593 ++reciplist.len; 594 ++recips; 595 } 596 597 /* random = now() + (getpid() << 16); 598 _call_net switch (relayhost ? dns_ip(&ip, &host) : dns_mxip(&ip, &host, random)) { 599 case DNS_MEM: 600 temp_nomem(); 601 case DNS_SOFT: 602 temp_dns(); 603 case DNS_HARD: 604 perm_dns(); 605 case 1: 606 if (ip.len <= 0) 607 temp_dns(); 608 } 609 610 _call_net // { 611 if (ip.len <= 0) 612 perm_nomx(); 613 614 prefme = 100000; 615 for (i = 0; i < ip.len; ++i) 616 if (ipme_is(&ip.ix[i].ip)) 617 if (ip.ix[i].pref < prefme) 618 prefme = ip.ix[i].pref; 619 620 if (relayhost) 621 prefme = 300000; 622 623 for (i = 0; i < ip.len; ++i) 624 if (ip.ix[i].pref < prefme) 625 break; 626 627 if (i >= ip.len) 628 perm_ambigmx(); */ // All of this is already handled for smtpc. 629 630 _call_net for (i = 0; i < ip.len; ++i) 631 if (ip.ix[i].pref < prefme) { 632 if (tcpto(&ip.ix[i].ip)) 633 continue; 634 635 smtpfdin = socket(AF_INET, SOCK_STREAM, 0); 636 if (smtpfdin == -1) 637 temp_oserr(); 638 smtpfdou = smtpfdin; 639 640 if (timeoutconn(smtpfdin, &ip.ix[i].ip, (unsigned int)port, timeoutconnect) == 0) { 641 tcpto_err(&ip.ix[i].ip, 0); 642 partner = ip.ix[i].ip; 643 _call_app smtp(); /* does not return, unless greeting 644 * wrong 645 * smtp() would be the entry point of a qmail-remote-app*/ 646 } 647 tcpto_err(&ip.ix[i].ip, errno == error_timeout); 648 close(smtpfdin); 649 close(smtpfdou); 650 } 651 652 temp_noconn(); 653 }