summaryrefslogtreecommitdiffstats
path: root/src/trivial-rewrite
diff options
context:
space:
mode:
Diffstat (limited to 'src/trivial-rewrite')
l---------src/trivial-rewrite/.indent.pro1
-rw-r--r--src/trivial-rewrite/.printfck25
-rw-r--r--src/trivial-rewrite/Makefile.in199
-rw-r--r--src/trivial-rewrite/resolve.c828
-rw-r--r--src/trivial-rewrite/rewrite.c303
-rw-r--r--src/trivial-rewrite/transport.c438
-rw-r--r--src/trivial-rewrite/transport.h51
-rw-r--r--src/trivial-rewrite/transport.in45
-rw-r--r--src/trivial-rewrite/transport.ref22
-rw-r--r--src/trivial-rewrite/trivial-rewrite.c652
-rw-r--r--src/trivial-rewrite/trivial-rewrite.h92
11 files changed, 2656 insertions, 0 deletions
diff --git a/src/trivial-rewrite/.indent.pro b/src/trivial-rewrite/.indent.pro
new file mode 120000
index 0000000..5c837ec
--- /dev/null
+++ b/src/trivial-rewrite/.indent.pro
@@ -0,0 +1 @@
+../../.indent.pro \ No newline at end of file
diff --git a/src/trivial-rewrite/.printfck b/src/trivial-rewrite/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/src/trivial-rewrite/.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/trivial-rewrite/Makefile.in b/src/trivial-rewrite/Makefile.in
new file mode 100644
index 0000000..92fa666
--- /dev/null
+++ b/src/trivial-rewrite/Makefile.in
@@ -0,0 +1,199 @@
+SHELL = /bin/sh
+SRCS = trivial-rewrite.c rewrite.c resolve.c transport.c
+OBJS = trivial-rewrite.o rewrite.o resolve.o transport.o
+HDRS =
+TESTSRC =
+DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE)
+CFLAGS = $(DEBUG) $(OPT) $(DEFS)
+LIB =
+TESTPROG= transport
+PROG = trivial-rewrite
+LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \
+ ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX)
+LIB_DIR = ../../lib
+INC_DIR = ../../include
+BIN_DIR = ../../libexec
+
+.c.o:; $(CC) $(CFLAGS) -c $*.c
+
+all: $(PROG) $(LIB)
+
+$(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: transport_test
+
+root_tests:
+
+$(BIN_DIR)/$(PROG): $(PROG)
+ cp $(PROG) $@
+
+update: $(BIN_DIR)/$(PROG)
+
+transport: transport.c $(LIB) $(LIBS)
+ -mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS)
+ mv junk $@.o
+
+transport_test: transport transport.in transport.ref
+ $(SHLIB_ENV) sh transport.in >transport.tmp 2>&1
+ diff transport.ref transport.tmp
+ rm -f transport.tmp
+
+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 trivial-rewrite $(TESTPROG) junk $(LIB)
+ 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'
+resolve.o: ../../include/argv.h
+resolve.o: ../../include/attr.h
+resolve.o: ../../include/check_arg.h
+resolve.o: ../../include/dict.h
+resolve.o: ../../include/domain_list.h
+resolve.o: ../../include/htable.h
+resolve.o: ../../include/iostuff.h
+resolve.o: ../../include/mail_addr_find.h
+resolve.o: ../../include/mail_addr_form.h
+resolve.o: ../../include/mail_conf.h
+resolve.o: ../../include/mail_params.h
+resolve.o: ../../include/mail_proto.h
+resolve.o: ../../include/maps.h
+resolve.o: ../../include/match_list.h
+resolve.o: ../../include/match_parent_style.h
+resolve.o: ../../include/msg.h
+resolve.o: ../../include/myflock.h
+resolve.o: ../../include/mymalloc.h
+resolve.o: ../../include/nvtable.h
+resolve.o: ../../include/quote_822_local.h
+resolve.o: ../../include/quote_flags.h
+resolve.o: ../../include/resolve_clnt.h
+resolve.o: ../../include/resolve_local.h
+resolve.o: ../../include/split_at.h
+resolve.o: ../../include/string_list.h
+resolve.o: ../../include/stringops.h
+resolve.o: ../../include/sys_defs.h
+resolve.o: ../../include/tok822.h
+resolve.o: ../../include/valid_hostname.h
+resolve.o: ../../include/valid_mailhost_addr.h
+resolve.o: ../../include/valid_utf8_hostname.h
+resolve.o: ../../include/vbuf.h
+resolve.o: ../../include/vstream.h
+resolve.o: ../../include/vstring.h
+resolve.o: ../../include/vstring_vstream.h
+resolve.o: resolve.c
+resolve.o: transport.h
+resolve.o: trivial-rewrite.h
+rewrite.o: ../../include/argv.h
+rewrite.o: ../../include/attr.h
+rewrite.o: ../../include/check_arg.h
+rewrite.o: ../../include/dict.h
+rewrite.o: ../../include/htable.h
+rewrite.o: ../../include/iostuff.h
+rewrite.o: ../../include/mail_conf.h
+rewrite.o: ../../include/mail_params.h
+rewrite.o: ../../include/mail_proto.h
+rewrite.o: ../../include/maps.h
+rewrite.o: ../../include/msg.h
+rewrite.o: ../../include/myflock.h
+rewrite.o: ../../include/mymalloc.h
+rewrite.o: ../../include/nvtable.h
+rewrite.o: ../../include/resolve_clnt.h
+rewrite.o: ../../include/resolve_local.h
+rewrite.o: ../../include/split_at.h
+rewrite.o: ../../include/sys_defs.h
+rewrite.o: ../../include/tok822.h
+rewrite.o: ../../include/vbuf.h
+rewrite.o: ../../include/vstream.h
+rewrite.o: ../../include/vstring.h
+rewrite.o: ../../include/vstring_vstream.h
+rewrite.o: rewrite.c
+rewrite.o: trivial-rewrite.h
+transport.o: ../../include/argv.h
+transport.o: ../../include/attr.h
+transport.o: ../../include/check_arg.h
+transport.o: ../../include/dict.h
+transport.o: ../../include/events.h
+transport.o: ../../include/htable.h
+transport.o: ../../include/iostuff.h
+transport.o: ../../include/mail_addr_find.h
+transport.o: ../../include/mail_addr_form.h
+transport.o: ../../include/mail_params.h
+transport.o: ../../include/mail_proto.h
+transport.o: ../../include/maps.h
+transport.o: ../../include/match_list.h
+transport.o: ../../include/match_parent_style.h
+transport.o: ../../include/msg.h
+transport.o: ../../include/myflock.h
+transport.o: ../../include/mymalloc.h
+transport.o: ../../include/nvtable.h
+transport.o: ../../include/split_at.h
+transport.o: ../../include/stringops.h
+transport.o: ../../include/strip_addr.h
+transport.o: ../../include/sys_defs.h
+transport.o: ../../include/vbuf.h
+transport.o: ../../include/vstream.h
+transport.o: ../../include/vstring.h
+transport.o: transport.c
+transport.o: transport.h
+trivial-rewrite.o: ../../include/argv.h
+trivial-rewrite.o: ../../include/attr.h
+trivial-rewrite.o: ../../include/check_arg.h
+trivial-rewrite.o: ../../include/dict.h
+trivial-rewrite.o: ../../include/events.h
+trivial-rewrite.o: ../../include/htable.h
+trivial-rewrite.o: ../../include/iostuff.h
+trivial-rewrite.o: ../../include/mail_addr.h
+trivial-rewrite.o: ../../include/mail_conf.h
+trivial-rewrite.o: ../../include/mail_params.h
+trivial-rewrite.o: ../../include/mail_proto.h
+trivial-rewrite.o: ../../include/mail_server.h
+trivial-rewrite.o: ../../include/mail_version.h
+trivial-rewrite.o: ../../include/maps.h
+trivial-rewrite.o: ../../include/msg.h
+trivial-rewrite.o: ../../include/myflock.h
+trivial-rewrite.o: ../../include/mymalloc.h
+trivial-rewrite.o: ../../include/nvtable.h
+trivial-rewrite.o: ../../include/resolve_clnt.h
+trivial-rewrite.o: ../../include/resolve_local.h
+trivial-rewrite.o: ../../include/rewrite_clnt.h
+trivial-rewrite.o: ../../include/split_at.h
+trivial-rewrite.o: ../../include/stringops.h
+trivial-rewrite.o: ../../include/sys_defs.h
+trivial-rewrite.o: ../../include/tok822.h
+trivial-rewrite.o: ../../include/vbuf.h
+trivial-rewrite.o: ../../include/vstream.h
+trivial-rewrite.o: ../../include/vstring.h
+trivial-rewrite.o: ../../include/vstring_vstream.h
+trivial-rewrite.o: transport.h
+trivial-rewrite.o: trivial-rewrite.c
+trivial-rewrite.o: trivial-rewrite.h
diff --git a/src/trivial-rewrite/resolve.c b/src/trivial-rewrite/resolve.c
new file mode 100644
index 0000000..4e9ea2a
--- /dev/null
+++ b/src/trivial-rewrite/resolve.c
@@ -0,0 +1,828 @@
+/*++
+/* NAME
+/* resolve 3
+/* SUMMARY
+/* mail address resolver
+/* SYNOPSIS
+/* #include "trivial-rewrite.h"
+/*
+/* void resolve_init(void)
+/*
+/* int resolve_class(domain)
+/* const char *domain;
+/*
+/* void resolve_proto(context, stream)
+/* RES_CONTEXT *context;
+/* VSTREAM *stream;
+/* DESCRIPTION
+/* This module implements the trivial address resolving engine.
+/* It distinguishes between local and remote mail, and optionally
+/* consults one or more transport tables that map a destination
+/* to a transport, nexthop pair.
+/*
+/* resolve_init() initializes data structures that are private
+/* to this module. It should be called once before using the
+/* actual resolver routines.
+/*
+/* resolve_class() returns the address class for the specified
+/* domain, or -1 in case of error.
+/*
+/* resolve_proto() implements the client-server protocol:
+/* read one address in FQDN form, reply with a (transport,
+/* nexthop, internalized recipient) triple.
+/* STANDARDS
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* SEE ALSO
+/* 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 <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <split_at.h>
+#include <valid_utf8_hostname.h>
+#include <stringops.h>
+#include <mymalloc.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <resolve_local.h>
+#include <mail_conf.h>
+#include <quote_822_local.h>
+#include <tok822.h>
+#include <domain_list.h>
+#include <string_list.h>
+#include <match_parent_style.h>
+#include <maps.h>
+#include <mail_addr_find.h>
+#include <valid_mailhost_addr.h>
+
+/* Application-specific. */
+
+#include "trivial-rewrite.h"
+#include "transport.h"
+
+ /*
+ * The job of the address resolver is to map one recipient address to a
+ * triple of (channel, nexthop, recipient). The channel is the name of the
+ * delivery service specified in master.cf, the nexthop is (usually) a
+ * description of the next host to deliver to, and recipient is the final
+ * recipient address. The latter may differ from the input address as the
+ * result of stripping multiple layers of sender-specified routing.
+ *
+ * Addresses are resolved by their domain name. Known domain names are
+ * categorized into classes: local, virtual alias, virtual mailbox, relay,
+ * and everything else. Finding the address domain class is a matter of
+ * table lookups.
+ *
+ * Different address domain classes generally use different delivery channels,
+ * and may use class dependent ways to arrive at the corresponding nexthop
+ * information. With classes that do final delivery, the nexthop is
+ * typically the local machine hostname.
+ *
+ * The transport lookup table provides a means to override the domain class
+ * channel and/or nexhop information for specific recipients or for entire
+ * domain hierarchies.
+ *
+ * This works well in the general case. The only bug in this approach is that
+ * the structure of the nexthop information is transport dependent.
+ * Typically, the nexthop specifies a hostname, hostname + TCP Port, or the
+ * pathname of a UNIX-domain socket. However, with the error transport the
+ * nexthop field contains free text with the reason for non-delivery.
+ *
+ * Therefore, a transport map entry that overrides the channel but not the
+ * nexthop information (or vice versa) may produce surprising results. In
+ * particular, the free text nexthop information for the error transport is
+ * likely to confuse regular delivery agents; and conversely, a hostname or
+ * socket pathname is not an adequate text as reason for non-delivery.
+ *
+ * In the code below, rcpt_domain specifies the domain name that we will use
+ * when the transport table specifies a non-default channel but no nexthop
+ * information (we use a generic text when that non-default channel is the
+ * error transport).
+ */
+
+#define STR vstring_str
+#define LEN VSTRING_LEN
+
+ /*
+ * Some of the lists that define the address domain classes.
+ */
+static DOMAIN_LIST *relay_domains;
+static STRING_LIST *virt_alias_doms;
+static STRING_LIST *virt_mailbox_doms;
+
+static MAPS *relocated_maps;
+
+/* resolve_class - determine domain address class */
+
+int resolve_class(const char *domain)
+{
+ int ret;
+
+ /*
+ * Same order as in resolve_addr().
+ */
+ if ((ret = resolve_local(domain)) != 0)
+ return (ret > 0 ? RESOLVE_CLASS_LOCAL : -1);
+ if (virt_alias_doms) {
+ if (string_list_match(virt_alias_doms, domain))
+ return (RESOLVE_CLASS_ALIAS);
+ if (virt_alias_doms->error)
+ return (-1);
+ }
+ if (virt_mailbox_doms) {
+ if (string_list_match(virt_mailbox_doms, domain))
+ return (RESOLVE_CLASS_VIRTUAL);
+ if (virt_mailbox_doms->error)
+ return (-1);
+ }
+ if (relay_domains) {
+ if (string_list_match(relay_domains, domain))
+ return (RESOLVE_CLASS_RELAY);
+ if (relay_domains->error)
+ return (-1);
+ }
+ return (RESOLVE_CLASS_DEFAULT);
+}
+
+/* resolve_addr - resolve address according to rule set */
+
+static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr,
+ VSTRING *channel, VSTRING *nexthop,
+ VSTRING *nextrcpt, int *flags)
+{
+ const char *myname = "resolve_addr";
+ VSTRING *addr_buf = vstring_alloc(100);
+ TOK822 *tree = 0;
+ TOK822 *saved_domain = 0;
+ TOK822 *domain = 0;
+ char *destination;
+ const char *blame = 0;
+ const char *rcpt_domain;
+ ssize_t addr_len;
+ ssize_t loop_count;
+ ssize_t loop_max;
+ char *local;
+ char *oper;
+ char *junk;
+ const char *relay;
+ const char *xport;
+ const char *sender_key;
+ int rc;
+
+ *flags = 0;
+ vstring_strcpy(channel, "CHANNEL NOT UPDATED");
+ vstring_strcpy(nexthop, "NEXTHOP NOT UPDATED");
+ vstring_strcpy(nextrcpt, "NEXTRCPT NOT UPDATED");
+
+ /*
+ * The address is in internalized (unquoted) form.
+ *
+ * In an ideal world we would parse the externalized address form as given
+ * to us by the sender.
+ *
+ * However, in the real world we have to look for routing characters like
+ * %@! in the address local-part, even when that information is quoted
+ * due to the presence of special characters or whitespace. Although
+ * technically incorrect, this is needed to stop user@domain@domain relay
+ * attempts when forwarding mail to a Sendmail MX host.
+ *
+ * This suggests that we parse the address in internalized (unquoted) form.
+ * Unfortunately, if we do that, the unparser generates incorrect white
+ * space between adjacent non-operator tokens. Example: ``first last''
+ * needs white space, but ``stuff[stuff]'' does not. This is is not a
+ * problem when unparsing the result from parsing externalized forms,
+ * because the parser/unparser were designed for valid externalized forms
+ * where ``stuff[stuff]'' does not happen.
+ *
+ * As a workaround we start with the quoted form and then dequote the
+ * local-part only where needed. This will do the right thing in most
+ * (but not all) cases.
+ */
+ addr_len = strlen(addr);
+ quote_822_local(addr_buf, addr);
+ tree = tok822_scan_addr(vstring_str(addr_buf));
+
+ /*
+ * The optimizer will eliminate tests that always fail, and will replace
+ * multiple expansions of this macro by a GOTO to a single instance.
+ */
+#define FREE_MEMORY_AND_RETURN { \
+ if (saved_domain) \
+ tok822_free_tree(saved_domain); \
+ if(tree) \
+ tok822_free_tree(tree); \
+ if (addr_buf) \
+ vstring_free(addr_buf); \
+ return; \
+ }
+
+ /*
+ * Preliminary resolver: strip off all instances of the local domain.
+ * Terminate when no destination domain is left over, or when the
+ * destination domain is remote.
+ *
+ * XXX To whom it may concern. If you change the resolver loop below, or
+ * quote_822_local.c, or tok822_parse.c, be sure to re-run the tests
+ * under "make resolve_clnt_test" in the global directory.
+ */
+#define RESOLVE_LOCAL(domain) \
+ resolve_local(STR(tok822_internalize(addr_buf, domain, TOK822_STR_DEFL)))
+
+ for (loop_count = 0, loop_max = addr_len + 100; /* void */ ; loop_count++) {
+
+ /*
+ * XXX Should never happen, but if this happens with some
+ * pathological address, then that is not sufficient reason to
+ * disrupt the operation of an MTA.
+ */
+ if (loop_count > loop_max) {
+ msg_warn("resolve_addr: <%s>: giving up after %ld iterations",
+ addr, (long) loop_count);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ break;
+ }
+
+ /*
+ * Strip trailing dot at end of domain, but not dot-dot or at-dot.
+ * This merely makes diagnostics more accurate by leaving bogus
+ * addresses alone.
+ */
+ if (tree->tail
+ && tree->tail->type == '.'
+ && tok822_rfind_type(tree->tail, '@') != 0
+ && tree->tail->prev->type != '.'
+ && tree->tail->prev->type != '@')
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+
+ /*
+ * Strip trailing @.
+ */
+ if (var_resolve_nulldom
+ && tree->tail
+ && tree->tail->type == '@')
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+
+ /*
+ * Strip (and save) @domain if local.
+ *
+ * Grr. resolve_local() table lookups may fail. It may be OK for local
+ * file lookup code to abort upon failure, but with network-based
+ * tables it is preferable to return an error indication to the
+ * requestor.
+ */
+ if ((domain = tok822_rfind_type(tree->tail, '@')) != 0) {
+ if (domain->next && (rc = RESOLVE_LOCAL(domain->next)) <= 0) {
+ if (rc < 0) {
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ break;
+ }
+ tok822_sub_keep_before(tree, domain);
+ if (saved_domain)
+ tok822_free_tree(saved_domain);
+ saved_domain = domain;
+ domain = 0; /* safety for future change */
+ }
+
+ /*
+ * After stripping the local domain, if any, replace foo%bar by
+ * foo@bar, site!user by user@site, rewrite to canonical form, and
+ * retry.
+ */
+ if (tok822_rfind_type(tree->tail, '@')
+ || (var_swap_bangpath && tok822_rfind_type(tree->tail, '!'))
+ || (var_percent_hack && tok822_rfind_type(tree->tail, '%'))) {
+ rewrite_tree(&local_context, tree);
+ continue;
+ }
+
+ /*
+ * If the local-part is a quoted string, crack it open when we're
+ * permitted to do so and look for routing operators. This is
+ * technically incorrect, but is needed to stop relaying problems.
+ *
+ * XXX Do another feeble attempt to keep local-part info quoted.
+ */
+ if (var_resolve_dequoted
+ && tree->head && tree->head == tree->tail
+ && tree->head->type == TOK822_QSTRING
+ && ((oper = strrchr(local = STR(tree->head->vstr), '@')) != 0
+ || (var_percent_hack && (oper = strrchr(local, '%')) != 0)
+ || (var_swap_bangpath && (oper = strrchr(local, '!')) != 0))) {
+ if (*oper == '%')
+ *oper = '@';
+ tok822_internalize(addr_buf, tree->head, TOK822_STR_DEFL);
+ if (*oper == '@') {
+ junk = mystrdup(STR(addr_buf));
+ quote_822_local(addr_buf, junk);
+ myfree(junk);
+ }
+ tok822_free(tree->head);
+ tree->head = tok822_scan(STR(addr_buf), &tree->tail);
+ rewrite_tree(&local_context, tree);
+ continue;
+ }
+
+ /*
+ * An empty local-part or an empty quoted string local-part becomes
+ * the local MAILER-DAEMON, for consistency with our own From:
+ * message headers.
+ */
+ if (tree->head && tree->head == tree->tail
+ && tree->head->type == TOK822_QSTRING
+ && VSTRING_LEN(tree->head->vstr) == 0) {
+ tok822_free(tree->head);
+ tree->head = 0;
+ }
+ /* XXX Re-resolve the surrogate, in case already in user@domain form. */
+ if (tree->head == 0) {
+ tree->head = tok822_scan(var_empty_addr, &tree->tail);
+ continue;
+ }
+ /* XXX Re-resolve with @$myhostname for backwards compatibility. */
+ if (domain == 0 && saved_domain == 0) {
+ tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
+ continue;
+ }
+
+ /*
+ * We're done. There are no domains left to strip off the address,
+ * and all null local-part information is sanitized.
+ */
+ domain = 0;
+ break;
+ }
+
+ vstring_free(addr_buf);
+ addr_buf = 0;
+
+ /*
+ * Make sure the resolved envelope recipient has the user@domain form. If
+ * no domain was specified in the address, assume the local machine. See
+ * above for what happens with an empty address.
+ */
+ if (domain == 0) {
+ if (saved_domain) {
+ tok822_sub_append(tree, saved_domain);
+ saved_domain = 0;
+ } else {
+ tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0));
+ }
+ }
+
+ /*
+ * Transform the recipient address back to internal form.
+ *
+ * XXX This may produce incorrect results if we cracked open a quoted
+ * local-part with routing operators; see discussion above at the top of
+ * the big loop.
+ *
+ * XXX We explicitly disallow domain names in bare network address form. A
+ * network address destination should be formatted according to RFC 2821:
+ * it should be enclosed in [], and an IPv6 address should have an IPv6:
+ * prefix.
+ */
+ tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL);
+ rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
+ if (rcpt_domain == (char *) 1)
+ msg_panic("no @ in address: \"%s\"", STR(nextrcpt));
+ if (*rcpt_domain == '[') {
+ if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE))
+ *flags |= RESOLVE_FLAG_ERROR;
+ } else if (var_smtputf8_enable
+ && valid_utf8_string(STR(nextrcpt), LEN(nextrcpt)) == 0) {
+ *flags |= RESOLVE_FLAG_ERROR;
+ } else if (!valid_utf8_hostname(var_smtputf8_enable, rcpt_domain,
+ DONT_GRIPE)) {
+ if (var_resolve_num_dom && valid_hostaddr(rcpt_domain, DONT_GRIPE)) {
+ vstring_insert(nextrcpt, rcpt_domain - STR(nextrcpt), "[", 1);
+ vstring_strcat(nextrcpt, "]");
+ rcpt_domain = strrchr(STR(nextrcpt), '@') + 1;
+ if ((rc = resolve_local(rcpt_domain)) > 0) /* XXX */
+ domain = 0;
+ else if (rc < 0) {
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ } else {
+ *flags |= RESOLVE_FLAG_ERROR;
+ }
+ }
+ tok822_free_tree(tree);
+ tree = 0;
+
+ /*
+ * XXX Short-cut invalid address forms.
+ */
+ if (*flags & RESOLVE_FLAG_ERROR) {
+ *flags |= RESOLVE_CLASS_DEFAULT;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Recognize routing operators in the local-part, even when we do not
+ * recognize ! or % as valid routing operators locally. This is needed to
+ * prevent backup MX hosts from relaying third-party destinations through
+ * primary MX hosts, otherwise the backup host could end up on black
+ * lists. Ignore local swap_bangpath and percent_hack settings because we
+ * can't know how the next MX host is set up.
+ */
+ if (strcmp(STR(nextrcpt) + strcspn(STR(nextrcpt), "@!%") + 1, rcpt_domain))
+ *flags |= RESOLVE_FLAG_ROUTED;
+
+ /*
+ * With local, virtual, relay, or other non-local destinations, give the
+ * highest precedence to transport associated nexthop information.
+ *
+ * Otherwise, with relay or other non-local destinations, the relayhost
+ * setting overrides the recipient domain name, and the sender-dependent
+ * relayhost overrides both.
+ *
+ * XXX Nag if the recipient domain is listed in multiple domain lists. The
+ * result is implementation defined, and may break when internals change.
+ *
+ * For now, we distinguish only a fixed number of address classes.
+ * Eventually this may become extensible, so that new classes can be
+ * configured with their own domain list, delivery transport, and
+ * recipient table.
+ */
+#define STREQ(x,y) (strcmp((x), (y)) == 0)
+
+ if (domain != 0) {
+
+ /*
+ * Virtual alias domain.
+ */
+ if (virt_alias_doms
+ && string_list_match(virt_alias_doms, rcpt_domain)) {
+ if (var_helpful_warnings) {
+ if (virt_mailbox_doms
+ && string_list_match(virt_mailbox_doms, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_VIRT_ALIAS_DOMS,
+ VAR_VIRT_MAILBOX_DOMS);
+ if (relay_domains
+ && domain_list_match(relay_domains, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_VIRT_ALIAS_DOMS,
+ VAR_RELAY_DOMAINS);
+#if 0
+ if (strcasecmp_utf8(rcpt_domain, var_myorigin) == 0)
+ msg_warn("do not list $%s (%s) in %s",
+ VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS);
+#endif
+ }
+ vstring_strcpy(channel, MAIL_SERVICE_ERROR);
+ vstring_sprintf(nexthop, "5.1.1 User unknown%s",
+ var_show_unk_rcpt_table ?
+ " in virtual alias table" : "");
+ *flags |= RESOLVE_CLASS_ALIAS;
+ } else if (virt_alias_doms && virt_alias_doms->error != 0) {
+ msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Virtual mailbox domain.
+ */
+ else if (virt_mailbox_doms
+ && string_list_match(virt_mailbox_doms, rcpt_domain)) {
+ if (var_helpful_warnings) {
+ if (relay_domains
+ && domain_list_match(relay_domains, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_VIRT_MAILBOX_DOMS,
+ VAR_RELAY_DOMAINS);
+ }
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->virt_transport));
+ vstring_strcpy(nexthop, rcpt_domain);
+ blame = rp->virt_transport_name;
+ *flags |= RESOLVE_CLASS_VIRTUAL;
+ } else if (virt_mailbox_doms && virt_mailbox_doms->error != 0) {
+ msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ } else {
+
+ /*
+ * Off-host relay destination.
+ */
+ if (relay_domains
+ && domain_list_match(relay_domains, rcpt_domain)) {
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->relay_transport));
+ blame = rp->relay_transport_name;
+ *flags |= RESOLVE_CLASS_RELAY;
+ } else if (relay_domains && relay_domains->error != 0) {
+ msg_warn("%s lookup failure", VAR_RELAY_DOMAINS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Other off-host destination.
+ */
+ else {
+ if (rp->snd_def_xp_info
+ && (xport = mail_addr_find(rp->snd_def_xp_info,
+ sender_key = (*sender ? sender :
+ var_null_def_xport_maps_key),
+ (char **) 0)) != 0) {
+ if (*xport == 0) {
+ msg_warn("%s: ignoring null lookup result for %s",
+ rp->snd_def_xp_maps_name, sender_key);
+ xport = "DUNNO";
+ }
+ vstring_strcpy(channel, strcasecmp(xport, "DUNNO") == 0 ?
+ RES_PARAM_VALUE(rp->def_transport) : xport);
+ blame = rp->snd_def_xp_maps_name;
+ } else if (rp->snd_def_xp_info
+ && rp->snd_def_xp_info->error != 0) {
+ msg_warn("%s lookup failure", rp->snd_def_xp_maps_name);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ } else {
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->def_transport));
+ blame = rp->def_transport_name;
+ }
+ *flags |= RESOLVE_CLASS_DEFAULT;
+ }
+
+ /*
+ * With off-host delivery, sender-dependent or global relayhost
+ * override the recipient domain.
+ */
+ if (rp->snd_relay_info
+ && (relay = mail_addr_find(rp->snd_relay_info,
+ sender_key = (*sender ? sender :
+ var_null_relay_maps_key),
+ (char **) 0)) != 0) {
+ if (*relay == 0) {
+ msg_warn("%s: ignoring null lookup result for %s",
+ rp->snd_relay_maps_name, sender_key);
+ relay = 0;
+ } else if (strcasecmp_utf8(relay, "DUNNO") == 0)
+ relay = 0;
+ } else if (rp->snd_relay_info
+ && rp->snd_relay_info->error != 0) {
+ msg_warn("%s lookup failure", rp->snd_relay_maps_name);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ } else {
+ relay = 0;
+ }
+ /* Enforce all the relayhost precedences in one place. */
+ if (relay != 0) {
+ vstring_strcpy(nexthop, relay);
+ } else if (*RES_PARAM_VALUE(rp->relayhost))
+ vstring_strcpy(nexthop, RES_PARAM_VALUE(rp->relayhost));
+ else
+ vstring_strcpy(nexthop, rcpt_domain);
+ }
+ }
+
+ /*
+ * Local delivery.
+ *
+ * XXX Nag if the domain is listed in multiple domain lists. The effect is
+ * implementation defined, and may break when internals change.
+ */
+ else {
+ if (var_helpful_warnings) {
+ if (virt_alias_doms
+ && string_list_match(virt_alias_doms, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS);
+ if (virt_mailbox_doms
+ && string_list_match(virt_mailbox_doms, rcpt_domain))
+ msg_warn("do not list domain %s in BOTH %s and %s",
+ rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS);
+ }
+ vstring_strcpy(channel, RES_PARAM_VALUE(rp->local_transport));
+ vstring_strcpy(nexthop, rcpt_domain);
+ blame = rp->local_transport_name;
+ *flags |= RESOLVE_CLASS_LOCAL;
+ }
+
+ /*
+ * An explicit main.cf transport:nexthop setting overrides the nexthop.
+ *
+ * XXX We depend on this mechanism to enforce per-recipient concurrencies
+ * for local recipients. With "local_transport = local:$myhostname" we
+ * force mail for any domain in $mydestination/${proxy,inet}_interfaces
+ * to share the same queue.
+ */
+ if ((destination = split_at(STR(channel), ':')) != 0 && *destination)
+ vstring_strcpy(nexthop, destination);
+
+ /*
+ * Sanity checks.
+ */
+ if (*STR(channel) == 0) {
+ if (blame == 0)
+ msg_panic("%s: null blame", myname);
+ msg_warn("file %s/%s: parameter %s: null transport is not allowed",
+ var_config_dir, MAIN_CONF_FILE, blame);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ if (*STR(nexthop) == 0)
+ msg_panic("%s: null nexthop", myname);
+
+ /*
+ * The transport map can selectively override any transport and/or
+ * nexthop host info that is set up above. Unfortunately, the syntax for
+ * nexthop information is transport specific. We therefore need sane and
+ * intuitive semantics for transport map entries that specify a channel
+ * but no nexthop.
+ *
+ * With non-error transports, the initial nexthop information is the
+ * recipient domain. However, specific main.cf transport definitions may
+ * specify a transport-specific destination, such as a host + TCP socket,
+ * or the pathname of a UNIX-domain socket. With less precedence than
+ * main.cf transport definitions, a main.cf relayhost definition may also
+ * override nexthop information for off-host deliveries.
+ *
+ * With the error transport, the nexthop information is free text that
+ * specifies the reason for non-delivery.
+ *
+ * Because nexthop syntax is transport specific we reset the nexthop
+ * information to the recipient domain when the transport table specifies
+ * a transport without also specifying the nexthop information.
+ *
+ * Subtle note: reset nexthop even when the transport table does not change
+ * the transport. Otherwise it is hard to get rid of main.cf specified
+ * nexthop information.
+ *
+ * XXX Don't override the virtual alias class (error:User unknown) result.
+ */
+ if (rp->transport_info && !(*flags & RESOLVE_CLASS_ALIAS)) {
+ if (transport_lookup(rp->transport_info, STR(nextrcpt),
+ rcpt_domain, channel, nexthop) == 0
+ && rp->transport_info->transport_path->error != 0) {
+ msg_warn("%s lookup failure", rp->transport_maps_name);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ }
+
+ /*
+ * Bounce recipients that have moved, regardless of domain address class.
+ * We do this last, in anticipation of transport maps that can override
+ * the recipient address.
+ *
+ * The downside of not doing this in delivery agents is that this table has
+ * no effect on local alias expansion results. Such mail will have to
+ * make almost an entire iteration through the mail system.
+ */
+#define IGNORE_ADDR_EXTENSION ((char **) 0)
+
+ if (relocated_maps != 0) {
+ const char *newloc;
+
+ if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt),
+ IGNORE_ADDR_EXTENSION)) != 0) {
+ vstring_strcpy(channel, MAIL_SERVICE_ERROR);
+ /* 5.1.6 is the closest match, but not perfect. */
+ vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc);
+ } else if (relocated_maps->error != 0) {
+ msg_warn("%s lookup failure", VAR_RELOCATED_MAPS);
+ *flags |= RESOLVE_FLAG_FAIL;
+ FREE_MEMORY_AND_RETURN;
+ }
+ }
+
+ /*
+ * Bounce recipient addresses that start with `-'. External commands may
+ * misinterpret such addresses as command-line options.
+ *
+ * In theory I could say people should always carefully set up their
+ * master.cf pipe mailer entries with `--' before the first non-option
+ * argument, but mistakes will happen regardless.
+ *
+ * Therefore the protection is put in place here, where it cannot be
+ * bypassed.
+ */
+ if (var_allow_min_user == 0 && STR(nextrcpt)[0] == '-') {
+ *flags |= RESOLVE_FLAG_ERROR;
+ FREE_MEMORY_AND_RETURN;
+ }
+
+ /*
+ * Clean up.
+ */
+ FREE_MEMORY_AND_RETURN;
+}
+
+/* Static, so they can be used by the network protocol interface only. */
+
+static VSTRING *channel;
+static VSTRING *nexthop;
+static VSTRING *nextrcpt;
+static VSTRING *query;
+static VSTRING *sender;
+
+/* resolve_proto - read request and send reply */
+
+int resolve_proto(RES_CONTEXT *context, VSTREAM *stream)
+{
+ int flags;
+
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_SENDER, sender),
+ RECV_ATTR_STR(MAIL_ATTR_ADDR, query),
+ ATTR_TYPE_END) != 2)
+ return (-1);
+
+ resolve_addr(context, STR(sender), STR(query),
+ channel, nexthop, nextrcpt, &flags);
+
+ if (msg_verbose)
+ msg_info("`%s' -> `%s' -> (`%s' `%s' `%s' `%d')",
+ STR(sender), STR(query), STR(channel),
+ STR(nexthop), STR(nextrcpt), flags);
+
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags),
+ SEND_ATTR_STR(MAIL_ATTR_TRANSPORT, STR(channel)),
+ SEND_ATTR_STR(MAIL_ATTR_NEXTHOP, STR(nexthop)),
+ SEND_ATTR_STR(MAIL_ATTR_RECIP, STR(nextrcpt)),
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags),
+ ATTR_TYPE_END);
+
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("write resolver reply: %m");
+ return (-1);
+ }
+ return (0);
+}
+
+/* resolve_init - module initializations */
+
+void resolve_init(void)
+{
+ sender = vstring_alloc(100);
+ query = vstring_alloc(100);
+ channel = vstring_alloc(100);
+ nexthop = vstring_alloc(100);
+ nextrcpt = vstring_alloc(100);
+
+ if (*var_virt_alias_doms)
+ virt_alias_doms =
+ string_list_init(VAR_VIRT_ALIAS_DOMS, MATCH_FLAG_RETURN,
+ var_virt_alias_doms);
+
+ if (*var_virt_mailbox_doms)
+ virt_mailbox_doms =
+ string_list_init(VAR_VIRT_MAILBOX_DOMS, MATCH_FLAG_RETURN,
+ var_virt_mailbox_doms);
+
+ if (*var_relay_domains)
+ relay_domains =
+ domain_list_init(VAR_RELAY_DOMAINS, MATCH_FLAG_RETURN
+ | match_parent_style(VAR_RELAY_DOMAINS),
+ var_relay_domains);
+
+ if (*var_relocated_maps)
+ relocated_maps =
+ maps_create(VAR_RELOCATED_MAPS, var_relocated_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_UTF8_REQUEST);
+}
diff --git a/src/trivial-rewrite/rewrite.c b/src/trivial-rewrite/rewrite.c
new file mode 100644
index 0000000..483463c
--- /dev/null
+++ b/src/trivial-rewrite/rewrite.c
@@ -0,0 +1,303 @@
+/*++
+/* NAME
+/* rewrite 3
+/* SUMMARY
+/* mail address rewriter
+/* SYNOPSIS
+/* #include "trivial-rewrite.h"
+/*
+/* void rewrite_init(void)
+/*
+/* void rewrite_proto(stream)
+/* VSTREAM *stream;
+/*
+/* void rewrite_addr(context, addr, result)
+/* RWR_CONTEXT *context;
+/* char *addr;
+/* VSTRING *result;
+/*
+/* void rewrite_tree(context, tree)
+/* RWR_CONTEXT *context;
+/* TOK822 *tree;
+/*
+/* RWR_CONTEXT local_context;
+/* RWR_CONTEXT remote_context;
+/* DESCRIPTION
+/* This module implements the trivial address rewriting engine.
+/*
+/* rewrite_init() initializes data structures that are private
+/* to this module. It should be called once before using the
+/* actual rewriting routines.
+/*
+/* rewrite_proto() implements the client-server protocol: read
+/* one rule set name and one address in external (quoted) form,
+/* reply with the rewritten address in external form.
+/*
+/* rewrite_addr() rewrites an address string to another string.
+/* Both input and output are in external (quoted) form.
+/*
+/* rewrite_tree() rewrites a parse tree with a single address to
+/* another tree. A tree is a dummy node on top of a token list.
+/*
+/* local_context and remote_context provide domain names for
+/* completing incomplete address forms.
+/* STANDARDS
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* SEE ALSO
+/* 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 <stdlib.h>
+#include <string.h>
+
+#ifdef STRCASECMP_IN_STRINGS_H
+#include <strings.h>
+#endif
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <split_at.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_proto.h>
+#include <resolve_local.h>
+#include <tok822.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+#include "trivial-rewrite.h"
+
+RWR_CONTEXT local_context = {
+ VAR_MYORIGIN, &var_myorigin,
+ VAR_MYDOMAIN, &var_mydomain,
+};
+
+RWR_CONTEXT remote_context = {
+ VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
+ VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
+};
+
+static VSTRING *ruleset;
+static VSTRING *address;
+static VSTRING *result;
+
+/* rewrite_tree - rewrite address according to rule set */
+
+void rewrite_tree(RWR_CONTEXT *context, TOK822 *tree)
+{
+ TOK822 *colon;
+ TOK822 *domain;
+ TOK822 *bang;
+ TOK822 *local;
+ VSTRING *vstringval;
+
+ /*
+ * XXX If you change this module, quote_822_local.c, or tok822_parse.c,
+ * be sure to re-run the tests under "make rewrite_clnt_test" and "make
+ * resolve_clnt_test" in the global directory.
+ */
+
+ /*
+ * Sanity check.
+ */
+ if (tree->head == 0)
+ msg_panic("rewrite_tree: empty tree");
+
+ /*
+ * An empty address is a special case.
+ */
+ if (tree->head == tree->tail
+ && tree->tail->type == TOK822_QSTRING
+ && VSTRING_LEN(tree->tail->vstr) == 0)
+ return;
+
+ /*
+ * Treat a lone @ as if it were an empty address.
+ */
+ if (tree->head == tree->tail
+ && tree->tail->type == '@') {
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+ tok822_sub_append(tree, tok822_alloc(TOK822_QSTRING, ""));
+ return;
+ }
+
+ /*
+ * Strip source route.
+ */
+ if (tree->head->type == '@'
+ && (colon = tok822_find_type(tree->head, ':')) != 0
+ && colon != tree->tail)
+ tok822_free_tree(tok822_sub_keep_after(tree, colon));
+
+ /*
+ * Optionally, transform address forms without @.
+ */
+ if ((domain = tok822_rfind_type(tree->tail, '@')) == 0) {
+
+ /*
+ * Swap domain!user to user@domain.
+ */
+ if (var_swap_bangpath != 0
+ && (bang = tok822_find_type(tree->head, '!')) != 0) {
+ tok822_sub_keep_before(tree, bang);
+ local = tok822_cut_after(bang);
+ tok822_free(bang);
+ tok822_sub_prepend(tree, tok822_alloc('@', (char *) 0));
+ if (local)
+ tok822_sub_prepend(tree, local);
+ }
+
+ /*
+ * Promote user%domain to user@domain.
+ */
+ else if (var_percent_hack != 0
+ && (domain = tok822_rfind_type(tree->tail, '%')) != 0) {
+ domain->type = '@';
+ }
+
+ /*
+ * Append missing @origin
+ */
+ else if (var_append_at_myorigin != 0
+ && REW_PARAM_VALUE(context->origin) != 0
+ && REW_PARAM_VALUE(context->origin)[0] != 0) {
+ domain = tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->origin),
+ (TOK822 **) 0));
+ }
+ }
+
+ /*
+ * Append missing .domain, but leave broken forms ending in @ alone. This
+ * merely makes diagnostics more accurate by leaving bogus addresses
+ * alone.
+ *
+ * Backwards-compatibility warning: warn for "user@localhost" when there is
+ * no "localhost" in mydestination or in any other address class with an
+ * explicit domain list.
+ */
+ if (var_append_dot_mydomain != 0
+ && REW_PARAM_VALUE(context->domain) != 0
+ && REW_PARAM_VALUE(context->domain)[0] != 0
+ && (domain = tok822_rfind_type(tree->tail, '@')) != 0
+ && domain != tree->tail
+ && tok822_find_type(domain, TOK822_DOMLIT) == 0
+ && tok822_find_type(domain, '.') == 0) {
+ if (warn_compat_break_app_dot_mydomain
+ && (vstringval = domain->next->vstr) != 0) {
+ if (strcasecmp(vstring_str(vstringval), "localhost") != 0) {
+ msg_info("using backwards-compatible default setting "
+ VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
+ "\"%s.%s\"", vstring_str(vstringval),
+ vstring_str(vstringval), var_mydomain);
+ } else if (resolve_class("localhost") == RESOLVE_CLASS_DEFAULT) {
+ msg_info("using backwards-compatible default setting "
+ VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
+ "\"%s.%s\"; please add \"localhost\" to "
+ "mydestination or other address class",
+ vstring_str(vstringval), vstring_str(vstringval),
+ var_mydomain);
+ }
+ }
+ tok822_sub_append(tree, tok822_alloc('.', (char *) 0));
+ tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->domain),
+ (TOK822 **) 0));
+ }
+
+ /*
+ * Strip trailing dot at end of domain, but not dot-dot or @-dot. This
+ * merely makes diagnostics more accurate by leaving bogus addresses
+ * alone.
+ */
+ if (tree->tail->type == '.'
+ && tree->tail->prev
+ && tree->tail->prev->type != '.'
+ && tree->tail->prev->type != '@')
+ tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
+}
+
+/* rewrite_proto - read request and send reply */
+
+int rewrite_proto(VSTREAM *stream)
+{
+ RWR_CONTEXT *context;
+ TOK822 *tree;
+
+ if (attr_scan(stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_RULE, ruleset),
+ RECV_ATTR_STR(MAIL_ATTR_ADDR, address),
+ ATTR_TYPE_END) != 2)
+ return (-1);
+
+ if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_LOCAL) == 0)
+ context = &local_context;
+ else if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_REMOTE) == 0)
+ context = &remote_context;
+ else {
+ msg_warn("unknown context: %s", vstring_str(ruleset));
+ return (-1);
+ }
+
+ /*
+ * Sanity check. An address is supposed to be in externalized form.
+ */
+ if (*vstring_str(address) == 0) {
+ msg_warn("rewrite_addr: null address");
+ vstring_strcpy(result, vstring_str(address));
+ }
+
+ /*
+ * Convert the address from externalized (quoted) form to token list,
+ * rewrite it, and convert back.
+ */
+ else {
+ tree = tok822_scan_addr(vstring_str(address));
+ rewrite_tree(context, tree);
+ tok822_externalize(result, tree, TOK822_STR_DEFL);
+ tok822_free_tree(tree);
+ }
+ if (msg_verbose)
+ msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset),
+ vstring_str(address), vstring_str(result));
+
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags),
+ SEND_ATTR_STR(MAIL_ATTR_ADDR, vstring_str(result)),
+ ATTR_TYPE_END);
+
+ if (vstream_fflush(stream) != 0) {
+ msg_warn("write rewrite reply: %m");
+ return (-1);
+ }
+ return (0);
+}
+
+/* rewrite_init - module initializations */
+
+void rewrite_init(void)
+{
+ ruleset = vstring_alloc(100);
+ address = vstring_alloc(100);
+ result = vstring_alloc(100);
+}
diff --git a/src/trivial-rewrite/transport.c b/src/trivial-rewrite/transport.c
new file mode 100644
index 0000000..cd62d67
--- /dev/null
+++ b/src/trivial-rewrite/transport.c
@@ -0,0 +1,438 @@
+/*++
+/* NAME
+/* transport 3
+/* SUMMARY
+/* transport mapping
+/* SYNOPSIS
+/* #include "transport.h"
+/*
+/* TRANSPORT_INFO *transport_pre_init(maps_name, maps)
+/* const char *maps_name;
+/* const char *maps;
+/*
+/* void transport_post_init(info)
+/* TRANSPORT_INFO *info;
+/*
+/* int transport_lookup(info, address, rcpt_domain, channel, nexthop)
+/* TRANSPORT_INFO *info;
+/* const char *address;
+/* const char *rcpt_domain;
+/* VSTRING *channel;
+/* VSTRING *nexthop;
+/*
+/* void transport_free(info);
+/* TRANSPORT_INFO * info;
+/* DESCRIPTION
+/* This module implements access to the table that maps transport
+/* user@domain addresses to (channel, nexthop) tuples.
+/*
+/* transport_pre_init() performs initializations that should be
+/* done before the process enters the chroot jail, and
+/* before calling transport_lookup().
+/*
+/* transport_post_init() can be invoked after entering the chroot
+/* jail, and must be called before before calling transport_lookup().
+/*
+/* transport_lookup() finds the channel and nexthop for the given
+/* domain, and returns 1 if something was found. Otherwise, 0
+/* is returned.
+/* DIAGNOSTICS
+/* info->transport_path->error is non-zero when the lookup
+/* should be tried again.
+/* SEE ALSO
+/* maps(3), multi-dictionary search
+/* strip_addr(3), strip extension from address
+/* transport(5), format of transport map
+/* CONFIGURATION PARAMETERS
+/* transport_maps, names of maps to be searched.
+/* 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 <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <stringops.h>
+#include <mymalloc.h>
+#include <vstring.h>
+#include <split_at.h>
+#include <dict.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <strip_addr.h>
+#include <mail_params.h>
+#include <mail_addr_find.h>
+#include <match_parent_style.h>
+#include <mail_proto.h>
+
+/* Application-specific. */
+
+#include "transport.h"
+
+static int transport_match_parent_style;
+
+#define STR(x) vstring_str(x)
+
+static void transport_wildcard_init(TRANSPORT_INFO *);
+
+/* transport_pre_init - pre-jail initialization */
+
+TRANSPORT_INFO *transport_pre_init(const char *transport_maps_name,
+ const char *transport_maps)
+{
+ TRANSPORT_INFO *tp;
+
+ tp = (TRANSPORT_INFO *) mymalloc(sizeof(*tp));
+ tp->transport_path = maps_create(transport_maps_name, transport_maps,
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB
+ | DICT_FLAG_UTF8_REQUEST);
+ tp->wildcard_channel = tp->wildcard_nexthop = 0;
+ tp->wildcard_errno = 0;
+ tp->expire = 0;
+ return (tp);
+}
+
+/* transport_post_init - post-jail initialization */
+
+void transport_post_init(TRANSPORT_INFO *tp)
+{
+ transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS);
+ transport_wildcard_init(tp);
+}
+
+/* transport_free - destroy transport info */
+
+void transport_free(TRANSPORT_INFO *tp)
+{
+ if (tp->transport_path)
+ maps_free(tp->transport_path);
+ if (tp->wildcard_channel)
+ vstring_free(tp->wildcard_channel);
+ if (tp->wildcard_nexthop)
+ vstring_free(tp->wildcard_nexthop);
+ myfree((void *) tp);
+}
+
+/* update_entry - update from transport table entry */
+
+static void update_entry(const char *new_channel, const char *new_nexthop,
+ const char *rcpt_domain, VSTRING *channel,
+ VSTRING *nexthop)
+{
+
+ /*
+ * :[nexthop] means don't change the channel, and don't change the
+ * nexthop unless a non-default nexthop is specified. Thus, a right-hand
+ * side of ":" is the transport table equivalent of a NOOP.
+ */
+ if (*new_channel == 0) { /* :[nexthop] */
+ if (*new_nexthop != 0)
+ vstring_strcpy(nexthop, new_nexthop);
+ }
+
+ /*
+ * transport[:[nexthop]] means change the channel, and reset the nexthop
+ * to the default unless a non-default nexthop is specified.
+ */
+ else {
+ vstring_strcpy(channel, new_channel);
+ if (*new_nexthop != 0)
+ vstring_strcpy(nexthop, new_nexthop);
+ else if (strcmp(STR(channel), MAIL_SERVICE_ERROR) != 0
+ && strcmp(STR(channel), MAIL_SERVICE_RETRY) != 0)
+ vstring_strcpy(nexthop, rcpt_domain);
+ else
+ vstring_strcpy(nexthop, "Address is undeliverable");
+ }
+}
+
+/* parse_transport_entry - parse transport table entry */
+
+static void parse_transport_entry(const char *value, const char *rcpt_domain,
+ VSTRING *channel, VSTRING *nexthop)
+{
+ char *saved_value;
+ const char *host;
+
+#define FOUND 1
+#define NOTFOUND 0
+
+ /*
+ * It would be great if we could specify a recipient address in the
+ * lookup result. Unfortunately, we cannot simply run the result through
+ * a parser that recognizes "transport:user@domain" because the lookup
+ * result can have arbitrary content (especially in the case of the error
+ * mailer).
+ */
+ saved_value = mystrdup(value);
+ host = split_at(saved_value, ':');
+ update_entry(saved_value, host ? host : "", rcpt_domain, channel, nexthop);
+ myfree(saved_value);
+}
+
+/* transport_wildcard_init - (re) initialize wild-card lookup result */
+
+static void transport_wildcard_init(TRANSPORT_INFO *tp)
+{
+ VSTRING *channel = vstring_alloc(10);
+ VSTRING *nexthop = vstring_alloc(10);
+ const char *value;
+
+ /*
+ * Both channel and nexthop may be zero-length strings. Therefore we must
+ * use something else to represent "wild-card does not exist". We use
+ * null VSTRING pointers, for historical reasons.
+ */
+ if (tp->wildcard_channel)
+ vstring_free(tp->wildcard_channel);
+ if (tp->wildcard_nexthop)
+ vstring_free(tp->wildcard_nexthop);
+
+ /*
+ * Technically, the wildcard lookup pattern is redundant. A static map
+ * (keys always match, result is fixed string) could achieve the same:
+ *
+ * transport_maps = hash:/etc/postfix/transport static:xxx:yyy
+ *
+ * But the user interface of such an approach would be less intuitive. We
+ * tolerate the continued existence of wildcard lookup patterns because
+ * of human interface considerations.
+ */
+#define WILDCARD "*"
+#define FULL 0
+#define PARTIAL DICT_FLAG_FIXED
+
+ if ((value = maps_find(tp->transport_path, WILDCARD, FULL)) != 0) {
+ parse_transport_entry(value, "", channel, nexthop);
+ tp->wildcard_errno = 0;
+ tp->wildcard_channel = channel;
+ tp->wildcard_nexthop = nexthop;
+ if (msg_verbose)
+ msg_info("wildcard_{chan:hop}={%s:%s}",
+ vstring_str(channel), vstring_str(nexthop));
+ } else {
+ tp->wildcard_errno = tp->transport_path->error;
+ vstring_free(channel);
+ vstring_free(nexthop);
+ tp->wildcard_channel = 0;
+ tp->wildcard_nexthop = 0;
+ }
+ tp->expire = event_time() + 30; /* XXX make configurable */
+}
+
+/* transport_lookup - map a transport domain */
+
+int transport_lookup(TRANSPORT_INFO *tp, const char *addr,
+ const char *rcpt_domain,
+ VSTRING *channel, VSTRING *nexthop)
+{
+ char *ratsign = 0;
+ const char *value;
+
+#define STREQ(x,y) (strcmp((x), (y)) == 0)
+#define DISCARD_EXTENSION ((char **) 0)
+
+ /*
+ * The null recipient is rewritten to the local mailer daemon address.
+ */
+ if (*addr == 0) {
+ msg_warn("transport_lookup: null address - skipping table lookup");
+ return (NOTFOUND);
+ }
+
+ /*
+ * Look up the full and extension-stripped address, then match the domain
+ * and subdomains. Try the external form before the backwards-compatible
+ * internal form.
+ */
+#define LOOKUP_STRATEGY \
+ (MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN | \
+ (transport_match_parent_style == MATCH_FLAG_PARENT ? \
+ MA_FIND_PDMS : MA_FIND_PDDMDS))
+
+ if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0)
+ msg_panic("transport_lookup: bad address: \"%s\"", addr);
+
+ if ((value = mail_addr_find_strategy(tp->transport_path, addr, (char **) 0,
+ LOOKUP_STRATEGY)) != 0) {
+ parse_transport_entry(value, rcpt_domain, channel, nexthop);
+ return (FOUND);
+ }
+ if (tp->transport_path->error != 0)
+ return (NOTFOUND);
+
+ /*
+ * Fall back to the wild-card entry.
+ */
+ if (tp->wildcard_errno || event_time() > tp->expire)
+ transport_wildcard_init(tp);
+ if (tp->wildcard_errno) {
+ tp->transport_path->error = tp->wildcard_errno;
+ return (NOTFOUND);
+ } else if (tp->wildcard_channel) {
+ update_entry(STR(tp->wildcard_channel), STR(tp->wildcard_nexthop),
+ rcpt_domain, channel, nexthop);
+ return (FOUND);
+ }
+
+ /*
+ * We really did not find it.
+ */
+ return (NOTFOUND);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program. Read an address from stdin, and spit out
+ * the lookup result.
+ */
+
+#include <string.h>
+
+#include <mail_conf.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+static NORETURN usage(const char *progname)
+{
+ msg_fatal("usage: %s [-v] database", progname);
+}
+
+int main(int argc, char **argv)
+{
+ VSTRING *buffer = vstring_alloc(100);
+ VSTRING *channel = vstring_alloc(100);
+ VSTRING *nexthop = vstring_alloc(100);
+ TRANSPORT_INFO *tp;
+ char *bp;
+ char *addr_field;
+ char *rcpt_domain;
+ char *expect_channel;
+ char *expect_nexthop;
+ int status;
+ int ch;
+ int errs = 0;
+
+ /*
+ * Parse JCL.
+ */
+ while ((ch = GETOPT(argc, argv, "v")) > 0) {
+ switch (ch) {
+ case 'v':
+ msg_verbose++;
+ break;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (argc != optind + 1)
+ usage(argv[0]);
+
+ /*
+ * Initialize.
+ */
+#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0)
+
+ mail_conf_read(); /* XXX eliminate dependency. */
+ UPDATE(var_rcpt_delim, "+");
+ UPDATE(var_mydomain, "localdomain");
+ UPDATE(var_myorigin, "localhost.localdomain");
+ UPDATE(var_mydest, "localhost.localdomain");
+
+ tp = transport_pre_init("transport map", argv[optind]);
+ transport_post_init(tp);
+
+ while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
+ bp = STR(buffer);
+
+ /*
+ * Parse the input and expectations. XXX We can't expect empty
+ * fields, so require '-' instead.
+ */
+ if ((addr_field = mystrtok(&bp, ":")) == 0)
+ msg_fatal("no address field");
+ if ((rcpt_domain = strrchr(addr_field, '@')) == 0)
+ msg_fatal("no recipient domain");
+ rcpt_domain += 1;
+ expect_channel = mystrtok(&bp, ":");
+ expect_nexthop = mystrtok(&bp, ":");
+ if ((expect_channel != 0) != (expect_nexthop != 0))
+ msg_fatal("specify both channel and nexthop, or specify neither");
+ if (expect_channel) {
+ if (strcmp(expect_channel, "-") == 0)
+ *expect_channel = 0;
+ if (strcmp(expect_nexthop, "-") == 0)
+ *expect_nexthop = 0;
+ vstring_strcpy(channel, "DEFAULT");
+ vstring_strcpy(nexthop, rcpt_domain);
+ }
+ if (mystrtok(&bp, ":") != 0)
+ msg_fatal("garbage after nexthop field");
+
+ /*
+ * Lookups.
+ */
+ status = transport_lookup(tp, addr_field, rcpt_domain,
+ channel, nexthop);
+
+ /*
+ * Enforce expectations.
+ */
+ if (expect_nexthop && status) {
+ vstream_printf("%s:%s -> %s:%s \n",
+ addr_field, rcpt_domain,
+ STR(channel), STR(nexthop));
+ vstream_fflush(VSTREAM_OUT);
+ if (strcmp(expect_channel, STR(channel)) != 0) {
+ msg_warn("expect channel '%s' but got '%s'",
+ expect_channel, STR(channel));
+ errs = 1;
+ }
+ if (strcmp(expect_nexthop, STR(nexthop)) != 0) {
+ msg_warn("expect nexthop '%s' but got '%s'",
+ expect_nexthop, STR(nexthop));
+ errs = 1;
+ }
+ } else if (expect_nexthop && !status) {
+ vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain,
+ tp->transport_path->error ?
+ "(try again)" : "(not found)");
+ vstream_fflush(VSTREAM_OUT);
+ msg_warn("expect channel '%s' but got none", expect_channel);
+ msg_warn("expect nexthop '%s' but got none", expect_nexthop);
+ errs = 1;
+ } else if (!status) {
+ vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain,
+ tp->transport_path->error ?
+ "(try again)" : "(not found)");
+ }
+ }
+ transport_free(tp);
+ vstring_free(nexthop);
+ vstring_free(channel);
+ vstring_free(buffer);
+ exit(errs != 0);
+}
+
+#endif
diff --git a/src/trivial-rewrite/transport.h b/src/trivial-rewrite/transport.h
new file mode 100644
index 0000000..7db0b50
--- /dev/null
+++ b/src/trivial-rewrite/transport.h
@@ -0,0 +1,51 @@
+/*++
+/* NAME
+/* transport 3h
+/* SUMMARY
+/* transport mapping
+/* SYNOPSIS
+/* #include "transport.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * System library.
+ */
+#include <time.h>
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * Global library.
+ */
+#include <maps.h>
+
+ /*
+ * External interface.
+ */
+typedef struct TRANSPORT_INFO {
+ MAPS *transport_path;
+ VSTRING *wildcard_channel;
+ VSTRING *wildcard_nexthop;
+ int wildcard_errno;
+ time_t expire;
+} TRANSPORT_INFO;
+
+extern TRANSPORT_INFO *transport_pre_init(const char *, const char *);
+extern void transport_post_init(TRANSPORT_INFO *);
+extern int transport_lookup(TRANSPORT_INFO *, const char *, const char *, VSTRING *, VSTRING *);
+extern void transport_free(TRANSPORT_INFO *);
+
+/* 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/trivial-rewrite/transport.in b/src/trivial-rewrite/transport.in
new file mode 100644
index 0000000..1ed3a53
--- /dev/null
+++ b/src/trivial-rewrite/transport.in
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+# Format: address:expected_channel:expected_nexthop
+# No expectation means no match is expected.
+# Specify "-" to expect an empty string.
+
+echo ==== no wildcard
+${VALGRIND} ./transport 'inline:{rcpt1@example1.com=channel1:nexthop1, rcpt2@example2=channel2:, example3=channel3}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt1+ext1@example1.com:channel1:nexthop1
+rcpt2@example2:channel2:example2
+rcpt@example3:channel3:example3
+EOF
+
+echo ==== with wildcard channel and nexthop
+${VALGRIND} ./transport 'inline:{*=channel0:nexthop0, rcpt1@example1.com=channel1:nexthop1}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt2@example2:channel0:nexthop0
+EOF
+
+echo ==== with wildcard channel only
+${VALGRIND} ./transport 'inline:{*=channel0, rcpt1@example1.com=channel1:nexthop1}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt2@example2:channel0:example2
+EOF
+
+echo ==== with wildcard nexthop only
+${VALGRIND} ./transport 'inline:{*=:nexthop0, rcpt1@example1.com=channel1:nexthop1}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt2@example2:DEFAULT:nexthop0
+EOF
+
+echo ==== with wildcard empty fields.
+${VALGRIND} ./transport 'inline:{*=:, rcpt1@example1.com=channel1:nexthop1}' <<'EOF'
+rcpt1@example1.com:channel1:nexthop1
+rcpt2@example2:DEFAULT:example2
+EOF
+
+echo === subdomain test
+${VALGRIND} ./transport 'inline:{example=:example-result,.example=:dot-example-result}' <<'EOF'
+plain1+ext@other-example:
+foo@example:DEFAULT:example-result
+foo@sub.example:DEFAULT:dot-example-result
+foo@sub.sub.example:DEFAULT:dot-example-result
+EOF
diff --git a/src/trivial-rewrite/transport.ref b/src/trivial-rewrite/transport.ref
new file mode 100644
index 0000000..47ab07b
--- /dev/null
+++ b/src/trivial-rewrite/transport.ref
@@ -0,0 +1,22 @@
+==== no wildcard
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt1+ext1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> channel2:example2
+rcpt@example3:example3 -> channel3:example3
+==== with wildcard channel and nexthop
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> channel0:nexthop0
+==== with wildcard channel only
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> channel0:example2
+==== with wildcard nexthop only
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> DEFAULT:nexthop0
+==== with wildcard empty fields.
+rcpt1@example1.com:example1.com -> channel1:nexthop1
+rcpt2@example2:example2 -> DEFAULT:example2
+=== subdomain test
+plain1+ext@other-example:other-example -> (not found)
+foo@example:example -> DEFAULT:example-result
+foo@sub.example:sub.example -> DEFAULT:dot-example-result
+foo@sub.sub.example:sub.sub.example -> DEFAULT:dot-example-result
diff --git a/src/trivial-rewrite/trivial-rewrite.c b/src/trivial-rewrite/trivial-rewrite.c
new file mode 100644
index 0000000..29c55c9
--- /dev/null
+++ b/src/trivial-rewrite/trivial-rewrite.c
@@ -0,0 +1,652 @@
+/*++
+/* NAME
+/* trivial-rewrite 8
+/* SUMMARY
+/* Postfix address rewriting and resolving daemon
+/* SYNOPSIS
+/* \fBtrivial-rewrite\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBtrivial-rewrite\fR(8) daemon processes three types of client
+/* service requests:
+/* .IP "\fBrewrite \fIcontext address\fR"
+/* Rewrite an address to standard form, according to the
+/* address rewriting context:
+/* .RS
+/* .IP \fBlocal\fR
+/* Append the domain names specified with \fB$myorigin\fR or
+/* \fB$mydomain\fR to incomplete addresses; do \fBswap_bangpath\fR
+/* and \fBallow_percent_hack\fR processing as described below, and
+/* strip source routed addresses (\fI@site,@site:user@domain\fR)
+/* to \fIuser@domain\fR form.
+/* .IP \fBremote\fR
+/* Append the domain name specified with
+/* \fB$remote_header_rewrite_domain\fR to incomplete
+/* addresses. Otherwise the result is identical to that of
+/* the \fBlocal\fR address rewriting context. This prevents
+/* Postfix from appending the local domain to spam from poorly
+/* written remote clients.
+/* .RE
+/* .IP "\fBresolve \fIsender\fR \fIaddress\fR"
+/* Resolve the address to a (\fItransport\fR, \fInexthop\fR,
+/* \fIrecipient\fR, \fIflags\fR) quadruple. The meaning of
+/* the results is as follows:
+/* .RS
+/* .IP \fItransport\fR
+/* The delivery agent to use. This is the first field of an entry
+/* in the \fBmaster.cf\fR file.
+/* .IP \fInexthop\fR
+/* The host to send to and optional delivery method information.
+/* .IP \fIrecipient\fR
+/* The envelope recipient address that is passed on to \fInexthop\fR.
+/* .IP \fIflags\fR
+/* The address class, whether the address requires relaying,
+/* whether the address has problems, and whether the request failed.
+/* .RE
+/* .IP "\fBverify \fIsender\fR \fIaddress\fR"
+/* Resolve the address for address verification purposes.
+/* SERVER PROCESS MANAGEMENT
+/* .ad
+/* .fi
+/* The \fBtrivial-rewrite\fR(8) servers run under control by
+/* the Postfix master
+/* server. Each server can handle multiple simultaneous connections.
+/* When all servers are busy while a client connects, the master
+/* creates a new server process, provided that the trivial-rewrite
+/* server process limit is not exceeded.
+/* Each trivial-rewrite server terminates after
+/* serving at least \fB$max_use\fR clients of after \fB$max_idle\fR
+/* seconds of idle time.
+/* STANDARDS
+/* .ad
+/* .fi
+/* None. The command does not interact with the outside world.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBtrivial-rewrite\fR(8) daemon is not security sensitive.
+/* By default, this daemon does not talk to remote or local users.
+/* It can run at a fixed low privilege in a chrooted environment.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* On busy mail systems a long time may pass before a \fBmain.cf\fR
+/* change affecting \fBtrivial-rewrite\fR(8) is picked up. 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.
+/* COMPATIBILITY CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBresolve_dequoted_address (yes)\fR"
+/* Resolve a recipient address safely instead of correctly, by
+/* looking inside quotes.
+/* .PP
+/* Available with Postfix version 2.1 and later:
+/* .IP "\fBresolve_null_domain (no)\fR"
+/* Resolve an address that ends in the "@" null domain as if the
+/* local hostname were specified, instead of rejecting the address as
+/* invalid.
+/* .PP
+/* Available with Postfix version 2.3 and later:
+/* .IP "\fBresolve_numeric_domain (no)\fR"
+/* Resolve "user@ipaddress" as "user@[ipaddress]", instead of
+/* rejecting the address as invalid.
+/* .PP
+/* Available with Postfix version 2.5 and later:
+/* .IP "\fBallow_min_user (no)\fR"
+/* Allow a sender or recipient address to have `-' as the first
+/* character.
+/* ADDRESS REWRITING CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBmyorigin ($myhostname)\fR"
+/* The domain name that locally-posted mail appears to come
+/* from, and that locally posted mail is delivered to.
+/* .IP "\fBallow_percent_hack (yes)\fR"
+/* Enable the rewriting of the form "user%domain" to "user@domain".
+/* .IP "\fBappend_at_myorigin (yes)\fR"
+/* With locally submitted mail, append the string "@$myorigin" to mail
+/* addresses without domain information.
+/* .IP "\fBappend_dot_mydomain (Postfix >= 3.0: no, Postfix < 3.0: yes)\fR"
+/* With locally submitted mail, append the string ".$mydomain" to
+/* addresses that have no ".domain" information.
+/* .IP "\fBrecipient_delimiter (empty)\fR"
+/* The set of characters that can separate a user name from its
+/* extension (example: user+foo), or a .forward file name from its
+/* extension (example: .forward+foo).
+/* .IP "\fBswap_bangpath (yes)\fR"
+/* Enable the rewriting of "site!user" into "user@site".
+/* .PP
+/* Available in Postfix 2.2 and later:
+/* .IP "\fBremote_header_rewrite_domain (empty)\fR"
+/* Don't rewrite message headers from remote clients at all when
+/* this parameter is empty; otherwise, rewrite message headers and
+/* append the specified domain name to incomplete addresses.
+/* ROUTING CONTROLS
+/* .ad
+/* .fi
+/* The following is applicable to Postfix version 2.0 and later.
+/* Earlier versions do not have support for: virtual_transport,
+/* relay_transport, virtual_alias_domains, virtual_mailbox_domains
+/* or proxy_interfaces.
+/* .IP "\fBlocal_transport (local:$myhostname)\fR"
+/* The default mail delivery transport and next-hop destination
+/* for final delivery to domains listed with mydestination, and for
+/* [ipaddress] destinations that match $inet_interfaces or $proxy_interfaces.
+/* .IP "\fBvirtual_transport (virtual)\fR"
+/* The default mail delivery transport and next-hop destination for
+/* final delivery to domains listed with $virtual_mailbox_domains.
+/* .IP "\fBrelay_transport (relay)\fR"
+/* The default mail delivery transport and next-hop destination for
+/* remote delivery to domains listed with $relay_domains.
+/* .IP "\fBdefault_transport (smtp)\fR"
+/* The default mail delivery transport and next-hop destination for
+/* destinations that do not match $mydestination, $inet_interfaces,
+/* $proxy_interfaces, $virtual_alias_domains, $virtual_mailbox_domains,
+/* or $relay_domains.
+/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR"
+/* A list of Postfix features where the pattern "example.com" also
+/* matches subdomains of example.com,
+/* instead of requiring an explicit ".example.com" pattern.
+/* .IP "\fBrelayhost (empty)\fR"
+/* The next-hop destination of non-local mail; overrides non-local
+/* domains in recipient addresses.
+/* .IP "\fBtransport_maps (empty)\fR"
+/* Optional lookup tables with mappings from recipient address to
+/* (message delivery transport, next-hop destination).
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBsender_dependent_relayhost_maps (empty)\fR"
+/* A sender-dependent override for the global relayhost parameter
+/* setting.
+/* .PP
+/* Available in Postfix version 2.5 and later:
+/* .IP "\fBempty_address_relayhost_maps_lookup_key (<>)\fR"
+/* The sender_dependent_relayhost_maps search string that will be
+/* used instead of the null sender address.
+/* .PP
+/* Available in Postfix version 2.7 and later:
+/* .IP "\fBempty_address_default_transport_maps_lookup_key (<>)\fR"
+/* The sender_dependent_default_transport_maps search string that
+/* will be used instead of the null sender address.
+/* .IP "\fBsender_dependent_default_transport_maps (empty)\fR"
+/* A sender-dependent override for the global default_transport
+/* parameter setting.
+/* ADDRESS VERIFICATION CONTROLS
+/* .ad
+/* .fi
+/* Postfix version 2.1 introduces sender and recipient address verification.
+/* This feature is implemented by sending probe email messages that
+/* are not actually delivered.
+/* By default, address verification probes use the same route
+/* as regular mail. To override specific aspects of message
+/* routing for address verification probes, specify one or more
+/* of the following:
+/* .IP "\fBaddress_verify_local_transport ($local_transport)\fR"
+/* Overrides the local_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_virtual_transport ($virtual_transport)\fR"
+/* Overrides the virtual_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_relay_transport ($relay_transport)\fR"
+/* Overrides the relay_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_default_transport ($default_transport)\fR"
+/* Overrides the default_transport parameter setting for address
+/* verification probes.
+/* .IP "\fBaddress_verify_relayhost ($relayhost)\fR"
+/* Overrides the relayhost parameter setting for address verification
+/* probes.
+/* .IP "\fBaddress_verify_transport_maps ($transport_maps)\fR"
+/* Overrides the transport_maps parameter setting for address verification
+/* probes.
+/* .PP
+/* Available in Postfix version 2.3 and later:
+/* .IP "\fBaddress_verify_sender_dependent_relayhost_maps ($sender_dependent_relayhost_maps)\fR"
+/* Overrides the sender_dependent_relayhost_maps parameter setting for address
+/* verification probes.
+/* .PP
+/* Available in Postfix version 2.7 and later:
+/* .IP "\fBaddress_verify_sender_dependent_default_transport_maps ($sender_dependent_default_transport_maps)\fR"
+/* Overrides the sender_dependent_default_transport_maps parameter
+/* setting for address verification probes.
+/* 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 "\fBempty_address_recipient (MAILER-DAEMON)\fR"
+/* The recipient of mail addressed to the null address.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBmax_idle (100s)\fR"
+/* The maximum amount of time that an idle Postfix daemon process waits
+/* for an incoming connection before terminating voluntarily.
+/* .IP "\fBmax_use (100)\fR"
+/* The maximal number of incoming connections that a Postfix daemon
+/* process will service before terminating voluntarily.
+/* .IP "\fBrelocated_maps (empty)\fR"
+/* Optional lookup tables with new contact information for users or
+/* domains that no longer exist.
+/* .IP "\fBprocess_id (read-only)\fR"
+/* The process ID of a Postfix command or daemon process.
+/* .IP "\fBprocess_name (read-only)\fR"
+/* The process name of a Postfix command or daemon process.
+/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
+/* The location of the Postfix top-level queue directory.
+/* .IP "\fBshow_user_unknown_table_name (yes)\fR"
+/* Display the name of the recipient table in the "User unknown"
+/* responses.
+/* .IP "\fBsyslog_facility (mail)\fR"
+/* The syslog facility of Postfix logging.
+/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
+/* A prefix that is prepended to the process name in syslog
+/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available in Postfix version 2.0 and later:
+/* .IP "\fBhelpful_warnings (yes)\fR"
+/* Log warnings about problematic configuration settings, and provide
+/* helpful suggestions.
+/* .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
+/* postconf(5), configuration parameters
+/* transport(5), transport table format
+/* relocated(5), format of the "user has moved" table
+/* 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
+/* ADDRESS_CLASS_README, Postfix address classes howto
+/* ADDRESS_VERIFICATION_README, Postfix address verification
+/* 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 <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+#include <split_at.h>
+#include <stringops.h>
+#include <dict.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <resolve_local.h>
+#include <mail_conf.h>
+#include <resolve_clnt.h>
+#include <rewrite_clnt.h>
+#include <tok822.h>
+#include <mail_addr.h>
+
+/* Multi server skeleton. */
+
+#include <mail_server.h>
+
+/* Application-specific. */
+
+#include <trivial-rewrite.h>
+#include <transport.h>
+
+static VSTRING *command;
+
+ /*
+ * Tunable parameters.
+ */
+char *var_transport_maps;
+bool var_swap_bangpath;
+bool var_append_dot_mydomain;
+bool var_append_at_myorigin;
+bool var_percent_hack;
+char *var_local_transport;
+char *var_virt_transport;
+char *var_relay_transport;
+int var_resolve_dequoted;
+char *var_virt_alias_maps; /* XXX virtual_alias_domains */
+char *var_virt_mailbox_maps; /* XXX virtual_mailbox_domains */
+char *var_virt_alias_doms;
+char *var_virt_mailbox_doms;
+char *var_relocated_maps;
+char *var_def_transport;
+char *var_snd_def_xport_maps;
+char *var_empty_addr;
+int var_show_unk_rcpt_table;
+int var_resolve_nulldom;
+char *var_remote_rwr_domain;
+char *var_snd_relay_maps;
+char *var_null_relay_maps_key;
+char *var_null_def_xport_maps_key;
+int var_resolve_num_dom;
+bool var_allow_min_user;
+
+ /*
+ * Shadow personality for address verification.
+ */
+char *var_vrfy_xport_maps;
+char *var_vrfy_local_xport;
+char *var_vrfy_virt_xport;
+char *var_vrfy_relay_xport;
+char *var_vrfy_def_xport;
+char *var_vrfy_snd_def_xport_maps;
+char *var_vrfy_relayhost;
+char *var_vrfy_relay_maps;
+
+ /*
+ * Different resolver personalities depending on the kind of request.
+ */
+RES_CONTEXT resolve_regular = {
+ VAR_LOCAL_TRANSPORT, &var_local_transport,
+ VAR_VIRT_TRANSPORT, &var_virt_transport,
+ VAR_RELAY_TRANSPORT, &var_relay_transport,
+ VAR_DEF_TRANSPORT, &var_def_transport,
+ VAR_SND_DEF_XPORT_MAPS, &var_snd_def_xport_maps, 0,
+ VAR_RELAYHOST, &var_relayhost,
+ VAR_SND_RELAY_MAPS, &var_snd_relay_maps, 0,
+ VAR_TRANSPORT_MAPS, &var_transport_maps, 0
+};
+
+RES_CONTEXT resolve_verify = {
+ VAR_VRFY_LOCAL_XPORT, &var_vrfy_local_xport,
+ VAR_VRFY_VIRT_XPORT, &var_vrfy_virt_xport,
+ VAR_VRFY_RELAY_XPORT, &var_vrfy_relay_xport,
+ VAR_VRFY_DEF_XPORT, &var_vrfy_def_xport,
+ VAR_VRFY_SND_DEF_XPORT_MAPS, &var_vrfy_snd_def_xport_maps, 0,
+ VAR_VRFY_RELAYHOST, &var_vrfy_relayhost,
+ VAR_VRFY_RELAY_MAPS, &var_vrfy_relay_maps, 0,
+ VAR_VRFY_XPORT_MAPS, &var_vrfy_xport_maps, 0
+};
+
+ /*
+ * Connection management. When file-based lookup tables change we should
+ * restart at our convenience, but avoid client read errors. We restart
+ * rather than reopen, because the process may be chrooted (and if it isn't
+ * we still need code that handles the chrooted case anyway).
+ *
+ * Three variants are implemented. Only one should be used.
+ *
+ * ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
+ *
+ * This code detaches the trivial-rewrite process from the master, stops
+ * accepting new clients, and handles established clients in the background,
+ * asking them to reconnect the next time they send a request. The master
+ * creates a new process that accepts connections. This is reasonably safe
+ * because the number of trivial-rewrite server processes is small compared
+ * to the number of trivial-rewrite client processes. The few extra
+ * background processes should not make a difference in Postfix's footprint.
+ * However, once a daemon detaches from the master, its exit status will be
+ * lost, and abnormal termination may remain undetected. Timely restart is
+ * achieved by checking the table changed status every 10 seconds or so
+ * before responding to a client request.
+ *
+ * ifdef CHECK_TABLE_STATS_PERIODICALLY
+ *
+ * This code runs every 10 seconds and terminates the process when lookup
+ * tables have changed. This is subject to race conditions when established
+ * clients send a request while the server exits; those clients may read EOF
+ * instead of a server reply. If the experience with the oldest option
+ * (below) is anything to go by, however, then this is unlikely to be a
+ * problem during real deployment.
+ *
+ * ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT
+ *
+ * This is the old code. It checks the table changed status when a new client
+ * connects (i.e. before the server calls accept()), and terminates
+ * immediately. This is invisible for the connecting client, but is subject
+ * to race conditions when established clients send a request while the
+ * server exits; those clients may read EOF instead of a server reply. This
+ * has, however, not been a problem in real deployment. With the old code,
+ * timely restart is achieved by setting the ipc_ttl parameter to 60
+ * seconds, so that the table change status is checked several times a
+ * minute.
+ */
+int server_flags;
+
+ /*
+ * Define exactly one of these.
+ */
+/* #define DETACH_AND_ASK_CLIENTS_TO_RECONNECT /* correct and complex */
+#define CHECK_TABLE_STATS_PERIODICALLY /* quick */
+/* #define CHECK_TABLE_STATS_BEFORE_ACCEPT /* slow */
+
+/* rewrite_service - read request and send reply */
+
+static void rewrite_service(VSTREAM *stream, char *unused_service, char **argv)
+{
+ int status = -1;
+
+#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
+ static time_t last;
+ time_t now;
+ const char *table;
+
+#endif
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * Client connections are long-lived. Be sure to refesh timely.
+ */
+#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT
+ if (server_flags == 0 && (now = event_time()) - last > 10) {
+ if ((table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ if (multi_server_drain() == 0)
+ server_flags = 1;
+ }
+ last = now;
+ }
+#endif
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to address rewriting. All connection-management stuff is
+ * handled by the common code in multi_server.c.
+ */
+ if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
+ RECV_ATTR_STR(MAIL_ATTR_REQ, command),
+ ATTR_TYPE_END) == 1) {
+ if (strcmp(vstring_str(command), REWRITE_ADDR) == 0) {
+ status = rewrite_proto(stream);
+ } else if (strcmp(vstring_str(command), RESOLVE_REGULAR) == 0) {
+ status = resolve_proto(&resolve_regular, stream);
+ } else if (strcmp(vstring_str(command), RESOLVE_VERIFY) == 0) {
+ status = resolve_proto(&resolve_verify, stream);
+ } else {
+ msg_warn("bad command %.30s", printable(vstring_str(command), '?'));
+ }
+ }
+ if (status < 0)
+ multi_server_disconnect(stream);
+}
+
+/* pre_accept - see if tables have changed */
+
+#ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT
+
+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);
+ }
+}
+
+#endif
+
+static void check_table_stats(int unused_event, void *unused_context)
+{
+ const char *table;
+
+ if ((table = dict_changed_name()) != 0) {
+ msg_info("table %s has changed -- restarting", table);
+ exit(0);
+ }
+ event_request_timer(check_table_stats, (void *) 0, 10);
+}
+
+/* pre_jail_init - initialize before entering chroot jail */
+
+static void pre_jail_init(char *unused_name, char **unused_argv)
+{
+ command = vstring_alloc(100);
+ rewrite_init();
+ resolve_init();
+ if (*RES_PARAM_VALUE(resolve_regular.transport_maps))
+ resolve_regular.transport_info =
+ transport_pre_init(resolve_regular.transport_maps_name,
+ RES_PARAM_VALUE(resolve_regular.transport_maps));
+ if (*RES_PARAM_VALUE(resolve_verify.transport_maps))
+ resolve_verify.transport_info =
+ transport_pre_init(resolve_verify.transport_maps_name,
+ RES_PARAM_VALUE(resolve_verify.transport_maps));
+ if (*RES_PARAM_VALUE(resolve_regular.snd_relay_maps))
+ resolve_regular.snd_relay_info =
+ maps_create(resolve_regular.snd_relay_maps_name,
+ RES_PARAM_VALUE(resolve_regular.snd_relay_maps),
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST);
+ if (*RES_PARAM_VALUE(resolve_verify.snd_relay_maps))
+ resolve_verify.snd_relay_info =
+ maps_create(resolve_verify.snd_relay_maps_name,
+ RES_PARAM_VALUE(resolve_verify.snd_relay_maps),
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST);
+ if (*RES_PARAM_VALUE(resolve_regular.snd_def_xp_maps))
+ resolve_regular.snd_def_xp_info =
+ maps_create(resolve_regular.snd_def_xp_maps_name,
+ RES_PARAM_VALUE(resolve_regular.snd_def_xp_maps),
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST);
+ if (*RES_PARAM_VALUE(resolve_verify.snd_def_xp_maps))
+ resolve_verify.snd_def_xp_info =
+ maps_create(resolve_verify.snd_def_xp_maps_name,
+ RES_PARAM_VALUE(resolve_verify.snd_def_xp_maps),
+ DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX
+ | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST);
+}
+
+/* post_jail_init - initialize after entering chroot jail */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+ if (resolve_regular.transport_info)
+ transport_post_init(resolve_regular.transport_info);
+ if (resolve_verify.transport_info)
+ transport_post_init(resolve_verify.transport_info);
+ check_table_stats(0, (void *) 0);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton code */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_STR_TABLE str_table[] = {
+ VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0,
+ VAR_LOCAL_TRANSPORT, DEF_LOCAL_TRANSPORT, &var_local_transport, 1, 0,
+ VAR_VIRT_TRANSPORT, DEF_VIRT_TRANSPORT, &var_virt_transport, 1, 0,
+ VAR_RELAY_TRANSPORT, DEF_RELAY_TRANSPORT, &var_relay_transport, 1, 0,
+ VAR_DEF_TRANSPORT, DEF_DEF_TRANSPORT, &var_def_transport, 1, 0,
+ VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
+ VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0,
+ VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
+ VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0,
+ VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
+ VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
+ VAR_VRFY_XPORT_MAPS, DEF_VRFY_XPORT_MAPS, &var_vrfy_xport_maps, 0, 0,
+ VAR_VRFY_LOCAL_XPORT, DEF_VRFY_LOCAL_XPORT, &var_vrfy_local_xport, 1, 0,
+ VAR_VRFY_VIRT_XPORT, DEF_VRFY_VIRT_XPORT, &var_vrfy_virt_xport, 1, 0,
+ VAR_VRFY_RELAY_XPORT, DEF_VRFY_RELAY_XPORT, &var_vrfy_relay_xport, 1, 0,
+ VAR_VRFY_DEF_XPORT, DEF_VRFY_DEF_XPORT, &var_vrfy_def_xport, 1, 0,
+ VAR_VRFY_RELAYHOST, DEF_VRFY_RELAYHOST, &var_vrfy_relayhost, 0, 0,
+ VAR_REM_RWR_DOMAIN, DEF_REM_RWR_DOMAIN, &var_remote_rwr_domain, 0, 0,
+ VAR_SND_RELAY_MAPS, DEF_SND_RELAY_MAPS, &var_snd_relay_maps, 0, 0,
+ VAR_NULL_RELAY_MAPS_KEY, DEF_NULL_RELAY_MAPS_KEY, &var_null_relay_maps_key, 1, 0,
+ VAR_VRFY_RELAY_MAPS, DEF_VRFY_RELAY_MAPS, &var_vrfy_relay_maps, 0, 0,
+ VAR_SND_DEF_XPORT_MAPS, DEF_SND_DEF_XPORT_MAPS, &var_snd_def_xport_maps, 0, 0,
+ VAR_NULL_DEF_XPORT_MAPS_KEY, DEF_NULL_DEF_XPORT_MAPS_KEY, &var_null_def_xport_maps_key, 1, 0,
+ VAR_VRFY_SND_DEF_XPORT_MAPS, DEF_VRFY_SND_DEF_XPORT_MAPS, &var_vrfy_snd_def_xport_maps, 0, 0,
+ 0,
+ };
+ static const CONFIG_BOOL_TABLE bool_table[] = {
+ VAR_SWAP_BANGPATH, DEF_SWAP_BANGPATH, &var_swap_bangpath,
+ VAR_APP_AT_MYORIGIN, DEF_APP_AT_MYORIGIN, &var_append_at_myorigin,
+ VAR_PERCENT_HACK, DEF_PERCENT_HACK, &var_percent_hack,
+ VAR_RESOLVE_DEQUOTED, DEF_RESOLVE_DEQUOTED, &var_resolve_dequoted,
+ VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table,
+ VAR_RESOLVE_NULLDOM, DEF_RESOLVE_NULLDOM, &var_resolve_nulldom,
+ VAR_RESOLVE_NUM_DOM, DEF_RESOLVE_NUM_DOM, &var_resolve_num_dom,
+ VAR_ALLOW_MIN_USER, DEF_ALLOW_MIN_USER, &var_allow_min_user,
+ 0,
+ };
+ static const CONFIG_NBOOL_TABLE nbool_table[] = {
+ VAR_APP_DOT_MYDOMAIN, DEF_APP_DOT_MYDOMAIN, &var_append_dot_mydomain,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ multi_server_main(argc, argv, rewrite_service,
+ CA_MAIL_SERVER_STR_TABLE(str_table),
+ CA_MAIL_SERVER_BOOL_TABLE(bool_table),
+ CA_MAIL_SERVER_NBOOL_TABLE(nbool_table),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+#ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT
+ CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
+#endif
+ 0);
+}
diff --git a/src/trivial-rewrite/trivial-rewrite.h b/src/trivial-rewrite/trivial-rewrite.h
new file mode 100644
index 0000000..e27dd00
--- /dev/null
+++ b/src/trivial-rewrite/trivial-rewrite.h
@@ -0,0 +1,92 @@
+/*++
+/* NAME
+/* trivial-rewrite 3h
+/* SUMMARY
+/* mail address rewriter and resolver
+/* SYNOPSIS
+/* #include "trivial-rewrite.h"
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+#include <vstream.h>
+
+ /*
+ * Global library.
+ */
+#include <tok822.h>
+#include <maps.h>
+
+ /*
+ * Connection management.
+ */
+extern int server_flags;
+
+ /*
+ * rewrite.c
+ */
+typedef struct {
+ const char *origin_name; /* name of variable */
+ char **origin; /* default origin */
+ const char *domain_name; /* name of variable */
+ char **domain; /* default domain */
+} RWR_CONTEXT;
+
+#define REW_PARAM_VALUE(x) (*(x)) /* make it easy to do it right */
+
+extern void rewrite_init(void);
+extern int rewrite_proto(VSTREAM *);
+extern void rewrite_addr(RWR_CONTEXT *, char *, VSTRING *);
+extern void rewrite_tree(RWR_CONTEXT *, TOK822 *);
+extern RWR_CONTEXT local_context;
+extern RWR_CONTEXT inval_context;
+
+ /*
+ * resolve.c
+ */
+typedef struct {
+ const char *local_transport_name; /* name of variable */
+ char **local_transport; /* local transport:nexthop */
+ const char *virt_transport_name; /* name of variable */
+ char **virt_transport; /* virtual mailbox transport:nexthop */
+ const char *relay_transport_name; /* name of variable */
+ char **relay_transport; /* relay transport:nexthop */
+ const char *def_transport_name; /* name of variable */
+ char **def_transport; /* default transport:nexthop */
+ const char *snd_def_xp_maps_name; /* name of variable */
+ char **snd_def_xp_maps; /* maptype:mapname */
+ MAPS *snd_def_xp_info; /* handle */
+ const char *relayhost_name; /* name of variable */
+ char **relayhost; /* for relay and default transport */
+ const char *snd_relay_maps_name; /* name of variable */
+ char **snd_relay_maps; /* maptype:mapname */
+ MAPS *snd_relay_info; /* handle */
+ const char *transport_maps_name; /* name of variable */
+ char **transport_maps; /* maptype:mapname */
+ struct TRANSPORT_INFO *transport_info; /* handle */
+} RES_CONTEXT;
+
+#define RES_PARAM_VALUE(x) (*(x)) /* make it easy to do it right */
+
+extern void resolve_init(void);
+extern int resolve_proto(RES_CONTEXT *, VSTREAM *);
+extern int resolve_class(const char *);
+
+/* 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
+/*--*/