xref: /netbsd-src/external/ibm-public/postfix/dist/src/showq/showq.c (revision d536862b7d93d77932ef5de7eebdc48d76921b77)
1 /*	$NetBSD: showq.c,v 1.3 2020/03/18 19:05:20 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 
154 int     var_dup_filter_limit;
155 char   *var_empty_addr;
156 
157 static 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 
164 static 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 	    printable(STR(printable_quoted_addr), '?');
234 	    if (sender_seen++ > 0) {
235 		msg_warn("%s: duplicate sender address: %s "
236 			 "-- skipping remainder of this file",
237 			 id, STR(printable_quoted_addr));
238 		SHOWQ_CLEANUP_AND_RETURN;
239 	    }
240 	    attr_print(client, ATTR_FLAG_MORE,
241 		       SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
242 		       SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
243 		       SEND_ATTR_LONG(MAIL_ATTR_TIME, arrival_time > 0 ?
244 				      arrival_time : mtime),
245 		       SEND_ATTR_LONG(MAIL_ATTR_SIZE, msg_size),
246 		       SEND_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE,
247 				     (mode & MAIL_QUEUE_STAT_EXPIRE) != 0),
248 		       SEND_ATTR_STR(MAIL_ATTR_SENDER,
249 				     STR(printable_quoted_addr)),
250 		       ATTR_TYPE_END);
251 	    break;
252 	case REC_TYPE_RCPT:
253 	    if (sender_seen == 0) {
254 		msg_warn("%s: missing sender address: %s "
255 			 "-- skipping remainder of this file",
256 			 id, STR(printable_quoted_addr));
257 		SHOWQ_CLEANUP_AND_RETURN;
258 	    }
259 	    if (*start == 0)			/* can't happen? */
260 		start = var_empty_addr;
261 	    quote_822_local(printable_quoted_addr, start);
262 	    printable(STR(printable_quoted_addr), '?');
263 	    if (dup_filter == 0
264 	      || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0)
265 		attr_print(client, ATTR_FLAG_MORE,
266 			   SEND_ATTR_STR(MAIL_ATTR_RECIP,
267 					 STR(printable_quoted_addr)),
268 			   SEND_ATTR_STR(MAIL_ATTR_WHY, ""),
269 			   ATTR_TYPE_END);
270 	    break;
271 	case REC_TYPE_MESG:
272 	    if (msg_size_ok && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0)
273 		msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile));
274 	    break;
275 	case REC_TYPE_END:
276 	    break;
277 	}
278 
279 	/*
280 	 * Before listing any recipients from the queue file, try to list
281 	 * recipients from the corresponding defer logfile with per-recipient
282 	 * descriptions why delivery was deferred.
283 	 *
284 	 * The defer logfile is not necessarily complete: delivery may be
285 	 * interrupted (postfix stop or reload) before all recipients have
286 	 * been tried.
287 	 *
288 	 * Therefore we keep a record of recipients found in the defer logfile,
289 	 * and try to avoid listing those recipients again when processing
290 	 * recipients from the queue file.
291 	 */
292 	if (rec_type == REC_TYPE_FROM
293 	    && (logfile = bounce_log_open(MAIL_QUEUE_DEFER, id, O_RDONLY, 0)) != 0) {
294 	    if (dup_filter != 0)
295 		msg_panic("showq_report: attempt to reuse duplicate filter");
296 	    dup_filter = htable_create(var_dup_filter_limit);
297 	    if (rcpt_buf == 0)
298 		rcpt_buf = rcpb_create();
299 	    if (dsn_buf == 0)
300 		dsn_buf = dsb_create();
301 	    showq_reasons(client, logfile, rcpt_buf, dsn_buf, dup_filter);
302 	    if (bounce_log_close(logfile))
303 		msg_warn("close %s %s: %m", MAIL_QUEUE_DEFER, id);
304 	}
305     }
306     SHOWQ_CLEANUP_AND_RETURN;
307 }
308 
309 /* showq_reasons - show deferral reasons */
310 
311 static void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf,
312 			          DSN_BUF *dsn_buf, HTABLE *dup_filter)
313 {
314     RECIPIENT *rcpt = &rcpt_buf->rcpt;
315     DSN    *dsn = &dsn_buf->dsn;
316 
317     while (bounce_log_read(bp, rcpt_buf, dsn_buf) != 0) {
318 
319 	/*
320 	 * Update the duplicate filter.
321 	 */
322 	if (var_dup_filter_limit == 0
323 	    || dup_filter->used < var_dup_filter_limit)
324 	    if (htable_locate(dup_filter, rcpt->address) == 0)
325 		htable_enter(dup_filter, rcpt->address, (void *) 0);
326 
327 	attr_print(client, ATTR_FLAG_MORE,
328 		   SEND_ATTR_STR(MAIL_ATTR_RECIP, rcpt->address),
329 		   SEND_ATTR_STR(MAIL_ATTR_WHY, dsn->reason),
330 		   ATTR_TYPE_END);
331     }
332 }
333 
334 
335 /* showq_service - service client */
336 
337 static void showq_service(VSTREAM *client, char *unused_service, char **argv)
338 {
339     VSTREAM *qfile;
340     const char *path;
341     int     status;
342     char   *id;
343     struct stat st;
344     struct queue_info {
345 	char   *name;			/* queue name */
346 	char   *(*scan_next) (SCAN_DIR *);	/* flat or recursive */
347     };
348     struct queue_info *qp;
349 
350     static struct queue_info queue_info[] = {
351 	MAIL_QUEUE_MAILDROP, scan_dir_next,
352 	MAIL_QUEUE_ACTIVE, mail_scan_dir_next,
353 	MAIL_QUEUE_INCOMING, mail_scan_dir_next,
354 	MAIL_QUEUE_DEFERRED, mail_scan_dir_next,
355 	MAIL_QUEUE_HOLD, mail_scan_dir_next,
356 	0,
357     };
358 
359     /*
360      * Sanity check. This service takes no command-line arguments.
361      */
362     if (argv[0])
363 	msg_fatal("unexpected command-line argument: %s", argv[0]);
364 
365     /*
366      * Skip any files that have the wrong permissions. If we can't open an
367      * existing file, assume the system is out of resources or that it is
368      * mis-configured, and force backoff by raising a fatal error.
369      */
370     for (qp = queue_info; qp->name != 0; qp++) {
371 	SCAN_DIR *scan = scan_dir_open(qp->name);
372 	char   *saved_id = 0;
373 
374 	while ((id = qp->scan_next(scan)) != 0) {
375 
376 	    /*
377 	     * XXX I have seen showq loop on the same queue id. That would be
378 	     * an operating system bug, but who cares whose fault it is. Make
379 	     * sure this will never happen again.
380 	     */
381 	    if (saved_id) {
382 		if (strcmp(saved_id, id) == 0) {
383 		    msg_warn("readdir loop on queue %s id %s", qp->name, id);
384 		    break;
385 		}
386 		myfree(saved_id);
387 	    }
388 	    saved_id = mystrdup(id);
389 	    status = mail_open_ok(qp->name, id, &st, &path);
390 	    if (status == MAIL_OPEN_YES) {
391 		if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) {
392 		    showq_report(client, qp->name, id, qfile, (long) st.st_size,
393 				 st.st_mtime, st.st_mode);
394 		    if (vstream_fclose(qfile))
395 			msg_warn("close file %s %s: %m", qp->name, id);
396 		} else if (errno != ENOENT) {
397 		    msg_warn("open %s %s: %m", qp->name, id);
398 		}
399 	    }
400 	    vstream_fflush(client);
401 	}
402 	if (saved_id)
403 	    myfree(saved_id);
404 	scan_dir_close(scan);
405     }
406     attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END);
407 }
408 
409 MAIL_VERSION_STAMP_DECLARE;
410 
411 /* main - pass control to the single-threaded server skeleton */
412 
413 int     main(int argc, char **argv)
414 {
415     static const CONFIG_INT_TABLE int_table[] = {
416 	VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
417 	0,
418     };
419     CONFIG_STR_TABLE str_table[] = {
420 	VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
421 	0,
422     };
423 
424     /*
425      * Fingerprint executables and core dumps.
426      */
427     MAIL_VERSION_STAMP_ALLOCATE;
428 
429     single_server_main(argc, argv, showq_service,
430 		       CA_MAIL_SERVER_INT_TABLE(int_table),
431 		       CA_MAIL_SERVER_STR_TABLE(str_table),
432 		       0);
433 }
434