summaryrefslogtreecommitdiffstats
path: root/src/showq
diff options
context:
space:
mode:
Diffstat (limited to '')
l---------src/showq/.indent.pro1
-rw-r--r--src/showq/.printfck25
-rw-r--r--src/showq/Makefile.in95
-rw-r--r--src/showq/showq.c431
4 files changed, 552 insertions, 0 deletions
diff --git a/src/showq/.indent.pro b/src/showq/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/showq/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/showq/.printfck b/src/showq/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/showq/.printfck
@@ -0,0 +1,25 @@
+been_here_xt 2 0
+bounce_append 5 0
+cleanup_out_format 1 0
+defer_append 5 0
+mail_command 1 0
+mail_print 1 0
+msg_error 0 0
+msg_fatal 0 0
+msg_info 0 0
+msg_panic 0 0
+msg_warn 0 0
+opened 4 0
+post_mail_fprintf 1 0
+qmgr_message_bounce 2 0
+rec_fprintf 2 0
+sent 4 0
+smtp_cmd 1 0
+smtp_mesg_fail 2 0
+smtp_printf 1 0
+smtp_rcpt_fail 3 0
+smtp_site_fail 2 0
+udp_syslog 1 0
+vstream_fprintf 1 0
+vstream_printf 0 0
+vstring_sprintf 1 0
diff --git a/src/showq/Makefile.in b/src/showq/Makefile.in
new file mode 100644
index 0000000..47b82e7
--- /dev/null
+++ b/src/showq/Makefile.in
@@ -0,0 +1,95 @@
+SHELL = /bin/sh
+SRCS = showq.c
+OBJS = showq.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = showq
+INC_DIR = ../../include
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+$(PROG): $(OBJS) $(LIBS)
+ $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS)
+
+$(OBJS): ../../conf/makedefs.out
+
+Makefile: Makefile.in
+ cat ../../conf/makedefs.out $? >$@
+
+test: $(TESTPROG)
+
+tests:
+
+root_tests:
+
+update: ../../libexec/$(PROG)
+
+../../libexec/$(PROG): $(PROG)
+ cp $(PROG) ../../libexec
+
+printfck: $(OBJS) $(PROG)
+ rm -rf printfck
+ mkdir printfck
+ sed '1,/^# do not edit/!d' Makefile >printfck/Makefile
+ set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done
+ cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o`
+
+lint:
+ lint $(DEFS) $(SRCS) $(LINTFIX)
+
+clean:
+ rm -f *.o *core $(PROG) $(TESTPROG) junk
+ rm -rf printfck
+
+tidy: clean
+
+depend: $(MAKES)
+ (sed '1,/^# do not edit/!d' Makefile.in; \
+ set -e; for i in [a-z][a-z0-9]*.c; do \
+ $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \
+ -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \
+ -e 's/o: \.\//o: /' -e p -e '}' ; \
+ done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in
+ @$(EXPORT) make -f Makefile.in Makefile 1>&2
+
+# do not edit below this line - it is generated by 'make depend'
+showq.o: ../../include/attr.h
+showq.o: ../../include/bounce_log.h
+showq.o: ../../include/check_arg.h
+showq.o: ../../include/dsn.h
+showq.o: ../../include/dsn_buf.h
+showq.o: ../../include/htable.h
+showq.o: ../../include/iostuff.h
+showq.o: ../../include/mail_addr.h
+showq.o: ../../include/mail_conf.h
+showq.o: ../../include/mail_date.h
+showq.o: ../../include/mail_open_ok.h
+showq.o: ../../include/mail_params.h
+showq.o: ../../include/mail_proto.h
+showq.o: ../../include/mail_queue.h
+showq.o: ../../include/mail_scan_dir.h
+showq.o: ../../include/mail_server.h
+showq.o: ../../include/mail_version.h
+showq.o: ../../include/msg.h
+showq.o: ../../include/mymalloc.h
+showq.o: ../../include/nvtable.h
+showq.o: ../../include/quote_822_local.h
+showq.o: ../../include/quote_flags.h
+showq.o: ../../include/rcpt_buf.h
+showq.o: ../../include/rec_type.h
+showq.o: ../../include/recipient_list.h
+showq.o: ../../include/record.h
+showq.o: ../../include/scan_dir.h
+showq.o: ../../include/stringops.h
+showq.o: ../../include/sys_defs.h
+showq.o: ../../include/vbuf.h
+showq.o: ../../include/vstream.h
+showq.o: ../../include/vstring.h
+showq.o: ../../include/vstring_vstream.h
+showq.o: showq.c
diff --git a/src/showq/showq.c b/src/showq/showq.c
new file mode 100644
index 0000000..e6ca68f
--- /dev/null
+++ b/src/showq/showq.c
@@ -0,0 +1,431 @@
+/*++
+/* NAME
+/* showq 8
+/* SUMMARY
+/* list the Postfix mail queue
+/* SYNOPSIS
+/* \fBshowq\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBshowq\fR(8) daemon reports the Postfix mail queue status.
+/* The output is meant to be formatted by the postqueue(1) command,
+/* as it emulates the Sendmail `mailq' command.
+/*
+/* The \fBshowq\fR(8) daemon can also be run in stand-alone mode
+/* by the superuser. This mode of operation is used to emulate
+/* the `mailq' command while the Postfix mail system is down.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBshowq\fR(8) daemon can run in a chroot jail at fixed low
+/* privilege, and takes no input from the client. Its service port
+/* is accessible to local untrusted users, so the service can be
+/* susceptible to denial of service attacks.
+/* STANDARDS
+/* .ad
+/* .fi
+/* None. The \fBshowq\fR(8) daemon does not interact with the
+/* outside world.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically as \fBshowq\fR(8)
+/* processes run for only a limited amount of time. Use the command
+/* "\fBpostfix reload\fR" to speed up a change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdaemon_timeout (18000s)\fR"
+/* How much time a Postfix daemon process may take to handle a
+/* request before it is terminated by a built-in watchdog timer.
+/* .IP "\fBduplicate_filter_limit (1000)\fR"
+/* The maximal number of addresses remembered by the address
+/* duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or
+/* for \fBshowq\fR(8) queue displays.
+/* .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR"
+/* The recipient of mail addressed to the null address.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBmax_idle (100s)\fR"
+/* The maximum amount of time that an idle Postfix daemon process waits
+/* for an incoming connection before terminating voluntarily.
+/* .IP "\fBmax_use (100)\fR"
+/* The maximal number of incoming connections that a Postfix daemon
+/* process will service before terminating voluntarily.
+/* .IP "\fBprocess_id (read-only)\fR"
+/* The process ID of a Postfix command or daemon process.
+/* .IP "\fBprocess_name (read-only)\fR"
+/* The process name of a Postfix command or daemon process.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .IP "\fBsyslog_facility (mail)\fR"
+/* The syslog facility of Postfix logging.
+/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
+/* A prefix that is prepended to the process name in syslog
+/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available in Postfix version 2.9 and later:
+/* .IP "\fBenable_long_queue_ids (no)\fR"
+/* Enable long, non-repeating, queue IDs (queue file names).
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* FILES
+/* /var/spool/postfix, queue directories
+/* SEE ALSO
+/* pickup(8), local mail pickup service
+/* cleanup(8), canonicalize and enqueue mail
+/* qmgr(8), queue manager
+/* postconf(5), configuration parameters
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <scan_dir.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <htable.h>
+
+/* Global library. */
+
+#include <mail_queue.h>
+#include <mail_open_ok.h>
+#include <mail_proto.h>
+#include <mail_date.h>
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_scan_dir.h>
+#include <mail_conf.h>
+#include <record.h>
+#include <rec_type.h>
+#include <quote_822_local.h>
+#include <mail_addr.h>
+#include <bounce_log.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+int var_dup_filter_limit;
+char *var_empty_addr;
+
+static void showq_reasons(VSTREAM *, BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *,
+ HTABLE *);
+
+#define STR(x) vstring_str(x)
+
+/* showq_report - report status of sender and recipients */
+
+static void showq_report(VSTREAM *client, char *queue, char *id,
+ VSTREAM *qfile, long size, time_t mtime,
+ mode_t mode)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *printable_quoted_addr = vstring_alloc(100);
+ int rec_type;
+ time_t arrival_time = 0;
+ char *start;
+ long msg_size = size;
+ BOUNCE_LOG *logfile;
+ HTABLE *dup_filter = 0;
+ RCPT_BUF *rcpt_buf = 0;
+ DSN_BUF *dsn_buf = 0;
+ int sender_seen = 0;
+ int msg_size_ok = 0;
+
+ /*
+ * Let the optimizer worry about eliminating duplicate code.
+ */
+#define SHOWQ_CLEANUP_AND_RETURN { \
+ if (sender_seen > 0) \
+ attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END); \
+ vstring_free(buf); \
+ vstring_free(printable_quoted_addr); \
+ if (rcpt_buf) \
+ rcpb_free(rcpt_buf); \
+ if (dsn_buf) \
+ dsb_free(dsn_buf); \
+ if (dup_filter) \
+ htable_free(dup_filter, (void (*) (void *)) 0); \
+ }
+
+ /*
+ * XXX addresses in defer logfiles are in printable quoted form, while
+ * addresses in message envelope records are in raw unquoted form. This
+ * may change once we replace the present ad-hoc bounce/defer logfile
+ * format by one that is transparent for control etc. characters. See
+ * also: bounce/bounce_append_service.c.
+ *
+ * XXX With Postfix <= 2.0, "postsuper -r" results in obsolete size records
+ * from previous cleanup runs. Skip the obsolete size records.
+ */
+ while (!vstream_ferror(client) && (rec_type = rec_get(qfile, buf, 0)) > 0) {
+ start = vstring_str(buf);
+ if (msg_verbose)
+ msg_info("record %c %s", rec_type, printable(start, '?'));
+ switch (rec_type) {
+ case REC_TYPE_TIME:
+ /* TODO: parse seconds and microseconds. */
+ if (arrival_time == 0)
+ arrival_time = atol(start);
+ break;
+ case REC_TYPE_SIZE:
+ if (msg_size_ok == 0) {
+ msg_size_ok = (start[strspn(start, "0123456789 ")] == 0
+ && (msg_size = atol(start)) >= 0);
+ if (msg_size_ok == 0) {
+ msg_warn("%s: malformed size record: %.100s "
+ "-- using file size instead",
+ id, printable(start, '?'));
+ msg_size = size;
+ }
+ }
+ break;
+ case REC_TYPE_FROM:
+ if (*start == 0)
+ start = var_empty_addr;
+ quote_822_local(printable_quoted_addr, start);
+ printable(STR(printable_quoted_addr), '?');
+ if (sender_seen++ > 0) {
+ msg_warn("%s: duplicate sender address: %s "
+ "-- skipping remainder of this file",
+ id, STR(printable_quoted_addr));
+ SHOWQ_CLEANUP_AND_RETURN;
+ }
+ attr_print(client, ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue),
+ SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id),
+ SEND_ATTR_LONG(MAIL_ATTR_TIME, arrival_time > 0 ?
+ arrival_time : mtime),
+ SEND_ATTR_LONG(MAIL_ATTR_SIZE, msg_size),
+ SEND_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE,
+ (mode & MAIL_QUEUE_STAT_EXPIRE) != 0),
+ SEND_ATTR_STR(MAIL_ATTR_SENDER,
+ STR(printable_quoted_addr)),
+ ATTR_TYPE_END);
+ break;
+ case REC_TYPE_RCPT:
+ if (sender_seen == 0) {
+ msg_warn("%s: missing sender address: %s "
+ "-- skipping remainder of this file",
+ id, STR(printable_quoted_addr));
+ SHOWQ_CLEANUP_AND_RETURN;
+ }
+ if (*start == 0) /* can't happen? */
+ start = var_empty_addr;
+ quote_822_local(printable_quoted_addr, start);
+ printable(STR(printable_quoted_addr), '?');
+ if (dup_filter == 0
+ || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0)
+ attr_print(client, ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_RECIP,
+ STR(printable_quoted_addr)),
+ SEND_ATTR_STR(MAIL_ATTR_WHY, ""),
+ ATTR_TYPE_END);
+ break;
+ case REC_TYPE_MESG:
+ if (msg_size_ok && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0)
+ msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile));
+ break;
+ case REC_TYPE_END:
+ break;
+ }
+
+ /*
+ * Before listing any recipients from the queue file, try to list
+ * recipients from the corresponding defer logfile with per-recipient
+ * descriptions why delivery was deferred.
+ *
+ * The defer logfile is not necessarily complete: delivery may be
+ * interrupted (postfix stop or reload) before all recipients have
+ * been tried.
+ *
+ * Therefore we keep a record of recipients found in the defer logfile,
+ * and try to avoid listing those recipients again when processing
+ * recipients from the queue file.
+ */
+ if (rec_type == REC_TYPE_FROM
+ && (logfile = bounce_log_open(MAIL_QUEUE_DEFER, id, O_RDONLY, 0)) != 0) {
+ if (dup_filter != 0)
+ msg_panic("showq_report: attempt to reuse duplicate filter");
+ dup_filter = htable_create(var_dup_filter_limit);
+ if (rcpt_buf == 0)
+ rcpt_buf = rcpb_create();
+ if (dsn_buf == 0)
+ dsn_buf = dsb_create();
+ showq_reasons(client, logfile, rcpt_buf, dsn_buf, dup_filter);
+ if (bounce_log_close(logfile))
+ msg_warn("close %s %s: %m", MAIL_QUEUE_DEFER, id);
+ }
+ }
+ SHOWQ_CLEANUP_AND_RETURN;
+}
+
+/* showq_reasons - show deferral reasons */
+
+static void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf,
+ DSN_BUF *dsn_buf, HTABLE *dup_filter)
+{
+ RECIPIENT *rcpt = &rcpt_buf->rcpt;
+ DSN *dsn = &dsn_buf->dsn;
+
+ while (bounce_log_read(bp, rcpt_buf, dsn_buf) != 0) {
+
+ /*
+ * Update the duplicate filter.
+ */
+ if (var_dup_filter_limit == 0
+ || dup_filter->used < var_dup_filter_limit)
+ if (htable_locate(dup_filter, rcpt->address) == 0)
+ htable_enter(dup_filter, rcpt->address, (void *) 0);
+
+ attr_print(client, ATTR_FLAG_MORE,
+ SEND_ATTR_STR(MAIL_ATTR_RECIP, rcpt->address),
+ SEND_ATTR_STR(MAIL_ATTR_WHY, dsn->reason),
+ ATTR_TYPE_END);
+ }
+}
+
+
+/* showq_service - service client */
+
+static void showq_service(VSTREAM *client, char *unused_service, char **argv)
+{
+ VSTREAM *qfile;
+ const char *path;
+ int status;
+ char *id;
+ struct stat st;
+ struct queue_info {
+ char *name; /* queue name */
+ char *(*scan_next) (SCAN_DIR *); /* flat or recursive */
+ };
+ struct queue_info *qp;
+
+ static struct queue_info queue_info[] = {
+ MAIL_QUEUE_MAILDROP, scan_dir_next,
+ MAIL_QUEUE_ACTIVE, mail_scan_dir_next,
+ MAIL_QUEUE_INCOMING, mail_scan_dir_next,
+ MAIL_QUEUE_DEFERRED, mail_scan_dir_next,
+ MAIL_QUEUE_HOLD, mail_scan_dir_next,
+ 0,
+ };
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Skip any files that have the wrong permissions. If we can't open an
+ * existing file, assume the system is out of resources or that it is
+ * mis-configured, and force backoff by raising a fatal error.
+ */
+ for (qp = queue_info; qp->name != 0; qp++) {
+ SCAN_DIR *scan = scan_dir_open(qp->name);
+ char *saved_id = 0;
+
+ while ((id = qp->scan_next(scan)) != 0) {
+
+ /*
+ * XXX I have seen showq loop on the same queue id. That would be
+ * an operating system bug, but who cares whose fault it is. Make
+ * sure this will never happen again.
+ */
+ if (saved_id) {
+ if (strcmp(saved_id, id) == 0) {
+ msg_warn("readdir loop on queue %s id %s", qp->name, id);
+ break;
+ }
+ myfree(saved_id);
+ }
+ saved_id = mystrdup(id);
+ status = mail_open_ok(qp->name, id, &st, &path);
+ if (status == MAIL_OPEN_YES) {
+ if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) {
+ showq_report(client, qp->name, id, qfile, (long) st.st_size,
+ st.st_mtime, st.st_mode);
+ if (vstream_fclose(qfile))
+ msg_warn("close file %s %s: %m", qp->name, id);
+ } else if (errno != ENOENT) {
+ msg_warn("open %s %s: %m", qp->name, id);
+ }
+ }
+ vstream_fflush(client);
+ }
+ if (saved_id)
+ myfree(saved_id);
+ scan_dir_close(scan);
+ }
+ attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the single-threaded server skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_INT_TABLE int_table[] = {
+ VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
+ 0,
+ };
+ CONFIG_STR_TABLE str_table[] = {
+ VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ single_server_main(argc, argv, showq_service,
+ CA_MAIL_SERVER_INT_TABLE(int_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ 0);
+}