nightmaremail

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

qmail-local.c (19012B)


      1 #include <sys/types.h>
      2 #include <sys/stat.h>
      3 #include <stdlib.h>
      4 #include "readwrite.h"
      5 #include "sig.h"
      6 #include "env.h"
      7 #include "byte.h"
      8 #include "datetime.h"
      9 #include "exit.h"
     10 #include "fork.h"
     11 #include "open.h"
     12 #include "wait.h"
     13 #include "lock.h"
     14 #include "seek.h"
     15 #include "substdio.h"
     16 #include "getln.h"
     17 #include "strerr.h"
     18 #include "subfd.h"
     19 #include "sgetopt.h"
     20 #include "alloc.h"
     21 #include "error.h"
     22 #include "stralloc.h"
     23 #include "fmt.h"
     24 #include "str.h"
     25 #include "now.h"
     26 #include "case.h"
     27 #include "quote.h"
     28 #include "qmail.h"
     29 #include "slurpclose.h"
     30 #include "myctime.h"
     31 #include "gfrom.h"
     32 #include "auto_patrn.h"
     33 
     34 void _noreturn_ usage() {
     35 	strerr_die1x(100, "qmail-local: usage: qmail-local [ -nNT ] user homedir local dash ext domain sender aliasempty");
     36 }
     37 
     38 void _noreturn_ temp_nomem() {
     39 	strerr_die1x(111, "4.3.0 Out of memory.");
     40 }
     41 void _noreturn_ temp_rewind() {
     42 	strerr_die1x(111, "4.3.0 Unable to rewind message.");
     43 }
     44 void _noreturn_ temp_childcrashed() {
     45 	strerr_die1x(111, "4.3.0 Aack, child crashed.");
     46 }
     47 void _noreturn_ temp_fork() {
     48 	strerr_die3x(111, "4.3.0 Unable to fork: ", error_str(errno), ".");
     49 }
     50 void _noreturn_ temp_read() {
     51 	strerr_die3x(111, "4.3.0 Unable to read message: ", error_str(errno), ".");
     52 }
     53 void _noreturn_
     54 temp_slowlock()
     55 {
     56 	strerr_die1x(111, "4.3.0 File has been locked for 30 seconds straight.");
     57 }
     58 void _noreturn_
     59 temp_qmail(char *fn)
     60 {
     61 	strerr_die5x(111, "4.3.0 Unable to open ", fn, ": ", error_str(errno), " .");
     62 }
     63 
     64 int flagdoit = 0, flagprobe = 0;
     65 int flag99;
     66 
     67 char *user;
     68 char *homedir;
     69 char *local;
     70 char *dash;
     71 char *ext;
     72 char *host;
     73 char *sender;
     74 char *aliasempty;
     75 
     76 stralloc safeext = {0};
     77 stralloc ufline = {0};
     78 stralloc rpline = {0};
     79 stralloc envrecip = {0};
     80 stralloc dtline = {0};
     81 stralloc qme = {0};
     82 stralloc ueo = {0};
     83 stralloc cmds = {0};
     84 stralloc messline = {0};
     85 stralloc foo = {0};
     86 
     87 char buf[1024];
     88 char outbuf[1024];
     89 
     90 /* child process */
     91 
     92 char fntmptph[80 + FMT_ULONG * 2];
     93 char fnnewtph[80 + FMT_ULONG * 2];
     94 void tryunlinktmp() {
     95 	unlink(fntmptph);
     96 }
     97 void sigalrm() {
     98 	tryunlinktmp();
     99 	_exit(3);
    100 }
    101 
    102 void
    103 maildir_child(char *dir)
    104 {
    105 	unsigned long pid;
    106 	unsigned long time;
    107 	char myhost[64];
    108 	char *s;
    109 	int loop;
    110 	int fd;
    111 	substdio ss;
    112 	substdio ssout;
    113 
    114 	sig_alarmcatch(sigalrm);
    115 	if (chdir(dir) == -1) {
    116 		if (error_temp(errno))
    117 			_exit(1);
    118 		_exit(2);
    119 	}
    120 	pid = getpid();
    121 	myhost[0] = 0;
    122 	gethostname(myhost, sizeof(myhost));
    123 	for (loop = 0;; ++loop) {
    124 		time = now();
    125 		s = fntmptph;
    126 		s += fmt_str(s, "tmp/");
    127 		s += fmt_ulong(s, time);
    128 		*s++ = '.';
    129 		s += fmt_ulong(s, pid);
    130 		*s++ = '.';
    131 		s += fmt_strn(s, myhost, sizeof(myhost));
    132 		*s++ = 0;
    133 		alarm(86400);
    134 		fd = open_excl(fntmptph);
    135 		if (fd >= 0)
    136 			break;
    137 		if (errno == error_exist) {
    138 			/* really should never get to this point */
    139 			if (loop == 2)
    140 				_exit(1);
    141 			sleep(2);
    142 		} else {
    143 			_exit(1);
    144 		}
    145 	}
    146 	str_copy(fnnewtph, fntmptph);
    147 	byte_copy(fnnewtph, 3, "new");
    148 
    149 	substdio_fdbuf(&ss, read, 0, buf, sizeof(buf));
    150 	substdio_fdbuf(&ssout, write, fd, outbuf, sizeof(outbuf));
    151 	if (substdio_put(&ssout, rpline.s, rpline.len) == -1)
    152 		goto fail;
    153 	if (substdio_put(&ssout, dtline.s, dtline.len) == -1)
    154 		goto fail;
    155 
    156 	switch (substdio_copy(&ssout, &ss)) {
    157 	case -2:
    158 		tryunlinktmp();
    159 		_exit(4);
    160 	case -3:
    161 		goto fail;
    162 	}
    163 
    164 	if (substdio_flush(&ssout) == -1)
    165 		goto fail;
    166 	if (fsync(fd) == -1)
    167 		goto fail;
    168 	if (close(fd) == -1)
    169 		goto fail;	/* NFS dorks */
    170 
    171 	if (link(fntmptph, fnnewtph) == -1)
    172 		goto fail;
    173 	/* if it was error_exist, almost certainly successful; i hate NFS */
    174 	tryunlinktmp();
    175 	_exit(0);
    176 
    177 fail:	tryunlinktmp();
    178 	_exit(1);
    179 }
    180 
    181 /* end child process */
    182 
    183 void
    184 maildir(char *fn)
    185 {
    186 	int child;
    187 	int wstat;
    188 
    189 	if (seek_begin(0) == -1)
    190 		temp_rewind();
    191 
    192 	switch (child = fork()) {
    193 	case -1:
    194 		temp_fork();
    195 	case 0:
    196 		maildir_child(fn);
    197 		_exit(111);
    198 	}
    199 
    200 	wait_pid(&wstat, child);
    201 	if (wait_crashed(wstat))
    202 		temp_childcrashed();
    203 	switch (wait_exitcode(wstat)) {
    204 	case 0:
    205 		break;
    206 	case 2:
    207 		strerr_die1x(111, "4.2.1 Unable to chdir to maildir. ");
    208 	case 3:
    209 		strerr_die1x(111, "4.3.0 Timeout on maildir delivery. ");
    210 	case 4:
    211 		strerr_die1x(111, "4.3.0 Unable to read message. ");
    212 	default:
    213 		strerr_die1x(111, "4.3.0 Temporary error on maildir delivery. ");
    214 	}
    215 }
    216 
    217 void
    218 mailfile(char *fn)
    219 {
    220 	int fd;
    221 	substdio ss;
    222 	substdio ssout;
    223 	int match;
    224 	seek_pos pos;
    225 	int flaglocked;
    226 
    227 	if (seek_begin(0) == -1)
    228 		temp_rewind();
    229 
    230 	fd = open_append(fn);
    231 	if (fd == -1)
    232 		strerr_die5x(111, "4.2.1 Unable to open ", fn, ": ", error_str(errno), ".");
    233 
    234 	sig_alarmcatch(temp_slowlock);
    235 	alarm(30);
    236 	flaglocked = (lock_ex(fd) != -1);
    237 	alarm(0);
    238 	sig_alarmdefault();
    239 
    240 	seek_end(fd);
    241 	pos = seek_cur(fd);
    242 
    243 	substdio_fdbuf(&ss, read, 0, buf, sizeof(buf));
    244 	substdio_fdbuf(&ssout, write, fd, outbuf, sizeof(outbuf));
    245 	if (substdio_put(&ssout, ufline.s, ufline.len))
    246 		goto writeerrs;
    247 	if (substdio_put(&ssout, rpline.s, rpline.len))
    248 		goto writeerrs;
    249 	if (substdio_put(&ssout, dtline.s, dtline.len))
    250 		goto writeerrs;
    251 	for (;;) {
    252 		if (getln(&ss, &messline, &match, '\n') != 0) {
    253 			strerr_warn3("4.3.0 Unable to read message: ", error_str(errno), ".", 0);
    254 			if (flaglocked)
    255 				seek_trunc(fd, pos);
    256 			close(fd);
    257 			_exit(111);
    258 		}
    259 		if (!match && !messline.len)
    260 			break;
    261 		if (gfrom(messline.s, messline.len))
    262 			if (substdio_bput(&ssout, ">", 1))
    263 				goto writeerrs;
    264 		if (substdio_bput(&ssout, messline.s, messline.len))
    265 			goto writeerrs;
    266 		if (!match) {
    267 			if (substdio_bputs(&ssout, "\n"))
    268 				goto writeerrs;
    269 			break;
    270 		}
    271 	}
    272 	if (substdio_bputs(&ssout, "\n"))
    273 		goto writeerrs;
    274 	if (substdio_flush(&ssout))
    275 		goto writeerrs;
    276 	if (fsync(fd) == -1)
    277 		goto writeerrs;
    278 	close(fd);
    279 	return;
    280 
    281 writeerrs:
    282 	strerr_warn5("Unable to write ", fn, ": ", error_str(errno), "4.3.0 . ", 0);
    283 	if (flaglocked)
    284 		seek_trunc(fd, pos);
    285 	close(fd);
    286 	_exit(111);
    287 }
    288 
    289 void
    290 mailprogram(char *prog)
    291 {
    292 	int child;
    293 	char *(args[4]);
    294 	int wstat;
    295 
    296 	if (seek_begin(0) == -1)
    297 		temp_rewind();
    298 
    299 	/* About here is where you'd add the logic for Courieresque pipe-pipe deliveries. */
    300 
    301 	switch (child = fork()) {
    302 	case -1:
    303 		temp_fork();
    304 	case 0:
    305 		/* MXF XXX: Should add a control file, maybe with a per user override?, to allow this to be execlineb, instead. */
    306 		args[0] = "/bin/sh";
    307 		args[1] = "-c";
    308 		args[2] = prog;
    309 		args[3] = 0;
    310 		sig_pipedefault();
    311 		execv(*args, args);
    312 		strerr_die3x(111, "4.3.0 Unable to run /bin/sh: ", error_str(errno), ".");
    313 	}
    314 
    315 	wait_pid(&wstat, child);
    316 	if (wait_crashed(wstat))
    317 		temp_childcrashed();
    318 	switch (wait_exitcode(wstat)) {
    319 	case 100:
    320 	case 64:
    321 	case 65:
    322 	case 70:
    323 	case 76:
    324 	case 77:
    325 	case 78:
    326 	case 112:
    327 		_exit(100);
    328 	case 0:
    329 		break;
    330 	case 99:
    331 		flag99 = 1;
    332 		break;
    333 	default:
    334 		_exit(111);
    335 	}
    336 }
    337 
    338 unsigned long mailforward_qp = 0;
    339 
    340 void
    341 mailforward(char **recips)
    342 {
    343 	struct qmail qqt;
    344 	char *qqx;
    345 	substdio ss;
    346 	int match;
    347 
    348 	if (seek_begin(0) == -1)
    349 		temp_rewind();
    350 	substdio_fdbuf(&ss, read, 0, buf, sizeof(buf));
    351 
    352 	if (qmail_open(&qqt) == -1)
    353 		temp_fork();
    354 	mailforward_qp = qmail_qp(&qqt);
    355 	qmail_put(&qqt, dtline.s, dtline.len);
    356 	do {
    357 		if (getln(&ss, &messline, &match, '\n') != 0) {
    358 			qmail_fail(&qqt);
    359 			break;
    360 		}
    361 		qmail_put(&qqt, messline.s, messline.len);
    362 	}
    363 	while (match);
    364 	qmail_from(&qqt, ueo.s);
    365 	while (*recips)
    366 		qmail_to(&qqt, *recips++);
    367 	qqx = qmail_close(&qqt);
    368 	if (!*qqx)
    369 		return;
    370 	strerr_die3x(*qqx == 'D' ? 100 : 111, "Unable to forward message: ", qqx + 1, ".");
    371 }
    372 
    373 void
    374 bouncexf()
    375 {
    376 	int match;
    377 	substdio ss;
    378 
    379 	if (seek_begin(0) == -1)
    380 		temp_rewind();
    381 	substdio_fdbuf(&ss, read, 0, buf, sizeof(buf));
    382 	for (;;) {
    383 		if (getln(&ss, &messline, &match, '\n') != 0)
    384 			temp_read();
    385 		if (!match)
    386 			break;
    387 		if (messline.len <= 1)
    388 			break;
    389 		if (messline.len == dtline.len)
    390 			if (!str_diffn(messline.s, dtline.s, dtline.len))
    391 				strerr_die1x(100, "5.4.6 This message is looping: it already has my Delivered-To line. ");
    392 	}
    393 }
    394 
    395 void
    396 checkhome()
    397 {
    398 	struct stat st;
    399 
    400 	if (stat(".", &st) == -1)
    401 		strerr_die3x(111, "Unable to stat home directory: ", error_str(errno), "4.3.0 . ");
    402 	if (st.st_mode & auto_patrn)
    403 		strerr_die1x(111, "4.7.0 Uh-oh: home directory is writable. ");
    404 	if (st.st_mode & 01000) {
    405 		if (flagdoit)
    406 			strerr_die1x(111, "4.2.1 Home directory is sticky: user is editing their .qmail file. ");
    407 		else
    408 			strerr_warn1("Warning: home directory is sticky.", 0);
    409 	}
    410 }
    411 
    412 int
    413 qmeox(dashowner)
    414 	char *dashowner;
    415 {
    416 	struct stat st;
    417 
    418 	if (!stralloc_copys(&qme, ".qmail"))
    419 		temp_nomem();
    420 	if (!stralloc_cats(&qme, dash))
    421 		temp_nomem();
    422 	if (!stralloc_cat(&qme, &safeext))
    423 		temp_nomem();
    424 	if (!stralloc_cats(&qme, dashowner))
    425 		temp_nomem();
    426 	if (!stralloc_0(&qme))
    427 		temp_nomem();
    428 	if (stat(qme.s, &st) == -1) {
    429 		if (error_temp(errno))
    430 			temp_qmail(qme.s);
    431 		return -1;
    432 	}
    433 	return 0;
    434 }
    435 
    436 /* MXF NOTES: int *cutable == "PBR flag executable"
    437  */
    438 int
    439 qmeexists(int *fd, int *cutable)
    440 {
    441 	struct stat st;
    442 
    443 	if (!stralloc_0(&qme))
    444 		temp_nomem();
    445 
    446 	*fd = open_read(qme.s);
    447 	if (*fd == -1) {
    448 		if (error_temp(errno))
    449 			temp_qmail(qme.s);
    450 		if (errno == error_perm)
    451 			temp_qmail(qme.s);
    452 		if (errno == error_acces)
    453 			temp_qmail(qme.s);
    454 		return 0;
    455 	}
    456 
    457 	if (fstat(*fd, &st) == -1)
    458 		temp_qmail(qme.s);
    459 	if ((st.st_mode & S_IFMT) == S_IFREG) {
    460 		if (st.st_mode & auto_patrn)
    461 			strerr_die1x(111, "4.7.0 Uh-oh: .qmail file is writable. ");
    462 		*cutable = !!(st.st_mode & 0100);
    463 		return 1;
    464 	}
    465 	close(*fd);
    466 	return 0;
    467 }
    468 
    469 /* "" "": "" */
    470 /* "-/" "": "-/" "-/default" */
    471 /* "-/" "a": "-/a" "-/default" */
    472 /* "-/" "a-": "-/a-" "-/a-default" "-/default" */
    473 /* "-/" "a-b": "-/a-b" "-/a-default" "-/default" */
    474 /* "-/" "a-b-": "-/a-b-" "-/a-b-default" "-/a-default" "-/default" */
    475 /* "-/" "a-b-c": "-/a-b-c" "-/a-b-default" "-/a-default" "-/default" */
    476 /* MXF NOTE: notably, this does not support using custom dashes anywhere
    477  * except the first split of the address. */
    478 void
    479 qmesearch(int *fd, int *cutable)
    480 {
    481 	int i;
    482 
    483 	if (!stralloc_copys(&qme, ".qmail"))
    484 		temp_nomem();
    485 	if (!stralloc_cats(&qme, dash))
    486 		temp_nomem();
    487 	if (!stralloc_cat(&qme, &safeext))
    488 		temp_nomem();
    489 	if (qmeexists(fd, cutable)) {
    490 		if (safeext.len >= 7) {
    491 			i = safeext.len - 7;
    492 			if (byte_equal("default", 7, safeext.s + i))
    493 				if (i <= str_len(ext))	/* paranoia */
    494 					if (!env_put2("DEFAULT", ext + i))
    495 						temp_nomem();
    496 		}
    497 		return;
    498 	}
    499 
    500 	for (i = safeext.len; i >= 0; --i)
    501 		if (!i || (safeext.s[i - 1] == '-')) {
    502 			if (!stralloc_copys(&qme, ".qmail"))
    503 				temp_nomem();
    504 			if (!stralloc_cats(&qme, dash))
    505 				temp_nomem();
    506 			if (!stralloc_catb(&qme, safeext.s, i))
    507 				temp_nomem();
    508 			if (!stralloc_cats(&qme, "default"))
    509 				temp_nomem();
    510 			if (qmeexists(fd, cutable)) {
    511 				if (i <= str_len(ext))	/* paranoia */
    512 					if (!env_put2("DEFAULT", ext + i))
    513 						temp_nomem();
    514 				return;
    515 			}
    516 		}
    517 
    518 	*fd = -1;
    519 }
    520 
    521 unsigned long count_file = 0;
    522 unsigned long count_forward = 0;
    523 unsigned long count_program = 0;
    524 char count_buf[FMT_ULONG];
    525 
    526 void
    527 count_print()
    528 {
    529 	substdio_puts(subfdoutsmall, "did ");
    530 	substdio_put(subfdoutsmall, count_buf, fmt_ulong(count_buf, count_file));
    531 	substdio_puts(subfdoutsmall, "+");
    532 	substdio_put(subfdoutsmall, count_buf, fmt_ulong(count_buf, count_forward));
    533 	substdio_puts(subfdoutsmall, "+");
    534 	substdio_put(subfdoutsmall, count_buf, fmt_ulong(count_buf, count_program));
    535 	substdio_puts(subfdoutsmall, "\n");
    536 	if (mailforward_qp) {
    537 		substdio_puts(subfdoutsmall, "qp ");
    538 		substdio_put(subfdoutsmall, count_buf, fmt_ulong(count_buf, mailforward_qp));
    539 		substdio_puts(subfdoutsmall, "\n");
    540 	}
    541 	substdio_flush(subfdoutsmall);
    542 }
    543 
    544 void
    545 sayit(char *type, char *cmd, unsigned int len)
    546 {
    547 	substdio_puts(subfdoutsmall, type);
    548 	substdio_put(subfdoutsmall, cmd, len);
    549 	substdio_putsflush(subfdoutsmall, "\n");
    550 }
    551 
    552 int
    553 main(int argc, char **argv)
    554 {
    555 	int opt;
    556 	unsigned int i;
    557 	unsigned int j;
    558 	int fd;
    559 	unsigned int numforward;
    560 	char **recips;
    561 	datetime_sec starttime;
    562 	int flagforwardonly;
    563 	char *x;
    564 
    565 	umask(077);
    566 	sig_pipeignore();
    567 
    568 	if (!env_init())
    569 		temp_nomem();
    570 
    571 	flagdoit = 1;
    572 	while ((opt = getopt(argc, argv, "nNT")) != opteof)
    573 		switch (opt) {
    574 		case 'n':
    575 			flagdoit = 0;
    576 			break;
    577 		case 'N':
    578 			flagdoit = 1;
    579 			break;
    580 		case 'T':
    581 			flagdoit = 0;
    582 			flagprobe = 1;
    583 			break;
    584 		default:
    585 			usage();
    586 		}
    587 	argc -= optind;
    588 	argv += optind;
    589 
    590 	if (!(user = *argv++))
    591 		usage();
    592 	if (!(homedir = *argv++))
    593 		usage();
    594 	if (!(local = *argv++))
    595 		usage();
    596 	if (!(dash = *argv++))
    597 		usage();
    598 	if (!(ext = *argv++))
    599 		usage();
    600 	if (!(host = *argv++))
    601 		usage();
    602 	if (!(sender = *argv++))
    603 		usage();
    604 	if (!(aliasempty = *argv++))
    605 		usage();
    606 	if (*argv)
    607 		usage();
    608 
    609 	if (homedir[0] != '/')
    610 		usage();
    611 	if (chdir(homedir) == -1)
    612 		strerr_die5x(111, "4.3.0 Unable to switch to ", homedir, ": ", error_str(errno), ".");
    613 	checkhome();
    614 
    615 	if (!env_put2("HOST", host))
    616 		temp_nomem();
    617 	if (!env_put2("HOME", homedir))
    618 		temp_nomem();
    619 	if (!env_put2("USER", user))
    620 		temp_nomem();
    621 	if (!env_put2("LOCAL", local))
    622 		temp_nomem();
    623 
    624 	if (!stralloc_copys(&envrecip, local))
    625 		temp_nomem();
    626 	if (!stralloc_cats(&envrecip, "@"))
    627 		temp_nomem();
    628 	if (!stralloc_cats(&envrecip, host))
    629 		temp_nomem();
    630 
    631 	if (!stralloc_copy(&foo, &envrecip))
    632 		temp_nomem();
    633 	if (!stralloc_0(&foo))
    634 		temp_nomem();
    635 	if (!env_put2("RECIPIENT", foo.s))
    636 		temp_nomem();
    637 
    638 	if (!stralloc_copys(&dtline, "Delivered-To: "))
    639 		temp_nomem();
    640 	if (!stralloc_cat(&dtline, &envrecip))
    641 		temp_nomem();
    642 	for (i = 0; i < dtline.len; ++i)
    643 		if (dtline.s[i] == '\n')
    644 			dtline.s[i] = '_';
    645 	if (!stralloc_cats(&dtline, "\n"))
    646 		temp_nomem();
    647 
    648 	if (!stralloc_copy(&foo, &dtline))
    649 		temp_nomem();
    650 	if (!stralloc_0(&foo))
    651 		temp_nomem();
    652 	if (!env_put2("DTLINE", foo.s))
    653 		temp_nomem();
    654 
    655 	if (flagdoit)
    656 		bouncexf();
    657 
    658 	if (!env_put2("SENDER", sender))
    659 		temp_nomem();
    660 
    661 	if (!quote2(&foo, sender))
    662 		temp_nomem();
    663 	if (!stralloc_copys(&rpline, "Return-Path: <"))
    664 		temp_nomem();
    665 	if (!stralloc_cat(&rpline, &foo))
    666 		temp_nomem();
    667 	for (i = 0; i < rpline.len; ++i)
    668 		if (rpline.s[i] == '\n')
    669 			rpline.s[i] = '_';
    670 	if (!stralloc_cats(&rpline, ">\n"))
    671 		temp_nomem();
    672 
    673 	if (!stralloc_copy(&foo, &rpline))
    674 		temp_nomem();
    675 	if (!stralloc_0(&foo))
    676 		temp_nomem();
    677 	if (!env_put2("RPLINE", foo.s))
    678 		temp_nomem();
    679 
    680 	if (!stralloc_copys(&ufline, "From "))
    681 		temp_nomem();
    682 	if (*sender) {
    683 		unsigned int len;
    684 		char ch;
    685 
    686 		len = str_len(sender);
    687 		if (!stralloc_readyplus(&ufline, len))
    688 			temp_nomem();
    689 		for (i = 0; i < len; ++i) {
    690 			ch = sender[i];
    691 			if ((ch == ' ') || (ch == '\t') || (ch == '\n'))
    692 				ch = '-';
    693 			ufline.s[ufline.len + i] = ch;
    694 		}
    695 		ufline.len += len;
    696 	} else if (!stralloc_cats(&ufline, "MAILER-DAEMON"))
    697 		temp_nomem();
    698 	if (!stralloc_cats(&ufline, " "))
    699 		temp_nomem();
    700 	starttime = now();
    701 	if (!stralloc_cats(&ufline, myctime(starttime)))
    702 		temp_nomem();
    703 
    704 	if (!stralloc_copy(&foo, &ufline))
    705 		temp_nomem();
    706 	if (!stralloc_0(&foo))
    707 		temp_nomem();
    708 	if (!env_put2("UFLINE", foo.s))
    709 		temp_nomem();
    710 
    711 	x = ext;
    712 	if (!env_put2("EXT", x))
    713 		temp_nomem();
    714 	x += str_chr(x, '-');
    715 	if (*x)
    716 		++x;
    717 	if (!env_put2("EXT2", x))
    718 		temp_nomem();
    719 	x += str_chr(x, '-');
    720 	if (*x)
    721 		++x;
    722 	if (!env_put2("EXT3", x))
    723 		temp_nomem();
    724 	x += str_chr(x, '-');
    725 	if (*x)
    726 		++x;
    727 	if (!env_put2("EXT4", x))
    728 		temp_nomem();
    729 
    730 	if (!stralloc_copys(&safeext, ext))
    731 		temp_nomem();
    732 	case_lowerb(safeext.s, safeext.len);
    733 	for (i = 0; i < safeext.len; ++i)
    734 		if (safeext.s[i] == '.')
    735 			safeext.s[i] = ':';
    736 
    737 	i = str_len(host);
    738 	i = byte_rchr(host, i, '.');
    739 	if (!stralloc_copyb(&foo, host, i))
    740 		temp_nomem();
    741 	if (!stralloc_0(&foo))
    742 		temp_nomem();
    743 	if (!env_put2("HOST2", foo.s))
    744 		temp_nomem();
    745 	i = byte_rchr(host, i, '.');
    746 	if (!stralloc_copyb(&foo, host, i))
    747 		temp_nomem();
    748 	if (!stralloc_0(&foo))
    749 		temp_nomem();
    750 	if (!env_put2("HOST3", foo.s))
    751 		temp_nomem();
    752 	i = byte_rchr(host, i, '.');
    753 	if (!stralloc_copyb(&foo, host, i))
    754 		temp_nomem();
    755 	if (!stralloc_0(&foo))
    756 		temp_nomem();
    757 	if (!env_put2("HOST4", foo.s))
    758 		temp_nomem();
    759 
    760 	flagforwardonly = 0;
    761 	qmesearch(&fd, &flagforwardonly);
    762 	if (fd == -1)
    763 		if (*dash)
    764 			strerr_die1x(100, "5.1.1 Sorry, no mailbox here by that name.");
    765 
    766 	if (!stralloc_copys(&ueo, sender))
    767 		temp_nomem();
    768 	if (str_diff(sender, ""))
    769 		if (str_diff(sender, "#@[]"))
    770 			if (qmeox("-owner") == 0) {
    771 				if (qmeox("-owner-default") == 0) {
    772 					if (!stralloc_copys(&ueo, local))
    773 						temp_nomem();
    774 					if (!stralloc_cats(&ueo, "-owner-@"))
    775 						temp_nomem();
    776 					if (!stralloc_cats(&ueo, host))
    777 						temp_nomem();
    778 					if (!stralloc_cats(&ueo, "-@[]"))
    779 						temp_nomem();
    780 				} else {
    781 					if (!stralloc_copys(&ueo, local))
    782 						temp_nomem();
    783 					if (!stralloc_cats(&ueo, "-owner@"))
    784 						temp_nomem();
    785 					if (!stralloc_cats(&ueo, host))
    786 						temp_nomem();
    787 				}
    788 			}
    789 	if (!stralloc_0(&ueo))
    790 		temp_nomem();
    791 	if (!env_put2("NEWSENDER", ueo.s))
    792 		temp_nomem();
    793 
    794 	if (!stralloc_ready(&cmds, 0))
    795 		temp_nomem();
    796 	cmds.len = 0;
    797 	/* The full .qmail file must fit into memory for qmail-local. */
    798 	if (fd != -1)
    799 		if (slurpclose(fd, &cmds, 256) == -1)
    800 			temp_nomem();
    801 
    802 	if (!cmds.len) {
    803 		if (!stralloc_copys(&cmds, aliasempty))
    804 			temp_nomem();
    805 		flagforwardonly = 0;
    806 	}
    807 	if (!cmds.len || (cmds.s[cmds.len - 1] != '\n'))
    808 		if (!stralloc_cats(&cmds, "\n"))
    809 			temp_nomem();
    810 
    811 	numforward = 0;
    812 	i = 0;
    813 	for (j = 0; j < cmds.len; ++j)
    814 		if (cmds.s[j] == '\n') {
    815 			switch (cmds.s[i]) {
    816 			case '#':
    817 			case '.':
    818 			case '/':
    819 			case '|':
    820 				break;
    821 			default:
    822 				++numforward;
    823 			}
    824 			i = j + 1;
    825 		}
    826 
    827 	recips = calloc(numforward + 1, sizeof(char *));
    828 	if (!recips)
    829 		temp_nomem();
    830 	numforward = 0;
    831 
    832 	flag99 = 0;
    833 
    834 	i = 0;
    835 	/* Daniel, if you are reading, this is a crock.
    836 	 * An LUT would only cost you 2 to 6 kB on amd64, and is more easily
    837 	 * patched to extend. */
    838 	for (j = 0; j < cmds.len; ++j)
    839 		if (cmds.s[j] == '\n') {
    840 			unsigned int k = j;
    841 			cmds.s[j] = 0;
    842 			while ((k > i) && ((cmds.s[k - 1] == ' ') || (cmds.s[k - 1] == '\t')))
    843 				cmds.s[--k] = 0; /* I like this, though. Smart. */
    844 			switch (cmds.s[i]) {
    845 			case 0:	/* k == i */
    846 				if (i)
    847 					break;
    848 				strerr_die1x(111, "4.2.1 Uh-oh: first line of .qmail file is blank.");
    849 			case '#':
    850 				break;
    851 			case '.':
    852 			case '/':
    853 				++count_file;
    854 				if (flagforwardonly)
    855 					strerr_die1x(111, "4.7.0 Uh-oh: .qmail has file delivery but has x bit set.");
    856 				if (cmds.s[k - 1] == '/')
    857 					if (flagdoit)
    858 						maildir(cmds.s + i);
    859 					else
    860 						sayit("maildir ", cmds.s + i, k - i);
    861 				else if (flagdoit)
    862 					mailfile(cmds.s + i);
    863 				else
    864 					sayit("mboxrd ", cmds.s + i, k - i);
    865 				break;
    866 			case '|':
    867 				++count_program;
    868 				if (flagforwardonly)
    869 					strerr_die1x(111, "4.7.0 Uh-oh: .qmail has prog delivery but has x bit set.");
    870 				if (flagdoit)
    871 					mailprogram(cmds.s + i + 1);
    872 				else
    873 					sayit("program ", cmds.s + i + 1, k - i - 1);
    874 				break;
    875 			case '+':
    876 				if (str_equal(cmds.s + i + 1, "list"))
    877 					flagforwardonly = 1;
    878 				break;
    879 			case '&':
    880 				++i;
    881 			default:
    882 				++count_forward;
    883 				if (flagdoit)
    884 					recips[numforward++] = cmds.s + i;
    885 				else
    886 					sayit("forward ", cmds.s + i, k - i);
    887 				break;
    888 			}
    889 			i = j + 1;
    890 			if (flag99)
    891 				break;
    892 		}
    893 
    894 	if (numforward)
    895 		if (flagdoit) {
    896 			recips[numforward] = 0;
    897 			mailforward(recips);
    898 		}
    899 
    900 	count_print();
    901 	_exit(0);
    902 }