No empty .Rs/.Re
[netbsd-mini2440.git] / external / ibm-public / postfix / dist / src / showq / showq.c
blob19ef8f5c8cb9b66bf841b68278d8fe9ec32cb125
1 /* $NetBSD$ */
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 /* It is the program that emulates the sendmail `mailq' command.
14 /* The \fBshowq\fR(8) daemon can also be run in stand-alone mode
15 /* by the superuser. This mode of operation is used to emulate
16 /* the `mailq' command while the Postfix mail system is down.
17 /* SECURITY
18 /* .ad
19 /* .fi
20 /* The \fBshowq\fR(8) daemon can run in a chroot jail at fixed low
21 /* privilege, and takes no input from the client. Its service port
22 /* is accessible to local untrusted users, so the service can be
23 /* susceptible to denial of service attacks.
24 /* STANDARDS
25 /* .ad
26 /* .fi
27 /* None. The \fBshowq\fR(8) daemon does not interact with the
28 /* outside world.
29 /* DIAGNOSTICS
30 /* Problems and transactions are logged to \fBsyslogd\fR(8).
31 /* CONFIGURATION PARAMETERS
32 /* .ad
33 /* .fi
34 /* Changes to \fBmain.cf\fR are picked up automatically as \fBshowq\fR(8)
35 /* processes run for only a limited amount of time. Use the command
36 /* "\fBpostfix reload\fR" to speed up a change.
38 /* The text below provides only a parameter summary. See
39 /* \fBpostconf\fR(5) for more details including examples.
40 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
41 /* The default location of the Postfix main.cf and master.cf
42 /* configuration files.
43 /* .IP "\fBdaemon_timeout (18000s)\fR"
44 /* How much time a Postfix daemon process may take to handle a
45 /* request before it is terminated by a built-in watchdog timer.
46 /* .IP "\fBduplicate_filter_limit (1000)\fR"
47 /* The maximal number of addresses remembered by the address
48 /* duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or
49 /* for \fBshowq\fR(8) queue displays.
50 /* .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR"
51 /* The recipient of mail addressed to the null address.
52 /* .IP "\fBipc_timeout (3600s)\fR"
53 /* The time limit for sending or receiving information over an internal
54 /* communication channel.
55 /* .IP "\fBmax_idle (100s)\fR"
56 /* The maximum amount of time that an idle Postfix daemon process waits
57 /* for an incoming connection before terminating voluntarily.
58 /* .IP "\fBmax_use (100)\fR"
59 /* The maximal number of incoming connections that a Postfix daemon
60 /* process will service before terminating voluntarily.
61 /* .IP "\fBprocess_id (read-only)\fR"
62 /* The process ID of a Postfix command or daemon process.
63 /* .IP "\fBprocess_name (read-only)\fR"
64 /* The process name of a Postfix command or daemon process.
65 /* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
66 /* The location of the Postfix top-level queue directory.
67 /* .IP "\fBsyslog_facility (mail)\fR"
68 /* The syslog facility of Postfix logging.
69 /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
70 /* The mail system name that is prepended to the process name in syslog
71 /* records, so that "smtpd" becomes, for example, "postfix/smtpd".
72 /* FILES
73 /* /var/spool/postfix, queue directories
74 /* SEE ALSO
75 /* pickup(8), local mail pickup service
76 /* cleanup(8), canonicalize and enqueue mail
77 /* qmgr(8), queue manager
78 /* postconf(5), configuration parameters
79 /* master(8), process manager
80 /* syslogd(8), system logging
81 /* LICENSE
82 /* .ad
83 /* .fi
84 /* The Secure Mailer license must be distributed with this software.
85 /* AUTHOR(S)
86 /* Wietse Venema
87 /* IBM T.J. Watson Research
88 /* P.O. Box 704
89 /* Yorktown Heights, NY 10598, USA
90 /*--*/
92 /* System library. */
94 #include <sys_defs.h>
95 #include <sys/stat.h>
96 #include <dirent.h>
97 #include <stdlib.h>
98 #include <unistd.h>
99 #include <errno.h>
100 #include <fcntl.h>
101 #include <time.h>
102 #include <string.h>
103 #include <ctype.h>
105 /* Utility library. */
107 #include <msg.h>
108 #include <scan_dir.h>
109 #include <vstring.h>
110 #include <vstream.h>
111 #include <vstring_vstream.h>
112 #include <stringops.h>
113 #include <mymalloc.h>
114 #include <htable.h>
116 /* Global library. */
118 #include <mail_queue.h>
119 #include <mail_open_ok.h>
120 #include <mail_proto.h>
121 #include <mail_date.h>
122 #include <mail_params.h>
123 #include <mail_version.h>
124 #include <mail_scan_dir.h>
125 #include <mail_conf.h>
126 #include <record.h>
127 #include <rec_type.h>
128 #include <quote_822_local.h>
129 #include <mail_addr.h>
130 #include <bounce_log.h>
132 /* Single-threaded server skeleton. */
134 #include <mail_server.h>
136 /* Application-specific. */
138 int var_dup_filter_limit;
139 char *var_empty_addr;
141 #define STRING_FORMAT "%-10s %8s %-20s %s\n"
142 #define SENDER_FORMAT "%-11s %7ld %20.20s %s\n"
143 #define DROP_FORMAT "%-10s%c %7ld %20.20s (maildrop queue, sender UID %u)\n"
145 static void showq_reasons(VSTREAM *, BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *,
146 HTABLE *);
148 #define STR(x) vstring_str(x)
150 /* showq_report - report status of sender and recipients */
152 static void showq_report(VSTREAM *client, char *queue, char *id,
153 VSTREAM *qfile, long size, time_t mtime)
155 VSTRING *buf = vstring_alloc(100);
156 VSTRING *printable_quoted_addr = vstring_alloc(100);
157 int rec_type;
158 time_t arrival_time = 0;
159 char *start;
160 long msg_size = 0;
161 BOUNCE_LOG *logfile;
162 HTABLE *dup_filter = 0;
163 RCPT_BUF *rcpt_buf = 0;
164 DSN_BUF *dsn_buf = 0;
165 char status = (strcmp(queue, MAIL_QUEUE_ACTIVE) == 0 ? '*' :
166 strcmp(queue, MAIL_QUEUE_HOLD) == 0 ? '!' : ' ');
167 int msg_size_ok = 0;
170 * XXX addresses in defer logfiles are in printable quoted form, while
171 * addresses in message envelope records are in raw unquoted form. This
172 * may change once we replace the present ad-hoc bounce/defer logfile
173 * format by one that is transparent for control etc. characters. See
174 * also: bounce/bounce_append_service.c.
176 * XXX With Postfix <= 2.0, "postsuper -r" results in obsolete size records
177 * from previous cleanup runs. Skip the obsolete size records.
179 while (!vstream_ferror(client) && (rec_type = rec_get(qfile, buf, 0)) > 0) {
180 start = vstring_str(buf);
181 if (msg_verbose)
182 msg_info("record %c %s", rec_type, printable(start, '?'));
183 switch (rec_type) {
184 case REC_TYPE_TIME:
185 arrival_time = atol(start);
186 break;
187 case REC_TYPE_SIZE:
188 if (msg_size == 0) {
189 if ((msg_size_ok = ((msg_size = atol(start)) > 0)) == 0) {
190 msg_warn("%s: malformed size record: %.100s",
191 id, printable(start, '?'));
192 msg_size = size;
195 break;
196 case REC_TYPE_FROM:
197 if (*start == 0)
198 start = var_empty_addr;
199 quote_822_local(printable_quoted_addr, start);
200 printable(STR(printable_quoted_addr), '?');
201 /* quote_822_local() saves buf, so we can reuse its space. */
202 vstring_sprintf(buf, "%s%c", id, status);
203 vstream_fprintf(client, SENDER_FORMAT, STR(buf),
204 msg_size > 0 ? msg_size : size, arrival_time > 0 ?
205 asctime(localtime(&arrival_time)) :
206 asctime(localtime(&mtime)),
207 STR(printable_quoted_addr));
208 break;
209 case REC_TYPE_RCPT:
210 if (*start == 0) /* can't happen? */
211 start = var_empty_addr;
212 quote_822_local(printable_quoted_addr, start);
213 printable(STR(printable_quoted_addr), '?');
214 if (dup_filter == 0
215 || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0)
216 vstream_fprintf(client, STRING_FORMAT,
217 "", "", "", STR(printable_quoted_addr));
218 break;
219 case REC_TYPE_MESG:
220 if (msg_size_ok && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0)
221 msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile));
222 break;
223 case REC_TYPE_END:
224 break;
228 * With the heading printed, see if there is a defer logfile. The
229 * defer logfile is not necessarily complete: delivery may be
230 * interrupted (postfix stop or reload) before all recipients have
231 * been tried.
233 * Therefore we keep a record of recipients found in the defer logfile,
234 * and try to avoid listing those recipients again when processing
235 * the remainder of the queue file.
237 if (rec_type == REC_TYPE_FROM
238 && dup_filter == 0
239 && (logfile = bounce_log_open(MAIL_QUEUE_DEFER, id, O_RDONLY, 0)) != 0) {
240 dup_filter = htable_create(var_dup_filter_limit);
241 if (rcpt_buf == 0)
242 rcpt_buf = rcpb_create();
243 if (dsn_buf == 0)
244 dsn_buf = dsb_create();
245 showq_reasons(client, logfile, rcpt_buf, dsn_buf, dup_filter);
246 if (bounce_log_close(logfile))
247 msg_warn("close %s %s: %m", MAIL_QUEUE_DEFER, id);
250 vstring_free(buf);
251 vstring_free(printable_quoted_addr);
252 if (rcpt_buf)
253 rcpb_free(rcpt_buf);
254 if (dsn_buf)
255 dsb_free(dsn_buf);
256 if (dup_filter)
257 htable_free(dup_filter, (void (*) (char *)) 0);
260 /* showq_reasons - show deferral reasons */
262 static void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf,
263 DSN_BUF *dsn_buf, HTABLE *dup_filter)
265 char *saved_reason = 0;
266 int padding;
267 RECIPIENT *rcpt = &rcpt_buf->rcpt;
268 DSN *dsn = &dsn_buf->dsn;
270 while (bounce_log_read(bp, rcpt_buf, dsn_buf) != 0) {
273 * Update the duplicate filter.
275 if (var_dup_filter_limit == 0
276 || dup_filter->used < var_dup_filter_limit)
277 if (htable_locate(dup_filter, rcpt->address) == 0)
278 htable_enter(dup_filter, rcpt->address, (char *) 0);
281 * Don't print the reason when the previous recipient had the same
282 * problem.
284 if (saved_reason == 0 || strcmp(saved_reason, dsn->reason) != 0) {
285 if (saved_reason)
286 myfree(saved_reason);
287 saved_reason = mystrdup(dsn->reason);
288 if ((padding = 76 - strlen(saved_reason)) < 0)
289 padding = 0;
290 vstream_fprintf(client, "%*s(%s)\n", padding, "", saved_reason);
292 vstream_fprintf(client, STRING_FORMAT, "", "", "", rcpt->address);
294 if (saved_reason)
295 myfree(saved_reason);
299 /* showq_service - service client */
301 static void showq_service(VSTREAM *client, char *unused_service, char **argv)
303 VSTREAM *qfile;
304 const char *path;
305 int status;
306 char *id;
307 int file_count;
308 unsigned long queue_size = 0;
309 struct stat st;
310 struct queue_info {
311 char *name; /* queue name */
312 char *(*scan_next) (SCAN_DIR *); /* flat or recursive */
314 struct queue_info *qp;
316 static struct queue_info queue_info[] = {
317 MAIL_QUEUE_MAILDROP, scan_dir_next,
318 MAIL_QUEUE_ACTIVE, mail_scan_dir_next,
319 MAIL_QUEUE_INCOMING, mail_scan_dir_next,
320 MAIL_QUEUE_DEFERRED, mail_scan_dir_next,
321 MAIL_QUEUE_HOLD, mail_scan_dir_next,
326 * Sanity check. This service takes no command-line arguments.
328 if (argv[0])
329 msg_fatal("unexpected command-line argument: %s", argv[0]);
332 * Skip any files that have the wrong permissions. If we can't open an
333 * existing file, assume the system is out of resources or that it is
334 * mis-configured, and force backoff by raising a fatal error.
336 file_count = 0;
337 for (qp = queue_info; qp->name != 0; qp++) {
338 SCAN_DIR *scan = scan_dir_open(qp->name);
339 char *saved_id = 0;
341 while ((id = qp->scan_next(scan)) != 0) {
344 * XXX I have seen showq loop on the same queue id. That would be
345 * an operating system bug, but who cares whose fault it is. Make
346 * sure this will never happen again.
348 if (saved_id) {
349 if (strcmp(saved_id, id) == 0) {
350 msg_warn("readdir loop on queue %s id %s", qp->name, id);
351 break;
353 myfree(saved_id);
355 saved_id = mystrdup(id);
356 status = mail_open_ok(qp->name, id, &st, &path);
357 if (status == MAIL_OPEN_YES) {
358 if (file_count == 0)
359 vstream_fprintf(client, STRING_FORMAT,
360 "-Queue ID-", "--Size--",
361 "----Arrival Time----",
362 "-Sender/Recipient-------");
363 else
364 vstream_fprintf(client, "\n");
365 if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) {
366 queue_size += st.st_size;
367 showq_report(client, qp->name, id, qfile, (long) st.st_size,
368 st.st_mtime);
369 if (vstream_fclose(qfile))
370 msg_warn("close file %s %s: %m", qp->name, id);
371 } else if (strcmp(qp->name, MAIL_QUEUE_MAILDROP) == 0) {
372 queue_size += st.st_size;
373 vstream_fprintf(client, DROP_FORMAT, id, ' ',
374 (long) st.st_size,
375 asctime(localtime(&st.st_mtime)),
376 (unsigned) st.st_uid);
377 } else if (errno != ENOENT)
378 msg_fatal("open %s %s: %m", qp->name, id);
379 file_count++;
380 vstream_fflush(client);
382 vstream_fflush(client);
384 if (saved_id)
385 myfree(saved_id);
386 scan_dir_close(scan);
388 if (file_count == 0)
389 vstream_fprintf(client, "Mail queue is empty\n");
390 else {
391 vstream_fprintf(client, "\n-- %lu Kbytes in %d Request%s.\n",
392 queue_size / 1024, file_count,
393 file_count == 1 ? "" : "s");
397 MAIL_VERSION_STAMP_DECLARE;
399 /* main - pass control to the single-threaded server skeleton */
401 int main(int argc, char **argv)
403 static const CONFIG_INT_TABLE int_table[] = {
404 VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
407 CONFIG_STR_TABLE str_table[] = {
408 VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
413 * Fingerprint executables and core dumps.
415 MAIL_VERSION_STAMP_ALLOCATE;
417 single_server_main(argc, argv, showq_service,
418 MAIL_SERVER_INT_TABLE, int_table,
419 MAIL_SERVER_STR_TABLE, str_table,