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