CANONICAL(5)

πŸ‘‘ CANONICAL(5) β€” the Postfix canonical table format, bestie edition πŸ’…

okay so you found the canonical page, literally the Postfix canonical table format, and I'm so proud of you for reading docs. no cap. let's get into it 🫢


✨ NAME ✨

canonical β€” Postfix canonical table format. that's it, that's the name, iconic. 🌸


✨ SYNOPSIS ✨

postmap /etc/postfix/canonical
postmap -q "string" /etc/postfix/canonical
postmap -q - /etc/postfix/canonical <inputfile

✨ DESCRIPTION ✨

So the canonical(5) table is optional, and it's the one that says "when you see this address, actually make it that address" β€” for local AND non-local addresses. πŸ’« The mapping gets used by the cleanup(8) daemon, and it happens before mail gets stored into the queue. And the address mapping is recursive, iykyk, it keeps going.

Normally you write your canonical(5) table as a plain text file, and that file is the input to the postmap(1) command, which makes an indexed file so lookups are FAST. ⚑

Run this:

postmap /etc/postfix/canonical

...to rebuild a default-type indexed file after you change the text file. Or run:

postmap type:/etc/postfix/canonical

...to specify an explicit type.

The default indexed file type is set by the default_database_type parameter. Depending on your platform it might be one of lmdb:, cdb:, hash:, or dbm: β€” and yes, without the trailing ':'. πŸ’€

When the table comes from other means β€” think NIS, LDAP, or SQL β€” the same lookups are done, exactly like ordinary indexed files. Managing those databases? Not Postfix's problem, that's outside the scope of Postfix, so don't @ me.

Alternatively the table can be a regular-expression map where the patterns themselves are regular expressions, or you can point lookups at a TCP-based server. In those cases the lookups work a little differently, described down below under "REGULAR EXPRESSION TABLES" or "TCP-BASED TABLES". πŸ¦‹

By default, the canonical(5) mapping hits both message header addresses (the ones that appear inside messages) and message envelope addresses (like the addresses used in SMTP protocol commands). Which ones get hit is controlled by the canonical_classes parameter. πŸ‘‘

NOTE: Postfix versions 2.2 and later rewrite message headers from remote SMTP clients only if the client matches the local_header_rewrite_clients parameter, or if the remote_header_rewrite_domain configuration parameter specifies a non-empty value. To get the behavior from before Postfix 2.2, specify "local_header_rewrite_clients = static:all". πŸ“

Typically you'd use canonical(5) to swap login names for Firstname.Lastname, or to clean up addresses that some legacy mail system spat out. πŸ™ˆ

And pleaseeee do not confuse canonical(5) mapping with virtual alias support or local aliasing. If you want to change the destination but not the headers, use the virtual(5) or aliases(5) map instead. this is the vibe check, don't mix them up bestie πŸ’…


✨ CASE FOLDING ✨

The search string gets folded to lowercase before the database lookup. But as of Postfix 2.3, the search string is not case folded with database types like regexp: or pcre: β€” because those lookup fields can match both upper and lower case. 🌈


✨ TABLE FORMAT ✨

The input format for the postmap(1) command goes like this:

  • pattern address β€” when pattern matches a mail address, replace it by the corresponding address.
  • blank lines and comments β€” empty lines and whitespace-only lines are ignored, and so are lines whose first non-whitespace character is a '#'. πŸŽ€
  • multi-line text β€” a logical line starts with non-whitespace text. A line that starts with whitespace continues a logical line.

✨ TABLE SEARCH ORDER ✨

When you're looking up from indexed files like DB or DBM, or from networked tables like NIS, LDAP or SQL, every user@domain query produces a sequence of query patterns, like described below. πŸ“

Each query pattern gets sent to each specified lookup table before trying the next query pattern β€” until a match is found.

  • user@domain address β€” replace user@domain by address. This form has the highest precedence. πŸ”₯

This is super useful for cleaning up addresses produced by legacy mail systems. It can also be used to produce Firstname.Lastname style addresses, but see below for a simpler solution. ✨

  • user address β€” replace user@site by address when site is equal to $myorigin, when site is listed in $mydestination, or when it is listed in $inet_interfaces or $proxy_interfaces.

This form is useful for replacing login names by Firstname.Lastname. πŸ’–

  • @domain address β€” replace other addresses in domain by address. This form has the lowest precedence.
Note: @domain is a wild-card. πŸ‘€ When this form is applied to recipient addresses, the Postfix SMTP server accepts mail for any recipient in domain, regardless of whether that recipient exists. This may turn your mail system into a backscatter source: Postfix first accepts mail for non-existent recipients and then tries to return that mail as "undeliverable" to the often forged sender address. πŸ’€

To avoid backscatter with mail for a wild-card domain, replace the wild-card mapping with explicit 1:1 mappings, or add a reject_unverified_recipient restriction for that domain:

smtpd_recipient_restrictions =
    ...
    reject_unauth_destination
    check_recipient_access
        inline:{example.com=reject_unverified_recipient}
unverified_recipient_reject_code = 550

In the above example, Postfix may contact a remote server if the recipient is rewritten to a remote address.


✨ RESULT ADDRESS REWRITING ✨

The lookup result gets address rewriting applied to it:

  • When the result has the form @otherdomain, the result becomes the same user in otherdomain. πŸ¦‹
  • When "append_at_myorigin=yes", append "@$myorigin" to addresses without "@domain".
  • When "append_dot_mydomain=yes", append ".$mydomain" to addresses without ".domain".

✨ ADDRESS EXTENSION ✨

When a mail address localpart contains the optional recipient delimiter (e.g., user+foo@domain), the lookup order becomes: user+foo@domain, user@domain, user+foo, user, and @domain. 🎯

The propagate_unmatched_extensions parameter controls whether an unmatched address extension (+foo) is propagated to the result of the table lookup. πŸ’«


✨ REGULAR EXPRESSION TABLES ✨

This section is all about how the table lookups change when your table is given in the form of regular expressions. For a description of regular expression lookup table syntax, see regexp_table(5) or pcre_table(5). πŸ“–

Each pattern is a regular expression applied to the entire address being looked up. So user@domain mail addresses are not broken up into their user and @domain constituent parts, and user+foo is not broken up into user and foo. nope, we're keeping it whole. 🫢

Patterns are applied in the order as specified in the table, until a pattern is found that matches the search string. ✨

Results are the same as with indexed file lookups, with the extra feature that parenthesized substrings from the pattern can be interpolated as $1, $2 and so on. slay. πŸ’…


✨ TCP-BASED TABLES ✨

This section is all about how the table lookups change when lookups are directed to a TCP-based server. For a description of the TCP client/server lookup protocol, see tcp_table(5). Heads up: this feature is not available up to and including Postfix version 2.4. πŸ“Œ

Each lookup operation uses the entire address once. So user@domain mail addresses are not broken up into their user and @domain constituent parts, and user+foo is not broken up into user and foo. 🧚

Results are the same as with indexed file lookups. ✨


✨ BUGS ✨

The table format does not understand quoting conventions. πŸ™ƒ that's the bug. that's it.


✨ CONFIGURATION PARAMETERS ✨

These are the main.cf parameters that are especially relevant. The text below is only a parameter summary. See postconf(5) for more details, including examples. πŸ‘‘

  • canonical_classes (default: envelope_sender, envelope_recipient, header_sender, header_recipient) β€” what addresses are subject to canonical_maps address mapping. πŸ’–
  • canonical_maps (default: empty) β€” optional address mapping lookup tables for message headers and envelopes.
  • recipient_canonical_maps (default: empty) β€” optional address mapping lookup tables for envelope and header recipient addresses. πŸŽ€
  • sender_canonical_maps (default: empty) β€” optional address mapping lookup tables for envelope and header sender addresses.
  • propagate_unmatched_extensions (default: canonical, virtual) β€” what address lookup tables copy an address extension from the lookup key to the lookup result.

Other parameters of interest: πŸ‘€

  • inet_interfaces (default: all) β€” the local network interface addresses that this mail system receives mail on.
  • local_header_rewrite_clients (default: permit_inet_interfaces) β€” rewrite or add message headers in mail from these clients, updating incomplete addresses with the domain name in $myorigin or $mydomain, and adding missing headers.
  • proxy_interfaces (default: empty) β€” the remote network interface addresses that this mail system receives mail on by way of a proxy or network address translation unit.
  • masquerade_classes (default: envelope_sender, header_sender, header_recipient) β€” what addresses are subject to address masquerading. 🌈
  • masquerade_domains (default: empty) β€” optional list of domains whose subdomain structure will be stripped off in email addresses.
  • masquerade_exceptions (default: empty) β€” optional list of user names that are not subjected to address masquerading, even when their addresses match $masquerade_domains.
  • mydestination (default: $myhostname, localhost.$mydomain, localhost) β€” the list of domains that are delivered via the $local_transport mail delivery transport.
  • myorigin (default: $myhostname) β€” the domain name that locally-posted mail appears to come from, and that locally posted mail is delivered to. πŸ¦‹
  • owner_request_special (default: yes) β€” enable special treatment for owner-listname entries in the aliases(5) file, and don't split owner-listname and listname-request address localparts when the recipient_delimiter is set to "-".
  • remote_header_rewrite_domain (default: empty) β€” rewrite or add message headers in mail from remote clients if the remote_header_rewrite_domain parameter value is non-empty, updating incomplete addresses with the domain specified in the remote_header_rewrite_domain parameter, and adding missing headers. πŸ’œ

✨ SEE ALSO ✨


okay bestie that's everything for part 1, and trust me there is more coming, so don't go anywhere πŸ’–

✨ README FILES ✨

okay bestie, so the docs live somewhere on your system and you're like "where tho πŸ₯Ί" β€” literally just ask postfix, she'll tell you:

  • Run postconf readme_directory to find where the READMEs are chilling.
  • Run postconf html_directory to find the HTML version instead, if that's your vibe.

And then the two big ones you're gonna wanna read:

  • DATABASE_README β€” the whole Postfix lookup table overview, like the ✨lore✨ of lookup tables.
  • ADDRESS_REWRITING_README β€” the address rewriting guide, aka how your addresses get their glow-up. πŸ’…

πŸŽ€ LICENSE πŸŽ€

The Secure Mailer license must be distributed with this software. No skips, no "ooo I'll add it later", no cap. It comes with the thing, periodt. πŸ“œ


πŸ‘‘ AUTHOR(S) πŸ‘‘

shoutout to the main character who wrote all this, Wietse Venema 🫢 β€” here's his whole contact card situation, kept exactly as it's written:

  • Wietse Venema 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
like. two addresses. two eras. iconique behavior honestly 🌈

and that's a wrap on the page, babe πŸ’– the footer down at the bottom is just our girl CANONICAL(5) holding it down, so whenever you see that lil tag you know exactly which page you're reading. stream responsibly, read the READMEs, and go rewrite those addresses πŸŒΈπŸ¦‹βœ¨

πŸ“¦ where this page comes from