nightmaremail

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

mxf-send.c (19274B)


      1 #include <sys/types.h>
      2 #include <sys/stat.h>
      3 #include <sys/time.h>
      4 #include "readwrite.h"
      5 #include "sig.h"
      6 #include "direntry.h"
      7 #include "control.h"
      8 #include "select.h"
      9 #include "open.h"
     10 #include "seek.h"
     11 #include "exit.h"
     12 #include "lock.h"
     13 #include "ndelay.h"
     14 #include "now.h"
     15 #include "getln.h"
     16 
     17 #ifdef USING_SKALIBS
     18 #include <skalibs/buffer.h>
     19 #include <skalibs/iopause.h>
     20 #include <skalibs/stralloc.h>
     21 #include <skalibs/tai.h>
     22 #else
     23 #include "substdio.h"
     24 #include "stralloc.h"
     25 #endif
     26 
     27 #include "error.h"
     28 
     29 #include "str.h"
     30 #include "byte.h"
     31 
     32 #include "fmt.h"
     33 #include "scan.h"
     34 #include "alloc.h"
     35 
     36 #include "case.h"
     37 #include "auto_qmail.h"
     38 #include "trigger.h"
     39 #include "newfield.h"
     40 #include "quote.h"
     41 #include "qmail.h"
     42 #include "qsutil.h"
     43 #include "prioq.h"
     44 #include "constmap.h"
     45 #include "fmtqfn.h"
     46 #include "readsubdir.h"
     47 
     48 /* critical timing feature #1: if not triggered, do not busy-loop */
     49 /* critical timing feature #2: if triggered, respond within fixed time */
     50 /* important timing feature: when triggered, respond instantly */
     51 #define SLEEP_TODO 1500		/* check todo/ every 25 minutes in any case */
     52 #define SLEEP_FUZZ 1		/* slop a bit on sleeps to avoid zeno effect */
     53 #define SLEEP_FOREVER 86400	/* absolute maximum time spent in iopause() */
     54 #define SLEEP_CLEANUP 76431	/* time between cleanups */
     55 #define SLEEP_SYSFAIL 123
     56 #define OSSIFIED 129600		/* 36 hours; _must_ exceed q-q's DEATH (24
     57 				 * hours) */
     58 
     59 int lifetime = 604800;
     60 
     61 stralloc percenthack = {0};
     62 struct constmap mappercenthack;
     63 stralloc locals = {0};
     64 struct constmap maplocals;
     65 stralloc vdoms = {0};
     66 struct constmap mapvdoms;
     67 stralloc envnoathost = {0};
     68 stralloc bouncefrom = {0};
     69 stralloc bouncehost = {0};
     70 stralloc doublebounceto = {0};
     71 stralloc doublebouncehost = {0};
     72 
     73 char strnum2[FMT_ULONG];
     74 char strnum3[FMT_ULONG];
     75 
     76 #define CHANNELS 2
     77 char *chanaddr[CHANNELS] = {"local/", "remote/"};
     78 char *chanstatusmsg[CHANNELS] = {" local ", " remote "};
     79 char *tochan[CHANNELS] = {" to local ", " to remote "};
     80 int chanfdout[CHANNELS] = {1, 3}; /* hardcoded FDs as set up by -start are a crock. */
     81 int chanfdin[CHANNELS] = {2, 4}; /* see above. */
     82 int chanskip[CHANNELS] = {10, 20}; /* add this many seconds to quadr. retry */
     83 
     84 int flagexitasap = 0;
     85 void
     86 sigterm()
     87 {
     88 	flagexitasap = 1;
     89 }
     90 int flagrunasap = 0;
     91 void
     92 sigalrm()
     93 {
     94 	flagrunasap = 1;
     95 }
     96 int flagreadasap = 0;
     97 void
     98 sighup()
     99 {
    100 	flagreadasap = 1;
    101 }
    102 
    103 void cleandied() {
    104 	log1("alert: the clean channel driver exited. to ensure that it is restarted, I will too.\n");
    105 	flagexitasap = 1;
    106 }
    107 
    108 int flagspawnalive[CHANNELS];
    109 void spawndied(int c){
    110 	log1("alert: one or more delivery channel drivers exited. to ensure that it is restarted, I will too.\n");
    111 	flagspawnalive[c] = 0;
    112 	flagexitasap = 1;
    113 }
    114 
    115 #define REPORTMAX 10000
    116 
    117 datetime_sec recent;
    118 
    119 
    120 /* this file is too long ----------------------------------------- FILENAMES
    121 
    122 stralloc fn = {0};
    123 stralloc fn2 = {0};
    124 char fnmake_strnum[FMT_ULONG];
    125 
    126 void
    127 fnmake_init()
    128 {
    129 	while (!stralloc_ready(&fn, FMTQFN))
    130 		nomem();
    131 	while (!stralloc_ready(&fn2, FMTQFN))
    132 		nomem();
    133 }
    134 
    135 void fnmake_info(unsigned long id){
    136 	fn.len = fmtqfn(fn.s, "info/", id, 1);
    137 }
    138 void fnmake_todo(unsigned long id){
    139 	fn.len = fmtqfn(fn.s, "todo/", id, 0);
    140 }
    141 void fnmake_mess(unsigned long id){
    142 	fn.len = fmtqfn(fn.s, "mess/", id, 1);
    143 }
    144 void fnmake_foop(unsigned long id){
    145 	fn.len = fmtqfn(fn.s, "foop/", id, 0);
    146 }
    147 void fnmake_split(unsigned long id){
    148 	fn.len = fmtqfn(fn.s, "", id, 1);
    149 }
    150 void
    151 fnmake2_bounce(unsigned long id)
    152 {
    153 	fn2.len = fmtqfn(fn2.s, "bounce/", id, 0);
    154 }
    155 void
    156 fnmake_chanaddr(unsigned long id, int c)
    157 {
    158 	fn.len = fmtqfn(fn.s, chanaddr[c], id, 1);
    159 }
    160 */
    161 
    162 /* this file is too long ----------------------------------------- REWRITING */
    163 /* the future is here! the rewriting routines have been moved to rewrite.c. */
    164 
    165 /* this file is too long ---------------------------------------------- INFO */
    166 
    167 int
    168 getinfo(stralloc * sa, datetime_sec * dt, unsigned long id)
    169 {
    170 	int fdinfo;
    171 	struct stat st;
    172 	static stralloc line = {
    173 		0
    174 	};
    175 	int match;
    176 	substdio ss;
    177 	char buf[128];
    178 
    179 	fnmake_info(id, &fn);
    180 	fdinfo = open_read(fn.s);
    181 	if (fdinfo == -1)
    182 		return 0;
    183 	if (fstat(fdinfo, &st) == -1) {
    184 		close(fdinfo);
    185 		return 0;
    186 	}
    187 	substdio_fdbuf(&ss, read, fdinfo, buf, sizeof(buf));
    188 	if (getln(&ss, &line, &match, '\0') == -1) {
    189 		close(fdinfo);
    190 		return 0;
    191 	}
    192 	close(fdinfo);
    193 	if (!match)
    194 		return 0;
    195 	if (line.s[0] != 'F')
    196 		return 0;
    197 
    198 	*dt = st.st_mtime;
    199 	while (!stralloc_copys(sa, line.s + 1))
    200 		nomem();
    201 	while (!stralloc_0(sa))
    202 		nomem();
    203 	return 1;
    204 }
    205 
    206 
    207 /* this file is too long ----------------------------------- PRIORITY QUEUES */
    208 
    209 prioq pqdone = {0};		/* -todo +info; HOPEFULLY -local -remote */
    210 prioq pqchan[CHANNELS] = {{0}, {0}};
    211 /* pqchan 0: -todo +info +local ?remote */
    212 /* pqchan 1: -todo +info ?local +remote */
    213 prioq pqfail = {0};		/* stat() failure; has to be pqadded again */
    214 
    215 void
    216 pqadd(unsigned long id)
    217 {
    218 	struct prioq_elt pe;
    219 	struct prioq_elt pechan[CHANNELS];
    220 	int flagchan[CHANNELS];
    221 	struct stat st;
    222 	int c;
    223 
    224 #define CHECKSTAT if (errno != error_noent) goto fail;
    225 
    226 	fnmake_info(id, &fn);
    227 	if (stat(fn.s, &st) == -1) {
    228 		CHECKSTAT
    229 			return;	/* someone yanking our chain */
    230 	}
    231 
    232 	fnmake_todo(id, &fn);
    233 	if (stat(fn.s, &st) != -1)
    234 		return;		/* look, ma, dad crashed writing info! */
    235 	CHECKSTAT
    236 
    237 		for (c = 0; c < CHANNELS; ++c) {
    238 		fnmake_chanaddr(id, c, &fn);
    239 		if (stat(fn.s, &st) == -1) {
    240 			flagchan[c] = 0;
    241 			CHECKSTAT
    242 		} else {
    243 			flagchan[c] = 1;
    244 			pechan[c].id = id;
    245 			pechan[c].dt = st.st_mtime;
    246 		}
    247 	}
    248 
    249 	for (c = 0; c < CHANNELS; ++c)
    250 		if (flagchan[c])
    251 			while (!prioq_insert(&pqchan[c], &pechan[c]))
    252 				nomem();
    253 
    254 	for (c = 0; c < CHANNELS; ++c)
    255 		if (flagchan[c])
    256 			break;
    257 	if (c == CHANNELS) {
    258 		pe.id = id;
    259 		pe.dt = now();
    260 		while (!prioq_insert(&pqdone, &pe))
    261 			nomem();
    262 	}
    263 
    264 	return;
    265 
    266 fail:
    267 	log3("warning: unable to stat ", fn.s, "; will try again later\n");
    268 	pe.id = id;
    269 	pe.dt = now() + SLEEP_SYSFAIL;
    270 	while (!prioq_insert(&pqfail, &pe))
    271 		nomem();
    272 }
    273 
    274 void
    275 pqstart()
    276 {
    277 	readsubdir rs;
    278 	int x;
    279 	unsigned long id;
    280 
    281 	readsubdir_init(&rs, "info", pausedir);
    282 
    283 	while ((x = readsubdir_next(&rs, &id)))
    284 		if (x > 0)
    285 			pqadd(id);
    286 }
    287 
    288 void
    289 pqfinish()
    290 {
    291 	int c;
    292 	struct prioq_elt pe;
    293 	struct timeval ut[2] = {0};
    294 
    295 	for (c = 0; c < CHANNELS; ++c)
    296 		while (prioq_min(&pqchan[c], &pe)) {
    297 			prioq_delmin(&pqchan[c]);
    298 			fnmake_chanaddr(pe.id, c, &fn);
    299 			ut[0].tv_sec = ut[1].tv_sec = pe.dt;
    300 			if (utimes(fn.s, ut) == -1)
    301 				log3("warning: unable to utime ", fn.s, "; message will be retried too soon\n");
    302 		}
    303 }
    304 
    305 void
    306 pqrun()
    307 {
    308 	int c;
    309 	unsigned int i;
    310 	for (c = 0; c < CHANNELS; ++c)
    311 		if (pqchan[c].p)
    312 			if (pqchan[c].len)
    313 				for (i = 0; i < pqchan[c].len; ++i)
    314 					pqchan[c].p[i].dt = recent;
    315 }
    316 
    317 
    318 /* this file is too long ---------------------------------------------- JOBS */
    319 
    320 struct job {
    321 	int refs;		/* if 0, this struct is unused */
    322 	unsigned long id;
    323 	int channel;
    324 	datetime_sec retry;
    325 	stralloc sender;
    326 	int numtodo;
    327 	int flaghiteof;
    328 	int flagdying;
    329 }
    330 ;
    331 
    332 int numjobs;
    333 struct job *jo;
    334 
    335 void
    336 job_init()
    337 {
    338 	int j;
    339 	while (!(jo = (struct job *)alloc(numjobs * sizeof(struct job))))
    340 		nomem();
    341 	for (j = 0; j < numjobs; ++j) {
    342 		jo[j].refs = 0;
    343 		jo[j].sender.s = 0;
    344 	}
    345 }
    346 
    347 int
    348 job_avail()
    349 {
    350 	int j;
    351 	for (j = 0; j < numjobs; ++j)
    352 		if (!jo[j].refs)
    353 			return 1;
    354 	return 0;
    355 }
    356 
    357 int
    358 job_open(unsigned long id, int channel)
    359 {
    360 	int j;
    361 	for (j = 0; j < numjobs; ++j)
    362 		if (!jo[j].refs)
    363 			break;
    364 	if (j == numjobs)
    365 		return -1;
    366 	jo[j].refs = 1;
    367 	jo[j].id = id;
    368 	jo[j].channel = channel;
    369 	jo[j].numtodo = 0;
    370 	jo[j].flaghiteof = 0;
    371 	return j;
    372 }
    373 
    374 void
    375 job_close(int j)
    376 {
    377 	struct prioq_elt pe;
    378 	struct stat st;
    379 
    380 	if (0 < --jo[j].refs)
    381 		return;
    382 
    383 	pe.id = jo[j].id;
    384 	pe.dt = jo[j].retry;
    385 	if (jo[j].flaghiteof && !jo[j].numtodo) {
    386 		fnmake_chanaddr(jo[j].id, jo[j].channel, &fn);
    387 		if (unlink(fn.s) == -1) {
    388 			log3("warning: unable to unlink ", fn.s, "; will try again later\n");
    389 			pe.dt = now() + SLEEP_SYSFAIL;
    390 		} else {
    391 			int c;
    392 			for (c = 0; c < CHANNELS; ++c)
    393 				if (c != jo[j].channel) {
    394 					fnmake_chanaddr(jo[j].id, c, &fn);
    395 					if (stat(fn.s, &st) == 0)
    396 						return;	/* more channels going */
    397 					if (errno != error_noent) {
    398 						log3("warning: unable to stat ", fn.s, "\n");
    399 						break;	/* this is the only
    400 							 * reason for HOPEFULLY */
    401 					}
    402 				}
    403 			pe.dt = now();
    404 			while (!prioq_insert(&pqdone, &pe))
    405 				nomem();
    406 			return;
    407 		}
    408 	}
    409 
    410 	while (!prioq_insert(&pqchan[jo[j].channel], &pe))
    411 		nomem();
    412 }
    413 
    414 
    415 /* this file is too long ------------------------------------------- BOUNCES */
    416 
    417 char *
    418 stripvdomprepend(char *recip)
    419 {
    420 	unsigned int i;
    421 	char *domain;
    422 	unsigned int domainlen;
    423 	char *prepend;
    424 
    425 	i = str_rchr(recip, '@');
    426 	if (!recip[i])
    427 		return recip;
    428 	domain = recip + i + 1;
    429 	domainlen = str_len(domain);
    430 
    431 	for (i = 0; i <= domainlen; ++i)
    432 		if ((i == 0) || (i == domainlen) || (domain[i] == '.'))
    433 			if ((prepend = constmap(&mapvdoms, domain + i, domainlen - i))) {
    434 				if (!*prepend)
    435 					break;
    436 				i = str_len(prepend);
    437 				if (str_diffn(recip, prepend, i))
    438 					break;
    439 				if (recip[i] != '-')
    440 					break;
    441 				return recip + i + 1;
    442 			}
    443 	return recip;
    444 }
    445 
    446 stralloc bouncetext = {0};
    447 
    448 void
    449 addbounce(unsigned long id, char *recip, char *report)
    450 {
    451 	int fd;
    452 	unsigned int pos;
    453 	int w;
    454 	while (!stralloc_copys(&bouncetext, "<"))
    455 		nomem();
    456 	while (!stralloc_cats(&bouncetext, stripvdomprepend(recip)))
    457 		nomem();
    458 	for (pos = 0; pos < bouncetext.len; ++pos)
    459 		if (bouncetext.s[pos] == '\n')
    460 			bouncetext.s[pos] = '_';
    461 	while (!stralloc_cats(&bouncetext, ">:\n"))
    462 		nomem();
    463 	while (!stralloc_cats(&bouncetext, report))
    464 		nomem();
    465 	if (report[0])
    466 		if (report[str_len(report) - 1] != '\n')
    467 			while (!stralloc_cats(&bouncetext, "\n"))
    468 				nomem();
    469 	for (pos = bouncetext.len - 2; pos > 0; --pos)
    470 		if (bouncetext.s[pos] == '\n')
    471 			if (bouncetext.s[pos - 1] == '\n')
    472 				bouncetext.s[pos] = '/';
    473 	while (!stralloc_cats(&bouncetext, "\n"))
    474 		nomem();
    475 	fnmake_bounce(id, &fn2);
    476 	for (;;) {
    477 		fd = open_append(fn2.s);
    478 		if (fd != -1)
    479 			break;
    480 		log1("alert: unable to append to bounce message; HELP! sleeping...\n");
    481 		sleep(10);
    482 	}
    483 	pos = 0;
    484 	while (pos < bouncetext.len) {
    485 		w = write(fd, bouncetext.s + pos, bouncetext.len - pos);
    486 		if (w == 0 || w == -1) {
    487 			log1("alert: unable to append to bounce message; HELP! sleeping...\n");
    488 			sleep(10);
    489 		} else
    490 			pos += w;
    491 	}
    492 	close(fd);
    493 }
    494 
    495 int
    496 injectbounce(unsigned long id)
    497 {
    498 	struct qmail qqt;
    499 	struct stat st;
    500 	char *bouncesender;
    501 	char *bouncerecip;
    502 	int r;
    503 	int fd;
    504 	substdio ssread;
    505 	char buf[128];
    506 	char inbuf[128];
    507 	static stralloc sender = {
    508 		0
    509 	};
    510 	static stralloc quoted = {
    511 		0
    512 	};
    513 	datetime_sec birth;
    514 	unsigned long qp;
    515 
    516 	if (!getinfo(&sender, &birth, id))
    517 		return 0;	/* XXX: print warning */
    518 
    519 	/* owner-@host-@[] -> owner-@host */
    520 	if (sender.len >= 5)
    521 		if (str_equal(sender.s + sender.len - 5, "-@[]")) {
    522 			sender.len -= 4;
    523 			sender.s[sender.len - 1] = 0;
    524 		}
    525 
    526 	fnmake_bounce(id, &fn2);
    527 	fnmake_mess(id, &fn);
    528 	if (stat(fn2.s, &st) == -1) {
    529 		if (errno == error_noent)
    530 			return 1;
    531 		log3("warning: unable to stat ", fn2.s, "\n");
    532 		return 0;
    533 	}
    534 	if (str_equal(sender.s, "#@[]"))
    535 		log3("triple bounce: discarding ", fn2.s, "\n");
    536 	else {
    537 		if (qmail_open(&qqt) == -1) {
    538 			log1("warning: unable to start qmail-queue, will try later\n");
    539 			return 0;
    540 		}
    541 		qp = qmail_qp(&qqt);
    542 
    543 		if (*sender.s) {
    544 			bouncesender = "";
    545 			bouncerecip = sender.s;
    546 		} else {
    547 			bouncesender = "#@[]";
    548 			bouncerecip = doublebounceto.s;
    549 		}
    550 
    551 		while (!newfield_datemake(now()))
    552 			nomem();
    553 		qmail_put(&qqt, newfield_date.s, newfield_date.len);
    554 		qmail_puts(&qqt, "From: ");
    555 		while (!quote(&quoted, &bouncefrom))
    556 			nomem();
    557 		qmail_put(&qqt, quoted.s, quoted.len);
    558 		qmail_puts(&qqt, "@");
    559 		qmail_put(&qqt, bouncehost.s, bouncehost.len);
    560 		qmail_puts(&qqt, "\nTo: ");
    561 		while (!quote2(&quoted, bouncerecip))
    562 			nomem();
    563 		/* MAYBE TODO: import Manvendra's MIME bounce handling code? */
    564 		qmail_put(&qqt, quoted.s, quoted.len);
    565 		qmail_puts(&qqt, "\n\
    566 Subject: Failure Notice\n\
    567 \n\
    568 Hi. This is the qmail-send (NightmareMail queue manager) program at ");
    569 		qmail_put(&qqt, bouncehost.s, bouncehost.len);
    570 		qmail_puts(&qqt, *sender.s ? ".\n\
    571 I'm afraid I wasn't able to deliver your message to the following addresses.\n\
    572 This is a permanent error. I've given up. Sorry it didn't work out.\n\
    573 For further assistance, mail the postmaster.\n\
    574 \n\
    575 " : ".\n\
    576 I tried to deliver a bounce message to this address, but the bounce bounced!\n\
    577 \n\
    578 ");
    579 
    580 		fd = open_read(fn2.s);
    581 		if (fd == -1)
    582 			qmail_fail(&qqt);
    583 		else {
    584 			substdio_fdbuf(&ssread, read, fd, inbuf, sizeof(inbuf));
    585 			while ((r = substdio_get(&ssread, buf, sizeof(buf))) > 0)
    586 				qmail_put(&qqt, buf, r);
    587 			close(fd);
    588 			if (r == -1)
    589 				qmail_fail(&qqt);
    590 		}
    591 
    592 		qmail_puts(&qqt, *sender.s ? "--- Below this line is a copy of the message.\n\n" : "--- Below this line is the original bounce.\n\n");
    593 		qmail_puts(&qqt, "Return-Path: <");
    594 		while (!quote2(&quoted, sender.s))
    595 			nomem();
    596 		qmail_put(&qqt, quoted.s, quoted.len);
    597 		qmail_puts(&qqt, ">\n");
    598 
    599 		fd = open_read(fn.s);
    600 		if (fd == -1)
    601 			qmail_fail(&qqt);
    602 		else {
    603 			substdio_fdbuf(&ssread, read, fd, inbuf, sizeof(inbuf));
    604 			while ((r = substdio_get(&ssread, buf, sizeof(buf))) > 0)
    605 				qmail_put(&qqt, buf, r);
    606 			close(fd);
    607 			if (r == -1)
    608 				qmail_fail(&qqt);
    609 		}
    610 
    611 		qmail_from(&qqt, bouncesender);
    612 		qmail_to(&qqt, bouncerecip);
    613 		if (*qmail_close(&qqt)) {
    614 			log1("warning: trouble injecting bounce message, will try later\n");
    615 			return 0;
    616 		}
    617 
    618 		strnum2[fmt_ulong(strnum2, id)] = 0;
    619 		qslog2("bounce msg ", strnum2);
    620 		strnum2[fmt_ulong(strnum2, qp)] = 0;
    621 		log3(" qp ", strnum2, "\n");
    622 	}
    623 	if (unlink(fn2.s) == -1) {
    624 		log3("warning: unable to unlink ", fn2.s, "\n");
    625 		return 0;
    626 	}
    627 	return 1;
    628 }
    629 
    630 /* this file is too long ---------------------------------------------- MAIN */
    631 
    632 int
    633 getcontrols()
    634 {
    635 	if (control_init() == -1)
    636 		return 0;
    637 	if (control_readint(&lifetime, "control/queuelifetime") == -1)
    638 		return 0;
    639 	if (control_readint(&concurrency[0], "control/concurrencylocal") == -1)
    640 		return 0;
    641 	if (control_readint(&concurrency[1], "control/concurrencyremote") == -1)
    642 		return 0;
    643 	if (control_rldef(&envnoathost, "control/envnoathost", 1, "envnoathost") != 1)
    644 		return 0;
    645 	if (control_rldef(&bouncefrom, "control/bouncefrom", 0, "MAILER-DAEMON") != 1)
    646 		return 0;
    647 	if (control_rldef(&bouncehost, "control/bouncehost", 1, "bouncehost") != 1)
    648 		return 0;
    649 	if (control_rldef(&doublebouncehost, "control/doublebouncehost", 1, "doublebouncehost") != 1)
    650 		return 0;
    651 	if (control_rldef(&doublebounceto, "control/doublebounceto", 0, "postmaster") != 1)
    652 		return 0;
    653 	if (!stralloc_cats(&doublebounceto, "@"))
    654 		return 0;
    655 	if (!stralloc_cat(&doublebounceto, &doublebouncehost))
    656 		return 0;
    657 	if (!stralloc_0(&doublebounceto))
    658 		return 0;
    659 	if (control_readfile(&locals, "control/locals", 1) != 1)
    660 		return 0;
    661 	if (!constmap_init(&maplocals, locals.s, locals.len, 0))
    662 		return 0;
    663 	switch (control_readfile(&percenthack, "control/percenthack", 0)) {
    664 	case -1:
    665 		return 0;
    666 	case 0:
    667 		if (!constmap_init(&mappercenthack, "", 0, 0))
    668 			return 0;
    669 		break;
    670 	case 1:
    671 		if (!constmap_init(&mappercenthack, percenthack.s, percenthack.len, 0))
    672 			return 0;
    673 		break;
    674 	}
    675 	switch (control_readfile(&vdoms, "control/virtualdomains", 0)) {
    676 	case -1:
    677 		return 0;
    678 	case 0:
    679 		if (!constmap_init(&mapvdoms, "", 0, 1))
    680 			return 0;
    681 		break;
    682 	case 1:
    683 		if (!constmap_init(&mapvdoms, vdoms.s, vdoms.len, 1))
    684 			return 0;
    685 		break;
    686 	}
    687 	return 1;
    688 }
    689 
    690 stralloc newlocals = {0};
    691 stralloc newvdoms = {0};
    692 
    693 void
    694 regetcontrols()
    695 {
    696 	int r;
    697 
    698 	if (control_readfile(&newlocals, "control/locals", 1) != 1) {
    699 		log1("alert: unable to reread control/locals\n");
    700 		return;
    701 	}
    702 	r = control_readfile(&newvdoms, "control/virtualdomains", 0);
    703 	if (r == -1) {
    704 		log1("alert: unable to reread control/virtualdomains\n");
    705 		return;
    706 	}
    707 
    708 	constmap_free(&maplocals);
    709 	constmap_free(&mapvdoms);
    710 
    711 	while (!stralloc_copy(&locals, &newlocals))
    712 		nomem();
    713 	while (!constmap_init(&maplocals, locals.s, locals.len, 0))
    714 		nomem();
    715 
    716 	if (r) {
    717 		while (!stralloc_copy(&vdoms, &newvdoms))
    718 			nomem();
    719 		while (!constmap_init(&mapvdoms, vdoms.s, vdoms.len, 1))
    720 			nomem();
    721 	} else
    722 		while (!constmap_init(&mapvdoms, "", 0, 1))
    723 			nomem();
    724 }
    725 
    726 void
    727 reread()
    728 {
    729 	if (chdir(auto_qmail) == -1) {
    730 		log1("alert: unable to reread controls: unable to switch to home directory\n");
    731 		return;
    732 	}
    733 	regetcontrols();
    734 	while (chdir("queue") == -1) {
    735 		log1("alert: unable to switch back to queue directory; HELP! sleeping...\n");
    736 		sleep(10);
    737 	}
    738 }
    739 
    740 int
    741 main(void)
    742 {
    743 	int fd;
    744 	datetime_sec wakeup;
    745 	fd_set rfds;
    746 	fd_set wfds;
    747 	int nfds;
    748 	struct timeval tv;
    749 	int c;
    750 
    751 	/* Bit of a brainage here I guess:
    752 	 * for use under qmail-start-np, should we accept, on our argv, paths to
    753 	 * our channelspawns' inputs and outputs?
    754 	 */
    755 	if (chdir(auto_qmail) == -1) {
    756 		log1("alert: cannot start: unable to switch to home directory\n");
    757 		_exit(111);
    758 	}
    759 	if (!getcontrols()) {
    760 		log1("alert: cannot start: unable to read controls\n");
    761 		_exit(111);
    762 	}
    763 	if (chdir("queue") == -1) {
    764 		log1("alert: cannot start: unable to switch to queue directory\n");
    765 		_exit(111);
    766 	}
    767 	sig_pipeignore();
    768 	sig_termcatch(sigterm);
    769 	sig_alarmcatch(sigalrm);
    770 	sig_hangupcatch(sighup);
    771 	sig_childdefault();
    772 	umask(077);
    773 
    774 	fd = open_write("lock/sendmutex");
    775 	if (fd == -1) {
    776 		log1("alert: cannot start: unable to open mutex\n");
    777 		_exit(111);
    778 	}
    779 	if (lock_exnb(fd) == -1) {
    780 		log1("alert: cannot start: qmail-send is already running\n");
    781 		_exit(111);
    782 	}
    783 
    784 	numjobs = 0;
    785 	for (c = 0; c < CHANNELS; ++c) {
    786 		char ch;
    787 		int u;
    788 		int r;
    789 		do
    790 			r = read(chanfdin[c], &ch, 1);
    791 		while ((r == -1) && (errno == error_intr));
    792 		if (r < 1) {
    793 			log1("alert: cannot start: hath the daemon spawn no fire? one of my delivery channels didn't start correctly.\n");
    794 			_exit(111);
    795 		}
    796 		u = (unsigned int)(unsigned char)ch;
    797 		if (concurrency[c] > u)
    798 			concurrency[c] = u;
    799 		numjobs += concurrency[c];
    800 	}
    801 
    802 	fnmake_init();
    803 
    804 	comm_init();
    805 
    806 	pqstart();
    807 	job_init();
    808 	del_init();
    809 	pass_init();
    810 	todo_init();
    811 	cleanup_init();
    812 
    813 	while (!flagexitasap || !del_canexit()) {
    814 		recent = now();
    815 
    816 		if (flagrunasap) {
    817 			flagrunasap = 0;
    818 			pqrun();
    819 		}
    820 		if (flagreadasap) {
    821 			flagreadasap = 0;
    822 			reread();
    823 		}
    824 
    825 		wakeup = recent + SLEEP_FOREVER;
    826 		FD_ZERO(&rfds);
    827 		FD_ZERO(&wfds);
    828 		nfds = 1;
    829 
    830 		comm_selprep(&nfds, &wfds); /* this isn't arcane at all. /s */
    831 		del_selprep(&nfds, &rfds);
    832 		pass_selprep(&wakeup);
    833 		todo_selprep(&nfds, &rfds, &wakeup);
    834 		cleanup_selprep(&wakeup);
    835 		/* Lightning brainage:
    836 		 * So here's what all that actually does.
    837 		 * It prepares the writability for communication queues.
    838 		 * It prepares the readability for delivery report FDs.
    839 		 * It adjusts the earliest time the loop will wake up for the passes.
    840 		 * It adjusts wakeup and prepares readability for the todo.
    841 		 * and it adjusts wakeup for cleanup.
    842 		 */
    843 
    844 		if (wakeup <= recent)
    845 			tv.tv_sec = 0;
    846 		else
    847 			tv.tv_sec = wakeup - recent + SLEEP_FUZZ;
    848 		tv.tv_usec = 0;
    849 
    850 		if (select(nfds, &rfds, &wfds, NULL, &tv) == -1)
    851 			if (errno == error_intr)
    852 				;
    853 			else
    854 				log1("warning: trouble in select\n");
    855 		else {
    856 			recent = now();
    857 
    858 			//XXX:	should have a queue structure
    859 			comm_do(&wfds);
    860 			del_do(&rfds);
    861 			todo_do(&rfds);
    862 			pass_do();
    863 			cleanup_do();
    864 		}
    865 	}
    866 	pqfinish();
    867 	log1("status: exiting\n");
    868 	_exit(0);
    869 };