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