Home | History | Annotate | Line # | Download | only in flush
      1 /*	$NetBSD: flush.c,v 1.4 2022/10/08 16:12:45 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	flush 8
      6 /* SUMMARY
      7 /*	Postfix fast flush server
      8 /* SYNOPSIS
      9 /*	\fBflush\fR [generic Postfix daemon options]
     10 /* DESCRIPTION
     11 /*	The \fBflush\fR(8) server maintains a record of deferred
     12 /*	mail by destination.
     13 /*	This information is used to improve the performance of the SMTP
     14 /*	\fBETRN\fR request, and of its command-line equivalent,
     15 /*	"\fBsendmail -qR\fR" or "\fBpostqueue -f\fR".
     16 /*	This program expects to be run from the \fBmaster\fR(8) process
     17 /*	manager.
     18 /*
     19 /*	The record is implemented as a per-destination logfile with
     20 /*	as contents the queue IDs of deferred mail. A logfile is
     21 /*	append-only, and is truncated when delivery is requested
     22 /*	for the corresponding destination. A destination is the
     23 /*	part on the right-hand side of the right-most \fB@\fR in
     24 /*	an email address.
     25 /*
     26 /*	Per-destination logfiles of deferred mail are maintained only for
     27 /*	eligible destinations. The list of eligible destinations is
     28 /*	specified with the \fBfast_flush_domains\fR configuration parameter,
     29 /*	which defaults to \fB$relay_domains\fR.
     30 /*
     31 /*	This server implements the following requests:
     32 /* .IP "\fBadd\fI sitename queueid\fR"
     33 /*	Inform the \fBflush\fR(8) server that the message with the specified
     34 /*	queue ID is queued for the specified destination.
     35 /* .IP "\fBsend_site\fI sitename\fR"
     36 /*	Request delivery of mail that is queued for the specified
     37 /*	destination.
     38 /* .IP "\fBsend_file\fI queueid\fR"
     39 /*	Request delivery of the specified deferred message.
     40 /* .IP \fBrefresh\fR
     41 /*	Refresh non-empty per-destination logfiles that were not read in
     42 /*	\fB$fast_flush_refresh_time\fR hours, by simulating
     43 /*	send requests (see above) for the corresponding destinations.
     44 /* .sp
     45 /*	Delete empty per-destination logfiles that were not updated in
     46 /*	\fB$fast_flush_purge_time\fR days.
     47 /* .sp
     48 /*	This request completes in the background.
     49 /* .IP \fBpurge\fR
     50 /*	Do a \fBrefresh\fR for all per-destination logfiles.
     51 /* SECURITY
     52 /* .ad
     53 /* .fi
     54 /*	The \fBflush\fR(8) server is not security-sensitive. It does not
     55 /*	talk to the network, and it does not talk to local users.
     56 /*	The fast flush server can run chrooted at fixed low privilege.
     57 /* DIAGNOSTICS
     58 /*	Problems and transactions are logged to \fBsyslogd\fR(8)
     59 /*	or \fBpostlogd\fR(8).
     60 /* BUGS
     61 /*	Fast flush logfiles are truncated only after a "send"
     62 /*	request, not when mail is actually delivered, and therefore can
     63 /*	accumulate outdated or redundant data. In order to maintain sanity,
     64 /*	"refresh" must be executed periodically. This can
     65 /*	be automated with a suitable wakeup timer setting in the
     66 /*	\fBmaster.cf\fR configuration file.
     67 /*
     68 /*	Upon receipt of a request to deliver mail for an eligible
     69 /*	destination, the \fBflush\fR(8) server requests delivery of all messages
     70 /*	that are listed in that destination's logfile, regardless of the
     71 /*	recipients of those messages. This is not an issue for mail
     72 /*	that is sent to a \fBrelay_domains\fR destination because
     73 /*	such mail typically only has recipients in one domain.
     74 /* CONFIGURATION PARAMETERS
     75 /* .ad
     76 /* .fi
     77 /*	Changes to \fBmain.cf\fR are picked up automatically as \fBflush\fR(8)
     78 /*	processes run for only a limited amount of time. Use the command
     79 /*	"\fBpostfix reload\fR" to speed up a change.
     80 /*
     81 /*	The text below provides only a parameter summary. See
     82 /*	\fBpostconf\fR(5) for more details including examples.
     83 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
     84 /*	The default location of the Postfix main.cf and master.cf
     85 /*	configuration files.
     86 /* .IP "\fBdaemon_timeout (18000s)\fR"
     87 /*	How much time a Postfix daemon process may take to handle a
     88 /*	request before it is terminated by a built-in watchdog timer.
     89 /* .IP "\fBfast_flush_domains ($relay_domains)\fR"
     90 /*	Optional list of destinations that are eligible for per-destination
     91 /*	logfiles with mail that is queued to those destinations.
     92 /* .IP "\fBfast_flush_refresh_time (12h)\fR"
     93 /*	The time after which a non-empty but unread per-destination "fast
     94 /*	flush" logfile needs to be refreshed.
     95 /* .IP "\fBfast_flush_purge_time (7d)\fR"
     96 /*	The time after which an empty per-destination "fast flush" logfile
     97 /*	is deleted.
     98 /* .IP "\fBipc_timeout (3600s)\fR"
     99 /*	The time limit for sending or receiving information over an internal
    100 /*	communication channel.
    101 /* .IP "\fBmax_idle (100s)\fR"
    102 /*	The maximum amount of time that an idle Postfix daemon process waits
    103 /*	for an incoming connection before terminating voluntarily.
    104 /* .IP "\fBmax_use (100)\fR"
    105 /*	The maximal number of incoming connections that a Postfix daemon
    106 /*	process will service before terminating voluntarily.
    107 /* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR"
    108 /*	A list of Postfix features where the pattern "example.com" also
    109 /*	matches subdomains of example.com,
    110 /*	instead of requiring an explicit ".example.com" pattern.
    111 /* .IP "\fBprocess_id (read-only)\fR"
    112 /*	The process ID of a Postfix command or daemon process.
    113 /* .IP "\fBprocess_name (read-only)\fR"
    114 /*	The process name of a Postfix command or daemon process.
    115 /* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
    116 /*	The location of the Postfix top-level queue directory.
    117 /* .IP "\fBsyslog_facility (mail)\fR"
    118 /*	The syslog facility of Postfix logging.
    119 /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
    120 /*	A prefix that is prepended to the process name in syslog
    121 /*	records, so that, for example, "smtpd" becomes "prefix/smtpd".
    122 /* .PP
    123 /*	Available in Postfix 3.3 and later:
    124 /* .IP "\fBservice_name (read-only)\fR"
    125 /*	The master.cf service name of a Postfix daemon process.
    126 /* FILES
    127 /*	/var/spool/postfix/flush, "fast flush" logfiles.
    128 /* SEE ALSO
    129 /*	smtpd(8), SMTP server
    130 /*	qmgr(8), queue manager
    131 /*	postconf(5), configuration parameters
    132 /*	master(5), generic daemon options
    133 /*	master(8), process manager
    134 /*	postlogd(8), Postfix logging
    135 /*	syslogd(8), system logging
    136 /* README FILES
    137 /* .ad
    138 /* .fi
    139 /*	Use "\fBpostconf readme_directory\fR" or
    140 /*	"\fBpostconf html_directory\fR" to locate this information.
    141 /* .na
    142 /* .nf
    143 /*	ETRN_README, Postfix ETRN howto
    144 /* LICENSE
    145 /* .ad
    146 /* .fi
    147 /*	The Secure Mailer license must be distributed with this software.
    148 /* HISTORY
    149 /*	This service was introduced with Postfix version 1.0.
    150 /* AUTHOR(S)
    151 /*	Wietse Venema
    152 /*	IBM T.J. Watson Research
    153 /*	P.O. Box 704
    154 /*	Yorktown Heights, NY 10598, USA
    155 /*
    156 /*	Wietse Venema
    157 /*	Google, Inc.
    158 /*	111 8th Avenue
    159 /*	New York, NY 10011, USA
    160 /*--*/
    161 
    162 /* System library. */
    163 
    164 #include <sys_defs.h>
    165 #include <sys/stat.h>
    166 #include <sys/time.h>
    167 #include <unistd.h>
    168 #include <stdlib.h>
    169 #include <utime.h>
    170 #include <errno.h>
    171 #include <ctype.h>
    172 #include <string.h>
    173 
    174 /* Utility library. */
    175 
    176 #include <msg.h>
    177 #include <events.h>
    178 #include <vstream.h>
    179 #include <vstring.h>
    180 #include <vstring_vstream.h>
    181 #include <myflock.h>
    182 #include <htable.h>
    183 #include <dict.h>
    184 #include <scan_dir.h>
    185 #include <stringops.h>
    186 #include <safe_open.h>
    187 #include <warn_stat.h>
    188 #include <midna_domain.h>
    189 
    190 /* Global library. */
    191 
    192 #include <mail_params.h>
    193 #include <mail_version.h>
    194 #include <mail_queue.h>
    195 #include <mail_proto.h>
    196 #include <mail_flush.h>
    197 #include <flush_clnt.h>
    198 #include <mail_conf.h>
    199 #include <mail_scan_dir.h>
    200 #include <maps.h>
    201 #include <domain_list.h>
    202 #include <match_parent_style.h>
    203 
    204 /* Single server skeleton. */
    205 
    206 #include <mail_server.h>
    207 
    208 /* Application-specific. */
    209 
    210  /*
    211   * Tunable parameters. The fast_flush_domains parameter is not defined here,
    212   * because it is also used by the global library, and therefore is owned by
    213   * the library.
    214   */
    215 int     var_fflush_refresh;
    216 int     var_fflush_purge;
    217 
    218  /*
    219   * Flush policy stuff.
    220   */
    221 static DOMAIN_LIST *flush_domains;
    222 
    223  /*
    224   * Some hard-wired policy: how many queue IDs we remember while we're
    225   * flushing a logfile (duplicate elimination). Sites with 1000+ emails
    226   * queued should arrange for permanent connectivity.
    227   */
    228 #define FLUSH_DUP_FILTER_SIZE	10000	/* graceful degradation */
    229 
    230  /*
    231   * Silly little macros.
    232   */
    233 #define STR(x)			vstring_str(x)
    234 #define STREQ(x,y)		(STRREF(x) == STRREF(y) || strcmp(x,y) == 0)
    235 
    236  /*
    237   * Forward declarations resulting from breaking up routines according to
    238   * name space: domain names versus safe-to-use pathnames.
    239   */
    240 static int flush_add_path(const char *, const char *);
    241 static int flush_send_path(const char *, int);
    242 
    243  /*
    244   * Do we only refresh the per-destination logfile, or do we really request
    245   * mail delivery as if someone sent ETRN? If the latter, we must override
    246   * information about unavailable hosts or unavailable transports.
    247   *
    248   * When selectively flushing deferred mail, we need to override the queue
    249   * manager's "dead destination" information and unthrottle transports and
    250   * queues. There are two options:
    251   *
    252   * - Unthrottle all transports and queues before we move mail to the incoming
    253   * queue. This is less accurate, but has the advantage when flushing lots of
    254   * mail, because Postfix can skip delivery of flushed messages after it
    255   * discovers that a destination is (still) unavailable.
    256   *
    257   * - Unthrottle some transports and queues after the queue manager moves mail
    258   * to the active queue. This is more accurate, but has the disadvantage when
    259   * flushing lots of mail, because Postfix cannot skip delivery of flushed
    260   * messages after it discovers that a destination is (still) unavailable.
    261   */
    262 #define REFRESH_ONLY		0
    263 #define UNTHROTTLE_BEFORE	(1<<0)
    264 #define UNTHROTTLE_AFTER	(1<<1)
    265 
    266 /* flush_site_to_path - convert domain or [addr] to harmless string */
    267 
    268 static VSTRING *flush_site_to_path(VSTRING *path, const char *site)
    269 {
    270     const char *ptr;
    271     int     ch;
    272 
    273     /*
    274      * Convert the name to ASCII, so that we don't to end up with non-ASCII
    275      * names in the file system. The IDNA library functions fold case.
    276      */
    277 #ifndef NO_EAI
    278     if ((site = midna_domain_to_ascii(site)) == 0)
    279 	return (0);
    280 #endif
    281 
    282     /*
    283      * Allocate buffer on the fly; caller still needs to clean up.
    284      */
    285     if (path == 0)
    286 	path = vstring_alloc(10);
    287 
    288     /*
    289      * Mask characters that could upset the name-to-queue-file mapping code.
    290      */
    291     for (ptr = site; (ch = *(unsigned const char *) ptr) != 0; ptr++)
    292 	if (ISALNUM(ch))
    293 	    VSTRING_ADDCH(path, tolower(ch));
    294 	else
    295 	    VSTRING_ADDCH(path, '_');
    296     VSTRING_TERMINATE(path);
    297 
    298     if (msg_verbose)
    299 	msg_info("site %s to path %s", site, STR(path));
    300 
    301     return (path);
    302 }
    303 
    304 /* flush_add_service - append queue ID to per-site fast flush logfile */
    305 
    306 static int flush_add_service(const char *site, const char *queue_id)
    307 {
    308     const char *myname = "flush_add_service";
    309     VSTRING *site_path;
    310     int     status;
    311 
    312     if (msg_verbose)
    313 	msg_info("%s: site %s queue_id %s", myname, site, queue_id);
    314 
    315     /*
    316      * If this site is not eligible for logging, deny the request.
    317      */
    318     if (domain_list_match(flush_domains, site) == 0)
    319 	return (flush_domains->error ? FLUSH_STAT_FAIL : FLUSH_STAT_DENY);
    320 
    321     /*
    322      * Map site to path and update log.
    323      */
    324     if ((site_path = flush_site_to_path((VSTRING *) 0, site)) == 0)
    325 	return (FLUSH_STAT_DENY);
    326     status = flush_add_path(STR(site_path), queue_id);
    327     vstring_free(site_path);
    328 
    329     return (status);
    330 }
    331 
    332 /* flush_add_path - add record to log */
    333 
    334 static int flush_add_path(const char *path, const char *queue_id)
    335 {
    336     const char *myname = "flush_add_path";
    337     VSTREAM *log;
    338 
    339     /*
    340      * Sanity check.
    341      */
    342     if (!mail_queue_id_ok(path))
    343 	return (FLUSH_STAT_BAD);
    344 
    345     /*
    346      * Open the logfile or bust.
    347      */
    348     if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path,
    349 			       O_CREAT | O_APPEND | O_WRONLY, 0600)) == 0)
    350 	msg_fatal("%s: open fast flush logfile %s: %m", myname, path);
    351 
    352     /*
    353      * We must lock the logfile, so that we don't lose information due to
    354      * concurrent access. If the lock takes too long, the Postfix watchdog
    355      * will eventually take care of the problem, but it will take a while.
    356      */
    357     if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
    358 	msg_fatal("%s: lock fast flush logfile %s: %m", myname, path);
    359 
    360     /*
    361      * Append the queue ID. With 15 bits of microsecond time, a queue ID is
    362      * not recycled often enough for false hits to be a problem. If it does,
    363      * then we could add other signature information, such as the file size
    364      * in bytes.
    365      */
    366     vstream_fprintf(log, "%s\n", queue_id);
    367     if (vstream_fflush(log))
    368 	msg_warn("write fast flush logfile %s: %m", path);
    369 
    370     /*
    371      * Clean up.
    372      */
    373     if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
    374 	msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path);
    375     if (vstream_fclose(log) != 0)
    376 	msg_warn("write fast flush logfile %s: %m", path);
    377 
    378     return (FLUSH_STAT_OK);
    379 }
    380 
    381 /* flush_send_service - flush mail queued for site */
    382 
    383 static int flush_send_service(const char *site, int how)
    384 {
    385     const char *myname = "flush_send_service";
    386     VSTRING *site_path;
    387     int     status;
    388 
    389     if (msg_verbose)
    390 	msg_info("%s: site %s", myname, site);
    391 
    392     /*
    393      * If this site is not eligible for logging, deny the request.
    394      */
    395     if (domain_list_match(flush_domains, site) == 0)
    396 	return (flush_domains->error ? FLUSH_STAT_FAIL : FLUSH_STAT_DENY);
    397 
    398     /*
    399      * Map site name to path name and flush the log.
    400      */
    401     if ((site_path = flush_site_to_path((VSTRING *) 0, site)) == 0)
    402 	return (FLUSH_STAT_DENY);
    403     status = flush_send_path(STR(site_path), how);
    404     vstring_free(site_path);
    405 
    406     return (status);
    407 }
    408 
    409 /* flush_one_file - move one queue file to incoming queue */
    410 
    411 static int flush_one_file(const char *queue_id, VSTRING *queue_file,
    412 			          struct utimbuf * tbuf, int how)
    413 {
    414     const char *myname = "flush_one_file";
    415     const char *queue_name;
    416     const char *path;
    417 
    418     /*
    419      * Some other instance of this program may flush some logfile and may
    420      * just have moved this queue file to the incoming queue.
    421      */
    422     for (queue_name = MAIL_QUEUE_DEFERRED; /* see below */ ;
    423 	 queue_name = MAIL_QUEUE_INCOMING) {
    424 	path = mail_queue_path(queue_file, queue_name, queue_id);
    425 	if (utime(path, tbuf) == 0)
    426 	    break;
    427 	if (errno != ENOENT)
    428 	    msg_warn("%s: update %s time stamps: %m", myname, path);
    429 	if (STREQ(queue_name, MAIL_QUEUE_INCOMING))
    430 	    return (0);
    431     }
    432 
    433     /*
    434      * With the UNTHROTTLE_AFTER strategy, we leave it up to the queue
    435      * manager to unthrottle transports and queues as it reads recipients
    436      * from a queue file. We request this unthrottle operation by setting the
    437      * group read permission bit.
    438      *
    439      * Note: we must avoid using chmod(). It is not only slower than fchmod()
    440      * but it is also less secure. With chmod(), an attacker could repeatedly
    441      * send requests to the flush server and trick it into changing
    442      * permissions of non-queue files, by exploiting a race condition.
    443      *
    444      * We use safe_open() because we don't validate the file content before
    445      * modifying the file status.
    446      */
    447     if (how & UNTHROTTLE_AFTER) {
    448 	VSTRING *why;
    449 	struct stat st;
    450 	VSTREAM *fp;
    451 
    452 	for (why = vstring_alloc(1); /* see below */ ;
    453 	     queue_name = MAIL_QUEUE_INCOMING,
    454 	     path = mail_queue_path(queue_file, queue_name, queue_id)) {
    455 	    if ((fp = safe_open(path, O_RDWR, 0, &st, -1, -1, why)) != 0)
    456 		break;
    457 	    if (errno != ENOENT)
    458 		msg_warn("%s: open %s: %s", myname, path, STR(why));
    459 	    if (errno != ENOENT || STREQ(queue_name, MAIL_QUEUE_INCOMING)) {
    460 		vstring_free(why);
    461 		return (0);
    462 	    }
    463 	}
    464 	vstring_free(why);
    465 	if ((st.st_mode & MAIL_QUEUE_STAT_READY) != MAIL_QUEUE_STAT_READY) {
    466 	    (void) vstream_fclose(fp);
    467 	    return (0);
    468 	}
    469 	if (fchmod(vstream_fileno(fp), st.st_mode | MAIL_QUEUE_STAT_UNTHROTTLE) < 0)
    470 	    msg_warn("%s: fchmod %s: %m", myname, path);
    471 	(void) vstream_fclose(fp);
    472     }
    473 
    474     /*
    475      * Move the file to the incoming queue, if it isn't already there.
    476      */
    477     if (STREQ(queue_name, MAIL_QUEUE_INCOMING) == 0
    478 	&& mail_queue_rename(queue_id, queue_name, MAIL_QUEUE_INCOMING) < 0
    479 	&& errno != ENOENT)
    480 	msg_warn("%s: rename from %s to %s: %m",
    481 		 path, queue_name, MAIL_QUEUE_INCOMING);
    482 
    483     /*
    484      * If we got here, we achieved something, so let's claim success.
    485      */
    486     return (1);
    487 }
    488 
    489 /* flush_send_path - flush logfile file */
    490 
    491 static int flush_send_path(const char *path, int how)
    492 {
    493     const char *myname = "flush_send_path";
    494     VSTRING *queue_id;
    495     VSTRING *queue_file;
    496     VSTREAM *log;
    497     struct utimbuf tbuf;
    498     static char qmgr_flush_trigger[] = {
    499 	QMGR_REQ_FLUSH_DEAD,		/* flush dead site/transport cache */
    500     };
    501     static char qmgr_scan_trigger[] = {
    502 	QMGR_REQ_SCAN_INCOMING,		/* scan incoming queue */
    503     };
    504     HTABLE *dup_filter;
    505     int     count;
    506 
    507     /*
    508      * Sanity check.
    509      */
    510     if (!mail_queue_id_ok(path))
    511 	return (FLUSH_STAT_BAD);
    512 
    513     /*
    514      * Open the logfile. If the file does not exist, then there is no queued
    515      * mail for this destination.
    516      */
    517     if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path, O_RDWR, 0600)) == 0) {
    518 	if (errno != ENOENT)
    519 	    msg_fatal("%s: open fast flush logfile %s: %m", myname, path);
    520 	return (FLUSH_STAT_OK);
    521     }
    522 
    523     /*
    524      * We must lock the logfile, so that we don't lose information when it is
    525      * truncated. Unfortunately, this means that the file can be locked for a
    526      * significant amount of time. If things really get stuck the Postfix
    527      * watchdog will take care of it.
    528      */
    529     if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0)
    530 	msg_fatal("%s: lock fast flush logfile %s: %m", myname, path);
    531 
    532     /*
    533      * With the UNTHROTTLE_BEFORE strategy, we ask the queue manager to
    534      * unthrottle all transports and queues before we move a deferred queue
    535      * file to the incoming queue. This minimizes a race condition where the
    536      * queue manager seizes a queue file before it knows that we want to
    537      * flush that message.
    538      *
    539      * This reduces the race condition time window to a very small amount (the
    540      * flush server does not really know when the queue manager reads its
    541      * command fifo). But there is a worse race, where the queue manager
    542      * moves a deferred queue file to the active queue before we have a
    543      * chance to expedite its delivery.
    544      */
    545     if (how & UNTHROTTLE_BEFORE)
    546 	mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service,
    547 		     qmgr_flush_trigger, sizeof(qmgr_flush_trigger));
    548 
    549     /*
    550      * This is the part that dominates running time: schedule the listed
    551      * queue files for delivery by updating their file time stamps and by
    552      * moving them from the deferred queue to the incoming queue. This should
    553      * take no more than a couple seconds under normal conditions. Filter out
    554      * duplicate queue file names to avoid hammering the file system, with
    555      * some finite limit on the amount of memory that we are willing to
    556      * sacrifice for duplicate filtering. Graceful degradation.
    557      *
    558      * By moving selected queue files from the deferred queue to the incoming
    559      * queue we optimize for the case where most deferred mail is for other
    560      * sites. If that assumption does not hold, i.e. all deferred mail is for
    561      * the same site, then doing a "fast flush" will cost more disk I/O than
    562      * a "slow flush" that delivers the entire deferred queue. This penalty
    563      * is only temporary - it will go away after we unite the active queue
    564      * and the incoming queue.
    565      */
    566     queue_id = vstring_alloc(10);
    567     queue_file = vstring_alloc(10);
    568     dup_filter = htable_create(10);
    569     tbuf.actime = tbuf.modtime = event_time();
    570     for (count = 0; vstring_get_nonl(queue_id, log) != VSTREAM_EOF; count++) {
    571 	if (!mail_queue_id_ok(STR(queue_id))) {
    572 	    msg_warn("bad queue id \"%.30s...\" in fast flush logfile %s",
    573 		     STR(queue_id), path);
    574 	    continue;
    575 	}
    576 	if (dup_filter->used >= FLUSH_DUP_FILTER_SIZE
    577 	    || htable_find(dup_filter, STR(queue_id)) == 0) {
    578 	    if (msg_verbose)
    579 		msg_info("%s: logfile %s: update queue file %s time stamps",
    580 			 myname, path, STR(queue_id));
    581 	    if (dup_filter->used <= FLUSH_DUP_FILTER_SIZE)
    582 		htable_enter(dup_filter, STR(queue_id), 0);
    583 	    count += flush_one_file(STR(queue_id), queue_file, &tbuf, how);
    584 	} else {
    585 	    if (msg_verbose)
    586 		msg_info("%s: logfile %s: skip queue file %s as duplicate",
    587 			 myname, path, STR(queue_file));
    588 	}
    589     }
    590     htable_free(dup_filter, (void (*) (void *)) 0);
    591     vstring_free(queue_file);
    592     vstring_free(queue_id);
    593 
    594     /*
    595      * Truncate the fast flush log.
    596      */
    597     if (count > 0 && ftruncate(vstream_fileno(log), (off_t) 0) < 0)
    598 	msg_fatal("%s: truncate fast flush logfile %s: %m", myname, path);
    599 
    600     /*
    601      * Workaround for noatime mounts. Use futimes() if available.
    602      */
    603     (void) utimes(VSTREAM_PATH(log), (struct timeval *) 0);
    604 
    605     /*
    606      * Request delivery and clean up.
    607      */
    608     if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0)
    609 	msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path);
    610     if (vstream_fclose(log) != 0)
    611 	msg_warn("%s: read fast flush logfile %s: %m", myname, path);
    612     if (count > 0) {
    613 	if (msg_verbose)
    614 	    msg_info("%s: requesting delivery for logfile %s", myname, path);
    615 	mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service,
    616 		     qmgr_scan_trigger, sizeof(qmgr_scan_trigger));
    617     }
    618     return (FLUSH_STAT_OK);
    619 }
    620 
    621 /* flush_send_file_service - flush one queue file */
    622 
    623 static int flush_send_file_service(const char *queue_id)
    624 {
    625     const char *myname = "flush_send_file_service";
    626     VSTRING *queue_file;
    627     struct utimbuf tbuf;
    628     static char qmgr_scan_trigger[] = {
    629 	QMGR_REQ_SCAN_INCOMING,		/* scan incoming queue */
    630     };
    631 
    632     /*
    633      * Sanity check.
    634      */
    635     if (!mail_queue_id_ok(queue_id))
    636 	return (FLUSH_STAT_BAD);
    637 
    638     if (msg_verbose)
    639 	msg_info("%s: requesting delivery for queue_id %s", myname, queue_id);
    640 
    641     queue_file = vstring_alloc(30);
    642     tbuf.actime = tbuf.modtime = event_time();
    643     if (flush_one_file(queue_id, queue_file, &tbuf, UNTHROTTLE_AFTER) > 0)
    644 	mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service,
    645 		     qmgr_scan_trigger, sizeof(qmgr_scan_trigger));
    646     vstring_free(queue_file);
    647 
    648     return (FLUSH_STAT_OK);
    649 }
    650 
    651 /* flush_refresh_service - refresh logfiles beyond some age */
    652 
    653 static int flush_refresh_service(int max_age)
    654 {
    655     const char *myname = "flush_refresh_service";
    656     SCAN_DIR *scan;
    657     char   *site_path;
    658     struct stat st;
    659     VSTRING *path = vstring_alloc(10);
    660 
    661     scan = scan_dir_open(MAIL_QUEUE_FLUSH);
    662     while ((site_path = mail_scan_dir_next(scan)) != 0) {
    663 	if (!mail_queue_id_ok(site_path))
    664 	    continue;				/* XXX grumble. */
    665 	mail_queue_path(path, MAIL_QUEUE_FLUSH, site_path);
    666 	if (stat(STR(path), &st) < 0) {
    667 	    if (errno != ENOENT)
    668 		msg_warn("%s: stat %s: %m", myname, STR(path));
    669 	    else if (msg_verbose)
    670 		msg_info("%s: %s: %m", myname, STR(path));
    671 	    continue;
    672 	}
    673 	if (st.st_size == 0) {
    674 	    if (st.st_mtime + var_fflush_purge < event_time()) {
    675 		if (unlink(STR(path)) < 0)
    676 		    msg_warn("remove logfile %s: %m", STR(path));
    677 		else if (msg_verbose)
    678 		    msg_info("%s: unlink %s, empty and unchanged for %d days",
    679 			     myname, STR(path), var_fflush_purge / 86400);
    680 	    } else if (msg_verbose)
    681 		msg_info("%s: skip logfile %s - empty log", myname, site_path);
    682 	} else if (st.st_atime + max_age < event_time()) {
    683 	    if (msg_verbose)
    684 		msg_info("%s: flush logfile %s", myname, site_path);
    685 	    flush_send_path(site_path, REFRESH_ONLY);
    686 	} else {
    687 	    if (msg_verbose)
    688 		msg_info("%s: skip logfile %s, unread for <%d hours(s) ",
    689 			 myname, site_path, max_age / 3600);
    690 	}
    691     }
    692     scan_dir_close(scan);
    693     vstring_free(path);
    694 
    695     return (FLUSH_STAT_OK);
    696 }
    697 
    698 /* flush_request_receive - receive request */
    699 
    700 static int flush_request_receive(VSTREAM *client_stream, VSTRING *request)
    701 {
    702     int     count;
    703 
    704     /*
    705      * Announce the protocol.
    706      */
    707     attr_print(client_stream, ATTR_FLAG_NONE,
    708 	       SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_FLUSH),
    709 	       ATTR_TYPE_END);
    710     (void) vstream_fflush(client_stream);
    711 
    712     /*
    713      * Kluge: choose the protocol depending on the request size.
    714      */
    715     if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) {
    716 	msg_warn("timeout while waiting for data from %s",
    717 		 VSTREAM_PATH(client_stream));
    718 	return (-1);
    719     }
    720     if ((count = peekfd(vstream_fileno(client_stream))) < 0) {
    721 	msg_warn("cannot examine read buffer of %s: %m",
    722 		 VSTREAM_PATH(client_stream));
    723 	return (-1);
    724     }
    725 
    726     /*
    727      * Short request: master trigger. Use the string+null protocol.
    728      */
    729     if (count <= 2) {
    730 	if (vstring_get_null(request, client_stream) == VSTREAM_EOF) {
    731 	    msg_warn("end-of-input while reading request from %s: %m",
    732 		     VSTREAM_PATH(client_stream));
    733 	    return (-1);
    734 	}
    735     }
    736 
    737     /*
    738      * Long request: real flush client. Use the attribute list protocol.
    739      */
    740     else {
    741 	if (attr_scan(client_stream,
    742 		      ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
    743 		      RECV_ATTR_STR(MAIL_ATTR_REQ, request),
    744 		      ATTR_TYPE_END) != 1) {
    745 	    return (-1);
    746 	}
    747     }
    748     return (0);
    749 }
    750 
    751 /* flush_service - perform service for client */
    752 
    753 static void flush_service(VSTREAM *client_stream, char *unused_service,
    754 			          char **argv)
    755 {
    756     VSTRING *request = vstring_alloc(10);
    757     VSTRING *site = 0;
    758     VSTRING *queue_id = 0;
    759     static char wakeup[] = {		/* master wakeup request */
    760 	TRIGGER_REQ_WAKEUP,
    761 	0,
    762     };
    763     int     status = FLUSH_STAT_BAD;
    764 
    765     /*
    766      * Sanity check. This service takes no command-line arguments.
    767      */
    768     if (argv[0])
    769 	msg_fatal("unexpected command-line argument: %s", argv[0]);
    770 
    771     /*
    772      * This routine runs whenever a client connects to the UNIX-domain socket
    773      * dedicated to the fast flush service. What we see below is a little
    774      * protocol to (1) read a request from the client (the name of the site)
    775      * and (2) acknowledge that we have received the request.
    776      *
    777      * All connection-management stuff is handled by the common code in
    778      * single_server.c.
    779      */
    780     if (flush_request_receive(client_stream, request) == 0) {
    781 	if (STREQ(STR(request), FLUSH_REQ_ADD)) {
    782 	    site = vstring_alloc(10);
    783 	    queue_id = vstring_alloc(10);
    784 	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
    785 			  RECV_ATTR_STR(MAIL_ATTR_SITE, site),
    786 			  RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
    787 			  ATTR_TYPE_END) == 2
    788 		&& mail_queue_id_ok(STR(queue_id)))
    789 		status = flush_add_service(STR(site), STR(queue_id));
    790 	    attr_print(client_stream, ATTR_FLAG_NONE,
    791 		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
    792 		       ATTR_TYPE_END);
    793 	} else if (STREQ(STR(request), FLUSH_REQ_SEND_SITE)) {
    794 	    site = vstring_alloc(10);
    795 	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
    796 			  RECV_ATTR_STR(MAIL_ATTR_SITE, site),
    797 			  ATTR_TYPE_END) == 1)
    798 		status = flush_send_service(STR(site), UNTHROTTLE_BEFORE);
    799 	    attr_print(client_stream, ATTR_FLAG_NONE,
    800 		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
    801 		       ATTR_TYPE_END);
    802 	} else if (STREQ(STR(request), FLUSH_REQ_SEND_FILE)) {
    803 	    queue_id = vstring_alloc(10);
    804 	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
    805 			  RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
    806 			  ATTR_TYPE_END) == 1)
    807 		status = flush_send_file_service(STR(queue_id));
    808 	    attr_print(client_stream, ATTR_FLAG_NONE,
    809 		       SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
    810 		       ATTR_TYPE_END);
    811 	} else if (STREQ(STR(request), FLUSH_REQ_REFRESH)
    812 		   || STREQ(STR(request), wakeup)) {
    813 	    attr_print(client_stream, ATTR_FLAG_NONE,
    814 		       SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK),
    815 		       ATTR_TYPE_END);
    816 	    vstream_fflush(client_stream);
    817 	    (void) flush_refresh_service(var_fflush_refresh);
    818 	} else if (STREQ(STR(request), FLUSH_REQ_PURGE)) {
    819 	    attr_print(client_stream, ATTR_FLAG_NONE,
    820 		       SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK),
    821 		       ATTR_TYPE_END);
    822 	    vstream_fflush(client_stream);
    823 	    (void) flush_refresh_service(0);
    824 	}
    825     } else
    826 	attr_print(client_stream, ATTR_FLAG_NONE,
    827 		   SEND_ATTR_INT(MAIL_ATTR_STATUS, status),
    828 		   ATTR_TYPE_END);
    829     vstring_free(request);
    830     if (site)
    831 	vstring_free(site);
    832     if (queue_id)
    833 	vstring_free(queue_id);
    834 }
    835 
    836 /* pre_jail_init - pre-jail initialization */
    837 
    838 static void pre_jail_init(char *unused_name, char **unused_argv)
    839 {
    840     flush_domains = domain_list_init(VAR_FFLUSH_DOMAINS, MATCH_FLAG_RETURN
    841 				   | match_parent_style(VAR_FFLUSH_DOMAINS),
    842 				     var_fflush_domains);
    843 }
    844 
    845 MAIL_VERSION_STAMP_DECLARE;
    846 
    847 /* main - pass control to the single-threaded skeleton */
    848 
    849 int     main(int argc, char **argv)
    850 {
    851     static const CONFIG_TIME_TABLE time_table[] = {
    852 	VAR_FFLUSH_REFRESH, DEF_FFLUSH_REFRESH, &var_fflush_refresh, 1, 0,
    853 	VAR_FFLUSH_PURGE, DEF_FFLUSH_PURGE, &var_fflush_purge, 1, 0,
    854 	0,
    855     };
    856 
    857     /*
    858      * Fingerprint executables and core dumps.
    859      */
    860     MAIL_VERSION_STAMP_ALLOCATE;
    861 
    862     single_server_main(argc, argv, flush_service,
    863 		       CA_MAIL_SERVER_TIME_TABLE(time_table),
    864 		       CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
    865 		       CA_MAIL_SERVER_UNLIMITED,
    866 		       0);
    867 }
    868