Documentation: added LINUX_README sections for logging in
a container, and for systemd logging workarounds. File:
proto/LINUX_README.hmtl.
+
+20220126
+
+ Added defensive logging while waiting for the master daemon
+ to initialize in the background. File: master/master_monitor.c.
+
+20220127
+
+ Cleanup: smtpprox hyperlink. File: proto/FILTER_README.html.
+
+20220128
+
+ Clenaup: standardize on FNV hash, having verified that
+ collisions will depend on the hash seed value, and that the
+ collision rate is low. Files: util/htable.c, util/fnv_hash.[hc].
and sends filtered mail back into Postfix with SMTP on localhost port 10026.
For non-SMTP capable content filtering software, Bennett Todd's SMTP proxy
-implements a nice PERL/SMTP content filtering framework. See: http://
-bent.latency.net/smtpprox/.
+implements a nice PERL/SMTP content filtering framework. See: https://
+web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/.
In the figure below, names followed by a number represent Postfix commands or
daemon programs. See the OVERVIEW document for an introduction to the Postfix
Major changes - configuration
-----------------------------
-[Feature 20210605] Support to inline the content of small cidr,
-pcre, and regexp tables.
+[Feature 20210605] Support to inline the content of small cidr:,
+pcre:, and regexp: tables in Postfix parameter values.
Example:
smtpd_forbidden_commands =
CONNECT GET POST regexp:{{/^[^A-Z]/ Thrash}}
+This is the new smtpd_forbidden_commands default value. It will
+immediately disconnect a remote SMTP client when a command does not
+start with a letter (a-z or A-Z).
+
The basic syntax is:
/etc/postfix/main.cf:
Postfix parses the result as if it is a file in /etc/postfix.
-Note: if a rule contains $, specify $$, to keep Postfix from trying
+Note: if a rule contains $, specify $$ to keep Postfix from trying
to do $name expansion as it evaluates the parameter value.
Major changes - lmdb support
----------------------------
-[Feature 20210605] Overhauled the LMDB client implementation, added
-integration tests for future-proofing.
+[Feature 20210605] Overhauled the LMDB client implementation, and
+added integration tests for future-proofing. There are no visible
+changes in documented behavior.
Major changes - logging
-----------------------
[Feature 20210815] To make the maillog_file feature more useful,
the postlog(1) command is now set-gid postdrop, so that unprivileged
-programs can write logging through the postlogd(8) daemon. This
+programs can write logging through the postlogd(8) daemon. This
required hardening the postlog(1) command against privilege escalation
-attacks.
+attacks. DO NOT turn on the set-gid bit with older postlog(1)
+implementations.
Major changes - pcre2 support
-----------------------------
library is no longer maintained). The Postfix build procedure
automatically detects if the pcre2 library is installed, and if it
is unavailable, the Postfix build procedure will detect if the
-legacy pcre library is installed. See PCRE_README if you need to
+legacy pcre library is installed. See PCRE_README if you need to
build Postfix with a specific library.
Visible differences: some error messages may have a different text,
[Feature 20220102] Postfix programs now randomize the initial state
of in-memory hash tables, to defend against hash collision attacks
-involving a large number of attacker-chosen lookup keys. Presently,
+involving a large number of attacker-chosen lookup keys. Presently,
the only known opportunity for such attacks involves remote SMTP
-client IPv6 addresses in the anvil(8) service. That would require
-making hundreds of short-lived connections per second, because the
-service ages out idle connections after 100s. Other tables with
-attacker-chosen lookup keys are by design limited in size. The fix
-is cheap, and therefore implemented for all Postfix in-memory hash
-tables. Problem reported by Pascal Junod.
+client IPv6 addresses in the anvil(8) service. The attack would
+require making hundreds of short-lived connections per second from
+thousands of different IP addresses, because the anvil(8) service
+drops inactive counters after 100s. Other in-memory hash tables
+with attacker-chosen lookup keys are by design limited in size. The
+fix is cheap, and therefore implemented for all Postfix in-memory
+hash tables. Problem reported by Pascal Junod.
[Feature 20211030] The postqueue command now sanitizes non-printable
-characters in strings before they are formatted as json output or
-legacy output. These outputs are piped into other programs that are
-run by administrative users. This closes a hypothetical opportunity
-for privilege escalation.
+characters (such as newlines) in strings before they are formatted
+as json or as legacy output. These outputs are piped into other
+programs that are run by administrative users. This closes a
+hypothetical opportunity for privilege escalation.
[Feature 20210815] Updated defense against remote clients or servers
-that 'trickle' SMTP or LMTP traffic.
+that 'trickle' SMTP or LMTP traffic, based on per-request deadlines
+and minimum data rates.
+
+Per-request deadlines:
The new {smtpd,smtp,lmtp}_per_request_deadline parameters replace
{smtpd,smtp,lmtp}_per_record_deadline, with backwards compatible
limit the combined time for the Postfix SMTP or LMTP client to send
a request and to receive a response.
-Additionally, the new smtpd_min_data_rate parameter enforces a
-minimum plaintext data transfer rate for DATA and BDAT requests,
-but only when smtpd_per_record_deadline is enabled. After a read
-operation transfers N plaintext bytes (possibly after TLS decryption),
-and after the DATA or BDAT request deadline is decreased by the
-elapsed time of that read operation, the DATA or BDAT request
-deadline is increased by N/smtpd_min_data_rate seconds. However,
-the deadline is never increased beyond the smtpd_timeout value. The
-default minimum data rate is 500 (bytes/second) but is still subject
-to change.
+Minimum data rates:
+
+The new smtpd_min_data_rate parameter enforces a minimum plaintext
+data transfer rate for DATA and BDAT requests, but only when
+smtpd_per_record_deadline is enabled. After a read operation transfers
+N plaintext bytes (possibly after TLS decryption), and after the
+DATA or BDAT request deadline is decreased by the elapsed time of
+that read operation, the DATA or BDAT request deadline is increased
+by N/smtpd_min_data_rate seconds. However, the deadline is never
+increased beyond the smtpd_timeout value. The default minimum data
+rate is 500 (bytes/second) but is still subject to change.
The new {smtp,lmtp}_min_data_rate parameters enforce the corresponding
minimum DATA transfer rates for the Postfix SMTP and LMTP client.
Major changes - tls support
---------------------------
-[Incompat 20220121] Renamed tlsproxy_client_level to
-tlsproxy_client_security_level, and tlsproxy_client_policy to
-tlsproxy_client_policy_maps, for consistent parameter naming
-(tlsproxy_client_xxx corresponds to smtp_tls_xxx).
-
-This change was made with backwards-compatible default settings,
-and with updated documentation.
+[Cleanup 20220121] The new tlsproxy_client_security_level parameter
+replaces tlsproxy_client_level, and the new tlsproxy_client_policy_maps
+parameter replaces tlsproxy_client_policy. This is for consistent
+parameter naming (tlsproxy_client_xxx corresponds to smtp_tls_xxx).
+This change was made with backwards-compatible default settings.
-[Feature 20210926] Postfix was updated to support OpenSSL 3.0.0
+[Feature 20210926] Postfix was updated to support OpenSSL 3.0.0 API
features, and to work around OpenSSL 3.0.0 bit-rot (avoid using
-deprecated features).
+deprecated API features).
Other code health
-----------------
[typos] Typo fixes by raf.
-[pre-release checks] Added pre-release checks to detect new typos,
-and missing entries in postfix-files (some documentation would not
-be installed), missing postlink rules (would result in missing
-hyperlinks in documentation), missing proxy_read_maps entries (the
-proxymap daemon would not automatically authorize some proxied maps).
+[pre-release checks] Added pre-release checks to detect a) new typos
+in documentation and source-code comments, b) missing entries in
+the postfix-files file (some documentation would not be installed),
+c) missing rules in the postlink script (some text would not have
+a hyperlink in documentation), and d) missing map-based $parameter
+names in the proxy_read_maps default value (the proxymap daemon
+would not automatically authorize some proxied maps).
[memory stream] Improved support for memory-based streams made it
-possible to eliminate ad-hoc code that converted tlsproxy(8) protocol
-data to and from serialized form, and to inline small cidr:, pcre:,
-and regexp: maps in main.cf.
+possible to inline small cidr:, pcre:, and regexp: maps in Postfix
+parameter values, and to eliminate some ad-hoc code that converted
+tlsproxy(8) protocol data to or from serialized form.
Disable -DSNAPSHOT and -DNONPROD in makedefs.
+ FILTER_README needs some text on multi-instance implementations,
+ and existing multi-instance references need to be updated.
+
Fix code that still uses "long" for data_size and data_offset,
and that uses "%ld" in sscanf().
<p> For non-SMTP capable content filtering software, Bennett Todd's
SMTP proxy implements a nice PERL/SMTP content filtering framework.
-See: <a href="http://bent.latency.net/smtpprox/">http://bent.latency.net/smtpprox/</a>. </p>
+See: <a href="https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/">https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/</a>. </p>
<p> In the figure below, names followed by a number represent
Postfix commands or daemon programs. See the <a href="OVERVIEW.html">OVERVIEW</a>
<p> For non-SMTP capable content filtering software, Bennett Todd's
SMTP proxy implements a nice PERL/SMTP content filtering framework.
-See: http://bent.latency.net/smtpprox/. </p>
+See: https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/. </p>
<p> In the figure below, names followed by a number represent
Postfix commands or daemon programs. See the OVERVIEW
IPL
yyyy
yyyymmdd
+Incompat
+Junod
+gid
+json
+postlogd
+proxied
+raf
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20220123"
+#define MAIL_RELEASE_DATE "20220128"
#define MAIL_VERSION_NUMBER "3.8"
#ifdef SNAPSHOT
/* 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. */
close(pipes[1]);
switch (timed_read(pipes[0], buf, 1, time_limit, (void *) 0)) {
default:
+ msg_warn("%m while waiting for daemon initialization");
/* The child process still runs, but something is wrong. */
(void) kill(pid, SIGKILL);
/* FALLTHROUGH */
case 0:
/* The child process exited prematurely. */
- msg_fatal("daemon initialization failure");
+ msg_fatal("daemon initialization failure -- see logs for details");
case 1:
/* The child process initialized successfully. */
exit(0);
split_qnameval.c argv_attr_print.c argv_attr_scan.c dict_file.c \
msg_logger.c logwriter.c unix_dgram_connect.c unix_dgram_listen.c \
byte_mask.c known_tcp_ports.c argv_split_at.c dict_stream.c \
- sane_strtol.c
+ sane_strtol.c hash_fnv.c
OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
split_qnameval.o argv_attr_print.o argv_attr_scan.o dict_file.o \
msg_logger.o logwriter.o unix_dgram_connect.o unix_dgram_listen.o \
byte_mask.o known_tcp_ports.o argv_split_at.o dict_stream.o \
- sane_strtol.o
+ sane_strtol.o hash_fnv.o
# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf.
# When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ),
# otherwise it sets the PLUGIN_* macros.
slmdb.h compat_va_copy.h dict_pipe.h dict_random.h \
valid_utf8_hostname.h midna_domain.h dict_union.h dict_inline.h \
check_arg.h argv_attr.h msg_logger.h logwriter.h byte_mask.h \
- known_tcp_ports.h sane_strtol.h
+ known_tcp_ports.h sane_strtol.h hash_fnv.h
TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
stream_test.c dup2_pass_on_exec.c
DEFS = -I. -D$(SYSTYPE)
$(SHLIB_ENV) ${VALGRIND} ./base32_code
dict_thash_test: ../postmap/postmap dict_thash.map dict_thash.in dict_thash.ref
- $(SHLIB_ENV) ../postmap/postmap -fs texthash:dict_thash.map 2>&1 | \
+ $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.map 2>&1 | \
LANG=C sort | diff dict_thash.map -
- $(SHLIB_ENV) ../postmap/postmap -fs texthash:dict_thash.in >dict_thash.tmp 2>&1
+ NORANDOMIZE=1 $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.in >dict_thash.tmp 2>&1
diff dict_thash.ref dict_thash.tmp
rm -f dict_thash.tmp
attr_scan0.o: msg.h
attr_scan0.o: mymalloc.h
attr_scan0.o: nvtable.h
+attr_scan0.o: stringops.h
attr_scan0.o: sys_defs.h
attr_scan0.o: vbuf.h
attr_scan0.o: vstream.h
attr_scan64.o: msg.h
attr_scan64.o: mymalloc.h
attr_scan64.o: nvtable.h
+attr_scan64.o: stringops.h
attr_scan64.o: sys_defs.h
attr_scan64.o: vbuf.h
attr_scan64.o: vstream.h
attr_scan_plain.o: msg.h
attr_scan_plain.o: mymalloc.h
attr_scan_plain.o: nvtable.h
+attr_scan_plain.o: stringops.h
attr_scan_plain.o: sys_defs.h
attr_scan_plain.o: vbuf.h
attr_scan_plain.o: vstream.h
get_hostname.o: mymalloc.h
get_hostname.o: sys_defs.h
get_hostname.o: valid_hostname.h
+hash_fnv.o: hash_fnv.c
+hash_fnv.o: hash_fnv.h
+hash_fnv.o: msg.h
+hash_fnv.o: sys_defs.h
hex_code.o: check_arg.h
hex_code.o: hex_code.c
hex_code.o: hex_code.h
host_port.o: valid_utf8_hostname.h
host_port.o: vbuf.h
host_port.o: vstring.h
+htable.o: hash_fnv.h
htable.o: htable.c
htable.o: htable.h
htable.o: msg.h
./attr_print0: send attr long_number = 1234
./attr_print0: send attr string = whoopee
./attr_print0: send attr data = [data 7 bytes]
-./attr_print0: send attr name foo-name value foo-value
./attr_print0: send attr name bar-name value bar-value
+./attr_print0: send attr name foo-name value foo-value
./attr_print0: send attr long_number = 4321
./attr_print0: send attr protocol = test
./attr_print0: send attr number = 4711
./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator)
./attr_scan0: input attribute name: {
./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
-./attr_scan0: input attribute name: foo-name
-./attr_scan0: input attribute value: foo-value
-./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan0: input attribute name: bar-name
./attr_scan0: input attribute value: bar-value
./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan0: input attribute name: foo-name
+./attr_scan0: input attribute value: foo-value
+./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan0: input attribute name: }
./attr_scan0: unknown_stream: wanted attribute: long_number
./attr_scan0: input attribute name: long_number
long_number 1234
string whoopee
data whoopee
-(hash) foo-name foo-value
(hash) bar-name bar-value
+(hash) foo-name foo-value
long_number 4321
number 4711
long_number 1234
string whoopee
data whoopee
-(hash) foo-name foo-value
(hash) bar-name bar-value
+(hash) foo-name foo-value
return: -1
./attr_print64: send attr long_number = 1234
./attr_print64: send attr string = whoopee
./attr_print64: send attr data = [data 7 bytes]
-./attr_print64: send attr name foo-name value foo-value
./attr_print64: send attr name bar-name value bar-value
+./attr_print64: send attr name foo-name value foo-value
./attr_print64: send attr long_number = 4321
./attr_print64: send attr protocol = test
./attr_print64: send attr number = 4711
./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator)
./attr_scan64: input attribute name: {
./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
-./attr_scan64: input attribute name: foo-name
-./attr_scan64: input attribute value: foo-value
-./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan64: input attribute name: bar-name
./attr_scan64: input attribute value: bar-value
./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan64: input attribute name: foo-name
+./attr_scan64: input attribute value: foo-value
+./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan64: input attribute name: }
./attr_scan64: unknown_stream: wanted attribute: long_number
./attr_scan64: input attribute name: long_number
long_number 1234
string whoopee
data whoopee
-(hash) foo-name foo-value
(hash) bar-name bar-value
+(hash) foo-name foo-value
long_number 4321
number 4711
long_number 1234
string whoopee
data whoopee
-(hash) foo-name foo-value
(hash) bar-name bar-value
+(hash) foo-name foo-value
return: -1
./attr_print_plain: send attr long_number = 1234
./attr_print_plain: send attr string = whoopee
./attr_print_plain: send attr data = [data 7 bytes]
-./attr_print_plain: send attr name foo-name value foo-value
./attr_print_plain: send attr name bar-name value bar-value
+./attr_print_plain: send attr name foo-name value foo-value
./attr_print_plain: send attr long_number = 4321
./attr_print_plain: send attr protocol = test
./attr_print_plain: send attr number = 4711
./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator)
./attr_scan_plain: input attribute name: {
./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
-./attr_scan_plain: input attribute name: foo-name
-./attr_scan_plain: input attribute value: foo-value
-./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan_plain: input attribute name: bar-name
./attr_scan_plain: input attribute value: bar-value
./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
+./attr_scan_plain: input attribute name: foo-name
+./attr_scan_plain: input attribute value: foo-value
+./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}')
./attr_scan_plain: input attribute name: }
./attr_scan_plain: unknown_stream: wanted attribute: long_number
./attr_scan_plain: input attribute name: long_number
long_number 1234
string whoopee
data whoopee
-(hash) foo-name foo-value
(hash) bar-name bar-value
+(hash) foo-name foo-value
long_number 4321
number 4711
long_number 1234
string whoopee
data whoopee
-(hash) foo-name foo-value
(hash) bar-name bar-value
+(hash) foo-name foo-value
return: -1
postmap: warning: dict_thash.in, line 2: record is in "key: value" format; is this an alias file?
postmap: warning: dict_thash.in, line 3: expected format: key whitespace value -- ignoring this line
postmap: warning: dict_thash.in, line 5: duplicate entry: "aaa"
-xxx: yyy
aaa bbb
+xxx: yyy
--- /dev/null
+/*++
+/* NAME
+/* hash_fnv 3
+/* SUMMARY
+/* Fowler/Noll/Vo hash function
+/* SYNOPSIS
+/* #include <hash_fnv.h>
+/*
+/* HASH_FNV_T hash_fnv(
+/* const void *src,
+/* size_t len)
+/* DESCRIPTION
+/* hash_fnv() implements the FNV type 1a hash function.
+/*
+/* To thwart collision attacks, the hash function is seeded
+/* once from /dev/urandom, and if that is unavailable, from
+/* wallclock time, monotonic system clocks, and the process
+/* ID. To disable seeding in tests, specify the NORANDOMIZE
+/* environment variable (the value does not matter).
+/*
+/* By default, the function is modified to avoid a sticky state
+/* where a zero hash value remains zero when the next input
+/* byte value is zero. Compile with -DSTRICT_FNV1A to get the
+/* standard behavior.
+/*
+/* The default HASH_FNV_T result type is uint64_t. When compiled
+/* with -DNO_64_BITS, the result type is uint32_t.
+/* SEE ALSO
+/* http://www.isthe.com/chongo/tech/comp/fnv/index.html
+/* https://softwareengineering.stackexchange.com/questions/49550/
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library
+ */
+#include <sys_defs.h>
+#include <string.h>
+#include <sys/time.h>
+#include <time.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+ /*
+ * Utility library.
+ */
+#include <msg.h>
+#include <hash_fnv.h>
+
+ /*
+ * Application-specific.
+ */
+#ifdef NO_64_BITS
+#define FNV_prime 0x01000193UL
+#define FNV_offset_basis 0x811c9dc5UL
+#else
+#define FNV_prime 0x00000100000001B3ULL
+#define FNV_offset_basis 0xcbf29ce484222325ULL
+#endif
+
+ /*
+ * Fall back to a mix of absolute and time-since-boot information in the
+ * rare case that /dev/urandom is unavailable.
+ */
+#ifdef CLOCK_UPTIME
+#define NON_WALLTIME_CLOCK CLOCK_UPTIME
+#elif defined(CLOCK_BOOTTIME)
+#define NON_WALLTIME_CLOCK CLOCK_BOOTTIME
+#elif defined(CLOCK_MONOTONIC)
+#define NON_WALLTIME_CLOCK CLOCK_MONOTONIC
+#elif defined(CLOCK_HIGHRES)
+#define NON_WALLTIME_CLOCK CLOCK_HIGHRES
+#endif
+
+/* fnv_seed - randomize the hash function */
+
+static HASH_FNV_T fnv_seed(void)
+{
+ HASH_FNV_T result = 0;
+
+ /*
+ * Medium-quality seed, for defenses against local and remote attacks.
+ */
+ int fd;
+ int count;
+
+ if ((fd = open("/dev/urandom", O_RDONLY)) > 0) {
+ count = read(fd, &result, sizeof(result));
+ (void) close(fd);
+ if (count == sizeof(result) && result != 0)
+ return (result);
+ }
+
+ /*
+ * Low-quality seed, for defenses against remote attacks. Based on 1) the
+ * time since boot (good when an attacker knows the program start time
+ * but not the system boot time), and 2) absolute time (good when an
+ * attacker does not know the program start time). Assumes a system with
+ * better than microsecond resolution, and a network stack that does not
+ * leak the time since boot, for example, through TCP or ICMP timestamps.
+ * With those caveats, this seed is good for 20-30 bits of randomness.
+ */
+#ifdef NON_WALLTIME_CLOCK
+ {
+ struct timespec ts;
+
+ if (clock_gettime(NON_WALLTIME_CLOCK, &ts) != 0)
+ msg_fatal("clock_gettime() failed: %m");
+ result += (HASH_FNV_T) ts.tv_sec ^ (HASH_FNV_T) ts.tv_nsec;
+ }
+#elif defined(USE_GETHRTIME)
+ result += gethrtime();
+#endif
+
+#ifdef CLOCK_REALTIME
+ {
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
+ msg_fatal("clock_gettime() failed: %m");
+ result += (HASH_FNV_T) ts.tv_sec ^ (HASH_FNV_T) ts.tv_nsec;
+ }
+#else
+ {
+ struct timeval tv;
+
+ if (GETTIMEOFDAY(&tv) != 0)
+ msg_fatal("gettimeofday() failed: %m");
+ result += (HASH_FNV_T) tv.tv_sec + (HASH_FNV_T) tv.tv_usec;
+ }
+#endif
+ return (result + getpid());
+}
+
+/* hash_fnv - modified FNV 1a hash */
+
+HASH_FNV_T hash_fnv(const void *src, size_t len)
+{
+ static HASH_FNV_T basis = FNV_offset_basis;
+ static int randomize = 1;
+ HASH_FNV_T hash;
+
+ /*
+ * Initialize.
+ */
+ while (randomize) {
+ if (getenv("NORANDOMIZE")) {
+ randomize = 0;
+ } else {
+ basis ^= fnv_seed();
+ if (basis != FNV_offset_basis)
+ randomize = 0;
+ }
+ }
+
+ /*
+ * Add 1 to each input character, to avoid a sticky state (with hash ==
+ * 0, doing "hash ^= 0" and "hash *= FNV_prime" would not change the hash
+ * value.
+ */
+#ifdef STRICT_FNV1A
+#define FNV_NEXT_CHAR(s) ((HASH_FNV_T) * (const unsigned char *) s++)
+#else
+#define FNV_NEXT_CHAR(s) (1 + (HASH_FNV_T) * (const unsigned char *) s++)
+#endif
+
+ hash = basis;
+ while (len-- > 0) {
+ hash ^= FNV_NEXT_CHAR(src);
+ hash *= FNV_prime;
+ }
+ return (hash);
+}
--- /dev/null
+#ifndef _HASH_FNV_H_INCLUDED_
+#define _HASH_FNV_H_INCLUDED_
+
+/*++
+/* NAME
+/* hash_fnv 3h
+/* SUMMARY
+/* Fowler/Noll/Vo hash function
+/* SYNOPSIS
+/* #include <hash_fnv.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Systemn library.
+ */
+#ifndef NO_STDINT_H
+#include <stdint.h>
+#endif
+
+ /*
+ * External interface.
+ */
+#ifdef NO_64_BITS
+#define HASH_FNV_T uint32_t
+#else
+#define HASH_FNV_T uint64_t
+#endif
+
+extern HASH_FNV_T hash_fnv(const void *, size_t);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+#endif
/* values to be remembered are not character pointers, proper casts
/* should be used or the code will not be portable.
/*
-/* To thwart collision attacks, the hash function is seeded
-/* once from /dev/urandom, and if that is unavailable, from
-/* wallclock-time and monotonic system clocks. To disable
-/* seeding for tests, specify NORANDOMIZE in the environment
-/* (the value does not matter).
-/*
/* htable_create() creates a table of the specified size and returns a
/* pointer to the result. The lookup keys are saved with mystrdup().
/* htable_enter() stores a (key, value) pair into the specified table
#include <sys_defs.h>
#include <string.h>
-#include <sys/time.h>
-#include <time.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <unistd.h>
/* Local stuff */
#include "mymalloc.h"
#include "msg.h"
-#include "htable.h"
-
- /*
- * Fall back to a mix of absolute and time-since-boot information in the
- * rare case that /dev/urandom is unavailable.
- */
-#ifdef CLOCK_UPTIME
-#define NON_WALLTIME_CLOCK CLOCK_UPTIME
-#elif defined(CLOCK_BOOTTIME)
-#define NON_WALLTIME_CLOCK CLOCK_BOOTTIME
-#elif defined(CLOCK_MONOTONIC)
-#define NON_WALLTIME_CLOCK CLOCK_MONOTONIC
-#elif defined(CLOCK_HIGHRES)
-#define NON_WALLTIME_CLOCK CLOCK_HIGHRES
+#ifndef NO_HASH_FNV
+#include "hash_fnv.h"
#endif
+#include "htable.h"
-/* htable_seed - randomize the hash function */
-
-static size_t htable_seed(void)
-{
- uint32_t result = 0;
+/* htable_hash - hash a string */
- /*
- * Medium-quality seed, for defenses against local and remote attacks.
- */
- int fd;
- int count;
-
- if ((fd = open("/dev/urandom", O_RDONLY)) > 0) {
- count = read(fd, &result, sizeof(result));
- (void) close(fd);
- if (count == sizeof(result) && result != 0)
- return (result);
- }
+#ifndef NO_HASH_FNV
- /*
- * Low-quality seed, for defenses against remote attacks. Based on 1) the
- * time since boot (good when an attacker knows the program start time
- * but not the system boot time), and 2) absolute time (good when an
- * attacker does not know the program start time). Assumes a system with
- * better than microsecond resolution, and a network stack that does not
- * leak the time since boot, for example, through TCP or ICMP timestamps.
- * With those caveats, this seed is good for 20-30 bits of randomness.
- */
-#ifdef NON_WALLTIME_CLOCK
- {
- struct timespec ts;
+#define htable_hash(s, size) (hash_fnv((s), strlen(s)) % (size))
- if (clock_gettime(NON_WALLTIME_CLOCK, &ts) != 0)
- msg_fatal("clock_gettime() failed: %m");
- result += (size_t) ts.tv_sec ^ (size_t) ts.tv_nsec;
- }
-#elif defined(USE_GETHRTIME)
- result += gethrtime();
-#endif
-
-#ifdef CLOCK_REALTIME
- {
- struct timespec ts;
-
- if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
- msg_fatal("clock_gettime() failed: %m");
- result += (size_t) ts.tv_sec ^ (size_t) ts.tv_nsec;
- }
#else
- {
- struct timeval tv;
-
- if (GETTIMEOFDAY(&tv) != 0)
- msg_fatal("gettimeofday() failed: %m");
- result += (size_t) tv.tv_sec + (size_t) tv.tv_usec;
- }
-#endif
- return (result + getpid());
-}
-
-/* htable_hash - hash a string */
static size_t htable_hash(const char *s, size_t size)
{
- static size_t seed = 0;
- static int randomize = 1;
- size_t h;
+ size_t h = 0;
size_t g;
/*
- * Initialize.
+ * From the "Dragon" book by Aho, Sethi and Ullman.
*/
- while (seed == 0 && randomize) {
- if (getenv("NORANDOMIZE"))
- randomize = 0;
- else
- seed = htable_seed();
- }
- /*
- * Heavily mutilated code based on the "Dragon" book by Aho, Sethi and
- * Ullman. Updated to use a seed, to maintain 32+ bit state, and to make
- * the distance between colliding inputs seed-dependent.
- */
- h = seed;
while (*s) {
- g = h & 0xf0000000;
- h = (h << 4U) ^ (((g >> 28U) + 1) * (*(unsigned const char *) s++) + 1);
+ h = (h << 4U) + *(unsigned const char *) s++;
+ if ((g = (h & 0xf0000000)) != 0) {
+ h ^= (g >> 24U);
+ h ^= g;
+ }
}
return (h % size);
}
+#endif
/* htable_link - insert element into table */
/*
* Load a large number of strings and delete them in a random order.
*/
- msg_verbose = 1;
hash = htable_create(10);
while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF)
htable_enter(hash, vstring_str(buf), CAST_INT_TO_VOID_PTR(count++));