How to map domain and address aliases to username/mailbox account for per-user spamassassin bayes filtering?

Tobias Franzén lists.zxinn at otaking.se
Thu Apr 30 02:03:24 CEST 2015


Hello,

Today I constructed a SELECT query to extract a mapping between actual 
username, and alias name, from the postfixadmin database.
I use this to map recipient with actual users, and this part appears to 
be working.

For reference, and those who are interested:
$sql_select_policy = 'SELECT aliasmap_all.username AS sa_username FROM
   (SELECT aggregate.target_username AS username,
CONCAT(aggregate.recipient_user,"@",aliasmap_domain.alias_domain) AS 
recipient
    FROM
     (SELECT m.username AS target_username,
             m.local_part AS recipient_user,
             m.domain AS recipient_domain
      FROM mailbox AS m
      WHERE m.active=1
      UNION
      SELECT m.username AS target_username,
             SUBSTRING_INDEX(a.address,"@",1) AS recipient_user,
             SUBSTRING_INDEX(a.address,"@",-1) AS recipient_domain
      FROM mailbox AS m, alias AS a
      WHERE a.goto=m.username AND m.active=1 AND a.active=1
     ) AS aggregate
   LEFT JOIN
     (SELECT SUBSTRING(a1.goto,2) AS recipient_domain,
             SUBSTRING(a1.address,2) AS alias_domain
      FROM alias AS a1
      WHERE a1.active=1 AND SUBSTRING(a1.address,1,1)="@"
      UNION
      SELECT d1.domain AS recipient_domain,
             d1.domain AS alias_domain
      FROM domain AS d1
      WHERE d1.active=1 AND d1.backupmx=0
      UNION
      SELECT d2.target_domain AS recipient_domain,
             d2.alias_domain AS alias_domain
      FROM alias_domain AS d2
      WHERE d2.active=1
     ) AS aliasmap_domain
     ON aliasmap_domain.recipient_domain=aggregate.recipient_domain
   ) AS aliasmap_all
WHERE recipient IN (%k)';


The following is from a mail filtered for the hostmaster alias, pointing 
to the actual final destination "mailbox" (in this example).

Apr 30 01:21:04.961 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) lookup_sql_field(sa_userconf) rec=0, "hostmaster at example.com" 
result: undef
Apr 30 01:21:04.961 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) lookup_sql_field, no such fields: sa_userconf
Apr 30 01:21:04.961 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) query_keys: cached hostmaster at example.com
Apr 30 01:21:04.961 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) lookup_hash(hostmaster at example.com) matches key ".", result=sql:
Apr 30 01:21:04.961 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) lookup [sa_userconf] => true,  "hostmaster at example.com" 
matches, result="sql:", matching_key="."
Apr 30 01:21:04.961 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) lookup_sql_field(sa_username) rec=0, "hostmaster at example.com" 
result: "mailbox at example.com"
Apr 30 01:21:04.961 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) lookup [sa_username] => true,  "hostmaster at example.com" 
matches, result="mailbox at example.com", matching_key="/cached/"
Apr 30 01:21:04.962 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) SA user config: "sql:mailbox at example.com", username: 
"mailbox at example.com", 0, (0)hostmaster at example.com
Apr 30 01:21:04.962 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) SA user config: saving SA user config
Apr 30 01:21:04.962 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) SA dbg: config: copying current conf to backup
Apr 30 01:21:05.001 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) loading SA user config from SQL mailbox at example.com
Apr 30 01:21:05.002 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) SA dbg: config: Conf::SQL: executing SQL: SELECT preference, 
value FROM sauserprefs WHERE username = 'mailbox at example.com' OR 
username = '$GLOBAL' OR username = CONCAT('%','example.com') ORDER BY 
username ASC
Apr 30 01:21:05.002 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) SA dbg: config: retrieving prefs for mailbox at example.com from 
SQL server
Apr 30 01:21:05.003 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) SA user config: switching SA (0) username "vscan" -> 
"mailbox at example.com"
Apr 30 01:21:05.003 mail.example.com /usr/local/sbin/amavisd[39930]: 
(39930-01) SA dbg: info: user has changed


I see that my query for user preferences (sauserprefs table) is loaded 
(from secrets.cf.)

However, at least two things indicate that the user preferences are not 
properly loaded, or at least not honored once we return to amavis.

The received test spam mail, after "attach" defang, states a required 
score of either 1.0 or 2.0 (this varies from time to time, with the same 
mail and same configuration):
Content analysis details:   (1001.0 points, 2.0 required)

amavis has overwritten the headers with its own, and shows required=6.2 
($sa_tag2_level_deflt=6.2 in amavisd.conf).
X-Spam-Status: Yes, score=1000.986 tagged_above=-100 required=6.2
     tests=[ALL_TRUSTED=-1, GTUBE=1000, PYZOR_CHECK=1.985,
     TVD_SPACE_RATIO=0.001] autolearn=no autolearn_force=no

Amavis also does not honor the "rewrite_header Subject" from 
spamassassin, nor from my user preferences.

This is confusing.

They seem to be mapped from different amavis-internal lookups, and the 
sa_userconf is ignored?

Apr 30 01:44:08.664 mail.example.com /usr/local/sbin/amavisd[39929]: 
(39929-06) lookup [spam_tag2_level] => true,  "hostmaster at example.com" 
matches, result="6.2", matching_key="(constant:6.2)"
Apr 30 01:44:08.664 mail.example.com /usr/local/sbin/amavisd[39929]: 
(39929-06) lookup [spam_subject_tag2] => true,  "hostmaster at example.com" 
matches, result="*** Spam [_SCORE_] *** ", matching_key="(constant:*** 
Spam [_SCORE_] *** )"
Apr 30 01:44:08.664 mail.example.com /usr/local/sbin/amavisd[39929]: 
(39929-06) headers CLUSTERING: NEW CLUSTER <hostmaster at example.com>: 
score=1000.986, tag=1, tag2=1, local=1, bl=, s=*** Spam [1000.986] *** , 
mangle=attach
... etc ...
Apr 30 01:44:08.665 mail.example.com /usr/local/sbin/amavisd[39929]: 
(39929-06) fwd: scanner provided a header field X-Spam-Status, but we 
preferred our own


Do I need to write additional queries to match these settings?
Will I need to expand my (already large) query in $sql_select_policy?
Where does the "required score" value seen in the "attach" mangler come 
from?

Regards,
Tobias

On 2015-04-29 01:25, Tobias Franzén wrote:
> Hello,
>
> My main goal is to leverage a header like X-Original-To, which I have 
> come to rely on in my old(yet current) mail server, with as little 
> overhead as possible.
>
> I've found two options which work, both with their own drawbacks.
> 1) Split incoming mail into 1-per-recipient and add X-Original-To 
> header for each mail with local recipient.
> This first option relies on before-content-filtering address mapping 
> (directly after adding the header) in postfix, which also allows 
> per-user bayes rules filtering in amavisd-new.
> Drawback: Extreme overhead as amavisd-new is called once each for all 
> local and all external recipient addressed in every incoming mail.
>
> 2) Pass mail through amavisd-new before dividing the mail into 
> 1-per-recipient to properly add X-Original-To header.
> This second option relies on after-content-filtering address mapping.
> Drawback: My current amavisd-new configuration does not know how to 
> apply per-user bayes rules filtering for virtual alias users.
>
>
> postfixadmin
> I use postfixadmin as the backend for my mapping of mailboxes and 
> aliases to store all mail recipient configuration.
> This includes "wildcard" domain alias (@example.net -> @example.com), 
> address alias (user_y at example.com -> user_x at example.com), and 
> mailboxes (user_x at example.com).
> Technically speaking, postfixadmin appears to allows for a 1-to-many 
> relationship for address aliases, however my setup only uses a 1-to-1 
> mapping, which should make what I'm attempting to do much easier.
>
> postfix
> SQL queries to fetch...
> Address alias: SELECT goto FROM alias WHERE address='%s' AND active='1'
> Domain alias: SELECT domain FROM domain WHERE domain='%s' AND 
> backupmx='0' AND active='1'
> Mailbox: SELECT maildir FROM mailbox WHERE username='%s' AND active='1'
>
> spamassassin
> I've configured spamassassin to use per-user bayes rules with mysql 
> backend. Also some per-user preferences using sauserprefs plugin from 
> Roundcube webmail.
>
> amavisd-new
> Integrating clamv and spamassassin filtering. I call on amavisd-new 
> via smtp content filter in postfix master.cf.
> # use userpref SQL connection from SA local.cf for ALL recipients
> @sa_userconf_maps = ({
>   '.' => 'sql:'
> });
>
> # use recipient email address as _USERNAME_ in userpref mySQL table 
> (_TABLE_)
> @sa_username_maps = new_RE (
>   [ qr'^([^@]+ at .*)'i => '${1}' ]
> );
>
>
> Is there a way to expand each email address in amavisd-new to traverse 
> the domain aliases and address aliases to find a match among the 
> mailboxes?
> Perhaps by using a stored procedure in mysql, or views, or virtual 
> tables, or something like that. I'm not yet familiar with these 
> aspects of mysql, and their integration with amavisd-new.
>
> Or can I somehow pass all actual usernames (where domains aliases and 
> address aliases expand to mailbox accounts) for an email from postfix 
> to amavisd-new without performing before-content-filter address 
> mapping in postfix?
>
> Although my usecases would involve very few such occurrences, I prefer 
> to not have the potential extreme overhead associated with executing 
> one instance per recipient in multi-recipient emails (both incoming 
> and outgoing). Although for outgoing I can at least bypass amavisd-new 
> in reasonably good conscience.
>
> Regards,
> Tobias
>



More information about the amavis-users mailing list