smtpd-tables - Man Page

table API for the smtpd daemon

Description

The smtpd(8) daemon provides a Simple Mail Transfer Protocol (SMTPD) implementation, which allows ordinary machines to become Mail eXchangers (MX). Some features that are commonly used by MX, such as querying databases for user credentials, are outside of the scope of SMTP and too complex to fit in smtpd(8).

Because an MX may need to provide these features, smtpd(8) provides an API to implement table(5) backends with a simple text-based protocol.

Design

smtpd-tables are programs that run as unique standalone processes, they do not share smtpd(8) address space. They are executed by smtpd(8) at startup and expected to run in an infinite loop, reading events and queries from standard input and writing responses to standard output. They are not allowed to terminate.

Because smtpd-tables are standalone programs that communicate with smtpd(8), they may run as different users than smtpd(8) and may be written in any language. smtpd-tables must not use blocking I/O, they must support answering asynchronously to smtpd(8).

Protocol

The protocol consist of human-readable lines exchanged between smtpd-tables and smtpd(8).

The protocol begins with a handshake. First, smtpd(8) provides smtpd-tables with general configuration information in the form of key-value lines, terminated by Ql config|ready. For example:

config|smtpd-version|7.5.0
config|protocol|0.1
config|tablename|devs
config|ready

Then, smtpd-tables register the supported services, terminating with Ql register|ready. For example:

register|alias
register|credentials
register|ready

Finally, smtpd(8) can start querying the table. For example:

table|0.1|1713795082.354255|devs|lookup|alias|b72508d|op

The “|” character is used to separate the fields and may only appear verbatim in the last field of the payload, in which case it should be considered a regular character and not a separator. No other field may contain a “|”.

Each request has a common set of fields, followed by some other fields that are operation-specific. The common format consists of a protocol prefix Sq table, the protocol version, the timestamp and the table name. For example:

table|0.1|1713795091.202157|devs

The protocol is inherently asynchronous, so multiple request may be sent without waiting for the table to reply. All the replies have a common prefix, followed by the operation-specific response. The common format consist of a prefix with the operation name in followed by Sq -result, and the unique ID of the request. For example:

lookup-result|b72508d

The list of operations, operation-specific parameters and responses are as follows:

update id

Ask the table to reload its configuration. The result is either Sq ok on success or Sq error and a message upon a failure to do so.

check service id query

Check whether query is present in the table. The result is Sq found if found, Sq not-found if not, or Sq error and a message upon an error.

lookup service id query

Look up a value in the table for given the query. The result is Sq found and the value if found, Sq not-found if not found, or Sq error and a message upon an error.

fetch service id

Fetch the next item from the table, eventually wrapping around. It is only supported for the source and relayhost services. The result is Sq found and the value if found, Sq not-found if the table is empty, or Sq error and a message upon an error.

Each service has a specific format for the result. The exact syntax for the values and eventually the keys are described in table(5). The services and their result format are as follows:

alias

One or more aliases separated by a comma.

auth

Only usable for check. Lookup key is username and cleartext password separated by Sq :.

domain

A domain name.

credentials

The user name, followed by Sq : and the encrypted password as per smtpctl(8) encrypt subcommand.

netaddr

IPv4 and IPv6 address or netmask.

userinfo

The user id, followed by Sq : then the group id, then Sq : and finally the home directory.

source

IPv4 and IPv6 address.

mailaddr

An username, a domain or a full email address.

addrname

Used to map IP addresses to hostnames.

Examples

Assuming the table is called “devs”, here's an example of a failed update transaction:

table|0.1|1713795097.394049|devs|update|478ff0d2
update-result|478ff0d2|error|failed to connect to the database

A check request for the netaddr service for the 192.168.0.7 IPv4 address which is not in the table:

table|0.1|1713795103.314423|devs|check|netaddr|e5862859|192.168.0.7
check-result|e5862859|not-found

A successful lookup request for the userinfo service for the user Sq op:

table|0.1|1713795110.354921|devs|lookup|userinfo|f993c74|op
lookup-result|f993c74|found|1000:1000:/home/op

A series of fetch requests for the source service that wraps around:

table|0.1|1713795116.227321|devs|fetch|source|189bd3ee
lookup-result|189bd3ee|found|192.168.1.7
table|0.1|1713795120.162438|devs|fetch|source|9e4c56d4
lookup-result|9e4c56d4|found|10.0.0.8
table|0.1|1713795122.930928|devs|fetch|source|f2c8b906
lookup-result|f2c8b906|found|192.168.1.7

See Also

smtpd(8)

History

smtpd-tables first appeared in OpenBSD 7.6.