Transport mapping doesn't work for extensions

Issue #5 new
Jonne Haß created an issue

If a recipient_delimiter is set the transport lookup for a user specific transport fails.

From virtual(8)

TABLE SEARCH ORDER

Normally, a lookup table is specified as a text file that serves as input to the postmap(1) command. The result, an indexed file in dbm or db format, is used for fast searching by the mail system.

The search order is as follows. The search stops upon the first successful lookup.

When the recipient has an optional address extension the user+extension@domain.tld address is looked up first.

With Postfix versions before 2.1, the optional address extension is always ignored.

The user@domain.tld address, without address extension, is looked up next.

Finally, the recipient @domain is looked up.

When the table is provided via other means such as NIS, LDAP or SQL, the same lookups are done as for ordinary indexed files.

Now does the current postfix_transport_map function return the domain transport if the local part doesn't match a user record. So the lookup for the extension already returns the domain default, but what we want is the user specific value.

Removing the the fallback from the function in order to rely on the lookup order described above doesn't work either, since (from pgsql_table(5))

%u When the input key is an address of the form user@domain, %u is replaced by the SQL quoted local part of the address. Otherwise, %u is replaced by the entire search string. If the localpart is empty, the query is suppressed and returns no results.

%d When the input key is an address of the form user@domain, %d is replaced by the SQL quoted domain part of the address. Otherwise, the query is suppressed and returns no results.

Which means the input @domain will never execute a query, domain defaults no longer work and fallback to the virtual_transport setting.

Another approach could be parsing the local part inside postfix_transport_map, though since recipient_delimiter is configurable that is hardly feasible.

The next approach would be passing %s to postfix_transport_map instead of %u and %d. and do the separation to local part and domain inside it. The gotcha here is that the docs are a bit misleading, example.org is passed as key, not @example.org. It's what I'm currently using, here's the modified function:

CREATE OR REPLACE FUNCTION public.postfix_transport_map(recipient character varying)
 RETURNS SETOF recipient_transport
 LANGUAGE plpgsql
 STABLE STRICT
AS $function$
    DECLARE
        record recipient_transport;
        localpart varchar(320) := NULLIF(split_part(recipient, '@', 1), recipient);
        the_domain varchar(320) := COALESCE(NULLIF(split_part(recipient, '@', 2), ''), recipient);
        did bigint := (SELECT gid FROM domain_name WHERE domainname = the_domain);
        transport_id bigint;
    BEGIN
        IF did IS NULL THEN
            RETURN;
        END IF;

        IF localpart IS NULL THEN
          SELECT tid INTO STRICT transport_id
            FROM domain_data
          WHERE gid = did;
        ELSE
          SELECT tid INTO transport_id
            FROM users
          WHERE gid = did AND local_part = localpart;
        END IF;

        FOR record IN
            SELECT recipient, transport
              FROM transport
             WHERE tid = transport_id
            LOOP
                RETURN NEXT record;
            END LOOP;
        RETURN;
    END;
$function$;

The last approach I see would be passing multiple maps to transport_maps, the first table just looking up user specific transports with %u and %d and the second table just looking up domain specific transports with %s.

Comments (0)

  1. Log in to comment