Spamassassin TxRep Reputation plugin e filtro Bayesiano (SQL)

18 agosto 2025 by Roberto Puzzanghera 0 commenti

TxRep was designed as an enhanced replacement of the AutoWhitelist plugin. TxRep, just like AWL, tracks scores of messages previously received, and adjusts the current message score, either by boosting messages from senders who send ham or penalizing senders who have sent spam previously. This not only treats some senders as if they were whitelisted but also treats spammers as if they were blacklisted. Each message from a particular sender adjusts the historical total score which can change them from a spammer if they send non-spam messages. Senders who are considered non-spammers can become treated as spammers if they send messages which appear to be spam. Simpler told TxRep is a score averaging system. It keeps track of the historical average of a sender, and pushes any subsequent mail towards that average.

The Bayesian classifier in Spamassassin tries to identify spam by looking at what are called tokens; words or short character sequences that are commonly found in spam or ham. If I've handed 100 messages to sa-learn that have the phrase penis enlargement and told it that those are all spam, when the 101st message comes in with the words penis and enlargment, the Bayesian classifier will be pretty sure that the new message is spam and will increase the spam score of that message.

In pratica Bayes è un classificatore statistico: guarda i token (parole, header, URL, ecc.) e calcola la probabilità che il messaggio sia spam senza interessarsi di chi manda, ma solo del contenuto.

Invece TxRep tiene traccia della reputazione del mittente (indirizzo email + IP).


Changelog

  • 18 agosto 2025: aggiunte parecchie informazioni alla sezione "Addestramento del sistema bayesiano"

Creazione delle tabelle

> mysql -u root -p

USE spamassassin;
CREATE TABLE txrep (
  username varchar(100) NOT NULL default '',
  email varchar(255) NOT NULL default '',
  ip varchar(40) NOT NULL default '',
  msgcount int(11) NOT NULL default '0',
  totscore float NOT NULL default '0',
  signedby varchar(255) NOT NULL default '',
  last_hit timestamp NOT NULL default CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (username,email,signedby,ip),
  KEY last_hit (last_hit)
) ENGINE=InnoDB;

CREATE TABLE bayes_expire (
  id int(11) NOT NULL default '0',
  runtime int(11) NOT NULL default '0',
  KEY bayes_expire_idx1 (id)
) ENGINE=InnoDB;

CREATE TABLE bayes_global_vars (
  variable varchar(30) NOT NULL default '',
  value varchar(200) NOT NULL default '',
  PRIMARY KEY  (variable)
) ENGINE=InnoDB;

INSERT INTO bayes_global_vars VALUES ('VERSION','3');

CREATE TABLE bayes_seen (
  id int(11) NOT NULL default '0',
  msgid varchar(200) binary NOT NULL default '',
  flag char(1) NOT NULL default '',
  PRIMARY KEY  (id,msgid)
) ENGINE=InnoDB;

CREATE TABLE bayes_token (
  id int(11) NOT NULL default '0',
  token binary(5) NOT NULL default '',
  spam_count int(11) NOT NULL default '0',
  ham_count int(11) NOT NULL default '0',
  atime int(11) NOT NULL default '0',
  PRIMARY KEY  (id, token),
  INDEX bayes_token_idx1 (id, atime)
) ENGINE=InnoDB;

CREATE TABLE bayes_vars (
  id int(11) NOT NULL AUTO_INCREMENT,
  username varchar(200) NOT NULL default '',
  spam_count int(11) NOT NULL default '0',
  ham_count int(11) NOT NULL default '0',
  token_count int(11) NOT NULL default '0',
  last_expire int(11) NOT NULL default '0',
  last_atime_delta int(11) NOT NULL default '0',
  last_expire_reduce int(11) NOT NULL default '0',
  oldest_token_age int(11) NOT NULL default '2147483647',
  newest_token_age int(11) NOT NULL default '0',
  PRIMARY KEY  (id),
  UNIQUE bayes_vars_idx1 (username)
) ENGINE=InnoDB;

Configurazione

Abilitare TxRep editando il file 80-bayes.cf

cat >> /etc/mail/spamassassin/90-sql.cf << EOF

use_bayes 1
bayes_auto_learn 1
use_txrep 1
txrep_factory Mail::SpamAssassin::SQLBasedAddrList
EOF

e il file v341.pre

# TxRep - Reputation database that replaces AWL 
loadplugin Mail::SpamAssassin::Plugin::TxRep

e commentando questa riga nel file /etc/mail/spamassassin/v310.pre:

# loadplugin Mail::SpamAssassin::Plugin::AWL

Creare il file /etc/mail/spamassassin/90-sql.cf e aggiungere i parametri di accesso a mysql:

# spamassassin MySQL user pwd
MYSQL_PWD=xxxxxxx
cat >> /etc/mail/spamassassin/90-sql.cf << __EOF__

# txRep
use_txrep 1 
txrep_autolearn 2
txrep_factory                   Mail::SpamAssassin::SQLBasedAddrList
user_awl_dsn                    DBI:mysql:spamassassin:localhost
user_awl_sql_username           spamassassin
user_awl_sql_password           ${MYSQL_PWD}
user_awl_sql_table              txrep

# bayesian
use_bayes 1 
bayes_auto_learn 1
bayes_store_module              Mail::SpamAssassin::BayesStore::MySQL
bayes_sql_dsn                   DBI:mysql:spamassassin:localhost
bayes_sql_username              spamassassin
bayes_sql_password              ${MYSQL_PWD}
# increasing bayes score for 99% and 99.9% probability  
#score BAYES_99  4.5 
#score BAYES_999 0.5 
# if bayes declared it as spam do not use other filters and discard the msg to speed up the sa process 
#shortcircuit BAYES_99           spam
#shortcircuit BAYES_00           ham
__EOF__

Una volta che il sistema bayesiano di apprendimento sarà giunto a un buon punto di efficienza, tale da meritarsi una certa fiducia da parte nostra, potrebbe essere conveniente aumentare il suo score a diposizione, che è di 3.5 per una probabilità di spam da 99 a 100% e di 0.2 per una probabilità di spam da 99.9 a 100% (punteggi che si vanno a sommare). Per esempio si potrà porre qualcosa del genere nel nostro local.cf:

score BAYES_99 4.5 
score BAYES_999 0.5

Test

Per testare il funzionamento del sistema di "addestramento" (learning) salvare un messaggio spam in formato non elaborato (raw per intenderci) nel file spam.txt e lanciare sa-learn in questo modo (supponendo che postmaster@yourdomain.tld sia l'indirizzo email del destinatario)

sa-learn --debug --spam --username=user@domain.tld spam.txt

Addestramento del sistema bayesiano

Il classificatore bayesiano può assegnare un punteggio che funga da indice di quanto un dato messaggio sia da considerarsi spam o meno solo se ha già processato almeno 200 messaggi di spam e altrettanti di non spam (ham). E' quindi il momento di addestrare il sistema dandogli in pasto una cartella dove vi sono almeno 200 messaggi (preferibilmente molti di più e circa in egual numero tra ham e spam) che siamo sicuri siano solo spam e un altra di solo ham.

Lanciamo sa-learn. Per la mailbox con lo spam:

sa-learn --showdots --username=user@domain.tld --spam spam-directory/*

E per la mailbox con ham:

sa-learn --showdots --username=user@domain.tld --ham ham-directory/*

E' importante fare entrambe le cose.

Nei comandi precedenti è indicato l'utente al quale si riferisce l'addestramento (esso va a finire nella colonna username della tabella bayes_vars). Infatti è necessario ripetere il processo per ogni utente. Se si amministra un piccolo server di utenti affini (una famiglia o una piccola azienda, per esempio) o di utenti molto poco attivi, si può decidere di utilizzare un database unico. In questo caso è necessario aggiungere la seguente impostazione a spamassassin

bayes_sql_override_username   spamd

dove spamd sarebbe il nome di un utente fittizio, che si può anche rimpiazzare con un altro nome.

Quando gli utenti sono nuovi o poco attivi si può allenare Bayes con degli archivi pubblici come ad esempio:

Quando Bayes sembra non dare buoni risultati è necessario ispezionare o anche ricreare il database. Ecco come vedere i dati presenti nel database per un certo utente:

# sa-learn --username=user@domain.tld --dump magic 
0.000          0          3          0  non-token data: bayes db version
0.000          0      1403          0  non-token data: nspam
0.000          0      35828          0  non-token data: nham
0.000          0     131377          0  non-token data: ntokens
0.000          0 1752483848          0  non-token data: oldest atime
0.000          0 1755514834          0  non-token data: newest atime
0.000          0          0          0  non-token data: last journal sync atime
0.000          0 1755209886          0  non-token data: last expiry atime
0.000          0    2764800          0  non-token data: last expire atime delta
0.000          0      25694          0  non-token data: last expire reduction count

dove nspam e nham sono il numero di token che sono considerati rispettivamente spam e ham.

Si può anche vedere la probabilità che un dato token sia classificato come spam nel seguente modo insieme al numero di email spam con quel token (seconda colonna) e al numero di messaggi ham con quel token (terza colonna):

# sa-learn --username=user@domain.tld --dump magic
0.001          0         18 1734626230  0c3a0e5670
0.004          0          4 1625134245  d86d9aa596
0.016          1          1 1745383124  cbeff1a8d7
0.003          0          6 1737832208  5eeffebb61
0.016          0          1 1619626749  6349b16724
0.016          1          1 1632300906  44e5d3d677
0.016          0          1 1697654287  a113ad81e7
0.016          0          1 1711770221  7f1a642f1d

Nel caso si abbiano sospetti che Bayes sia stato addestrato male (per esempio si ottiene il tag BAYES_00 in corrispondenza di un palese spam, come se si fosse addestrato Bayes invertendo ham e spam) si può ripulire il database di un utente in questo modo, prima di procedere nuovamente al suo addestramento:

sa-learn --username=user@domain.tld --clear

Si può controllare come viene classificato un dato messaggio di spam (spam.txt nell'esempio sotto) in questo modo:

# spamc -r < spam.txt

pts rule name              description
---- ---------------------- --------------------------------------------------
0.7 SPF_SOFTFAIL           SPF: sender does not match SPF record (softfail)
4.5 BAYES_99               BODY: Bayes spam probability is 99 to 100%
                           [score: 1.0000]
0.5 BAYES_999              BODY: Bayes spam probability is 99.9 to 100%
                           [score: 1.0000]
-0.0 NO_RELAYS              Informational: message was not relayed via SMTP
0.1 URI_HEX                URI: URI hostname has long hexadecimal sequence
0.0 HTML_FONT_LOW_CONTRAST BODY: HTML font color similar or identical to
                           background
0.0 T_MXG_EMAIL_FRAG       BODY: URI with email in fragment
0.0 HTML_MESSAGE           BODY: HTML included in message
0.0 PDS_FROM_NAME_TO_DOMAIN From:name looks like To:domain
0.0 PDS_FRNOM_TODOM_NAKED_TO Naked to From name equals to Domain
0.0 TO_NO_BRKTS_HTML_IMG   To: lacks brackets and HTML and one image

Quando si ottiene BAYES_00 con un messaggio di spam è segno che il processo di addestramento non è stato fatto utilizzando cartelle con puro spam/ham ed è necessario ripeterlo.

Manutenzione

La tabella txrep andrà ovviamente a riempirsi di record con il passare dei giorni, a una velocità che dipende dal traffico del vostro mail server con un conseguente e progressivo deterioramento della velocità delle query sql. La maggior parte dei records rappresentano degli eventi di spam isolati, che raramente si ripresenteranno e sarai d'accordo che potrebbe essere più conveniente cancellare periodicamente questi record, magari una volta al mese o una volta alla settimana, a seconda del carico di lavoro del mail server.

Creiamo quindi un file che racchiude la query che deve cancellare questi record. E' necessario personalizzarlo inserendo l'account MySQL per spamassassin (naturalmente questo account deve avere accesso al DB "spamassassin" sia dall'IP del mail server che da quello di apache (userprefs via Roundcube che da localhost):

# spamassassin MySQL user pwd
MYSQL_PWD=xxxxxxx

cat > /usr/local/bin/txrep_purge.sh << __EOF__ 
#!/bin/sh 
/usr/bin/mysql -u spamassassin -p"sa-pwd" -e "USE spamassassin; DELETE FROM txrep WHERE last_hit <= (now() - INTERVAL 120 day);" 
exit 0 
__EOF__

chown root:mysql /usr/local/bin/txrep_purge.sh 
chmod ug+x /usr/local/bin/txrep_purge.sh 
chmod o-rwx /usr/local/bin/txrep_purge.sh

Quindi "spamassassin" è l'utente mysql e "[password]" è la password. Non aggiungere spazi dopo l'ìopzione -p se non si vuole che il programma presenti la richiesta di digitazione della password, cosa non voluta dato che il tutto deve essere lanciato da un cronjob.

Infine aggiungere il cronjob, ad esempio:

cat >> /etc/cron.d/qmail << EOF
# txrep
1 1 * 25 * /usr/local/bin/txrep_purge.sh >> /var/log/cron​
EOF

Aggiungi un commento

qmail notes

Pay me a coffee:

PayPal - The safer, easier way to pay online.

LXC scripts
Other contents
Guide per gli utenti