summaryrefslogtreecommitdiffstats
path: root/src/qmqpd
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmqpd')
l---------src/qmqpd/.indent.pro1
-rw-r--r--src/qmqpd/.printfck25
-rw-r--r--src/qmqpd/Makefile.in139
-rw-r--r--src/qmqpd/qmqpd.c865
-rw-r--r--src/qmqpd/qmqpd.h95
-rw-r--r--src/qmqpd/qmqpd_peer.c309
-rw-r--r--src/qmqpd/qmqpd_state.c99
7 files changed, 1533 insertions, 0 deletions
diff --git a/src/qmqpd/.indent.pro b/src/qmqpd/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/qmqpd/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/qmqpd/.printfck b/src/qmqpd/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/qmqpd/.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/qmqpd/Makefile.in b/src/qmqpd/Makefile.in
new file mode 100644
index 0000000..d4cdf33
--- /dev/null
+++ b/src/qmqpd/Makefile.in
@@ -0,0 +1,139 @@
+SHELL = /bin/sh
+SRCS = qmqpd.c qmqpd_state.c qmqpd_peer.c
+OBJS = qmqpd.o qmqpd_state.o qmqpd_peer.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+TESTPROG=
+PROG = qmqpd
+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
+ cp *.h 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 *.db *.out *.tmp
+ 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'
+qmqpd.o: ../../include/argv.h
+qmqpd.o: ../../include/attr.h
+qmqpd.o: ../../include/check_arg.h
+qmqpd.o: ../../include/cleanup_user.h
+qmqpd.o: ../../include/debug_peer.h
+qmqpd.o: ../../include/dict.h
+qmqpd.o: ../../include/htable.h
+qmqpd.o: ../../include/inet_proto.h
+qmqpd.o: ../../include/input_transp.h
+qmqpd.o: ../../include/iostuff.h
+qmqpd.o: ../../include/lex_822.h
+qmqpd.o: ../../include/mail_conf.h
+qmqpd.o: ../../include/mail_date.h
+qmqpd.o: ../../include/mail_params.h
+qmqpd.o: ../../include/mail_proto.h
+qmqpd.o: ../../include/mail_server.h
+qmqpd.o: ../../include/mail_stream.h
+qmqpd.o: ../../include/mail_version.h
+qmqpd.o: ../../include/match_list.h
+qmqpd.o: ../../include/match_parent_style.h
+qmqpd.o: ../../include/msg.h
+qmqpd.o: ../../include/myflock.h
+qmqpd.o: ../../include/mymalloc.h
+qmqpd.o: ../../include/namadr_list.h
+qmqpd.o: ../../include/netstring.h
+qmqpd.o: ../../include/nvtable.h
+qmqpd.o: ../../include/quote_822_local.h
+qmqpd.o: ../../include/quote_flags.h
+qmqpd.o: ../../include/rec_type.h
+qmqpd.o: ../../include/recipient_list.h
+qmqpd.o: ../../include/record.h
+qmqpd.o: ../../include/smtputf8.h
+qmqpd.o: ../../include/sys_defs.h
+qmqpd.o: ../../include/vbuf.h
+qmqpd.o: ../../include/verp_sender.h
+qmqpd.o: ../../include/vstream.h
+qmqpd.o: ../../include/vstring.h
+qmqpd.o: qmqpd.c
+qmqpd.o: qmqpd.h
+qmqpd_peer.o: ../../include/attr.h
+qmqpd_peer.o: ../../include/check_arg.h
+qmqpd_peer.o: ../../include/htable.h
+qmqpd_peer.o: ../../include/inet_proto.h
+qmqpd_peer.o: ../../include/iostuff.h
+qmqpd_peer.o: ../../include/mail_params.h
+qmqpd_peer.o: ../../include/mail_proto.h
+qmqpd_peer.o: ../../include/mail_stream.h
+qmqpd_peer.o: ../../include/msg.h
+qmqpd_peer.o: ../../include/myaddrinfo.h
+qmqpd_peer.o: ../../include/mymalloc.h
+qmqpd_peer.o: ../../include/nvtable.h
+qmqpd_peer.o: ../../include/sock_addr.h
+qmqpd_peer.o: ../../include/split_at.h
+qmqpd_peer.o: ../../include/stringops.h
+qmqpd_peer.o: ../../include/sys_defs.h
+qmqpd_peer.o: ../../include/valid_hostname.h
+qmqpd_peer.o: ../../include/valid_mailhost_addr.h
+qmqpd_peer.o: ../../include/vbuf.h
+qmqpd_peer.o: ../../include/vstream.h
+qmqpd_peer.o: ../../include/vstring.h
+qmqpd_peer.o: qmqpd.h
+qmqpd_peer.o: qmqpd_peer.c
+qmqpd_state.o: ../../include/attr.h
+qmqpd_state.o: ../../include/check_arg.h
+qmqpd_state.o: ../../include/cleanup_user.h
+qmqpd_state.o: ../../include/htable.h
+qmqpd_state.o: ../../include/iostuff.h
+qmqpd_state.o: ../../include/mail_proto.h
+qmqpd_state.o: ../../include/mail_stream.h
+qmqpd_state.o: ../../include/mymalloc.h
+qmqpd_state.o: ../../include/nvtable.h
+qmqpd_state.o: ../../include/sys_defs.h
+qmqpd_state.o: ../../include/vbuf.h
+qmqpd_state.o: ../../include/vstream.h
+qmqpd_state.o: ../../include/vstring.h
+qmqpd_state.o: qmqpd.h
+qmqpd_state.o: qmqpd_state.c
diff --git a/src/qmqpd/qmqpd.c b/src/qmqpd/qmqpd.c
new file mode 100644
index 0000000..d94d33d
--- /dev/null
+++ b/src/qmqpd/qmqpd.c
@@ -0,0 +1,865 @@
+/*++
+/* NAME
+/* qmqpd 8
+/* SUMMARY
+/* Postfix QMQP server
+/* SYNOPSIS
+/* \fBqmqpd\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The Postfix QMQP server receives one message per connection.
+/* Each message is piped through the \fBcleanup\fR(8)
+/* daemon, and is placed into the \fBincoming\fR queue as one
+/* single queue file. The program expects to be run from the
+/* \fBmaster\fR(8) process manager.
+/*
+/* The QMQP server implements one access policy: only explicitly
+/* authorized client hosts are allowed to use the service.
+/* SECURITY
+/* .ad
+/* .fi
+/* The QMQP server is moderately security-sensitive. It talks to QMQP
+/* clients and to DNS servers on the network. The QMQP server can be
+/* run chrooted at fixed low privilege.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* The QMQP protocol provides only one server reply per message
+/* delivery. It is therefore not possible to reject individual
+/* recipients.
+/*
+/* The QMQP protocol requires the server to receive the entire
+/* message before replying. If a message is malformed, or if any
+/* netstring component is longer than acceptable, Postfix replies
+/* immediately and closes the connection. It is left up to the
+/* client to handle the situation.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically, as \fBqmqpd\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.
+/* CONTENT INSPECTION CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBcontent_filter (empty)\fR"
+/* After the message is queued, send the entire message to the
+/* specified \fItransport:destination\fR.
+/* .IP "\fBreceive_override_options (empty)\fR"
+/* Enable or disable recipient validation, built-in content
+/* filtering, or address mapping.
+/* SMTPUTF8 CONTROLS
+/* .ad
+/* .fi
+/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+/* .IP "\fBsmtputf8_enable (yes)\fR"
+/* Enable preliminary SMTPUTF8 support for the protocols described
+/* in RFC 6531..6533.
+/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR"
+/* Detect that a message requires SMTPUTF8 support for the specified
+/* mail origin classes.
+/* .PP
+/* Available in Postfix version 3.2 and later:
+/* .IP "\fBenable_idna2003_compatibility (no)\fR"
+/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008,
+/* when converting UTF-8 domain names to/from the ASCII form that is
+/* used for DNS lookups.
+/* RESOURCE AND RATE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBline_length_limit (2048)\fR"
+/* Upon input, long lines are chopped up into pieces of at most
+/* this length; upon delivery, long lines are reconstructed.
+/* .IP "\fBhopcount_limit (50)\fR"
+/* The maximal number of Received: message headers that is allowed
+/* in the primary message headers.
+/* .IP "\fBmessage_size_limit (10240000)\fR"
+/* The maximal size in bytes of a message, including envelope information.
+/* .IP "\fBqmqpd_timeout (300s)\fR"
+/* The time limit for sending or receiving information over the network.
+/* TROUBLE SHOOTING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBdebug_peer_level (2)\fR"
+/* The increment in verbose logging level when a nexthop destination,
+/* remote client or server name or network address matches a pattern
+/* given with the debug_peer_list parameter.
+/* .IP "\fBdebug_peer_list (empty)\fR"
+/* Optional list of nexthop destination, remote client or server
+/* name or network address patterns that, if matched, cause the verbose
+/* logging level to increase by the amount specified in $debug_peer_level.
+/* .IP "\fBsoft_bounce (no)\fR"
+/* Safety net to keep mail queued that would otherwise be returned to
+/* the sender.
+/* TARPIT CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBqmqpd_error_delay (1s)\fR"
+/* How long the Postfix QMQP server will pause before sending a negative
+/* reply to the remote QMQP client.
+/* MISCELLANEOUS CONTROLS
+/* .ad
+/* .fi
+/* .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 "\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 "\fBqmqpd_authorized_clients (empty)\fR"
+/* What remote QMQP clients are allowed to connect to the Postfix QMQP
+/* server port.
+/* .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".
+/* .IP "\fBverp_delimiter_filter (-=+)\fR"
+/* The characters Postfix accepts as VERP delimiter characters on the
+/* Postfix \fBsendmail\fR(1) command line and in SMTP commands.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBqmqpd_client_port_logging (no)\fR"
+/* Enable logging of the remote QMQP client port in addition to
+/* the hostname and IP address.
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* http://cr.yp.to/proto/qmqp.html, QMQP protocol
+/* cleanup(8), message canonicalization
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* QMQP_README, Postfix ezmlm-idx howto.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* .ad
+/* .fi
+/* The qmqpd service was introduced with Postfix version 1.1.
+/* 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 <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <netstring.h>
+#include <dict.h>
+#include <inet_proto.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h>
+#include <record.h>
+#include <rec_type.h>
+#include <mail_proto.h>
+#include <cleanup_user.h>
+#include <mail_date.h>
+#include <mail_conf.h>
+#include <debug_peer.h>
+#include <mail_stream.h>
+#include <namadr_list.h>
+#include <quote_822_local.h>
+#include <match_parent_style.h>
+#include <lex_822.h>
+#include <verp_sender.h>
+#include <input_transp.h>
+#include <smtputf8.h>
+
+/* Single-threaded server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific */
+
+#include <qmqpd.h>
+
+ /*
+ * Tunable parameters. Make sure that there is some bound on the length of a
+ * netstring, so that the mail system stays in control even when a malicious
+ * client sends netstrings of unreasonable length. The recipient count limit
+ * is enforced by the message size limit.
+ */
+int var_qmqpd_timeout;
+int var_qmqpd_err_sleep;
+char *var_filter_xport;
+char *var_qmqpd_clients;
+char *var_input_transp;
+bool var_qmqpd_client_port_log;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+#define DO_LOG 1
+#define DONT_LOG 0
+
+ /*
+ * Access control. This service should be exposed only to explicitly
+ * authorized clients. There is no default authorization.
+ */
+static NAMADR_LIST *qmqpd_clients;
+
+ /*
+ * Transparency: before mail is queued, do we allow address mapping,
+ * automatic bcc, header/body checks?
+ */
+int qmqpd_input_transp_mask;
+
+/* qmqpd_open_file - open a queue file */
+
+static void qmqpd_open_file(QMQPD_STATE *state)
+{
+ int cleanup_flags;
+
+ /*
+ * Connect to the cleanup server. Log client name/address with queue ID.
+ */
+ cleanup_flags = input_transp_cleanup(CLEANUP_FLAG_MASK_EXTERNAL,
+ qmqpd_input_transp_mask);
+ cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_QMQPD);
+ state->dest = mail_stream_service(MAIL_CLASS_PUBLIC, var_cleanup_service);
+ if (state->dest == 0
+ || attr_print(state->dest->stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags),
+ ATTR_TYPE_END) != 0)
+ msg_fatal("unable to connect to the %s %s service",
+ MAIL_CLASS_PUBLIC, var_cleanup_service);
+ state->cleanup = state->dest->stream;
+ state->queue_id = mystrdup(state->dest->id);
+ msg_info("%s: client=%s", state->queue_id, state->namaddr);
+
+ /*
+ * Record the time of arrival. Optionally, enable content filtering (not
+ * bloody likely, but present for the sake of consistency with all other
+ * Postfix points of entrance).
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT,
+ REC_TYPE_TIME_ARG(state->arrival_time));
+ if (*var_filter_xport)
+ rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport);
+}
+
+/* qmqpd_read_content - receive message content */
+
+static void qmqpd_read_content(QMQPD_STATE *state)
+{
+ state->where = "receiving message content";
+ netstring_get(state->client, state->message, var_message_limit);
+}
+
+/* qmqpd_copy_sender - copy envelope sender */
+
+static void qmqpd_copy_sender(QMQPD_STATE *state)
+{
+ char *end_prefix;
+ char *end_origin;
+ int verp_requested;
+ static char verp_delims[] = "-=";
+
+ /*
+ * If the sender address looks like prefix@origin-@[], then request
+ * variable envelope return path delivery, with an envelope sender
+ * address of prefi@origin, and with VERP delimiters of x and =. This
+ * way, the recipients will see envelope sender addresses that look like:
+ * prefixuser=domain@origin.
+ */
+ state->where = "receiving sender address";
+ netstring_get(state->client, state->buf, var_line_limit);
+ VSTRING_TERMINATE(state->buf);
+ verp_requested =
+ ((end_origin = vstring_end(state->buf) - 4) > STR(state->buf)
+ && strcmp(end_origin, "-@[]") == 0
+ && (end_prefix = strchr(STR(state->buf), '@')) != 0 /* XXX */
+ && --end_prefix < end_origin - 2 /* non-null origin */
+ && end_prefix > STR(state->buf)); /* non-null prefix */
+ if (verp_requested) {
+ verp_delims[0] = end_prefix[0];
+ if (verp_delims_verify(verp_delims) != 0) {
+ state->err |= CLEANUP_STAT_CONT; /* XXX */
+ vstring_sprintf(state->why_rejected, "Invalid VERP delimiters: \"%s\". Need two characters from \"%s\"",
+ verp_delims, var_verp_filter);
+ }
+ memmove(end_prefix, end_prefix + 1, end_origin - end_prefix - 1);
+ vstring_truncate(state->buf, end_origin - STR(state->buf) - 1);
+ }
+ if (state->err == CLEANUP_STAT_OK
+ && REC_PUT_BUF(state->cleanup, REC_TYPE_FROM, state->buf) < 0)
+ state->err = CLEANUP_STAT_WRITE;
+ if (verp_requested)
+ if (state->err == CLEANUP_STAT_OK
+ && rec_put(state->cleanup, REC_TYPE_VERP, verp_delims, 2) < 0)
+ state->err = CLEANUP_STAT_WRITE;
+ state->sender = mystrndup(STR(state->buf), LEN(state->buf));
+}
+
+/* qmqpd_write_attributes - save session attributes */
+
+static void qmqpd_write_attributes(QMQPD_STATE *state)
+{
+
+ /*
+ * Logging attributes, also used for XFORWARD.
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_NAME, state->name);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_ADDR, state->rfc_addr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_CLIENT_PORT, state->port);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_ORIGIN, state->namaddr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_LOG_PROTO_NAME, state->protocol);
+
+ /*
+ * For consistency with the smtpd Milter client, we need to provide the
+ * real client attributes to the cleanup Milter client. This does not
+ * matter much with qmqpd which speaks to trusted clients only, but we
+ * want to be sure that the cleanup input protocol is ready when a new
+ * type of network daemon is added to receive mail from the Internet.
+ *
+ * See also the comments in smtpd.c.
+ */
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_NAME, state->name);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_ADDR, state->addr);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_CLIENT_PORT, state->port);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%u",
+ MAIL_ATTR_ACT_CLIENT_AF, state->addr_family);
+ rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s",
+ MAIL_ATTR_ACT_PROTO_NAME, state->protocol);
+
+ /* XXX What about the address rewriting context? */
+}
+
+/* qmqpd_copy_recipients - copy message recipients */
+
+static void qmqpd_copy_recipients(QMQPD_STATE *state)
+{
+ int ch;
+
+ /*
+ * Remember the first recipient. We are done when we read the over-all
+ * netstring terminator.
+ *
+ * XXX This approach violates abstractions, but it is a heck of a lot more
+ * convenient than counting the over-all byte count down to zero, like
+ * qmail does.
+ */
+ state->where = "receiving recipient address";
+ while ((ch = VSTREAM_GETC(state->client)) != ',') {
+ vstream_ungetc(state->client, ch);
+ netstring_get(state->client, state->buf, var_line_limit);
+ if (state->err == CLEANUP_STAT_OK
+ && REC_PUT_BUF(state->cleanup, REC_TYPE_RCPT, state->buf) < 0)
+ state->err = CLEANUP_STAT_WRITE;
+ state->rcpt_count++;
+ if (state->recipient == 0)
+ state->recipient = mystrndup(STR(state->buf), LEN(state->buf));
+ }
+}
+
+/* qmqpd_next_line - get line from buffer, return last char, newline, or -1 */
+
+static int qmqpd_next_line(VSTRING *message, char **start, int *len,
+ char **next)
+{
+ char *beyond = STR(message) + LEN(message);
+ char *enough = *next + var_line_limit;
+ char *cp;
+
+ /*
+ * Stop at newline or at some limit. Don't look beyond the end of the
+ * buffer.
+ */
+#define UCHARPTR(x) ((unsigned char *) (x))
+
+ for (cp = *start = *next; /* void */ ; cp++) {
+ if (cp >= beyond)
+ return ((*len = (*next = cp) - *start) > 0 ? UCHARPTR(cp)[-1] : -1);
+ if (*cp == '\n')
+ return ((*len = cp - *start), (*next = cp + 1), '\n');
+ if (cp >= enough)
+ return ((*len = cp - *start), (*next = cp), UCHARPTR(cp)[-1]);
+ }
+}
+
+/* qmqpd_write_content - write the message content segment */
+
+static void qmqpd_write_content(QMQPD_STATE *state)
+{
+ char *start;
+ char *next;
+ int len;
+ int rec_type;
+ int first = 1;
+ int ch;
+
+ /*
+ * Start the message content segment. Prepend our own Received: header to
+ * the message content. List the recipient only when a message has one
+ * recipient. Otherwise, don't list the recipient to avoid revealing Bcc:
+ * recipients that are supposed to be invisible.
+ */
+ rec_fputs(state->cleanup, REC_TYPE_MESG, "");
+ rec_fprintf(state->cleanup, REC_TYPE_NORM, "Received: from %s (%s [%s])",
+ state->name, state->name, state->rfc_addr);
+ if (state->rcpt_count == 1 && state->recipient) {
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tby %s (%s) with %s id %s",
+ var_myhostname, var_mail_name,
+ state->protocol, state->queue_id);
+ quote_822_local(state->buf, state->recipient);
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tfor <%s>; %s", STR(state->buf),
+ mail_date(state->arrival_time.tv_sec));
+ } else {
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tby %s (%s) with %s",
+ var_myhostname, var_mail_name, state->protocol);
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\tid %s; %s", state->queue_id,
+ mail_date(state->arrival_time.tv_sec));
+ }
+#ifdef RECEIVED_ENVELOPE_FROM
+ quote_822_local(state->buf, state->sender);
+ rec_fprintf(state->cleanup, REC_TYPE_NORM,
+ "\t(envelope-from <%s>)", STR(state->buf));
+#endif
+
+ /*
+ * Write the message content.
+ *
+ * XXX Force an empty record when the queue file content begins with
+ * whitespace, so that it won't be considered as being part of our own
+ * Received: header. What an ugly Kluge.
+ *
+ * XXX Deal with UNIX-style From_ lines at the start of message content just
+ * in case.
+ */
+ for (next = STR(state->message); /* void */ ; /* void */ ) {
+ if ((ch = qmqpd_next_line(state->message, &start, &len, &next)) < 0)
+ break;
+ if (ch == '\n')
+ rec_type = REC_TYPE_NORM;
+ else
+ rec_type = REC_TYPE_CONT;
+ if (first) {
+ if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) {
+ rec_fprintf(state->cleanup, rec_type,
+ "X-Mailbox-Line: %.*s", len, start);
+ continue;
+ }
+ first = 0;
+ if (len > 0 && IS_SPACE_TAB(start[0]))
+ rec_put(state->cleanup, REC_TYPE_NORM, "", 0);
+ }
+ if (rec_put(state->cleanup, rec_type, start, len) < 0) {
+ state->err = CLEANUP_STAT_WRITE;
+ return;
+ }
+ }
+}
+
+/* qmqpd_close_file - close queue file */
+
+static void qmqpd_close_file(QMQPD_STATE *state)
+{
+
+ /*
+ * Send the end-of-segment markers.
+ */
+ if (state->err == CLEANUP_STAT_OK)
+ if (rec_fputs(state->cleanup, REC_TYPE_XTRA, "") < 0
+ || rec_fputs(state->cleanup, REC_TYPE_END, "") < 0
+ || vstream_fflush(state->cleanup))
+ state->err = CLEANUP_STAT_WRITE;
+
+ /*
+ * Finish the queue file or finish the cleanup conversation.
+ */
+ if (state->err == 0)
+ state->err = mail_stream_finish(state->dest, state->why_rejected);
+ else
+ mail_stream_cleanup(state->dest);
+ state->dest = 0;
+}
+
+/* qmqpd_reply - send status to client and optionally log message */
+
+static void qmqpd_reply(QMQPD_STATE *state, int log_message,
+ int status_code, const char *fmt,...)
+{
+ va_list ap;
+
+ /*
+ * Optionally change hard errors into retryable ones. Send the reply and
+ * optionally log it. Always insert a delay before reporting a problem.
+ * This slows down software run-away conditions.
+ */
+ if (status_code == QMQPD_STAT_HARD && var_soft_bounce)
+ status_code = QMQPD_STAT_RETRY;
+ VSTRING_RESET(state->buf);
+ VSTRING_ADDCH(state->buf, status_code);
+ va_start(ap, fmt);
+ vstring_vsprintf_append(state->buf, fmt, ap);
+ va_end(ap);
+ NETSTRING_PUT_BUF(state->client, state->buf);
+ if (log_message)
+ (status_code == QMQPD_STAT_OK ? msg_info : msg_warn) ("%s: %s: %s",
+ state->queue_id, state->namaddr, STR(state->buf) + 1);
+ if (status_code != QMQPD_STAT_OK)
+ sleep(var_qmqpd_err_sleep);
+ netstring_fflush(state->client);
+}
+
+/* qmqpd_send_status - send mail transaction completion status */
+
+static void qmqpd_send_status(QMQPD_STATE *state)
+{
+
+ /*
+ * One message may suffer from multiple errors, so complain only about
+ * the most severe error.
+ *
+ * See also: smtpd.c
+ */
+ state->where = "sending completion status";
+
+ if (state->err == CLEANUP_STAT_OK) {
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_OK,
+ "Ok: queued as %s", state->queue_id);
+ } else if ((state->err & CLEANUP_STAT_DEFER) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: %s", STR(state->why_rejected));
+ } else if ((state->err & CLEANUP_STAT_BAD) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: internal error %d", state->err);
+ } else if ((state->err & CLEANUP_STAT_SIZE) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: message too large");
+ } else if ((state->err & CLEANUP_STAT_HOPS) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: too many hops");
+ } else if ((state->err & CLEANUP_STAT_CONT) != 0) {
+ qmqpd_reply(state, DO_LOG, STR(state->why_rejected)[0] == '4' ?
+ QMQPD_STAT_RETRY : QMQPD_STAT_HARD,
+ "Error: %s", STR(state->why_rejected));
+ } else if ((state->err & CLEANUP_STAT_WRITE) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: queue file write error");
+ } else if ((state->err & CLEANUP_STAT_RCPT) != 0) {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
+ "Error: no recipients specified");
+ } else {
+ qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
+ "Error: internal error %d", state->err);
+ }
+}
+
+/* qmqpd_receive - receive QMQP message+sender+recipients */
+
+static void qmqpd_receive(QMQPD_STATE *state)
+{
+
+ /*
+ * Open a queue file. This must be first so that we can simplify the
+ * error logging and always include the queue ID information.
+ */
+ qmqpd_open_file(state);
+
+ /*
+ * Read and ignore the over-all netstring length indicator.
+ */
+ state->where = "receiving QMQP packet header";
+ (void) netstring_get_length(state->client);
+
+ /*
+ * XXX Read the message content into memory, because Postfix expects to
+ * store the sender before storing the message content. Fixing that
+ * requires changes to pickup, cleanup, qmgr, and perhaps elsewhere, so
+ * that will have to happen later when I have more time. However, QMQP is
+ * used for mailing list distribution, so the bulk of the volume is
+ * expected to be not message content but recipients, and recipients are
+ * not accumulated in memory.
+ */
+ qmqpd_read_content(state);
+
+ /*
+ * Read and write the envelope sender.
+ */
+ qmqpd_copy_sender(state);
+
+ /*
+ * Record some session attributes.
+ */
+ qmqpd_write_attributes(state);
+
+ /*
+ * Read and write the envelope recipients, including the optional big
+ * brother recipient.
+ */
+ qmqpd_copy_recipients(state);
+
+ /*
+ * Start the message content segment, prepend our own Received: header,
+ * and write the message content.
+ */
+ if (state->err == 0)
+ qmqpd_write_content(state);
+
+ /*
+ * Close the queue file.
+ */
+ qmqpd_close_file(state);
+
+ /*
+ * Report the completion status to the client.
+ */
+ qmqpd_send_status(state);
+}
+
+/* qmqpd_proto - speak the QMQP "protocol" */
+
+static void qmqpd_proto(QMQPD_STATE *state)
+{
+ int status;
+
+ netstring_setup(state->client, var_qmqpd_timeout);
+
+ switch (status = vstream_setjmp(state->client)) {
+
+ default:
+ msg_panic("qmqpd_proto: unknown status %d", status);
+
+ case NETSTRING_ERR_EOF:
+ state->reason = "lost connection";
+ break;
+
+ case NETSTRING_ERR_TIME:
+ state->reason = "read/write timeout";
+ break;
+
+ case NETSTRING_ERR_FORMAT:
+ state->reason = "netstring format error";
+ if (vstream_setjmp(state->client) == 0)
+ if (state->reason && state->where)
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s",
+ state->reason, state->where);
+ break;
+
+ case NETSTRING_ERR_SIZE:
+ state->reason = "netstring length exceeds storage limit";
+ if (vstream_setjmp(state->client) == 0)
+ if (state->reason && state->where)
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s",
+ state->reason, state->where);
+ break;
+
+ case 0:
+
+ /*
+ * See if we want to talk to this client at all.
+ */
+ if (namadr_list_match(qmqpd_clients, state->name, state->addr) != 0) {
+ qmqpd_receive(state);
+ } else if (qmqpd_clients->error == 0) {
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD,
+ "Error: %s is not authorized to use this service",
+ state->namaddr);
+ } else {
+ qmqpd_reply(state, DONT_LOG, QMQPD_STAT_RETRY,
+ "Error: server configuration error");
+ }
+ break;
+ }
+
+ /*
+ * Log abnormal session termination. Indicate the last recognized state
+ * before things went wrong.
+ */
+ if (state->reason && state->where)
+ msg_info("%s: %s: %s while %s",
+ state->queue_id ? state->queue_id : "NOQUEUE",
+ state->namaddr, state->reason, state->where);
+}
+
+/* qmqpd_service - service one client */
+
+static void qmqpd_service(VSTREAM *stream, char *unused_service, char **argv)
+{
+ QMQPD_STATE *state;
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * For sanity, require that at least one of INET or INET6 is enabled.
+ * Otherwise, we can't look up interface information, and we can't
+ * convert names or addresses.
+ */
+ if (inet_proto_info()->ai_family_list[0] == 0)
+ msg_fatal("all network protocols are disabled (%s = %s)",
+ VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * This routine runs when a client has connected to our network port.
+ * Look up and sanitize the peer name and initialize some connection-
+ * specific state.
+ */
+ state = qmqpd_state_alloc(stream);
+
+ /*
+ * See if we need to turn on verbose logging for this client.
+ */
+ debug_peer_check(state->name, state->addr);
+
+ /*
+ * Provide the QMQP service.
+ */
+ msg_info("connect from %s", state->namaddr);
+ qmqpd_proto(state);
+ msg_info("disconnect from %s", state->namaddr);
+
+ /*
+ * After the client has gone away, clean up whatever we have set up at
+ * connection time.
+ */
+ debug_peer_restore();
+ qmqpd_state_free(state);
+}
+
+/* pre_accept - see if tables have changed */
+
+static void pre_accept(char *unused_name, char **unused_argv)
+{
+ const char *table;
+
+ if ((table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ exit(0);
+ }
+}
+
+/* pre_jail_init - pre-jail initialization */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+ debug_peer_init();
+ qmqpd_clients =
+ namadr_list_init(VAR_QMQPD_CLIENTS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_QMQPD_CLIENTS),
+ var_qmqpd_clients);
+}
+
+/* post_jail_init - post-jail initialization */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Initialize the receive transparency options: do we want unknown
+ * recipient checks, do we want address mapping.
+ */
+ qmqpd_input_transp_mask =
+ input_transp_mask(VAR_INPUT_TRANSP, var_input_transp);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - the main program */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_QMTPD_TMOUT, DEF_QMTPD_TMOUT, &var_qmqpd_timeout, 1, 0,
+ VAR_QMTPD_ERR_SLEEP, DEF_QMTPD_ERR_SLEEP, &var_qmqpd_err_sleep, 0, 0,
+ 0,
+ };
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0,
+ VAR_QMQPD_CLIENTS, DEF_QMQPD_CLIENTS, &var_qmqpd_clients, 0, 0,
+ VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_QMQPD_CLIENT_PORT_LOG, DEF_QMQPD_CLIENT_PORT_LOG, &var_qmqpd_client_port_log,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ /*
+ * Pass control to the single-threaded service skeleton.
+ */
+ single_server_main(argc, argv, qmqpd_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ 0);
+}
diff --git a/src/qmqpd/qmqpd.h b/src/qmqpd/qmqpd.h
new file mode 100644
index 0000000..aad185b
--- /dev/null
+++ b/src/qmqpd/qmqpd.h
@@ -0,0 +1,95 @@
+/*++
+/* NAME
+/* qmqpd 3h
+/* SUMMARY
+/* Postfix QMQP server
+/* SYNOPSIS
+/* include "qmqpd.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <sys/time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstream.h>
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <mail_stream.h>
+
+ /*
+ * Per-session state.
+ */
+typedef struct {
+ int err; /* error flags */
+ VSTREAM *client; /* client connection */
+ VSTRING *message; /* message buffer */
+ VSTRING *buf; /* line buffer */
+ struct timeval arrival_time; /* start of session */
+ char *name; /* client name */
+ char *addr; /* client IP address */
+ char *port; /* client TCP port */
+ char *namaddr; /* name[addr]:port */
+ char *rfc_addr; /* RFC 2821 client IP address */
+ int addr_family; /* address family */
+ char *queue_id; /* queue file ID */
+ VSTREAM *cleanup; /* cleanup server */
+ MAIL_STREAM *dest; /* cleanup server */
+ int rcpt_count; /* recipient count */
+ char *reason; /* exception name */
+ char *sender; /* sender address */
+ char *recipient; /* recipient address */
+ char *protocol; /* protocol name */
+ char *where; /* protocol state */
+ VSTRING *why_rejected; /* REJECT reason */
+} QMQPD_STATE;
+
+ /*
+ * Representation of unknown upstream client or message information within
+ * qmqpd processes. This is not the representation that Postfix uses in
+ * queue files, in queue manager delivery requests, or in XCLIENT/XFORWARD
+ * commands!
+ */
+#define CLIENT_ATTR_UNKNOWN "unknown"
+
+#define CLIENT_NAME_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_ADDR_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_PORT_UNKNOWN CLIENT_ATTR_UNKNOWN
+#define CLIENT_NAMADDR_UNKNOWN CLIENT_ATTR_UNKNOWN
+
+ /*
+ * QMQP protocol status codes.
+ */
+#define QMQPD_STAT_OK 'K'
+#define QMQPD_STAT_RETRY 'Z'
+#define QMQPD_STAT_HARD 'D'
+
+ /*
+ * qmqpd_state.c
+ */
+QMQPD_STATE *qmqpd_state_alloc(VSTREAM *);
+void qmqpd_state_free(QMQPD_STATE *);
+
+ /*
+ * qmqpd_peer.c
+ */
+void qmqpd_peer_init(QMQPD_STATE *);
+void qmqpd_peer_reset(QMQPD_STATE *);
+
+/* 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
+/*--*/
diff --git a/src/qmqpd/qmqpd_peer.c b/src/qmqpd/qmqpd_peer.c
new file mode 100644
index 0000000..41cd009
--- /dev/null
+++ b/src/qmqpd/qmqpd_peer.c
@@ -0,0 +1,309 @@
+/*++
+/* NAME
+/* qmqpd_peer 3
+/* SUMMARY
+/* look up peer name/address information
+/* SYNOPSIS
+/* #include "qmqpd.h"
+/*
+/* void qmqpd_peer_init(state)
+/* QMQPD_STATE *state;
+/*
+/* void qmqpd_peer_reset(state)
+/* QMQPD_STATE *state;
+/* DESCRIPTION
+/* The qmqpd_peer_init() routine attempts to produce a printable
+/* version of the peer name and address of the specified socket.
+/* Where information is unavailable, the name and/or address
+/* are set to "unknown".
+/*
+/* qmqpd_peer_init() updates the following fields:
+/* .IP name
+/* The client hostname. An unknown name is represented by the
+/* string "unknown".
+/* .IP addr
+/* Printable representation of the client address.
+/* .IP namaddr
+/* String of the form: "name[addr]:port".
+/* .PP
+/* qmqpd_peer_reset() releases memory allocated by qmqpd_peer_init().
+/* 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/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <myaddrinfo.h>
+#include <sock_addr.h>
+#include <inet_proto.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_proto.h>
+#include <valid_mailhost_addr.h>
+#include <mail_params.h>
+
+/* Application-specific. */
+
+#include "qmqpd.h"
+
+/* qmqpd_peer_init - initialize peer information */
+
+void qmqpd_peer_init(QMQPD_STATE *state)
+{
+ const char *myname = "qmqpd_peer_init";
+ struct sockaddr_storage ss;
+ struct sockaddr *sa;
+ SOCKADDR_SIZE sa_length;
+ const INET_PROTO_INFO *proto_info = inet_proto_info();
+
+ sa = (struct sockaddr *) &ss;
+ sa_length = sizeof(ss);
+
+ /*
+ * Look up the peer address information.
+ */
+ if (getpeername(vstream_fileno(state->client), sa, &sa_length) >= 0) {
+ errno = 0;
+ }
+
+ /*
+ * If peer went away, give up.
+ */
+ if (errno != 0 && errno != ENOTSOCK) {
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ state->addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN);
+ state->addr_family = AF_UNSPEC;
+ state->port = mystrdup(CLIENT_PORT_UNKNOWN);
+ }
+
+ /*
+ * Convert the client address to printable address and hostname.
+ *
+ * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, while
+ * Postfix IPv6 (or IPv4) support is turned off, don't (skip to the final
+ * else clause, pretend the origin is localhost[127.0.0.1], and become an
+ * open relay).
+ */
+ else if (errno == 0
+ && (sa->sa_family == AF_INET
+#ifdef AF_INET6
+ || sa->sa_family == AF_INET6
+#endif
+ )) {
+ MAI_HOSTNAME_STR client_name;
+ MAI_HOSTADDR_STR client_addr;
+ MAI_SERVPORT_STR client_port;
+ int aierr;
+ char *colonp;
+
+ /*
+ * Sanity check: we can't use sockets that we're not configured for.
+ */
+ if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0)
+ msg_fatal("cannot handle socket type %s with \"%s = %s\"",
+#ifdef AF_INET6
+ sa->sa_family == AF_INET6 ? "AF_INET6" :
+#endif
+ sa->sa_family == AF_INET ? "AF_INET" :
+ "other", VAR_INET_PROTOCOLS, var_inet_protocols);
+
+ /*
+ * Sorry, but there are some things that we just cannot do while
+ * connected to the network.
+ */
+ if (geteuid() != var_owner_uid || getuid() != var_owner_uid) {
+ msg_error("incorrect QMQP server privileges: uid=%lu euid=%lu",
+ (unsigned long) getuid(), (unsigned long) geteuid());
+ msg_fatal("the Postfix QMQP server must run with $%s privileges",
+ VAR_MAIL_OWNER);
+ }
+
+ /*
+ * Convert the client address to printable form.
+ */
+ if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr,
+ &client_port, 0)) != 0)
+ msg_fatal("%s: cannot convert client address/port to string: %s",
+ myname, MAI_STRERROR(aierr));
+ state->port = mystrdup(client_port.buf);
+
+ /*
+ * XXX Require that the infrastructure strips off the IPv6 datalink
+ * suffix to avoid false alarms with strict address syntax checks.
+ */
+#ifdef HAS_IPV6
+ if (strchr(client_addr.buf, '%') != 0)
+ msg_panic("%s: address %s has datalink suffix",
+ myname, client_addr.buf);
+#endif
+
+ /*
+ * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on,
+ * but only if IPv4 support is enabled (why would anyone want to turn
+ * it off)? With IPv4 support enabled we have no need for the IPv6
+ * form in logging, hostname verification and access checks.
+ */
+#ifdef HAS_IPV6
+ if (sa->sa_family == AF_INET6) {
+ if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0
+ && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa))
+ && (colonp = strrchr(client_addr.buf, ':')) != 0) {
+ struct addrinfo *res0;
+
+ if (msg_verbose > 1)
+ msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"",
+ myname, client_addr.buf, colonp + 1);
+
+ state->addr = mystrdup(colonp + 1);
+ state->rfc_addr = mystrdup(colonp + 1);
+ state->addr_family = AF_INET;
+ aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0);
+ if (aierr)
+ msg_fatal("%s: cannot convert %s from string to binary: %s",
+ myname, state->addr, MAI_STRERROR(aierr));
+ sa_length = res0->ai_addrlen;
+ if (sa_length > sizeof(ss))
+ sa_length = sizeof(ss);
+ memcpy((void *) sa, res0->ai_addr, sa_length);
+ freeaddrinfo(res0);
+ }
+
+ /*
+ * Following RFC 2821 section 4.1.3, an IPv6 address literal gets
+ * a prefix of 'IPv6:'. We do this consistently for all IPv6
+ * addresses that appear in headers or envelopes. The fact
+ * that valid_mailhost_addr() enforces the form helps of course.
+ * We use the form without IPV6: prefix when doing access
+ * control, or when accessing the connection cache.
+ */
+ else {
+ state->addr = mystrdup(client_addr.buf);
+ state->rfc_addr =
+ concatenate(IPV6_COL, client_addr.buf, (char *) 0);
+ state->addr_family = sa->sa_family;
+ }
+ }
+
+ /*
+ * An IPv4 address is in dotted quad decimal form.
+ */
+ else
+#endif
+ {
+ state->addr = mystrdup(client_addr.buf);
+ state->rfc_addr = mystrdup(client_addr.buf);
+ state->addr_family = sa->sa_family;
+ }
+
+ /*
+ * Look up and sanity check the client hostname.
+ *
+ * It is unsafe to allow numeric hostnames, especially because there
+ * exists pressure to turn off the name->addr double check. In that
+ * case an attacker could trivally bypass access restrictions.
+ *
+ * sockaddr_to_hostname() already rejects malformed or numeric names.
+ */
+#define REJECT_PEER_NAME(state) { \
+ myfree(state->name); \
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN); \
+ }
+
+ if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name,
+ (MAI_SERVNAME_STR *) 0, 0)) != 0) {
+ state->name = mystrdup(CLIENT_NAME_UNKNOWN);
+ } else {
+ struct addrinfo *res0;
+ struct addrinfo *res;
+
+ state->name = mystrdup(client_name.buf);
+
+ /*
+ * Reject the hostname if it does not list the peer address.
+ */
+ aierr = hostname_to_sockaddr_pf(state->name, state->addr_family,
+ (char *) 0, 0, &res0);
+ if (aierr) {
+ msg_warn("hostname %s does not resolve to address %s: %s",
+ state->name, state->addr, MAI_STRERROR(aierr));
+ REJECT_PEER_NAME(state);
+ } else {
+ for (res = res0; /* void */ ; res = res->ai_next) {
+ if (res == 0) {
+ msg_warn("hostname %s does not resolve to address %s",
+ state->addr, state->name);
+ REJECT_PEER_NAME(state);
+ break;
+ }
+ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) {
+ msg_info("skipping address family %d for host %s",
+ res->ai_family, state->name);
+ continue;
+ }
+ if (sock_addr_cmp_addr(res->ai_addr, sa) == 0)
+ break; /* keep peer name */
+ }
+ freeaddrinfo(res0);
+ }
+ }
+ }
+
+ /*
+ * If it's not Internet, assume the client is local, and avoid using the
+ * naming service because that can hang when the machine is disconnected.
+ */
+ else {
+ state->name = mystrdup("localhost");
+ state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */
+ state->rfc_addr = mystrdup("127.0.0.1");/* XXX bogus. */
+ state->addr_family = AF_UNSPEC;
+ state->port = mystrdup("0"); /* XXX bogus. */
+ }
+
+ /*
+ * Do the name[addr]:port formatting for pretty reports.
+ */
+ state->namaddr =
+ concatenate(state->name, "[", state->addr, "]",
+ var_qmqpd_client_port_log ? ":" : (char *) 0,
+ state->port, (char *) 0);
+}
+
+/* qmqpd_peer_reset - destroy peer information */
+
+void qmqpd_peer_reset(QMQPD_STATE *state)
+{
+ myfree(state->name);
+ myfree(state->addr);
+ myfree(state->namaddr);
+ myfree(state->rfc_addr);
+ myfree(state->port);
+}
diff --git a/src/qmqpd/qmqpd_state.c b/src/qmqpd/qmqpd_state.c
new file mode 100644
index 0000000..9bee879
--- /dev/null
+++ b/src/qmqpd/qmqpd_state.c
@@ -0,0 +1,99 @@
+/*++
+/* NAME
+/* qmqpd_state 3
+/* SUMMARY
+/* Postfix QMQP server
+/* SYNOPSIS
+/* #include "qmqpd.h"
+/*
+/* QMQPD_STATE *qmqpd_state_alloc(stream)
+/* VSTREAM *stream;
+/*
+/* void qmqpd_state_free(state)
+/* QMQPD_STATE *state;
+/* DESCRIPTION
+/* qmqpd_state_alloc() creates and initializes session context.
+/*
+/* qmqpd_state_free() destroys session context.
+/*
+/* Arguments:
+/* .IP stream
+/* Stream connected to peer. The stream is not copied.
+/* DIAGNOSTICS
+/* All errors are fatal.
+/* 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
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <vstream.h>
+#include <vstring.h>
+
+/* Global library. */
+
+#include <mail_stream.h>
+#include <cleanup_user.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include <qmqpd.h>
+
+/* qmqpd_state_alloc - allocate and initialize session state */
+
+QMQPD_STATE *qmqpd_state_alloc(VSTREAM *stream)
+{
+ QMQPD_STATE *state;
+
+ state = (QMQPD_STATE *) mymalloc(sizeof(*state));
+ state->err = CLEANUP_STAT_OK;
+ state->client = stream;
+ state->message = vstring_alloc(1000);
+ state->buf = vstring_alloc(100);
+ GETTIMEOFDAY(&state->arrival_time);
+ qmqpd_peer_init(state);
+ state->queue_id = 0;
+ state->cleanup = 0;
+ state->dest = 0;
+ state->rcpt_count = 0;
+ state->reason = 0;
+ state->sender = 0;
+ state->recipient = 0;
+ state->protocol = MAIL_PROTO_QMQP;
+ state->where = "initializing client connection";
+ state->why_rejected = vstring_alloc(10);
+ return (state);
+}
+
+/* qmqpd_state_free - destroy session state */
+
+void qmqpd_state_free(QMQPD_STATE *state)
+{
+ vstring_free(state->message);
+ vstring_free(state->buf);
+ qmqpd_peer_reset(state);
+ if (state->queue_id)
+ myfree(state->queue_id);
+ if (state->dest)
+ mail_stream_cleanup(state->dest);
+ if (state->sender)
+ myfree(state->sender);
+ if (state->recipient)
+ myfree(state->recipient);
+ vstring_free(state->why_rejected);
+ myfree((void *) state);
+}