From a848231ae0f346dc7cc000973fbeb65b0894ee92 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 21:59:03 +0200 Subject: Adding upstream version 3.8.5. Signed-off-by: Daniel Baumann --- src/qmqpd/.indent.pro | 1 + src/qmqpd/.printfck | 25 ++ src/qmqpd/Makefile.in | 139 ++++++++ src/qmqpd/qmqpd.c | 865 ++++++++++++++++++++++++++++++++++++++++++++++++ src/qmqpd/qmqpd.h | 95 ++++++ src/qmqpd/qmqpd_peer.c | 309 +++++++++++++++++ src/qmqpd/qmqpd_state.c | 99 ++++++ 7 files changed, 1533 insertions(+) create mode 120000 src/qmqpd/.indent.pro create mode 100644 src/qmqpd/.printfck create mode 100644 src/qmqpd/Makefile.in create mode 100644 src/qmqpd/qmqpd.c create mode 100644 src/qmqpd/qmqpd.h create mode 100644 src/qmqpd/qmqpd_peer.c create mode 100644 src/qmqpd/qmqpd_state.c (limited to 'src/qmqpd') 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 +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single-threaded server skeleton. */ + +#include + +/* Application-specific */ + +#include + + /* + * 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 + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include + +/* 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); +} -- cgit v1.2.3