nightmaremail

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

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 }