Documentation: fixed wrong default in INSTALL and postconf(5).
Files: proto/INSTALL.html, proto/postconf.proto.
+
+20260105
+
+ Cleanup: added missing entries to the proxy_read_maps default
+ value for more complete coverage. File: global/mail_params.h.
+
+20260106
+
+ Cleanup: added more missing entries to the proxy_read_maps
+ default value, based on documentation that parameters support
+ type:table inputs. Files: mantools/check-proxy-type-table,
+ global/mail_params.h
+
+20260107
+
+ Cleanup: added missing initialization for parameters with
+ a non-empty default value that specifies a lookup table.
+ File: proxymap/proxymap.c.
+
+ Cleanup: the proxymap service now detects table references
+ in /file/name and in restriction_classes. File: proxymap/proxymap.c
+
+ Feature: export the proxymap authorization tables, for
+ testing, and for potential use by other tools. File:
+ proxymap/proxymap.c
+
+ Cleanup: re-run 'make depend'. File: cleanup/Makefile.in.
missing-proxy-read-maps-check:
$(SHLIB_ENV) mantools/missing-proxy-read-maps | diff /dev/null -
+ $(SHLIB_ENV) mantools/check-proxy-type-table | diff /dev/null -
typo-check: spell-cc spell-install-proto-text spell-proto-html \
check-spell-history
--- /dev/null
+#!/usr/bin/perl
+
+# List the names of parameters that are documented to support "type:table",
+# but that still need to be listed in proxy_read_maps. Some parameters
+# may never use proxymapped tables, but we want to maximize coverage of
+# the proxy_read_maps parameter name list for non-proxymap purposes.
+
+use strict;
+
+my $proto_doc = "proto/postconf.proto";
+my $proxy_read = "bin/postconf -dh proxy_read_maps";
+my %from_proto = ();
+my %from_proxy = ();
+my %proto_stops = (
+ "authorized_verp_clients" => 1, # Obsolete
+ "master_service_disable" => 1, # False positive
+);
+my $param = "";
+my $debug = 0;
+
+# Get the names of parameters that mention "type:table" in their
+# documentation. This will not find smtpd_mumble_restrictions but those
+# are already covered elsewhere.
+
+open PROTO, $proto_doc || die "Open $proto_doc: $!\n";
+while (<PROTO>) {
+ if (/^%PARAM\s+(\S+)/) { $param = $1; print "got: $param\n" if $debug; }
+ if ($param && /\btype:table\b/ && !/\bnot\b/ && !$proto_stops{$param}) {
+ print $_ if ($debug);
+ $from_proto{$param} = 1;
+ }
+}
+close PROTO || die "Read $proto_doc: $!\n";
+
+# Get the names of parameters that appear in proxy_read_maps. We use
+# these as a stop list for the above documentation-based approach.
+
+open PROXY, "$proxy_read|" || die "Run $proxy_read: $!\n";
+for $param (split(/\s+/, <PROXY>)) {
+ $param = substr($param, 1); # left-hand-side chop()
+ $from_proxy{$param} = 1;
+}
+close PROXY || die "Run $proxy_read: $!\n";
+
+if ($debug) {
+ for $param (sort keys %from_proxy) { print "from proxy: $param\n"; }
+ for $param (sort keys %from_proto) { print "from proto: $param\n"; }
+}
+
+# List parameters with "type:table" that are not listed in proxy_read_maps.
+
+for $param (sort keys %from_proto) { print "$param\n" unless $from_proxy{$param}; }
verification in progress File verify verify c
virtual virtual c
with setgid permissions File postlogd postlogd c
+ default value with a lookup table File proxymap proxymap c
+ in file name and in restriction_classes File proxymap proxymap c
+ proxymap proxymap c
cleanup_message.o: ../../include/vstring.h
cleanup_message.o: cleanup.h
cleanup_message.o: cleanup_message.c
+cleanup_message_test.o: ../../include/argv.h
+cleanup_message_test.o: ../../include/attr.h
+cleanup_message_test.o: ../../include/been_here.h
+cleanup_message_test.o: ../../include/check_arg.h
+cleanup_message_test.o: ../../include/cleanup_user.h
+cleanup_message_test.o: ../../include/dict.h
+cleanup_message_test.o: ../../include/dsn_mask.h
+cleanup_message_test.o: ../../include/header_body_checks.h
+cleanup_message_test.o: ../../include/header_opts.h
+cleanup_message_test.o: ../../include/hfrom_format.h
+cleanup_message_test.o: ../../include/htable.h
+cleanup_message_test.o: ../../include/mail_conf.h
+cleanup_message_test.o: ../../include/mail_params.h
+cleanup_message_test.o: ../../include/mail_stream.h
+cleanup_message_test.o: ../../include/maps.h
+cleanup_message_test.o: ../../include/match_list.h
+cleanup_message_test.o: ../../include/milter.h
+cleanup_message_test.o: ../../include/mime_state.h
+cleanup_message_test.o: ../../include/msg.h
+cleanup_message_test.o: ../../include/msg_vstream.h
+cleanup_message_test.o: ../../include/myflock.h
+cleanup_message_test.o: ../../include/mymalloc.h
+cleanup_message_test.o: ../../include/nvtable.h
+cleanup_message_test.o: ../../include/rec_type.h
+cleanup_message_test.o: ../../include/record.h
+cleanup_message_test.o: ../../include/resolve_clnt.h
+cleanup_message_test.o: ../../include/string_list.h
+cleanup_message_test.o: ../../include/stringops.h
+cleanup_message_test.o: ../../include/sys_defs.h
+cleanup_message_test.o: ../../include/tok822.h
+cleanup_message_test.o: ../../include/vbuf.h
+cleanup_message_test.o: ../../include/vstream.h
+cleanup_message_test.o: ../../include/vstring.h
+cleanup_message_test.o: cleanup.h
+cleanup_message_test.o: cleanup_message_test.c
cleanup_milter.o: ../../include/argv.h
cleanup_milter.o: ../../include/attr.h
cleanup_milter.o: ../../include/been_here.h
" $" VAR_SMTP_HEAD_CHKS \
" $" VAR_SMTP_MIME_CHKS \
" $" VAR_SMTP_NEST_CHKS \
- " $" VAR_SMTPD_REJECT_FILTER_MAPS
+ " $" VAR_SMTPD_REJECT_FILTER_MAPS \
+ " $" VAR_DEBUG_PEER_LIST \
+ " $" VAR_ETRN_CHECKS \
+ " $" VAR_FFLUSH_DOMAINS \
+ " $" VAR_FLUSH_ACL \
+ " $" VAR_LMTP_CACHE_DEST \
+ " $" VAR_LOC_RWR_CLIENTS \
+ " $" VAR_MASQ_EXCEPTIONS \
+ " $" VAR_PSC_ACL \
+ " $" VAR_PSC_ALLIST_IF \
+ " $" VAR_PSC_FORBID_CMDS \
+ " $" VAR_QMQPD_CLIENTS \
+ " $" VAR_SHOWQ_ACL \
+ " $" VAR_SMTP_CACHE_DEST \
+ " $" VAR_SMTPD_ACL_PERM_LOG \
+ " $" VAR_SMTPD_FORBID_CMDS \
+ " $" VAR_SMTPD_HOGGERS \
+ " $" VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS \
+ " $" VAR_SMTPD_SASL_MECH_FILTER \
+ " $" VAR_SMTP_REQTLS_POLICY \
+ " $" VAR_SMTP_SASL_MECHS \
+ " $" VAR_SUBMIT_ACL \
+ " $" VAR_VERP_CLIENTS \
+ " $" VAR_XCLIENT_HOSTS \
+ " $" VAR_XFORWARD_HOSTS \
+
extern char *var_proxy_read_maps;
#define VAR_PROXY_WRITE_MAPS "proxy_write_maps"
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20251231"
+#define MAIL_RELEASE_DATE "20260110"
#define MAIL_VERSION_NUMBER "3.12"
#ifdef SNAPSHOT
test: $(TESTPROG)
-tests:
+tests: empty_proxymap_table empty_proxywrite_table \
+ proxy_read_map_works_for_proxied proxy_read_map_works_for_both \
+ proxy_write_works smtpd_restriction_classes_works file_name_works \
+ ignores_non_dictionary_form
+
+# Create a configuration that references no tables. The default tables
+# are platform-specific and that would complicate tests.
+SETUP_FILES = \
+ echo rm -f main.cf master.cf; \
+ echo alias_maps = >> main.cf; \
+ echo local_recipient_maps = >> main.cf; \
+ echo smtpd_forbidden_commands = >> main.cf; \
+ echo postscreen_cache_map = >> main.cf; \
+ echo address_verify_map = >> main.cf; \
+
+CLEANUP_FILES = rm -f main.cf master.cf
+
+empty_proxymap_table: $(PROG)
+ @echo RUN empty_proxymap_table
+ @$(SETUP_FILES)
+ touch -t 197101010000 main.cf
+ MAIL_CONFIG=`pwd` $(SHLIB_ENV) ${VALGRIND} ./$(PROG) \
+ -SVnexport-all-proxymap | diff /dev/null -
+ @$(CLEANUP_FILES)
+ @echo PASS empty_proxymap_table; echo
+
+empty_proxywrite_table: $(PROG)
+ @echo RUN empty_proxywrite_table
+ @$(SETUP_FILES)
+ touch -t 197101010000 main.cf
+ MAIL_CONFIG=`pwd` $(SHLIB_ENV) ${VALGRIND} ./$(PROG) \
+ -SVnexport-all-proxywrite | diff /dev/null -
+ @$(CLEANUP_FILES)
+ @echo PASS empty_proxywrite_table; echo
+
+proxy_read_map_works_for_proxied: $(PROG)
+ @echo RUN proxy_read_map_works_for_proxied
+ @$(SETUP_FILES)
+ echo local_recipient_maps = proxy:unix:passwd.byname cdb:/some/aliases >> main.cf
+ touch -t 197101010000 main.cf
+ echo unix:passwd.byname > proxy_read_map_works_for_proxied.tmp
+ $(SHLIB_ENV) ${VALGRIND} MAIL_CONFIG=`pwd` ./$(PROG) \
+ -SVnexport-proxy-proxymap | \
+ diff proxy_read_map_works_for_proxied.tmp -
+ @$(CLEANUP_FILES) proxy_read_map_works_for_proxied.tmp
+ @echo PASS proxy_read_map_works_for_proxied; echo
+
+proxy_read_map_works_for_both: $(PROG)
+ @echo RUN proxy_read_map_works_for_both
+ @$(SETUP_FILES)
+ echo local_recipient_maps = proxy:unix:passwd.byname cdb:/some/aliases >> main.cf
+ touch -t 197101010000 main.cf
+ echo cdb:/some/aliases > proxy_read_map_works_for_both.tmp
+ echo unix:passwd.byname >> proxy_read_map_works_for_both.tmp
+ $(SHLIB_ENV) ${VALGRIND} MAIL_CONFIG=`pwd` ./$(PROG) \
+ -SVnexport-all-proxymap | diff proxy_read_map_works_for_both.tmp -
+ @$(CLEANUP_FILES) proxy_read_map_works_for_both.tmp
+ @echo PASS proxy_read_map_works_for_both; echo
+
+proxy_write_works: $(PROG)
+ @echo RUN proxy_write_works
+ @$(SETUP_FILES)
+ echo postscreen_cache_map = proxy:lmdb:/some/path >> main.cf
+ touch -t 197101010000 main.cf
+ echo lmdb:/some/path > proxy_write_works.tmp
+ $(SHLIB_ENV) ${VALGRIND} MAIL_CONFIG=`pwd` ./$(PROG) \
+ -SVnexport-proxy-proxywrite | \
+ diff proxy_write_works.tmp -
+ @$(CLEANUP_FILES) proxy_write_works.tmp
+ @echo PASS proxy_write_works; echo
+
+smtpd_restriction_classes_works: $(PROG)
+ @echo RUN smtpd_restriction_classes_works
+ @$(SETUP_FILES)
+ echo smtpd_restriction_classes = foo >> main.cf
+ echo 'foo = $$bar' >> main.cf
+ echo bar = proxy:lmdb:/some/file >> main.cf
+ touch -t 197101010000 main.cf
+ echo lmdb:/some/file > smtpd_restriction_classes_works.tmp
+ $(SHLIB_ENV) ${VALGRIND} MAIL_CONFIG=`pwd` ./$(PROG) \
+ -SVnexport-proxy-proxymap | \
+ diff smtpd_restriction_classes_works.tmp -
+ @$(CLEANUP_FILES) smtpd_restriction_classes_works.tmp
+ @echo PASS smtpd_restriction_classes_works; echo
+
+file_name_works: $(PROG)
+ @echo RUN file_name_works
+ @$(SETUP_FILES)
+ echo mynetworks = `pwd`/file_name_works_file >> main.cf
+ echo proxy:lmdb:/some/file > file_name_works_file
+ touch -t 197101010000 main.cf
+ echo lmdb:/some/file > file_name_works.tmp
+ $(SHLIB_ENV) ${VALGRIND} MAIL_CONFIG=`pwd` ./$(PROG) \
+ -SVnexport-proxy-proxymap | \
+ diff file_name_works.tmp -
+ @$(CLEANUP_FILES) file_name_works.tmp file_name_works_file
+ @echo PASS file_name_works; echo
+
+ignores_non_dictionary_form: $(PROG)
+ @echo RUN ignores_non_dictionary_form
+ @$(SETUP_FILES)
+ echo "mynetworks = map:/path [123::456]" >> main.cf
+ touch -t 197101010000 main.cf
+ echo map:/path > ignores_non_dictionary_form.tmp
+ $(SHLIB_ENV) ${VALGRIND} MAIL_CONFIG=`pwd` ./$(PROG) \
+ -SVnexport-all-proxymap | diff ignores_non_dictionary_form.tmp -
+ @$(CLEANUP_FILES) ignores_non_dictionary_form.tmp
+ @echo PASS ignores_non_dictionary_form; echo
root_tests:
proxymap.o: ../../include/myflock.h
proxymap.o: ../../include/mymalloc.h
proxymap.o: ../../include/nvtable.h
+proxymap.o: ../../include/readlline.h
proxymap.o: ../../include/stringops.h
proxymap.o: ../../include/sys_defs.h
proxymap.o: ../../include/vbuf.h
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
+#include <ctype.h>
/* Utility library. */
#include <dict.h>
#include <dict_pipe.h>
#include <dict_union.h>
+#include <readlline.h>
/* Global library. */
char *var_verify_map;
char *var_smtpd_snd_auth_maps;
char *var_psc_cache_map;
+char *var_smtpd_forbid_cmds;
+char *var_psc_forbid_cmds;
char *var_proxy_read_maps;
char *var_proxy_write_maps;
+char *var_rest_classes;
/*
* The pre-approved, pre-parsed list of maps.
*/
static int proxy_writer;
+ /*
+ * Do we implement the proxymap or proxywrite service, or do we export
+ * type:table information to stdout? The exported info is without the proxy:
+ * prefix.
+ *
+ * EXPORT_PROXY lists only tables that proxied.
+ *
+ * EXPORT_ALL lists both proxied and non-proxied tables that Postfix is
+ * configured to use. This requires that proxy_read_maps and
+ * proxy_write_maps list all tables, including those that will never be
+ * proxied.
+ */
+static enum {
+ EXPORT_NONE, /* Answer requests */
+ EXPORT_PROXY, /* Export proxied type:table */
+ EXPORT_ALL, /* Export all type:table */
+} proxy_exporter = EXPORT_NONE;
+
/*
* Silly little macros.
*/
return (dict_open(map, open_flags, dict_flags));
}
+static void authorize_file_content(const char *);
+
/* authorize_proxied_maps - recursively authorize maps */
static void authorize_proxied_maps(char *bp)
if ((type_name = mystrtokq(&type_name, sep, parens)) == 0)
continue;
}
+ /* Recurse into /file/name. */
+ if (*type_name == '/') {
+ authorize_file_content(type_name);
+ continue;
+ }
+ /* Skip [ipv6::addr] and other non-table forms. */
+ if (!ISALNUM(*type_name) || strchr(type_name, ':') == 0)
+ continue;
/* Recurse into nested map (pipemap, unionmap). */
if ((nested_info = get_nested_dict_name(type_name)) != 0) {
char *err;
authorize_proxied_maps(nested_info);
continue;
}
- if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
- continue;
- do {
- type_name += PROXY_COLON_LEN;
- } while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN));
+ switch (proxy_exporter) {
+ case EXPORT_NONE:
+ case EXPORT_PROXY:
+ if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
+ continue;
+ do {
+ type_name += PROXY_COLON_LEN;
+ } while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN));
+ break;
+ case EXPORT_ALL:
+ while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
+ type_name += PROXY_COLON_LEN;
+ break;
+ }
if (strchr(type_name, ':') != 0
&& htable_locate(proxy_auth_maps, type_name) == 0) {
(void) htable_enter(proxy_auth_maps, type_name, (void *) 0);
}
}
-/* post_jail_init - initialization after privilege drop */
+/* authorize_file_content - authorize table references in /file/name */
+
+static void authorize_file_content(const char *file_name)
+{
+ VSTREAM *fp;
+ VSTRING *buf;
+
+ if ((fp = vstream_fopen(file_name, O_RDONLY, 0)) == 0) {
+ msg_warn("open %s: %m", file_name);
+ return;
+ }
+ buf = vstring_alloc(100);
+ while (readlline(buf, fp, (int *) 0))
+ authorize_proxied_maps(STR(buf));
+ if (vstream_ferror(fp))
+ msg_warn("read %s: %m", file_name);
+ vstring_free(buf);
+ vstream_fclose(fp);
+}
+
+/* authorize_rest_classes - scan restriction classes for table references */
+
+static void authorize_rest_classes(const char *rest_classes)
+{
+ char *bp, *saved_rest_classes, *class_name, *saved_class_val;
+ const char *class_val;
+
+ bp = saved_rest_classes = mystrdup(rest_classes);
+ while ((class_name = mystrtok(&bp, CHARS_COMMA_SP)) != 0) {
+ if ((class_val = mail_conf_lookup_eval(class_name)) != 0) {
+ saved_class_val = mystrdup(class_val);
+ authorize_proxied_maps(saved_class_val);
+ myfree(saved_class_val);
+ }
+ }
+ myfree(saved_rest_classes);
+}
+
+/* cmp_ht_key - qsort helper for ht_info pointer array */
+
+static int cmp_ht_key(const void *a, const void *b)
+{
+ HTABLE_INFO **ap = (HTABLE_INFO **) a;
+ HTABLE_INFO **bp = (HTABLE_INFO **) b;
+
+ return (strcmp(ap[0]->key, bp[0]->key));
+}
+
+/* export_type_name_entries - serialize table authorizations to stdout */
-static void post_jail_init(char *service_name, char **unused_argv)
+static void export_type_name_entries(void)
+{
+ HTABLE_INFO **ht_info, **ht;
+
+ ht_info = htable_list(proxy_auth_maps);
+ qsort((void *) ht_info, proxy_auth_maps->used, sizeof(*ht_info),
+ cmp_ht_key);
+ for (ht = ht_info; *ht; ht++)
+ vstream_printf("%s\n", ht[0]->key);
+ vstream_fflush(VSTREAM_OUT);
+ myfree(ht_info);
+}
+
+/* pre_jail_init - initialization after privilege drop */
+
+static void pre_jail_init(char *service_name, char **unused_argv)
{
char *saved_filter;
+ /*
+ * Is this a request for service, or export?
+ */
+ if (strncmp(service_name, "export-all-", 11) == 0) {
+ proxy_exporter = EXPORT_ALL;
+ service_name += 11;
+ } else if (strncmp(service_name, "export-proxy-", 13) == 0) {
+ proxy_exporter = EXPORT_PROXY;
+ service_name += 13;
+ }
+
/*
* Are we proxy writer?
*/
authorize_proxied_maps(saved_filter);
myfree(saved_filter);
+ /*
+ * Authorize tables in restriction_classes.
+ */
+ if (proxy_writer == 0 && *var_rest_classes)
+ authorize_rest_classes(var_rest_classes);
+
+ /*
+ * If run as exporter, serialize the authorization table to stdout.
+ */
+ if (proxy_exporter != EXPORT_NONE) {
+ export_type_name_entries();
+ exit(0);
+ }
+
/*
* Never, ever, get killed by a master signal, as that could corrupt a
* persistent database when we're in the middle of an update.
int main(int argc, char **argv)
{
+
+ /*
+ * Respect the proxy_read_maps and proxy_write_maps dependency graphs.
+ * First, initialize the parameters that specify tables in their
+ * non-empty default values, then initialize the parameters that depend
+ * on those parameters, and so on. Only at the end initialize
+ * proxy_read_maps and proxy_write_maps.
+ *
+ * TODO(wietse) remove parameters below that have empty default values. It
+ * is sufficient to list their $name in the proxy_*_maps default values.
+ * The list below should be checked with a tool that runs during
+ * pre-release-checks.
+ *
+ * List parameters even if their defaults do not specify proxied queries (as
+ * of Postfix 3.11, only local_recipient_maps does that). The goal is to
+ * implement an authoritative source of truth tat covers all Postfix
+ * table lookups.
+ */
static const CONFIG_STR_TABLE str_table[] = {
+ /* Dependencies of proxy_read_maps. */
VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0,
- VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0,
VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0,
+ VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0,
+ VAR_PSC_FORBID_CMDS, DEF_PSC_FORBID_CMDS, &var_psc_forbid_cmds, 0, 0,
+ /* Dependencies of proxy_write_maps. */
+ VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0,
VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0,
/* The following two must be last for $mapname to work as expected. */
VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0,
VAR_PROXY_WRITE_MAPS, DEF_PROXY_WRITE_MAPS, &var_proxy_write_maps, 0, 0,
+ /* Settings that don't affect or depend on proxy_read/write_maps. */
+ VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0,
0,
};
*/
multi_server_main(argc, argv, proxymap_service,
CA_MAIL_SERVER_STR_TABLE(str_table),
- CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
CA_MAIL_SERVER_POST_ACCEPT(post_accept),
/* XXX CA_MAIL_SERVER_SOLITARY if proxywrite */