1159045Smaxim/* $NetBSD: showq.c,v 1.5 2023/12/23 20:30:45 christos Exp $ */ 2159045Smaxim 3159045Smaxim/*++ 4159045Smaxim/* NAME 5159045Smaxim/* showq 8 6159045Smaxim/* SUMMARY 7159045Smaxim/* list the Postfix mail queue 8159045Smaxim/* SYNOPSIS 9159045Smaxim/* \fBshowq\fR [generic Postfix daemon options] 10159045Smaxim/* DESCRIPTION 11159045Smaxim/* The \fBshowq\fR(8) daemon reports the Postfix mail queue status. 12159045Smaxim/* The output is meant to be formatted by the postqueue(1) command, 13159045Smaxim/* as it emulates the Sendmail `mailq' command. 14159045Smaxim/* 15159045Smaxim/* The \fBshowq\fR(8) daemon can also be run in stand-alone mode 16159045Smaxim/* by the superuser. This mode of operation is used to emulate 17159045Smaxim/* the `mailq' command while the Postfix mail system is down. 18159045Smaxim/* SECURITY 19159045Smaxim/* .ad 20159045Smaxim/* .fi 21159045Smaxim/* The \fBshowq\fR(8) daemon can run in a chroot jail at fixed low 22159045Smaxim/* privilege, and takes no input from the client. Its service port 23159045Smaxim/* is accessible to local untrusted users, so the service can be 24159045Smaxim/* susceptible to denial of service attacks. 25159045Smaxim/* STANDARDS 26159045Smaxim/* .ad 27159045Smaxim/* .fi 28159045Smaxim/* None. The \fBshowq\fR(8) daemon does not interact with the 29159045Smaxim/* outside world. 30246670Spluknet/* DIAGNOSTICS 31159045Smaxim/* Problems and transactions are logged to \fBsyslogd\fR(8) 32159045Smaxim/* or \fBpostlogd\fR(8). 33246670Spluknet/* CONFIGURATION PARAMETERS 34159045Smaxim/* .ad 35246670Spluknet/* .fi 36159045Smaxim/* Changes to \fBmain.cf\fR are picked up automatically as \fBshowq\fR(8) 37159045Smaxim/* processes run for only a limited amount of time. Use the command 38159045Smaxim/* "\fBpostfix reload\fR" to speed up a change. 39159045Smaxim/* 40159045Smaxim/* The text below provides only a parameter summary. See 41159045Smaxim/* \fBpostconf\fR(5) for more details including examples. 42246670Spluknet/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" 43159045Smaxim/* The default location of the Postfix main.cf and master.cf 44159045Smaxim/* configuration files. 45246670Spluknet/* .IP "\fBdaemon_timeout (18000s)\fR" 46159045Smaxim/* How much time a Postfix daemon process may take to handle a 47159045Smaxim/* request before it is terminated by a built-in watchdog timer. 48246670Spluknet/* .IP "\fBduplicate_filter_limit (1000)\fR" 49159045Smaxim/* The maximal number of addresses remembered by the address 50159045Smaxim/* duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or 51159045Smaxim/* for \fBshowq\fR(8) queue displays. 52159045Smaxim/* .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR" 53159045Smaxim/* The recipient of mail addressed to the null address. 54159045Smaxim/* .IP "\fBipc_timeout (3600s)\fR" 55159045Smaxim/* The time limit for sending or receiving information over an internal 56159045Smaxim/* communication channel. 57159045Smaxim/* .IP "\fBmax_idle (100s)\fR" 58246670Spluknet/* The maximum amount of time that an idle Postfix daemon process waits 59246670Spluknet/* for an incoming connection before terminating voluntarily. 60159045Smaxim/* .IP "\fBmax_use (100)\fR" 61246670Spluknet/* The maximal number of incoming connections that a Postfix daemon 62246670Spluknet/* process will service before terminating voluntarily. 63246670Spluknet/* .IP "\fBprocess_id (read-only)\fR" 64246670Spluknet/* The process ID of a Postfix command or daemon process. 65246670Spluknet/* .IP "\fBprocess_name (read-only)\fR" 66159045Smaxim/* The process name of a Postfix command or daemon process. 67246670Spluknet/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" 68246670Spluknet/* The location of the Postfix top-level queue directory. 69246670Spluknet/* .IP "\fBsyslog_facility (mail)\fR" 70159045Smaxim/* The syslog facility of Postfix logging. 71159045Smaxim/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" 72159045Smaxim/* A prefix that is prepended to the process name in syslog 73159045Smaxim/* records, so that, for example, "smtpd" becomes "prefix/smtpd". 74159045Smaxim/* .PP 75159045Smaxim/* Available in Postfix version 2.9 and later: 76159045Smaxim/* .IP "\fBenable_long_queue_ids (no)\fR" 77246670Spluknet/* Enable long, non-repeating, queue IDs (queue file names). 78159045Smaxim/* .PP 79159045Smaxim/* Available in Postfix 3.3 and later: 80246670Spluknet/* .IP "\fBservice_name (read-only)\fR" 81246670Spluknet/* The master.cf service name of a Postfix daemon process. 82246670Spluknet/* FILES 83246670Spluknet/* /var/spool/postfix, queue directories 84246670Spluknet/* SEE ALSO 85246670Spluknet/* pickup(8), local mail pickup service 86246670Spluknet/* cleanup(8), canonicalize and enqueue mail 87246670Spluknet/* qmgr(8), queue manager 88159045Smaxim/* postconf(5), configuration parameters 89159045Smaxim/* master(8), process manager 90246670Spluknet/* postlogd(8), Postfix logging 91246670Spluknet/* syslogd(8), system logging 92159045Smaxim/* LICENSE 93159045Smaxim/* .ad 94246670Spluknet/* .fi 95246670Spluknet/* The Secure Mailer license must be distributed with this software. 96246670Spluknet/* AUTHOR(S) 97246670Spluknet/* Wietse Venema 98246670Spluknet/* IBM T.J. Watson Research 99246670Spluknet/* P.O. Box 704 100246670Spluknet/* Yorktown Heights, NY 10598, USA 101246670Spluknet/* 102246670Spluknet/* Wietse Venema 103246670Spluknet/* Google, Inc. 104246670Spluknet/* 111 8th Avenue 105246670Spluknet/* New York, NY 10011, USA 106246670Spluknet/*--*/ 107246670Spluknet 108246670Spluknet/* System library. */ 109246670Spluknet 110246670Spluknet#include <sys_defs.h> 111246670Spluknet#include <sys/stat.h> 112246670Spluknet#include <dirent.h> 113246670Spluknet#include <stdlib.h> 114246670Spluknet#include <unistd.h> 115246670Spluknet#include <errno.h> 116246670Spluknet#include <fcntl.h> 117246670Spluknet#include <time.h> 118246670Spluknet#include <string.h> 119246670Spluknet#include <ctype.h> 120246670Spluknet 121246670Spluknet/* Utility library. */ 122246670Spluknet 123246670Spluknet#include <msg.h> 124246670Spluknet#include <scan_dir.h> 125246670Spluknet#include <vstring.h> 126246670Spluknet#include <vstream.h> 127246670Spluknet#include <vstring_vstream.h> 128246670Spluknet#include <stringops.h> 129246670Spluknet#include <mymalloc.h> 130246670Spluknet#include <htable.h> 131159045Smaxim 132159045Smaxim/* Global library. */ 133246670Spluknet 134246670Spluknet#include <mail_queue.h> 135246670Spluknet#include <mail_open_ok.h> 136246670Spluknet#include <mail_proto.h> 137246670Spluknet#include <mail_date.h> 138246670Spluknet#include <mail_params.h> 139246670Spluknet#include <mail_version.h> 140246670Spluknet#include <mail_scan_dir.h> 141246670Spluknet#include <mail_conf.h> 142246670Spluknet#include <record.h> 143246670Spluknet#include <rec_type.h> 144246670Spluknet#include <quote_822_local.h> 145246670Spluknet#include <mail_addr.h> 146246670Spluknet#include <bounce_log.h> 147246670Spluknet 148246670Spluknet/* Single-threaded server skeleton. */ 149246670Spluknet 150246670Spluknet#include <mail_server.h> 151246670Spluknet 152246670Spluknet/* Application-specific. */ 153246670Spluknet 154246670Spluknetint var_dup_filter_limit; 155246670Spluknetchar *var_empty_addr; 156246670Spluknet 157246670Spluknetstatic void showq_reasons(VSTREAM *, BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *, 158246670Spluknet HTABLE *); 159246670Spluknet 160246670Spluknet#define STR(x) vstring_str(x) 161246670Spluknet 162246670Spluknet/* showq_report - report status of sender and recipients */ 163246670Spluknet 164246670Spluknetstatic void showq_report(VSTREAM *client, char *queue, char *id, 165159045Smaxim VSTREAM *qfile, long size, time_t mtime, 166159045Smaxim mode_t mode) 167246670Spluknet{ 168246670Spluknet VSTRING *buf = vstring_alloc(100); 169159045Smaxim VSTRING *printable_quoted_addr = vstring_alloc(100); 170246670Spluknet int rec_type; 171246670Spluknet time_t arrival_time = 0; 172246670Spluknet char *start; 173246670Spluknet long msg_size = size; 174246670Spluknet BOUNCE_LOG *logfile; 175159045Smaxim HTABLE *dup_filter = 0; 176246670Spluknet RCPT_BUF *rcpt_buf = 0; 177246670Spluknet DSN_BUF *dsn_buf = 0; 178159045Smaxim int sender_seen = 0; 179246670Spluknet int msg_size_ok = 0; 180159045Smaxim 181246670Spluknet /* 182246670Spluknet * Let the optimizer worry about eliminating duplicate code. 183246670Spluknet */ 184159045Smaxim#define SHOWQ_CLEANUP_AND_RETURN { \ 185246670Spluknet if (sender_seen > 0) \ 186246670Spluknet attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END); \ 187246670Spluknet vstring_free(buf); \ 188246670Spluknet vstring_free(printable_quoted_addr); \ 189246670Spluknet if (rcpt_buf) \ 190246670Spluknet rcpb_free(rcpt_buf); \ 191159045Smaxim if (dsn_buf) \ 192246670Spluknet dsb_free(dsn_buf); \ 193246670Spluknet if (dup_filter) \ 194246670Spluknet htable_free(dup_filter, (void (*) (void *)) 0); \ 195246670Spluknet } 196159045Smaxim 197246670Spluknet /* 198246670Spluknet * XXX addresses in defer logfiles are in printable quoted form, while 199246670Spluknet * addresses in message envelope records are in raw unquoted form. This 200246670Spluknet * may change once we replace the present ad-hoc bounce/defer logfile 201246670Spluknet * format by one that is transparent for control etc. characters. See 202246670Spluknet * also: bounce/bounce_append_service.c. 203246670Spluknet * 204246670Spluknet * XXX With Postfix <= 2.0, "postsuper -r" results in obsolete size records 205159045Smaxim * from previous cleanup runs. Skip the obsolete size records. 206246670Spluknet */ 207159045Smaxim while (!vstream_ferror(client) && (rec_type = rec_get(qfile, buf, 0)) > 0) { 208246670Spluknet start = vstring_str(buf); 209246670Spluknet if (msg_verbose) 210246670Spluknet msg_info("record %c %s", rec_type, printable(start, '?')); 211246670Spluknet switch (rec_type) { 212159045Smaxim case REC_TYPE_TIME: 213246670Spluknet /* TODO: parse seconds and microseconds. */ 214159045Smaxim if (arrival_time == 0) 215246670Spluknet arrival_time = atol(start); 216159045Smaxim break; 217159045Smaxim case REC_TYPE_SIZE: 218159045Smaxim if (msg_size_ok == 0) { 219246670Spluknet msg_size_ok = (start[strspn(start, "0123456789 ")] == 0 220159045Smaxim && (msg_size = atol(start)) >= 0); 221159045Smaxim if (msg_size_ok == 0) { 222159045Smaxim msg_warn("%s: malformed size record: %.100s " 223246670Spluknet "-- using file size instead", 224159045Smaxim id, printable(start, '?')); 225246670Spluknet msg_size = size; 226159045Smaxim } 227246670Spluknet } 228246670Spluknet break; 229246670Spluknet case REC_TYPE_FROM: 230159095Smaxim if (*start == 0) 231246670Spluknet start = var_empty_addr; 232246670Spluknet quote_822_local(printable_quoted_addr, start); 233246670Spluknet /* For consistency with REC_TYPE_RCPT below. */ 234246670Spluknet printable(STR(printable_quoted_addr), '?'); 235246670Spluknet if (sender_seen++ > 0) { 236246670Spluknet msg_warn("%s: duplicate sender address: %s " 237246670Spluknet "-- skipping remainder of this file", 238246670Spluknet id, STR(printable_quoted_addr)); 239246670Spluknet SHOWQ_CLEANUP_AND_RETURN; 240246670Spluknet } 241246670Spluknet attr_print(client, ATTR_FLAG_MORE, 242246670Spluknet SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue), 243246670Spluknet SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id), 244246670Spluknet SEND_ATTR_LONG(MAIL_ATTR_TIME, arrival_time > 0 ? 245246670Spluknet arrival_time : mtime), 246159045Smaxim SEND_ATTR_LONG(MAIL_ATTR_SIZE, msg_size), 247159045Smaxim SEND_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, 248159045Smaxim (mode & MAIL_QUEUE_STAT_EXPIRE) != 0), 249159045Smaxim SEND_ATTR_STR(MAIL_ATTR_SENDER, 250159045Smaxim STR(printable_quoted_addr)), 251246670Spluknet ATTR_TYPE_END); 252159045Smaxim break; 253159045Smaxim case REC_TYPE_RCPT: 254159045Smaxim if (sender_seen == 0) { 255159045Smaxim msg_warn("%s: missing sender address: %s " 256246670Spluknet "-- skipping remainder of this file", 257159045Smaxim id, STR(printable_quoted_addr)); 258159045Smaxim SHOWQ_CLEANUP_AND_RETURN; 259159045Smaxim } 260159045Smaxim if (*start == 0) /* can't happen? */ 261159045Smaxim start = var_empty_addr; 262159045Smaxim quote_822_local(printable_quoted_addr, start); 263159045Smaxim /* For consistency with recipients in bounce logfile. */ 264246670Spluknet printable(STR(printable_quoted_addr), '?'); 265159045Smaxim if (dup_filter == 0 266159045Smaxim || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0) 267159045Smaxim attr_print(client, ATTR_FLAG_MORE, 268246670Spluknet SEND_ATTR_STR(MAIL_ATTR_RECIP, 269159045Smaxim STR(printable_quoted_addr)), 270159045Smaxim SEND_ATTR_STR(MAIL_ATTR_WHY, ""), 271246670Spluknet ATTR_TYPE_END); 272159045Smaxim break; 273246670Spluknet case REC_TYPE_MESG: 274159045Smaxim if (msg_size_ok && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0) 275246670Spluknet msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile)); 276159045Smaxim break; 277246670Spluknet case REC_TYPE_END: 278246670Spluknet break; 279159045Smaxim } 280246670Spluknet 281246670Spluknet /* 282246670Spluknet * Before listing any recipients from the queue file, try to list 283246670Spluknet * recipients from the corresponding defer logfile with per-recipient 284246670Spluknet * descriptions why delivery was deferred. 285246670Spluknet * 286246670Spluknet * The defer logfile is not necessarily complete: delivery may be 287246670Spluknet * interrupted (postfix stop or reload) before all recipients have 288246670Spluknet * been tried. 289159045Smaxim * 290159045Smaxim * Therefore we keep a record of recipients found in the defer logfile, 291159045Smaxim * and try to avoid listing those recipients again when processing 292159045Smaxim * recipients from the queue file. 293159045Smaxim */ 294159045Smaxim if (rec_type == REC_TYPE_FROM 295159045Smaxim && (logfile = bounce_log_open(MAIL_QUEUE_DEFER, id, O_RDONLY, 0)) != 0) { 296159045Smaxim if (dup_filter != 0) 297246670Spluknet msg_panic("showq_report: attempt to reuse duplicate filter"); 298159045Smaxim dup_filter = htable_create(var_dup_filter_limit); 299159045Smaxim if (rcpt_buf == 0) 300159045Smaxim rcpt_buf = rcpb_create(); 301246670Spluknet if (dsn_buf == 0) 302246670Spluknet dsn_buf = dsb_create(); 303246670Spluknet showq_reasons(client, logfile, rcpt_buf, dsn_buf, dup_filter); 304246670Spluknet if (bounce_log_close(logfile)) 305246670Spluknet msg_warn("close %s %s: %m", MAIL_QUEUE_DEFER, id); 306246670Spluknet } 307246670Spluknet } 308246670Spluknet SHOWQ_CLEANUP_AND_RETURN; 309246670Spluknet} 310246670Spluknet 311246670Spluknet/* showq_reasons - show deferral reasons */ 312246670Spluknet 313159045Smaximstatic void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf, 314246670Spluknet DSN_BUF *dsn_buf, HTABLE *dup_filter) 315159045Smaxim{ 316246670Spluknet RECIPIENT *rcpt = &rcpt_buf->rcpt; 317246670Spluknet DSN *dsn = &dsn_buf->dsn; 318159045Smaxim 319246670Spluknet while (bounce_log_read(bp, rcpt_buf, dsn_buf) != 0) { 320246670Spluknet 321246670Spluknet /* 322246670Spluknet * Update the duplicate filter. 323246670Spluknet */ 324246670Spluknet if (var_dup_filter_limit == 0 325246670Spluknet || dup_filter->used < var_dup_filter_limit) 326246670Spluknet if (htable_locate(dup_filter, rcpt->address) == 0) 327246670Spluknet htable_enter(dup_filter, rcpt->address, (void *) 0); 328246670Spluknet 329246670Spluknet attr_print(client, ATTR_FLAG_MORE, 330246670Spluknet SEND_ATTR_STR(MAIL_ATTR_RECIP, rcpt->address), 331246670Spluknet SEND_ATTR_STR(MAIL_ATTR_WHY, dsn->reason), 332246670Spluknet ATTR_TYPE_END); 333246670Spluknet } 334246670Spluknet} 335159045Smaxim 336246670Spluknet 337246670Spluknet/* showq_service - service client */ 338246670Spluknet 339246670Spluknetstatic void showq_service(VSTREAM *client, char *unused_service, char **argv) 340159045Smaxim{ 341246670Spluknet VSTREAM *qfile; 342159045Smaxim const char *path; 343159045Smaxim int status; 344246670Spluknet char *id; 345159045Smaxim struct stat st; 346159045Smaxim struct queue_info { 347159045Smaxim char *name; /* queue name */ 348159045Smaxim char *(*scan_next) (SCAN_DIR *); /* flat or recursive */ 349246670Spluknet }; 350246670Spluknet struct queue_info *qp; 351159045Smaxim 352159045Smaxim static struct queue_info queue_info[] = { 353246670Spluknet MAIL_QUEUE_MAILDROP, scan_dir_next, 354246670Spluknet MAIL_QUEUE_ACTIVE, mail_scan_dir_next, 355159045Smaxim MAIL_QUEUE_INCOMING, mail_scan_dir_next, 356246670Spluknet MAIL_QUEUE_DEFERRED, mail_scan_dir_next, 357246670Spluknet MAIL_QUEUE_HOLD, mail_scan_dir_next, 358159045Smaxim 0, 359246670Spluknet }; 360246670Spluknet 361159045Smaxim /* 362159045Smaxim * Sanity check. This service takes no command-line arguments. 363159045Smaxim */ 364159045Smaxim if (argv[0]) 365159045Smaxim msg_fatal("unexpected command-line argument: %s", argv[0]); 366159045Smaxim 367159045Smaxim /* 368246670Spluknet * Protocol identification. 369246670Spluknet */ 370159045Smaxim (void) attr_print(client, ATTR_FLAG_NONE, 371246670Spluknet SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SHOWQ), 372246670Spluknet ATTR_TYPE_END); 373246670Spluknet 374246670Spluknet /* 375246670Spluknet * Skip any files that have the wrong permissions. If we can't open an 376246670Spluknet * existing file, assume the system is out of resources or that it is 377246670Spluknet * mis-configured, and force backoff by raising a fatal error. 378246670Spluknet */ 379246670Spluknet for (qp = queue_info; qp->name != 0; qp++) { 380246670Spluknet SCAN_DIR *scan = scan_dir_open(qp->name); 381246670Spluknet char *saved_id = 0; 382246670Spluknet 383246670Spluknet while ((id = qp->scan_next(scan)) != 0) { 384246670Spluknet 385246670Spluknet /* 386246670Spluknet * XXX I have seen showq loop on the same queue id. That would be 387246670Spluknet * an operating system bug, but who cares whose fault it is. Make 388246670Spluknet * sure this will never happen again. 389246670Spluknet */ 390246670Spluknet if (saved_id) { 391246670Spluknet if (strcmp(saved_id, id) == 0) { 392246670Spluknet msg_warn("readdir loop on queue %s id %s", qp->name, id); 393246670Spluknet break; 394246670Spluknet } 395246670Spluknet myfree(saved_id); 396246670Spluknet } 397246670Spluknet saved_id = mystrdup(id); 398246670Spluknet status = mail_open_ok(qp->name, id, &st, &path); 399246670Spluknet if (status == MAIL_OPEN_YES) { 400246670Spluknet if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) { 401246670Spluknet showq_report(client, qp->name, id, qfile, (long) st.st_size, 402246670Spluknet st.st_mtime, st.st_mode); 403246670Spluknet if (vstream_fclose(qfile)) 404246670Spluknet msg_warn("close file %s %s: %m", qp->name, id); 405246670Spluknet } else if (errno != ENOENT) { 406246670Spluknet msg_warn("open %s %s: %m", qp->name, id); 407246670Spluknet } 408246670Spluknet } 409246670Spluknet vstream_fflush(client); 410246670Spluknet } 411246670Spluknet if (saved_id) 412246670Spluknet myfree(saved_id); 413246670Spluknet scan_dir_close(scan); 414246670Spluknet } 415246670Spluknet attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END); 416246670Spluknet} 417246670Spluknet 418246670SpluknetMAIL_VERSION_STAMP_DECLARE; 419246670Spluknet 420246670Spluknet/* main - pass control to the single-threaded server skeleton */ 421246670Spluknet 422246670Spluknetint main(int argc, char **argv) 423246670Spluknet{ 424246670Spluknet static const CONFIG_INT_TABLE int_table[] = { 425246670Spluknet VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0, 426246670Spluknet 0, 427246670Spluknet }; 428246670Spluknet CONFIG_STR_TABLE str_table[] = { 429246670Spluknet VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0, 430246670Spluknet 0, 431246670Spluknet }; 432246670Spluknet 433159045Smaxim /* 434159045Smaxim * Fingerprint executables and core dumps. 435246670Spluknet */ 436246670Spluknet MAIL_VERSION_STAMP_ALLOCATE; 437246670Spluknet 438246670Spluknet single_server_main(argc, argv, showq_service, 439246670Spluknet CA_MAIL_SERVER_INT_TABLE(int_table), 440246670Spluknet CA_MAIL_SERVER_STR_TABLE(str_table), 441246670Spluknet 0); 442246670Spluknet} 443246670Spluknet