(Generated by  groff(1))

S-postgray manual

This plain  groff(1) HTML output has only been fixed slightly — i am sorry for false list indentions etc.!

S-postgray [v0.8.3, 2024-06-23] — postfix protocol policy (RFC 6647 graylisting) server


s-postgray [options]
[options] −−shutdown
[options] −−startup
[options] −−status
[options] −−test-mode [options]



A RFC 6647 graylisting postfix(1) policy service. Graylisting defers message acceptance a configurable number of times via a standardized SMTP response (see allow(5), RFC 5321), which does not prevent message delivery from SMTP M(essage) T(ransfer) A(gent)s, but can help against simple spam producing programs.

Client allow (white) and block (black) lists of domain names (exact and wildcard) as well as IPv4 and IPv6 addresses (exact or in CIDR notation) are supported. All the data is stored in main memory, synchronization with backing store is only performed when the server process is started or stopped, and the graylist database which needs to handle network data uses a cryptographically secure hash function. Operating-system dependent best-effort sandboxing is used, optionally −−untamed .

The program is intended to be started via the postfix(1) spawn(8) daemon, which takes care of the correct user and group credentials; It’s single-instance server is then managed automatically, with a configurable lifetime: by (compile-time) default it times out without active client connections. An explicit server −−startup may be enforced: −−server-timeout is ignored in this mode. The server −−status can be queried, and its synchronized −−shutdown can be enforced. The server PID is managed in the synchronized file ‘NAME.pid’ within −−store-path .

Sending the ‘USR2’ signal will save the graylist database, whereas ‘USR1’ logs some statistics. Dependent upon the operating-system sandbox, and −−untamed , sending a ‘HUP’ signal will re-evaluate the configuration, and update the graylist database accordingly; if supported, it may not be possible to change or add file paths (without violating security constraints), and modifying −−limit s can never be hard-reflected by the (already entered) sandbox; Sending ‘TERM’ initiates a server shutdown, and causes active clients to terminate. (The client ignores these signals: pkill(1) may be used.)

As recommendet by RFC 6647 messages are identified by their recipient / sender / client_address value triple; in −−focus-sender mode recipients are ignored (see there). Here is an excessively lengthy but minimal postconf(5) example. (Especially address and DNS hostname checks and verifications are performed before the policy server is involved in the decision process.) Note the ‘DEFER_IF_PERMIT’ −−msg-defer used to signal per-recipient graylisting is counted against ‘smtpd_hard_error_limit’ and ‘smtpd_soft_error_limit’ parameters (−−focus-sender mode will generate only one error per message).

#@ /etc/postfix/master.cf:

postgray unix - n n - - spawn
argv=/usr/libexec/s-postgray -vvR /etc/postgray.rc -c 0

#@ /etc/postfix/main.cf:

default_privs = ANON-USR

# Client connection checks
smtpd_client_restrictions =
# permit_inet_interfaces, OR
#RELAY permit_tls_clientcerts,
#[RELAY] permit_sasl_authenticated,

smtpd_data_restrictions =

smtpd_helo_restrictions =
# permit_inet_interfaces, OR
#RELAY permit_tls_clientcerts,
#[RELAY] permit_sasl_authenticated,

# MAIL FROM Checks
smtpd_sender_restrictions =
#RELAY reject_authenticated_sender_login_mismatch,
# permit_inet_interfaces, OR
#RELAY permit_tls_clientcerts,
#[RELAY] permit_sasl_authenticated,
# Total no-goes database, eg: qq.com reject
# check_sender_access lmdb:/etc/postfix/sender_restrict,
# check_sender_access inline:{$mydomain=reject},
# With --focus-sender only! And --msg-allow=permit
# check_policy_service unix:private/postgray,
#VERIFY(..then) reject_unverified_sender,

smtpd_relay_before_recipient_restrictions = yes

# RCPT TO checks, relay policy
# Local+auth clients may specify any destination domain
smtpd_relay_restrictions =
# permit_inet_interfaces, OR
#RELAY permit_tls_clientcerts,
#[RELAY] permit_sasl_authenticated,

# RCPT TO checks, spam blocking policy
smtpd_recipient_restrictions =
# permit_inet_interfaces, OR
#RELAY permit_tls_clientcerts,
#[RELAY] permit_sasl_authenticated,
# Without --focus-sender only!
check_policy_service unix:private/postgray,
#VERIFY(..then) reject_unverified_sender,
#(VERIFY i would not) reject_unverified_recipient,

smtpd_policy_service_default_action = DUNNO

#@ /etc/s-postgray.rc

store-path /var/lib/pg

delay-min 0
server-timeout 0

# notorious
allow .gmail.com
allow .google.com
allow .outlook.com
allow .yahoo.com
allow .yandex.com
allow .yandex.net

allow .paypal.com
allow .paypal.de


Options may be given in short or long form, −−resource-file s only support the long form, and only a (logical) option subset. The minute limit is 32767 (15-bit), the maximum duration is thus 22 days. Other numbers have a limit of 31-bit (2147483647). −−test-mode performs a dry-run configuration syntax test, and outputs a normalized resource file. In the following DB means database, and GC garbage collection.

−−4-mask mask, −4 mask

IPv4 mask to strip off addresses before match. For example 24 masks all addresses in between and This is desirable since in practice MX farms are used, and/or IP addresses are selected from a pool.

−−6-mask mask, −6 mask

IPv6 mask to strip off addresses before match. Using a mask of 64 seems to be good practice (see −−4-mask ).

−−allow-file path, −A path

Load a file of whitelist entries in the syntax described for −−allow from within the server or −−test-mode . Lines are read as via −−resource-file .

−−allow spec, −a spec

Add a domain name or an IPv4 or IPv6 internet address, optionally in RFC 1519 CIDR notation with network mask (−−test-mode hints incorrect networks), to the list of allowed clients (whitelist) that are accepted with −−msg-allow . Domain names are matched exactly unless the first character is a period ‘.’, in which case the given domain and all its subdomains will match. For IP addresses the global masks −−4-mask and −−6-mask normalize the given address (range) if applicable. All constructs are matched via dictionary, except for CIDR ranges with masks smaller than the global ones, they are matched in the given order.


# This matches d.a.s but also a.b.c.d.a.s

# with --4-mask=24 this really is!

# with --6-mask=64 really 2a03:2880:20:6f06::/64
# --test-mode hints 2a03:2880:20:6f06:c000::/66

# with --6-mask=64 nonetheless 2a03:2880:20:4f00::/56
# This will _not_ be matched by dictionary but in order

If whitelisting is really performed that late in the processing chain it should include all big players and all normally expected endpoints; it may be useful to run for a few days with the special 0 −−count and inspect the log in order to create a whitelist. Some MTAs are picky, so driving for a while with a low count and in −−verbose mode to collect more data before increasing count etc. is worthwhile.

It should be noted that only the two VERP (variable envelope return path addresses) delimiters plus sign ‘+’ and equal sign ‘=’ are understood — mailing list software which chooses the hyphen-minus ‘-’ as a VERP delimiter (ezmlm instances are known which do) make a particularly bad choice because many mailing-lists have a hyphen-minus as a regular part of their name, so no automatic differentiation in between the customized address part and the regular address is possible: such addresses can only be placed in the whitelist, otherwise each and every received message will be graylisted.

−−block-file path, −B path

Load a file of blacklist entries in the syntax described for −−allow-file from within the server or −−test-mode .

−−block spec, −b spec

Add a blacklist entry, syntax identical to −−allow . Entries are rejected with −−msg-block . (Blocking should possibly be done earlier in the processing chain.)

−−count no, −c no

Number of SMTP message delivery retries before it is accepted. The special value 0 will accept messages immediately, and change the behaviour of some other settings, like −−limit-delay ; it may be useful when setting up the configuration and the whitelist. (Once regular usage begins that DB should possibly be removed.)

−−delay-max mins, −D mins

Duration until a message “is no longer a retry”, but interpreted as a new one with a reset −−count .

−−delay-min mins, −d mins

Duration until a message “is a retry”. Those which come sooner do not increment −−count .

−−delay-progressive , −p

If set −−delay-min is multiplied with each counted retry until −−count is reached. This mode asserts that the maximum delay ‘delay-min * count’ is smaller than −−delay-max .

−−focus-sender , −f

By default all of recipient (email address), sender (email address) and client address (IPv4 or IPv6 internet address) are used to identify messages for graylisting purposes. With this focus is on the sender, and the recipient is ignored. postconf(5) can then be changed to perform graylisting in ‘smtpd_sender_restrictions’ instead of ‘smtpd_recipient_restrictions’, for example to guard a following sender address verification; to accomplish this for real ‘−−msg-allow =permit’ and ‘−−msg-defer =DEFER 4.2.0 Service temporarily faded to Gray’ should be set, so that the verification is only reached for graylisted senders that passed the test, and ‘−−count =1’ might be sufficient. This setting cannot be changed at runtime, and it should be ensured all instances use the same one. An existing DB can be reused: the next load removes recipients, so this is one way (DB remains “compatible”).


Use a different GC behaviour: instead of removing −−gc-timeout entries upon the next garbage collection, keep them until the DB excesses −−limit (or memory constraints require DB shrinkage: condition logged).

−−gc-rebalance no, −G no

Number of DB GC runs before rebalancing occurs. Value 0 turns rebalancing off. Rebalancing only affects shrinking of the dictionary table, it is grown automatically as necessary, so a carefully chosen −−limit may render rebalancing undesired.

−−gc-timeout mins, −g mins

Duration until a DB entry is seen as unused and maybe removed. Each time an entry is used the timeout is reset. This timeout is also an indication for how often a GC shall be performed, but GC happens due to circumstances, too. And see −−gc-linger .

−−help , −h

A short help listing (not helpful, instead see −H or −−long-help ).

−−limit no, −L no

Number of DB entries until new ones are not handled, effectively turning them into accepted graylist members. (DB maintenance tries to achieve a maximum of 88 percent fill-level.) Data size depends on actual email (recipient /) sender / client_address value data, but is stored compactly; accounting say 256 bytes per entry seems to be (overly) plenty. There is also a large continuous lookup table memory chunk, accounting 1 MB per 10000000 entries may be proper. When saving file size is soft-limited to 2 GiB (two gigabyte), excess is discarded; if possible a hard limit up to that size via setrlimit(2) sandbox is established at −−startup : runtime hard limit adjustments are not possible.

−−limit-delay no, −l no

Smaller than −−limit , this number describes a limit after which creation of a new (yet unknown) entry is delayed by a one second sleep for throttling purposes. The value 0 disables this feature. By choosing the right settings for −−limit , −−limit-delay and −−gc-timeout it should be impossible to reach the graylist bypass limit. Not honoured for a 0 −−count .

−−msg-allow msg, −˜ msg

A message in access(5) format that is passed to postfix(1) for −−allow ed (recipient /) sender / client_address value combinations. This setting cannot be changed at runtime; there is a length limit. Defaults to ‘DUNNO’, but ‘OK’ or even ‘permit’ seem reasonable.

−−msg-block msg, ! msg

Like −−msg-allow , but for −−block ed value combinations. Defaults to ‘REJECT’, but ‘5.7.1 Please go away’ seems reasonable. This setting cannot be changed at runtime; there is a length limit.

−−msg-defer msg, −m msg

Like −−msg-allow , but used for graylisted value combinations (‘DUNNO’ is used for accepted ones). The default is ‘DEFER_IF_PERMIT 4.2.0 Service temporarily faded to Gray’, of which only ‘DEFER_IF_PERMIT’ is not optional; it uses an RFC 3463 extended status code:

# [4.2.0]
4.X.X Persistent Transient Failure
x.2.X Mailbox Status
X.2.0 Other or undefined mailbox status
# [4.1.7 (postfix during address verification in progress]
x.1.X Addressing Status
x.1.0 Other address status
x.1.7 Bad sender’s mailbox address syntax
# [4.7.1 (seen in wild; less friendly and portable!)]
x.7.X Security or Policy Status
x.7.0 Other or undefined security status
x.7.1 Delivery not authorized, message refused
This is useful only as a permanent error.

If postfix(1) address verification is used in addition, it may be better to use graylisting (maybe second-last and) before it, and return ‘DEFER 4.2.0’ instead, so that the more expensive address verification is performed only when graylisting permits continuation. This setting cannot be changed at runtime; there is a length limit.

−−long-help , −H

A long help listing.

−−once , −o

If given the client part will only process one message. The server process functions as usual.

−−resource-file path, −R path

A configuration file with long options (without double hyphen-minus ‘−−’). Each line forms an entry, leading and trailing whitespace is removed. If the first non-whitespace character is the number-sign ‘#’ the line is a comment and discarded. Empty lines are ignored, other lines can be folded over multiple input lines with a reverse-solidus ‘\’ before the newline: all leading whitespace of the next line is ignored. The server parses the configuration a second time, and from within −−store-path !

# Comment \

−−server-queue no, −q no

The number of concurrent clients a server can handle before accept(2)ing new ones is suspended. This setting cannot be changed at runtime.

−−server-timeout mins, −t mins

Duration until a S-postgray server which does not serve any clients terminates. The value 0 disables auto-termination; a −−startup server only terminates upon request. The statistics dumped on the signal ‘USR1’ are not saved in the DB, they only reflect the current server lifetime.

−−shutdown , −.

Force a running server process to exit. The client synchronizes on the server exit before its terminating. It exits EX_TEMPFAIL (75) when no server is running.

−−startup , −@

Startup a permanent server, to be used in startup scripts for example. Care should be taken to use the same user and group as spawn(8) will use for the client. It exits EX_TEMPFAIL (75) when a server is already running.

−−status , −%

Test whether server is running, exit according status.

−−store-path path, −s path

An accessible path to which S-postgray will change, and where the DB, server PID lock file, and server/client communication socket will be created. The directory should only be accessible by the user (and group) driving s-postgray, no effort is taken to modify umask(2) or path modes (chmod(2))! This setting cannot be changed at runtime.

−−test-mode , −#

Enable test mode: all options are evaluated, including −−allow-file , −−allow , −−block-file and −−block which are normally processed by only the server. Once the command line is worked the content of all white- and blacklists, as well as the final settings of above variables are shown in resource file format. The exit status indicates error. It is highly recommended to use this for configuration checks.

−−untamed , −u

The program always executes in a setrlimit(2) sandbox; dependent upon operating-system and compile-time (‘VAL_OS_SANDBOX’) an even more restricted compartment is entered. In order to be as strict as possible the latter, however, may make false assumptions on the internals of the used C library, causing security violations at runtime (causing the ‘smtpd_policy_service_default_action’ to take s-postgray’s place). This setting skips the latter, and cannot be changed at runtime.

−−verbose , −v

Increase log verbosity (two levels). May be of interest to improve the configuration, for example −−allow and −−block data is logged, as is the time necessary to save and load the DB.


postfix(1), access(5), spawn(8), verify(8)


Steffen Nurpmeso <steffen@sdaoden.eu>.

Copyright (c) 1997 - 2024, Steffen Nurpmeso <steffen@sdaoden.eu>
@(#)code-postgray.html-w42 1.4 2022-04-04T21:32:02+0000