1/*	$NetBSD: showq.c,v 1.5 2023/12/23 20:30:45 christos Exp $	*/
2
3/*++
4/* NAME
5/*	showq 8
6/* SUMMARY
7/*	list the Postfix mail queue
8/* SYNOPSIS
9/*	\fBshowq\fR [generic Postfix daemon options]
10/* DESCRIPTION
11/*	The \fBshowq\fR(8) daemon reports the Postfix mail queue status.
12/*	The output is meant to be formatted by the postqueue(1) command,
13/*	as it emulates the Sendmail `mailq' command.
14/*
15/*	The \fBshowq\fR(8) daemon can also be run in stand-alone mode
16/*	by the superuser. This mode of operation is used to emulate
17/*	the `mailq' command while the Postfix mail system is down.
18/* SECURITY
19/* .ad
20/* .fi
21/*	The \fBshowq\fR(8) daemon can run in a chroot jail at fixed low
22/*	privilege, and takes no input from the client. Its service port
23/*	is accessible to local untrusted users, so the service can be
24/*	susceptible to denial of service attacks.
25/* STANDARDS
26/* .ad
27/* .fi
28/*	None. The \fBshowq\fR(8) daemon does not interact with the
29/*	outside world.
30/* DIAGNOSTICS
31/*	Problems and transactions are logged to \fBsyslogd\fR(8)
32/*	or \fBpostlogd\fR(8).
33/* CONFIGURATION PARAMETERS
34/* .ad
35/* .fi
36/*	Changes to \fBmain.cf\fR are picked up automatically as \fBshowq\fR(8)
37/*	processes run for only a limited amount of time. Use the command
38/*	"\fBpostfix reload\fR" to speed up a change.
39/*
40/*	The text below provides only a parameter summary. See
41/*	\fBpostconf\fR(5) for more details including examples.
42/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
43/*	The default location of the Postfix main.cf and master.cf
44/*	configuration files.
45/* .IP "\fBdaemon_timeout (18000s)\fR"
46/*	How much time a Postfix daemon process may take to handle a
47/*	request before it is terminated by a built-in watchdog timer.
48/* .IP "\fBduplicate_filter_limit (1000)\fR"
49/*	The maximal number of addresses remembered by the address
50/*	duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or
51/*	for \fBshowq\fR(8) queue displays.
52/* .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR"
53/*	The recipient of mail addressed to the null address.
54/* .IP "\fBipc_timeout (3600s)\fR"
55/*	The time limit for sending or receiving information over an internal
56/*	communication channel.
57/* .IP "\fBmax_idle (100s)\fR"
58/*	The maximum amount of time that an idle Postfix daemon process waits
59/*	for an incoming connection before terminating voluntarily.
60/* .IP "\fBmax_use (100)\fR"
61/*	The maximal number of incoming connections that a Postfix daemon
62/*	process will service before terminating voluntarily.
63/* .IP "\fBprocess_id (read-only)\fR"
64/*	The process ID of a Postfix command or daemon process.
65/* .IP "\fBprocess_name (read-only)\fR"
66/*	The process name of a Postfix command or daemon process.
67/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
68/*	The location of the Postfix top-level queue directory.
69/* .IP "\fBsyslog_facility (mail)\fR"
70/*	The syslog facility of Postfix logging.
71/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
72/*	A prefix that is prepended to the process name in syslog
73/*	records, so that, for example, "smtpd" becomes "prefix/smtpd".
74/* .PP
75/*	Available in Postfix version 2.9 and later:
76/* .IP "\fBenable_long_queue_ids (no)\fR"
77/*	Enable long, non-repeating, queue IDs (queue file names).
78/* .PP
79/*	Available in Postfix 3.3 and later:
80/* .IP "\fBservice_name (read-only)\fR"
81/*	The master.cf service name of a Postfix daemon process.
82/* FILES
83/*	/var/spool/postfix, queue directories
84/* SEE ALSO
85/*	pickup(8), local mail pickup service
86/*	cleanup(8), canonicalize and enqueue mail
87/*	qmgr(8), queue manager
88/*	postconf(5), configuration parameters
89/*	master(8), process manager
90/*	postlogd(8), Postfix logging
91/*	syslogd(8), system logging
92/* LICENSE
93/* .ad
94/* .fi
95/*	The Secure Mailer license must be distributed with this software.
96/* AUTHOR(S)
97/*	Wietse Venema
98/*	IBM T.J. Watson Research
99/*	P.O. Box 704
100/*	Yorktown Heights, NY 10598, USA
101/*
102/*	Wietse Venema
103/*	Google, Inc.
104/*	111 8th Avenue
105/*	New York, NY 10011, USA
106/*--*/
107
108/* System library. */
109
110#include <sys_defs.h>
111#include <sys/stat.h>
112#include <dirent.h>
113#include <stdlib.h>
114#include <unistd.h>
115#include <errno.h>
116#include <fcntl.h>
117#include <time.h>
118#include <string.h>
119#include <ctype.h>
120
121/* Utility library. */
122
123#include <msg.h>
124#include <scan_dir.h>
125#include <vstring.h>
126#include <vstream.h>
127#include <vstring_vstream.h>
128#include <stringops.h>
129#include <mymalloc.h>
130#include <htable.h>
131
132/* Global library. */
133
134#include <mail_queue.h>
135#include <mail_open_ok.h>
136#include <mail_proto.h>
137#include <mail_date.h>
138#include <mail_params.h>
139#include <mail_version.h>
140#include <mail_scan_dir.h>
141#include <mail_conf.h>
142#include <record.h>
143#include <rec_type.h>
144#include <quote_822_local.h>
145#include <mail_addr.h>
146#include <bounce_log.h>
147
148/* Single-threaded server skeleton. */
149
150#include <mail_server.h>
151
152/* Application-specific. */
153
154int     var_dup_filter_limit;
155char   *var_empty_addr;
156
157static void showq_reasons(VSTREAM *, BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *,
158			          HTABLE *);
159
160#define STR(x)	vstring_str(x)
161
162/* showq_report - report status of sender and recipients */
163
164static void showq_report(VSTREAM *client, char *queue, char *id,
165			         VSTREAM *qfile, long size, time_t mtime,
166			         mode_t mode)
167{
168    VSTRING *buf = vstring_alloc(100);
169    VSTRING *printable_quoted_addr = vstring_alloc(100);
170    int     rec_type;
171    time_t  arrival_time = 0;
172    char   *start;
173    long    msg_size = size;
174    BOUNCE_LOG *logfile;
175    HTABLE *dup_filter = 0;
176    RCPT_BUF *rcpt_buf = 0;
177    DSN_BUF *dsn_buf = 0;
178    int     sender_seen = 0;
179    int     msg_size_ok = 0;
180
181    /*
182     * Let the optimizer worry about eliminating duplicate code.
183     */
184#define SHOWQ_CLEANUP_AND_RETURN { \
185	if (sender_seen > 0) \
186	    attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END); \
187	vstring_free(buf); \
188	vstring_free(printable_quoted_addr); \
189	if (rcpt_buf) \
190	    rcpb_free(rcpt_buf); \
191	if (dsn_buf) \
192	    dsb_free(dsn_buf); \
193	if (dup_filter) \
194	    htable_free(dup_filter, (void (*) (void *)) 0); \
195    }
196
197    /*
198     * XXX addresses in defer logfiles are in printable quoted form, while
199     * addresses in message envelope records are in raw unquoted form. This
200     * may change once we replace the present ad-hoc bounce/defer logfile
201     * format by one that is transparent for control etc. characters. See
202     * also: bounce/bounce_append_service.c.
203     *
204     * XXX With Postfix <= 2.0, "postsuper -r" results in obsolete size records
205     * from previous cleanup runs. Skip the obsolete size records.
206     */
207    while (!vstream_ferror(client) && (rec_type = rec_get(qfile, buf, 0)) > 0) {
208	start = vstring_str(buf);
209	if (msg_verbose)
210	    msg_info("record %c %s", rec_type, printable(start, '?'));
211	switch (rec_type) {
212	case REC_TYPE_TIME:
213	    /* TODO: parse seconds and microseconds. */
214	    if (arrival_time == 0)
215		arrival_time = atol(start);
216	    break;
217	case REC_TYPE_SIZE:
218	    if (msg_size_ok == 0) {
219		msg_size_ok = (start[strspn(start, "0123456789 ")] == 0
220			       && (msg_size = atol(start)) >= 0);
221		if (msg_size_ok == 0) {
222		    msg_warn("%s: malformed size record: %.100s "
223			     "-- using file size instead",
224			     id, printable(start, '?'));
225		    msg_size = size;
226		}
227	    }
228	    break;
229	case REC_TYPE_FROM:
230	    if (*start == 0)
231		start = var_empty_addr;
232	    quote_822_local(printable_quoted_addr, start);
233	    /* For consistency with REC_TYPE_RCPT below. */
234	    printable(STR(printable_quoted_addr), '?');
235	    if (sender_seen++ > 0) {
236		msg_warn("%s: duplicate sender address: %s "
237			 "-- skipping remainder of this file",
238			 id, STR(printable_quoted_addr));
239		SHOWQ_CLEANUP_AND_RETURN;
240	    }
241	    attr_print(client, ATTR_FLAG_MORE,
242		       SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
243		       SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
244		       SEND_ATTR_LONG(MAIL_ATTR_TIME, arrival_time > 0 ?
245				      arrival_time : mtime),
246		       SEND_ATTR_LONG(MAIL_ATTR_SIZE, msg_size),
247		       SEND_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE,
248				     (mode & MAIL_QUEUE_STAT_EXPIRE) != 0),
249		       SEND_ATTR_STR(MAIL_ATTR_SENDER,
250				     STR(printable_quoted_addr)),
251		       ATTR_TYPE_END);
252	    break;
253	case REC_TYPE_RCPT:
254	    if (sender_seen == 0) {
255		msg_warn("%s: missing sender address: %s "
256			 "-- skipping remainder of this file",
257			 id, STR(printable_quoted_addr));
258		SHOWQ_CLEANUP_AND_RETURN;
259	    }
260	    if (*start == 0)			/* can't happen? */
261		start = var_empty_addr;
262	    quote_822_local(printable_quoted_addr, start);
263	    /* For consistency with recipients in bounce logfile. */
264	    printable(STR(printable_quoted_addr), '?');
265	    if (dup_filter == 0
266	      || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0)
267		attr_print(client, ATTR_FLAG_MORE,
268			   SEND_ATTR_STR(MAIL_ATTR_RECIP,
269					 STR(printable_quoted_addr)),
270			   SEND_ATTR_STR(MAIL_ATTR_WHY, ""),
271			   ATTR_TYPE_END);
272	    break;
273	case REC_TYPE_MESG:
274	    if (msg_size_ok && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0)
275		msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile));
276	    break;
277	case REC_TYPE_END:
278	    break;
279	}
280
281	/*
282	 * Before listing any recipients from the queue file, try to list
283	 * recipients from the corresponding defer logfile with per-recipient
284	 * descriptions why delivery was deferred.
285	 *
286	 * The defer logfile is not necessarily complete: delivery may be
287	 * interrupted (postfix stop or reload) before all recipients have
288	 * been tried.
289	 *
290	 * Therefore we keep a record of recipients found in the defer logfile,
291	 * and try to avoid listing those recipients again when processing
292	 * recipients from the queue file.
293	 */
294	if (rec_type == REC_TYPE_FROM
295	    && (logfile = bounce_log_open(MAIL_QUEUE_DEFER, id, O_RDONLY, 0)) != 0) {
296	    if (dup_filter != 0)
297		msg_panic("showq_report: attempt to reuse duplicate filter");
298	    dup_filter = htable_create(var_dup_filter_limit);
299	    if (rcpt_buf == 0)
300		rcpt_buf = rcpb_create();
301	    if (dsn_buf == 0)
302		dsn_buf = dsb_create();
303	    showq_reasons(client, logfile, rcpt_buf, dsn_buf, dup_filter);
304	    if (bounce_log_close(logfile))
305		msg_warn("close %s %s: %m", MAIL_QUEUE_DEFER, id);
306	}
307    }
308    SHOWQ_CLEANUP_AND_RETURN;
309}
310
311/* showq_reasons - show deferral reasons */
312
313static void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf,
314			          DSN_BUF *dsn_buf, HTABLE *dup_filter)
315{
316    RECIPIENT *rcpt = &rcpt_buf->rcpt;
317    DSN    *dsn = &dsn_buf->dsn;
318
319    while (bounce_log_read(bp, rcpt_buf, dsn_buf) != 0) {
320
321	/*
322	 * Update the duplicate filter.
323	 */
324	if (var_dup_filter_limit == 0
325	    || dup_filter->used < var_dup_filter_limit)
326	    if (htable_locate(dup_filter, rcpt->address) == 0)
327		htable_enter(dup_filter, rcpt->address, (void *) 0);
328
329	attr_print(client, ATTR_FLAG_MORE,
330		   SEND_ATTR_STR(MAIL_ATTR_RECIP, rcpt->address),
331		   SEND_ATTR_STR(MAIL_ATTR_WHY, dsn->reason),
332		   ATTR_TYPE_END);
333    }
334}
335
336
337/* showq_service - service client */
338
339static void showq_service(VSTREAM *client, char *unused_service, char **argv)
340{
341    VSTREAM *qfile;
342    const char *path;
343    int     status;
344    char   *id;
345    struct stat st;
346    struct queue_info {
347	char   *name;			/* queue name */
348	char   *(*scan_next) (SCAN_DIR *);	/* flat or recursive */
349    };
350    struct queue_info *qp;
351
352    static struct queue_info queue_info[] = {
353	MAIL_QUEUE_MAILDROP, scan_dir_next,
354	MAIL_QUEUE_ACTIVE, mail_scan_dir_next,
355	MAIL_QUEUE_INCOMING, mail_scan_dir_next,
356	MAIL_QUEUE_DEFERRED, mail_scan_dir_next,
357	MAIL_QUEUE_HOLD, mail_scan_dir_next,
358	0,
359    };
360
361    /*
362     * Sanity check. This service takes no command-line arguments.
363     */
364    if (argv[0])
365	msg_fatal("unexpected command-line argument: %s", argv[0]);
366
367    /*
368     * Protocol identification.
369     */
370    (void) attr_print(client, ATTR_FLAG_NONE,
371		      SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SHOWQ),
372		      ATTR_TYPE_END);
373
374    /*
375     * Skip any files that have the wrong permissions. If we can't open an
376     * existing file, assume the system is out of resources or that it is
377     * mis-configured, and force backoff by raising a fatal error.
378     */
379    for (qp = queue_info; qp->name != 0; qp++) {
380	SCAN_DIR *scan = scan_dir_open(qp->name);
381	char   *saved_id = 0;
382
383	while ((id = qp->scan_next(scan)) != 0) {
384
385	    /*
386	     * XXX I have seen showq loop on the same queue id. That would be
387	     * an operating system bug, but who cares whose fault it is. Make
388	     * sure this will never happen again.
389	     */
390	    if (saved_id) {
391		if (strcmp(saved_id, id) == 0) {
392		    msg_warn("readdir loop on queue %s id %s", qp->name, id);
393		    break;
394		}
395		myfree(saved_id);
396	    }
397	    saved_id = mystrdup(id);
398	    status = mail_open_ok(qp->name, id, &st, &path);
399	    if (status == MAIL_OPEN_YES) {
400		if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) {
401		    showq_report(client, qp->name, id, qfile, (long) st.st_size,
402				 st.st_mtime, st.st_mode);
403		    if (vstream_fclose(qfile))
404			msg_warn("close file %s %s: %m", qp->name, id);
405		} else if (errno != ENOENT) {
406		    msg_warn("open %s %s: %m", qp->name, id);
407		}
408	    }
409	    vstream_fflush(client);
410	}
411	if (saved_id)
412	    myfree(saved_id);
413	scan_dir_close(scan);
414    }
415    attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END);
416}
417
418MAIL_VERSION_STAMP_DECLARE;
419
420/* main - pass control to the single-threaded server skeleton */
421
422int     main(int argc, char **argv)
423{
424    static const CONFIG_INT_TABLE int_table[] = {
425	VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
426	0,
427    };
428    CONFIG_STR_TABLE str_table[] = {
429	VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
430	0,
431    };
432
433    /*
434     * Fingerprint executables and core dumps.
435     */
436    MAIL_VERSION_STAMP_ALLOCATE;
437
438    single_server_main(argc, argv, showq_service,
439		       CA_MAIL_SERVER_INT_TABLE(int_table),
440		       CA_MAIL_SERVER_STR_TABLE(str_table),
441		       0);
442}
443