nightmaremail

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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 }