(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.0, 2022-09-16] — postfix protocol policy (RFC 6647 graylisting) server


s-postgray [options]
[options] −−shutdown
[options] −−startup
[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. This program aims in being fast and secure: 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.

This program is intented to be started via the postfix(1) spawn(8) daemon, the single-instance server is managed internally. The server lifetime is configurable, by (compile-time) default it will terminate itself after some time without any client connection. The server can be started directly via −−startup[33] , but −−server-timeout[31] is ignored in this mode. Sending the server a ‘HUP’ signal will re-evaluate the configuration (may result in program panic if a file disappeared or memory failures occur), sending it ‘USR2’ will save the graylist database, whereas sending ‘USR1’ will log some statistics. A synchronized server −−shutdown[32] can be enforced, or an asynchronous termination by sending the server a ‘TERM’ signal.

As recommendet by RFC 6647 messages are identified by their recipient / sender / client_address value triple; in −−focus-sender[18] 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[26] used to signal graylisting is counted against ‘smtpd_hard_error_limit’ and ‘smtpd_soft_error_limit’ parameters (−−focus-sender[18] mode can 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 =
# permit_inet_interfaces, OR
#RELAY reject_authenticated_sender_login_mismatch,
#RELAY permit_tls_clientcerts,
#[RELAY] permit_sasl_authenticated,
# Total no-goes database, eg: qq.com reject
# check_sender_access lmdb:/etc/postfix/sender_restrict,
# 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,


Options may be given in short or long form, −−resource-file[29] 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[35] 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[8] ).

−−allow-file path, −A path

Load a file of whitelist entries in the syntax described for −−allow[11] from within the server or −−test-mode[35] . 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.

−−allow spec, −a spec

Add a domain name or an IPv4 or IPv6 internet address, optionally in RFC 1519 CIDR notation with network mask, to the list of allowed clients (whitelist) that are accepted with −−msg-allow[24] . 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[8] and −−6-mask[9] 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
# instead of 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[14] 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[36] 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[10] from within the server or −−test-mode[35] .

−−block spec, −b spec

Add a blacklist entry, syntax identical to −−allow[11] . Entries are rejected with −−msg-block[25] . (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[23] ; 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[14] .

−−delay-min mins, −d mins

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

−−delay-progressive , −p

If set each counted retry doubles −−delay-min[16] for the next one until −−count[14] is reached.

−−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[24] =permit’ and ‘−−msg-defer[26] =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[14] =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”).

−−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[22] may render rebalancing undesired.

−−gc-timeout mins, −g mins

Duration until a DB entry is seen as unused and 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.

−−help , −h

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

−−limit no, −L no

Number of DB entries until new ones are not handled, effectively turning them into accepted graylist members. Data is stored compact, and the size depends on actual email (recipient /) sender / client_address value data, but accounting say 128 bytes per entry may be a guideline. In addition the dictionary head table resides in one large contiguous memory chunk, accounting 1 MB per 10000000 entries may be proper.

−−limit-delay no, −l no

Smaller than −−limit[22] , 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[22] , −−limit-delay[23] and −−gc-timeout[20] it should be impossible to reach the graylist bypass limit. Not honoured if for a 0 −−count[14] .

−−msg-allow msg, −˜ msg

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

−−msg-block msg, ! msg

Like −−msg-allow[24] , but for −−block[13] ed value combinations. Defaults to ‘REJECT’, but ‘5.7.1 Please go away’ seems reasonable.

−−msg-defer msg, −m msg

Like −−msg-allow[24] , 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
X.1.XXX Addressing Statu
# [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. Remarks: the old −−defer-msg option was a misnomer and is obsolete.

−−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. The server parses the configuration a second time, and from within −−store-path[34] !

−−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. 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 the server, for usage in daemon 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.

−−store-path path, −s path

An accessible path to which S-postgray will change, and where the DB and the server/client communication socket will be created. The directory should only be accessible by the s-postgray driving user (and group), 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[10] , −−allow[11] , −−block-file[12] and −−block[13] 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.

−−verbose , −v

Increase log verbosity (two levels). May be of interest to improve the configuration, for example −−allow[11] and −−block[13] 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 - 2022, Steffen Nurpmeso <steffen@sdaoden.eu>
@(#)code-postgray.html-w42 1.4 2022-04-04T21:32:02+0000