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