Come configurare un server di posta su linux

Con Postfix, Dovecot, OpenDmarc, SPF, DKIM, SpamAssassin

Aggiornata a giugno 2026

In questa guida ripeterò passo passo le operazioni che ho eseguito per l'installazione di un server di posta su Debian 13.

Diciamo più un memorandum che una guida, ma magari, chissà, tornerà utile a qualcun'altro.

Il mio server di posta attuale dispone di :

Tengo a precisare che nella lista dei comandi utilizzerò aptitude, più per fisima mia personale, ma se volete usare apt va bene ugualmente.

La guida dovrebbe funzionare anche su Ubuntu, se invece siete su alte distro con altri package manager (dnf, pacman, yum, ecc)... Usate un briciolo di immaginazione e convertite i comandi :)

Configurazione DNS

Per prima cosa bisogna impostare i DNS con il record MX, che dice al mondo "la posta per questo dominio consegnatela a questo server". Subito dopo, e qui si scorda quasi sempre, va impostato il PTR (reverse DNS) presso il provider/hosting che ci dà l'ip: è la corrispondenza inversa ip → hostname e deve combaciare col nome del server. Senza un PTR coerente metà dei provider (Gmail in testa) ci sbatte la posta in spam a prescindere da tutto il resto.

Gli altri record DNS (SPF, DKIM, DMARC) li vediamo man mano nelle rispettive sezioni, perché dipendono dalle chiavi e dalle configurazioni che genereremo.

Postfix

Iniziamo con l'installazione di del MTA Postfix. Perché Postfix e non Exim? Sinceramente l'ho scelto per la prima prima volta 15 anni fa semplicemente perché... non mi piaceva il nome Exim XD.

aptitude install -y postfix postfix-psql

Verrà chiesto che tipologia di configurazione, scegliere "Internet Site", successivamente inserire il nome host desiderato, ad esempio example.com

La prima cosa che consiglio di fare è inserire un alias di root al proprio utente: in /etc/aliases:

root:mio_username

E quindi poi lanciare

newaliases

In /etc/postfix/main.cf aggiungere (ci sono già le configurazioni per spamassassin e dovecot):


# Posizione delle chiavi (generate con letsencrypt, acme.sh o altro)
smtpd_tls_cert_file = /path/chiave_dominio.fullchain.pem
smtpd_tls_key_file = /path/chiave_dominio.privkey.pem
# hostname e domini che riceveranno la posta
myhostname = dominio.com
# Domini consegnati localmente (le mailbox vere e proprie su questa macchina)
mydestination = $myhostname, dominio.com, altrodominio.com
# Indirizzi ip considerati "fidati" (possono spedire senza autenticarsi).
# Lasciare quelli locali e aggiungere eventualmente l'ip pubblico del server
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 123.123.123.123 1234:1234:1234:1234::1
# Delimitatori per indirizzi alias (esempio indirizzo+alias@.. = indirzzo@..)
recipient_delimiter = +_
# Dimensione massima di un messaggio in byte (qui 50 MB)
message_size_limit = 52428800

# --- Mailbox virtuali ---
# Domini gestiti come "virtuali", cioè senza un utente di sistema dedicato
virtual_mailbox_domains = virtual.dominio.com
# Cartella base dove vengono salvate le mailbox virtuali
virtual_mailbox_base = /var/mail/vmail
# uid/gid di sistema con cui vengono scritte le mailbox virtuali (utente vmail)
virtual_gid_maps = static:5000
virtual_uid_maps = static:5000
# uid minimo accettato: protezione per non scrivere come root o utenti di sistema
virtual_minimum_uid = 100
# Tabella degli alias virtuali (da indirizzo -> a destinatario reale)
virtual_alias_maps = hash:/etc/postfix/virtual/addresses
# Consegna dei virtuali tramite il socket LMTP di Dovecot
virtual_transport = lmtp:unix:private/dovecot-lmtp

# --- Autenticazione SASL (delegata a Dovecot) ---
# Abilita l'autenticazione SMTP: senza questo nessuno può spedire da fuori
smtpd_sasl_auth_enable = yes
# Pretende STARTTLS prima dell'AUTH: niente credenziali in chiaro (es. sulla 25)
smtpd_tls_auth_only = yes
# Chi gestisce l'autenticazione: Dovecot, così username/password sono uno solo
smtpd_sasl_type = dovecot
# Socket condiviso con Dovecot per verificare le credenziali
smtpd_sasl_path = private/auth
# Formato mailbox per gli utenti locali: Maildir (un file per email)
home_mailbox = Maildir/
# Consegna locale tramite Dovecot LDA (così sieve e quota funzionano)
mailbox_command = /usr/lib/dovecot/dovecot-lda -f "$SENDER" -a "$RECIPIENT"

# --- Milter DKIM / DMARC / SpamAssassin ---
# Versione del protocollo milter (la 6 è quella moderna)
milter_protocol = 6
# Se un milter non risponde, accetta comunque la mail invece di scartarla:
# meglio una mail non firmata che un server che rifiuta tutto perché un
# demone è morto
milter_default_action = accept
# Catena dei milter: ogni mail in arrivo passa per DKIM, DMARC e SpamAssassin
smtpd_milters = local:opendkim/opendkim.sock,local:opendmarc/opendmarc.sock,local:spamass/spamass.sock
# Stessi milter anche per la posta generata localmente (es. firma DKIM in uscita)
non_smtpd_milters = $smtpd_milters
# Timeout (secondi) per la verifica SPF
policyd-spf_time_limit = 3600
# TLS "opportunistico": usalo se disponibile, ma non rifiutare se manca.
# In ricezione (smtpd) e in invio (smtp)
smtpd_tls_security_level = may
smtp_tls_security_level = may
# IPv6 in uscita: vincolalo all'indirizzo che ha il PTR corretto, altrimenti il
# kernel potrebbe usare un IPv6 "privacy" senza reverse -> Gmail lo penalizza
smtp_bind_address6 = 1234:1234:1234:1234::1
# Livello di log del TLS (1 = essenziale, utile per debug dei certificati)
smtpd_tls_loglevel = 1

# Pretende un HELO/EHLO prima di accettare posta (molti bot lo saltano)
smtpd_helo_required = yes
# Disabilita il comando VRFY: impedisce di "indovinare" gli indirizzi validi
disable_vrfy_command = yes

# Restrizioni applicate sul destinatario: vengono valutate in ordine, la prima
# che decide "vince". Mettendo per primi i due permit chi arriva dalle reti
# fidate o è autenticato salta i controlli 
smtpd_recipient_restrictions =
    permit_mynetworks,                 # ok se proviene dalle reti fidate
    permit_sasl_authenticated,         # ok se l'utente si è autenticato
    reject_unknown_recipient_domain,   # dominio destinatario inesistente
    reject_unauth_pipelining,          # client che "spara" comandi senza aspettare
    reject_invalid_hostname,           # HELO/EHLO con hostname malformato
    reject_non_fqdn_sender,            # indirizzo mittente non-FQDN (malformato)
    reject_non_fqdn_recipient,         # indirizzo destinatario non-FQDN
    reject_unknown_sender_domain,      # dominio del mittente inesistente
    reject_unauth_destination,         # niente open relay verso domini altrui
    reject_rbl_client zen.spamhaus.org,# rifiuta ip in blacklist Spamhaus
    check_policy_service unix:private/policyd-spf  # verifica SPF

# Forza almeno TLSv1.2: disabilita i protocolli vecchi e ormai insicuri.
# Vale sia in ricezione (smtpd) sia in invio (smtp)
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# --- Dovecot ---
# Consegna finale tramite LMTP di Dovecot
mailbox_transport = lmtp:unix:private/dovecot-lmtp

# --- SpamAssassin ---
# Tabelle di filtri PCRE su header e corpo (vedi sezione SpamAssassin)
header_checks = pcre:/etc/postfix/header_checks
body_checks = pcre:/etc/postfix/body_checks
# Pulizia degli header in uscita (toglie gli X-Spam interni)
smtp_header_checks = pcre:/etc/postfix/smtp_header_checks

# --- PostScreen: il buttafuori davanti alla porta 25 ---
# Prima di passare a smtpd postscreen osserva il comportamento
# del client e lo confronta con le blacklist: i bot già segnalati
# vengono bloccati all'inizio (va abilitato anche in master.cf)
postscreen_greet_action = enforce
# Blacklist DNS pesate: Spamhaus vale 3, SpamCop 2
postscreen_dnsbl_sites = zen.spamhaus.org*3, bl.spamcop.net*2
# Somma dei pesi oltre la quale scatta l'azione
postscreen_dnsbl_threshold = 3
# Cosa fare oltre soglia: enforce = rifiuta la connessione
postscreen_dnsbl_action = enforce

# --- Limiti per singolo client ---
# Max connessioni al minuto dallo stesso ip
smtpd_client_connection_rate_limit = 20
# Max connessioni simultanee dallo stesso ip
smtpd_client_connection_count_limit = 10
# Dopo un errore rallenta il client di 5s (fa perdere tempo ai bot)
smtpd_error_sleep_time = 5s
# Errori "soft" tollerati prima di iniziare a rallentare
smtpd_soft_error_limit = 3
# Errori oltre i quali si chiude la connessione
smtpd_hard_error_limit = 10

In /etc/postfix/master.cf aggiungere. Qui si definiscono i "servizi", cioè le porte su cui Postfix ascolta e come si comporta su ciascuna. Le righe -o sono override delle impostazioni globali di main.cf, validi solo per quel servizio: la stessa configurazione, ma più stretta o più larga a seconda della porta.

# Porta 25 (smtp): server-to-server, qui arriva la posta. Davanti ci mettiamo
# postscreen: si commenta la riga smtpd e si abilitano postscreen più
# i servizi di supporto (smtpd via "pass", dnsblog per le blacklist, tlsproxy)
#smtp      inet  n       -       -       -       -       smtpd
smtp      inet  n       -       -       -       1       postscreen
smtpd     pass  -       -       -       -       -       smtpd
dnsblog   unix  -       -       -       -       0       dnsblog
tlsproxy  unix  -       -       -       -       0       tlsproxy

# Porta 587 (submission): qui si autenticano i tuoi client (Thunderbird, ecc.)
submission inet n       -       -       -       -       smtpd
  -o syslog_name = postfix/submission                # etichetta nei log
  -o smtpd_tls_security_level = encrypt              # TLS obbligatorio, niente chiaro
  -o smtpd_sasl_auth_enable = yes                    # richiede login
  -o smtpd_client_restrictions = permit_sasl_authenticated,reject   # solo autenticati
  -o smtpd_relay_restrictions = permit_sasl_authenticated,reject    # niente relay anonimo
  -o smtpd_recipient_restrictions = permit_mynetworks,permit_sasl_authenticated,reject
  -o smtpd_sasl_type = dovecot                       # autenticazione via Dovecot
  -o smtpd_sasl_path = private/auth
  -o milter_macro_daemon_name = ORIGINATING          # posta "nostra": i milter la firmano (DKIM) invece di verificarla

# Porta 465 (smtps): submission con TLS "implicito" (wrappermode), per i
# client che si aspettano la connessione già cifrata fin dal primo byte
smtps     inet  n       -       -       -       -       smtpd
  -o syslog_name = postfix/smtps
  -o smtpd_tls_wrappermode = yes                     # TLS subito, senza STARTTLS
  -o smtpd_sasl_auth_enable = yes
  -o smtpd_client_restrictions = permit_sasl_authenticated,reject
  -o smtpd_relay_restrictions = permit_sasl_authenticated,reject   # niente relay anonimo
  -o smtpd_sasl_type = dovecot
  -o smtpd_sasl_path = private/auth
  -o milter_macro_daemon_name = ORIGINATING          # posta in uscita: firmata dai milter


# Servizio che esegue il controllo SPF (richiamato da check_policy_service)
policyd-spf  unix  -       n       n       -       0       spawn
user = policyd-spf argv=/usr/bin/policyd-spf

Per aggiungere utenti virtuali (ovvero alias "da → a") modificare il file /etc/postfix/virtual/addresses. A sinistra l'indirizzo che riceve, a destra dove finisce davvero la posta: può essere un utente locale oppure un indirizzo esterno qualsiasi, comodissimo per inoltrare verso altri utenti o altre email senza creare una mailbox vera.

root@domain.com     user              # la posta per root@ va all'utente locale
user2@domain.com    user2             # alias verso un altro utente locale
mario@domain.com    mario@altraemail.com   # inoltro verso un indirizzo esterno

E aggiungere i domini a /etc/postfix/virtual/domains, uno per riga: sono i domini per cui Postfix accetta di gestire questi alias.

localhost
domain.com
domain2.com

Poi eseguire i comandi per rigenerare:

postmap /etc/postfix/virtual
postmap /etc/postfix/virtual/addresses

Vi consiglio però di stare attenti a inoltrare email a provider esterni per evitare di compromettere la vostra reputazione: se avete un indirizzo spammato e rigirate tutto lo spam a gmail, poi gmail se la prende con voi e vi marca l'ip come spam

Dovecot

Dovecot è il programma che consente agli utenti di poter accedere alla propria posta, gestendo l'accesso tramite i protocolli IMAP e/o POP3. Per installare dovecot:

aptitude install dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd

Modificare le impostazioni per abilitare i protocolli in /etc/dovecot/dovecot.conf:

# imap: accesso alla posta dai client, lmtp: consegna da Postfix a Dovecot.
# Aggiungere pop3 a questa riga se si vuole anche il POP3
protocols = imap lmtp
!include_try /usr/share/dovecot/protocols.d/*.protocol

Poi impostare il driver e il percorso della posta, io preferisco maildir rispetto a mbox, meglio avere un file per email che tutte le mail in un unico, corruttibile, file. Modificare quindi il file /etc/dovecot/conf.d/10-mail.conf

mail_driver = maildir       # un file per email invece di un unico mbox
mail_path = ~/Maildir       # posizione della posta, dentro la home di ogni utente

Poi impostare i permessi e i servizo in /etc/dovecot/conf.d/10-master.conf. Qui si creano i due socket che fanno dialogare Postfix e Dovecot: uno per la consegna della posta (lmtp) e uno per l'autenticazione (auth); I socket vivono dentro la spool di Postfix proprio perché è Postfix a doverli leggere, da qui i permessi assegnati all'utente postfix:

service lmtp {
  # Socket su cui Postfix consegna la posta a Dovecot
  unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600          # leggibile solo dal proprietario
    user = postfix       # è Postfix che ci scrive dentro
    group = postfix
  }
}
service auth {
  # Socket condiviso per la verifica delle credenziali SASL
  unix_listener /var/spool/postfix/private/auth {
    mode = 0660          # leggibile da proprietario e gruppo
    user = postfix
    group = postfix
  }
}

Poi impostare come gli utenti possono effettuare il login in /etc/dovecot/conf.d/10-auth.conf:

# Normalizza lo username: toglie l'eventuale @dominio e lo abbassa a minuscolo,
# così "Mario@dominio.com" e "mario" finiscono sullo stesso account
auth_username_format = %{user|username|lower}
# Meccanismi di login accettati. plain e login vanno bene perché tanto la
# connessione è già cifrata da TLS (vedi submission/smtps)
auth_mechanisms = plain login

Ed impostare le chiavi in /etc/dovecot/conf.d/10-ssl.conf (stesso certificato usato da Postfix). Attenzione: in Dovecot 2.4 le direttive si chiamano ssl_server_cert_file e ssl_server_key_file e vogliono il percorso nudo (in 2.3 erano ssl_cert/ssl_key con una < davanti, ora la < non serve più 1 ):

ssl_server_cert_file = /path/chiave_dominio.fullchain.pem   # certificato + catena
ssl_server_key_file  = /path/chiave_dominio.privkey.pem     # chiave privata
ssl = required                                              # TLS obbligatorio: niente IMAP/POP3 in chiaro

Abilitare sieve in /etc/dovecot/conf.d/15-lda.conf ed impostare i delimitatori uguali a quelli di Postfix (altrimenti nome+etichetta@ non viene riconosciuto allo stesso modo dai due programmi). Sieve serve per le regole di filtraggio, tipo spostare lo spam nel cestino, lo useremo più avanti:

recipient_delimiter = +_          # stessi delimitatori di main.cf
protocol lda {
    # Boolean list of plugins to load
    mail_plugins { 
        sieve = yes               # abilita il filtraggio sieve in consegna
    } 
}

Utile anche abilitare il log rotate con /etc/logrotate.d/dovecot, così il log non cresce all'infinito mangiandosi il disco:

/var/log/dovecot.log {
  weekly            # ruota una volta a settimana
  rotate 4          # tiene 4 archivi storici (circa un mese)
  missingok         # niente errore se il log manca
  notifempty        # non ruota se è vuoto
  maxsize 500M      # ruota comunque se supera i 500M prima della settimana
  minsize 50M       # ma non ruotare se è ancora sotto i 50M
  compress          # comprime i vecchi log
  delaycompress     # rimanda la compressione di un giro (l'ultimo resta leggibile)
  sharedscripts     # esegue lo script sotto una volta sola
  postrotate
  doveadm log reopen   # dice a Dovecot di riaprire il file dopo la rotazione
  endscript
}

A questo punto far ripartire i servizi postfix e dovecot con systemctl restart postfix dovecot

Meglio anche impostare che il servizio dovecot riparta in caso di errore: si crea un file di "drop-in" che sovrascrive un pezzo dell'unit di systemd senza toccare quella originale (che a un aggiornamento verrebbe sovrascritta):

mkdir -p /etc/systemd/system/dovecot.service.d/
nano /etc/systemd/system/dovecot.service.d/restart.conf
[Service]
Restart = always      # riavvia sempre, qualunque sia il motivo dell'uscita
RestartSec = 5s       # aspetta 5 secondi prima di riprovare

E ricaricare quindi i servizi : systemctl daemon-reload

Un certificato per ogni dominio (SNI)

Fino a qui abbiamo usato un certificato per tutto. Va bene se i client si collegano sempre allo stesso nome (es. mail.dominio.com) e quel nome è dentro il certificato. Due accortezze però:

Per Dovecot basta inserire un blocco local_name per ogni nome, in un file dedicato tipo /etc/dovecot/conf.d/11-ssl-sni.conf:

local_name mail.dominio.com {
  ssl_server_cert_file = /path/dominio.com.fullchain.pem
  ssl_server_key_file  = /path/dominio.com.privkey.pem
}
local_name mail.altrodominio.it {
  ssl_server_cert_file = /path/altrodominio.it.fullchain.pem
  ssl_server_key_file  = /path/altrodominio.it.privkey.pem
}

Per Postfix (submission/smtps) serve una mappa nome → chiave + catena, in /etc/postfix/sni.map (prima la chiave privata, poi il fullchain):

mail.dominio.com       /path/dominio.com.privkey.pem      /path/dominio.com.fullchain.pem
mail.altrodominio.it   /path/altrodominio.it.privkey.pem  /path/altrodominio.it.fullchain.pem

Si compila con postmap -F (Postfix genera dei .db contenenti i PEM, non linka i nomi dei file) e si attiva in main.cf:

postmap -F hash:/etc/postfix/sni.map
postconf -e 'tls_server_sni_maps = hash:/etc/postfix/sni.map'
systemctl reload postfix

Occhio ai rinnovi: siccome postmap -F copia il contenuto dei certificati dentro il .db, dopo un rinnovo (in cui i file cambiano) il .db resta con la vecchia versione finché non lo si rigenera. Dovecot invece rilegge i file ad ogni reload.

Per Postfix basta uno script cron che ricompila e rigenera il db solo se qualcosa è cambiato, per esempio /etc/cron.daily/postfix-sni-refresh :

#!/bin/sh
MAP=/etc/postfix/sni.map
OLD=$(cksum "$MAP.db" 2>/dev/null)
postmap -F "hash:$MAP"
[ "$OLD" != "$(cksum "$MAP.db" 2>/dev/null)" ] && systemctl reload postfix
exit 0

Prova finale: chiedendo un nome via SNI deve tornare il certificato giusto.

openssl s_client \
  -connect mail.dominio.com:465 \
  -servername mail.dominio.com 
  </dev/null 2>/dev/null \
  | openssl x509 -noout -subject

SPF

Per spf basta installare postfix-policyd-spf-python e poi riavviare postfix

aptitude install postfix-policyd-spf-python
systemctl restart postfix

Ora bisogna impostare i campi TXT dei domini con v=spf1 mx ~all. Tradotto: mx autorizza a spedire i server elenati nei record MX del dominio (cioè il nostro), mentre ~all è un "softfail" che dice ai destinatari «tutto il resto è sospetto, ma non scartarlo a priori». Quando si è sicuri che tutto funziona si può irrigidire in -all (hardfail), che invece invita a rifiutare chiunque non sia autorizzato.

OpenDmarc

DMARC è il vigile che mette d'accordo SPF e DKIM: controlla che almeno uno dei due sia valido e allineato col mittente, e decide cosa fare di chi non passa. Durante l'installazione viene chiesto se configurare un database per i report: rispondo "No", a me i report aggregati non interessano.

aptitude install opendmarc
	-> No                          # niente database per i report
systemctl enable opendmarc

Modificare quindi la configurazione in /etc/opendmarc.conf:

# Socket condiviso con Postfix (dev'essere lo stesso indicato in smtpd_milters)
Socket local:/var/spool/postfix/opendmarc/opendmarc.sock
# Non controllare chi si è già autenticato da noi (è posta nostra in uscita)
IgnoreAuthenticatedClients true
# Pretende From, Date, ecc.: una mail senza header obbligatori è da buttare
RequiredHeaders    true
# Verifica SPF da solo, senza appoggiarsi a un milter SPF esterno
SPFSelfValidate true
# Lista di host da non sottoporre a DMARC (vedi sotto)
IgnoreHosts  /etc/opendmarc/ignore.hosts
# Crea la cartella del socket dentro la spool di Postfix e dà i permessi giusti
mkdir -p /var/spool/postfix/opendmarc
chown opendmarc:opendmarc /var/spool/postfix/opendmarc -R
chmod 750 /var/spool/postfix/opendmarc/ -R

Aggiunge postfix al gruppo opendmarc così può leggere il socket

adduser postfix opendmarc
systemctl restart opendmarc
systemctl restart postfix

Infine va pubblicato il record DNS di DMARC: un TXT su _dmarc.dominio.com con un valore tipo v=DMARC1; p=quarantine; rua=mailto:postmaster@dominio.com. La p è la politica che chiediamo agli altri server di applicare alla nostra posta che non passa i controlli: none osserva soltanto, quarantine la manda in spam, reject la rifiuta del tutto. Consiglio di partire da none finché non si è certi che SPF e DKIM siano allineati, e solo dopo stringere.

DKIM

DKIM aggiunge a ogni mail in uscita una firma crittografica negli header: il destinatario la verifica con la chiave pubblica pubblicata nel nostro DNS e sa così che il messaggio è davvero nostro e non di chissà chis sia. Noi teniamo la chiave privata sul server, la pubblica va nel DNS.

aptitude install opendkim opendkim-tools
gpasswd -a postfix opendkim          # postfix nel gruppo opendkim, per il socket
systemctl restart postfix
mkdir -p /etc/opendkim/keys
chown -R opendkim:opendkim /etc/opendkim
chmod go-rw /etc/opendkim/keys       # le chiavi non devono essere leggibili da altri
mkdir /var/spool/postfix/opendkim
chown opendkim:postfix /var/spool/postfix/opendkim

Configurare /etc/opendkim.conf:

# Come "normalizzare" header e corpo prima di firmare: relaxed tollera piccoli
# ritocchi sugli header (spazi, a capo), simple è rigido sul corpo
Canonicalization        relaxed/simple
# s = sign (firma in uscita), v = verify (verifica in entrata). "sv" = entrambi
Mode                    sv
# Non firmare automaticamente i sottodomini con la chiave del dominio padre
SubDomains              no
# DNS usati da OpenDKIM per pubblicare/verificare le chiavi
Nameservers             8.8.8.8,1.1.1.1

# Socket condiviso con Postfix (lo stesso indicato in smtpd_milters)
Socket                 local:/var/spool/postfix/opendkim/opendkim.sock

# Quale chiave usare per firmare (KeyTable) e a quali mittenti applicarla
# (SigningTable). "refile": file con caratteri jolly
KeyTable                refile:/etc/opendkim/KeyTable
SigningTable            refile:/etc/opendkim/SigningTable
# Host esterni di cui fidarsi senza verificarne la firma
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts
# Host "interni": la loro posta viene firmata, non verificata
InternalHosts           refile:/etc/opendkim/TrustedHosts

La SigningTable in /etc/opendkim/SigningTable associa i mittenti al selettore della chiave da usare (*@dominio.com = chiunque su quel dominio):

*@dominio.com         mail._domainkey.dominio.com
*@altrodominio.com    mail._domainkey.altrodominio.com

La KeyTable /etc/opendkim/KeyTable dice dove sta fisicamente la chiave privata, nel formato dominio:selettore:percorso_chiave:

mail._domainkey.dominio.com        dominio.com:mail:/etc/opendkim/keys/dominio.com/mail.private
mail._domainkey.altrodominio.com   altrodominio.com:mail:/etc/opendkim/keys/altrodominio.com/mail.private

Per generare le chiavi (una coppia pubblica/privata per dominio) basta usare opendkim-genkey, che genera già il record spezzato nel formato corretto. Con -b 2048 si sceglie la lunghezza: 2048 bit è lo standard odierno (Gmail e altri segnalano ormai le chiavi a 1024 come deboli). Scendi a -b 1024 solo se il tuo pannello DNS proprio non accetta record TXT lunghi, ma ormai è raro.

mkdir /etc/opendkim/keys/dominio.com
opendkim-genkey -b 2048 -d dominio.com -D /etc/opendkim/keys/dominio.com -s mail -v
# Permessi: la chiave privata deve appartenere a opendkim e basta
chmod 700 /etc/opendkim/keys/dominio.com
chmod 600 /etc/opendkim/keys/dominio.com/*
chown -R opendkim:opendkim /etc/opendkim/keys/dominio.com

Il comando crea anche un file mail.txt contenente il TXT da copiare nel record DNS su mail._domainkey.dominio.com (è la chiave pubblica che i destinatari useranno per verificare la firma). Una volta inserito il record nel DNS controllare che funzioni con:

opendkim-testkey -d dominio.com -s mail -vvv # deve dire "key OK"
dig +short TXT mail._domainkey.dominio.com   # mostra la chiave

In /etc/opendkim/TrustedHosts gli host la cui posta va firmata invece che verificata (tipicamente il server stesso e i propri domini):

127.0.0.1
::1
localhost
123.123.123.123
1234:1234:1234:1234::1
.dominio.com
.domaltrodominioinio.com

Infine in /etc/default/opendkim indicare lo stesso socket della configurazione, così il demone parte ascoltando dove Postfix si aspetta di trovarlo:

SOCKET="local:/var/spool/postfix/opendkim/opendkim.sock"

E poi basta far ripartire il tutto:

systemctl restart opendkim postfix

Ruotare o rinnovare una chiave DKIM

Prima o poi capita: vuoi passare a una chiave più robusta (da 1024 a 2048 bit) oppure semplicemente ruotarla per maniacali livelli di sicurezza. Va quindi generata una coppia nuova (nuova privata sul server, nuova pubblica nel DNS).

L'unica insidia è la propagazione DNS. Se riscrivi il TXT dello stesso selettore mail, finché in giro c'è ancora in cache la vecchia chiave pubblica (fino al TTL del record) la posta firmata con la nuova privata fallisce la verifica DKIM dai destinatari. Per evitarlo del tutto si usa un selettore nuovo (es. mail2, o datato tipo 2026a) e si fa il cambio in quest'ordine, senza nessun istante scoperto. Lascia il vecchio TXT mail._domainkey nel DNS ancora qualche giorno: la posta già spedita e firmata con la vecchia chiave deve poter essere verificata da chi la riceve in ritardo. Dopo 3-7 giorni puoi rimuoverlo e cancellare la vecchia mail.private.

Se proprio vuoi riusare lo stesso selettore mail, abbassa il TTL di quel record (es. a 300s) un TTL prima della sostituzione: riduce la finestra di fallimento, ma usare un selettore nuovo resta la strada più facile ed indolore.

SpamAssassin

SpamAssassin è il vero cuore antispam: assegna a ogni mail un punteggio sommando decine di regole, e oltre una certa soglia la marchia (o la rifiuta). Prima di scomodarlo, però, conviene far scartare a Postfix la robaccia più ovvia con due filtri PCRE secchi, così non sprechiamo nemmeno il tempo di analizzarla.

aptitude install postfix-pcre

In /etc/postfix/header_checks le regole sugli header. DISCARD finge di accettare la mail ma la butta nel nulla, senza dare allo spammer la soddisfazione di un errore:

/free mortgage quote/           DISCARD # classico spam di mutui
/repair your credit/            DISCARD
/To:.*<>/                       DISCARD # destinatario vuoto, mail malformata
/From:.*<>/                     DISCARD # mittente vuoto

E in /etc/postfix/body_checks le stesse regole, ma sul corpo del messaggio:

/free mortgage quote/     DISCARD
/repair your credit/      DISCARD

I file di mappa vanno compilati con postmap ogni volta che si modificano:

postmap /etc/postfix/header_checks
postmap /etc/postfix/body_checks
systemctl restart postfix

Ora SpamAssassin vero e proprio, col suo milter per agganciarlo a Postfix:

aptitude install spamassassin spamc
systemctl enable spamd
systemctl start spamd
aptitude install spamass-milter

In /etc/default/spamass-milter alcune opzioni utili:

# -I = NON scansionare la posta autenticata (submission/smtps con login):
# il milter gira anche in uscita, quindi senza questo una mail inviata
# che totalizza un punteggio alto verrebbe marcata come spam o 
# addirittura bloccata
OPTIONS="${OPTIONS} -I"

# -r 8 = rifiuta subito le mail con punteggio >= 8 (spam conclamato),
# invece di consegnarle solo marchiate
OPTIONS="${OPTIONS} -r 8"

# Non analizzare mail oltre i ~5 MB: sarebbero lente da scansionare e
# raramente lo spam è così pesante
OPTIONS="${OPTIONS} -- --max-size=5120000"
systemctl restart postfix spamass-milter

Abilitare anche l'aggiornamento automatico delle regole, così le firme antispam restano fresche senza pensarci:

nano /etc/cron.daily/spamassassin
	CRON=1                  # abilita l'aggiornamento giornaliero via cron
systemctl enable --now spamassassin-maintenance.timer

Veniamo al pezzo più divertente, /etc/spamassassin/local.cf, dove si ritoccano i punteggi delle regole. Ogni riga score cambia quanti punti vale una certa regola: numeri positivi spingono verso lo spam, negativi verso il "buono". Questi valori sono frutto di anni di aggiustamenti sulla mia posta, prendeteli come spunto e tarateli sui falsi positivi che capitano a voi:

score MISSING_FROM            5.0   # manca l'header From : , quasi sempre spam
score MISSING_DATE            5.0   # manca la data
score PDS_FROM_2_EMAILS       3.0   # due indirizzi diversi nel From
score EMPTY_MESSAGE           5.0   # messaggio vuoto
score FREEMAIL_DISPTO         2.0   # display-to verso un freemail sospetto
score FREEMAIL_FORGED_REPLYTO 3.5   # Reply-To falsificato su un freemail
score DKIM_ADSP_NXDOMAIN      5.0   # dominio firmatario inesistente
score FORGED_GMAIL_RCVD       2.5   # finto Received "da Gmail"
score FROM_FMBLA_NEWDOM       4.0   # mittente su dominio registrato da poco
score FROM_FMBLA_NEWDOM14     3.5   # ...meno di 14 giorni
score FROM_FMBLA_NEWDOM28     3.0   # ...meno di 28 giorni
score RCVD_IN_DNSWL_HI       -1.0   # in una whitelist DNS affidabile : -1, è buona
score RCVD_IN_BL_SPAMCOP_NET  3.0   # presente nella blacklist SpamCop
score HTML_FONT_LOW_CONTRAST  0.7   # testo a basso contrasto (trucco da spam)
score NORDNS_LOW_CONTRAST     3.0
score HTML_IMAGE_RATIO_02     2.0   # tutta immagine e poco testo
score HTML_IMAGE_ONLY_20      2.0   # solo immagine, niente testo
score URIBL_BLACK             3.0   # link verso domini in blacklist URIBL
# Reputazione Mailspike: H = buona (punteggi negativi), L = cattiva (positivi)
score RCVD_IN_MSPIKE_H2      -1.0
score RCVD_IN_MSPIKE_H3      -1.5
score RCVD_IN_MSPIKE_H4      -2.0
score RCVD_IN_MSPIKE_H5      -2.5

score RCVD_IN_MSPIKE_L3       1.9
score RCVD_IN_MSPIKE_L4       3.7
score RCVD_IN_MSPIKE_L5       4.5

score T_SPF_PERMERROR         2.0   # record SPF rotto/malformato

score URIBL_ABUSE_SURBL       4.5   # link verso domini nella blacklist abusi SURBL
score MAILING_LIST_MULTI      2.0   # più header da mailing list diverse nello stesso messaggio

Oltre a ritoccare i punteggi, si possono scrivere regole proprie (qui prefissate CHA_ per riconoscerle a colpo d'occhio). Una regola è fatta di tre righe: header/full definisce la condizione, describe la spiega e score le dà il peso:

# Mittente uguale al destinatario: un trucco vecchio ma ancora in giro
header    CHA_FROM_SAME_AS_TO     ALL =~ /\nFrom: ([^\n]+)\nTo: \1/sm
describe  CHA_FROM_SAME_AS_TO     From address is the same as To address.
score     CHA_FROM_SAME_AS_TO     2.0

# Return-Path vuoto <>
header    CHA_EMPTY_RETURN_PATH   ALL =~ /<>/i
describe  CHA_EMPTY_RETURN_PATH   empty address in the Return Path header.
score     CHA_EMPTY_RETURN_PATH   3.0

# La mail ha fallito il controllo DMARC (lo leggo dall'header che scrive OpenDmarc)
header    CHA_CUSTOM_DMARC_FAIL   Authentication-Results =~ /dmarc=fail/
describe  CHA_CUSTOM_DMARC_FAIL   This email failed DMARC check
score     CHA_CUSTOM_DMARC_FAIL   3.0

# Domini delle TLD da cui mi arriva solo spazzatura (regolatevi sulla vostra)
header    CHA_SPAM_DOMINI         From =~ /@[a-z0-9\-\.]+\.(club|icu|xyz|online|ru|su)/i
describe  CHA_SPAM_DOMINI         Spam da domini sospetti o russi
score     CHA_SPAM_DOMINI         4.5

# Charset cirillici: se non ricevete posta in russo, è quasi sempre spam
full      CHA_CHARSET_BLOCKED     /charset=(3D|")(koi8-r|windows-1251)(3D|")/is
score     CHA_CHARSET_BLOCKED     4.5
describe  CHA_CHARSET_BLOCKED     Email con charset russi

# Spam che rimbalza su Google Groups: liste con alias di 2 caratteri e relay
# Google, in cui non sei il vero destinatario. Qui entra in gioco `meta`, che
# combina più condizioni con && e ||: le regole che iniziano con `__` non
# servono solo per creare il meta finale
header   __CHA_GG_GROUP_ID        exists:X-Google-Group-Id
header   __CHA_LIST_ID_2CHAR      List-Id =~ /<[a-z0-9]{2}\./i
body     __CHA_UNSUB_2CHAR_BODY   /(?:^|[\s:<"'(])[a-z0-9]{2}\+unsubscribe@[a-z0-9.-]+/i
meta     CHA_SPAM_SHORT_LIST      ((__CHA_UNSUB_2CHAR_BODY || __CHA_LIST_ID_2CHAR) && __CHA_GG_GROUP_ID)
describe CHA_SPAM_SHORT_LIST      Lista con alias di 2 caratteri, relay Google Groups, non destinatario
score    CHA_SPAM_SHORT_LIST      4.0

Un esempio più ricco è la meta anti-phishing che intercetta l'impersonazione dei brand famosi (PayPal, Amazon, Poste, banche, Microsoft…). L'idea: se il From rivendica un brand ma il dominio reale del mittente non è quello ufficiale, è quasi sempre phishing. Per ogni brand due sotto-regole __: una rileva il nome nel From, l'altra riconosce i domini legittimi; la meta le combina come «brand citato e dominio non ufficiale»:

# nome del brand rivendicato nel From (display-name o indirizzo)
header __CHA_DN_PAYPAL   From =~ /paypal/i
header __CHA_DN_AMAZON   From =~ /amazon/i
header __CHA_DN_POSTE    From =~ /(poste\s*italiane|posteitaliane|bancoposta)/i
header __CHA_DN_RCUBE    From =~ /(roundcube|webmail)/i

# domini realmente ufficiali del brand (la parte dopo la @)
header __CHA_OK_PAYPAL   From:addr =~ /@([a-z0-9-]+\.)*paypal\.[a-z.]{2,}$/i
header __CHA_OK_AMAZON   From:addr =~ /@([a-z0-9-]+\.)*(amazon\.[a-z.]{2,}|amazonses\.com)$/i
header __CHA_OK_POSTE    From:addr =~ /@([a-z0-9-]+\.)*(poste\.it|posteitaliane\.it)$/i
# per roundcube/webmail il mittente "legittimo" è uno dei TUOI domini
header __CHA_OK_RCUBE    From:addr =~ /@([a-z0-9-]+\.)*(dominio\.com|altrodominio\.it)$/i

# scatta se un brand è citato ma il suo dominio ufficiale NON combacia
meta CHA_FAKE_DOMAINS ((__CHA_DN_PAYPAL && !__CHA_OK_PAYPAL) || (__CHA_DN_AMAZON && !__CHA_OK_AMAZON) || (__CHA_DN_POSTE && !__CHA_OK_POSTE) || (__CHA_DN_RCUBE && !__CHA_OK_RCUBE))
describe CHA_FAKE_DOMAINS Brand noto nel mittente ma dominio non ufficiale (phishing)
score CHA_FAKE_DOMAINS 4.5

Sullo stesso schema si aggiungono gli altri brand (Intesa Sanpaolo, UniCredit, Nexi, Aruba, Banca d'Italia, OpenAI, Netflix…): basta una coppia __CHA_DN_x / __CHA_OK_x e un'altra clausola nella meta. Il punteggio 4.5 da solo non marca spam (così un brand citato per caso in posta legittima non fa danni), ma sommato a qualunque altro segnale supera la soglia. Occhio ai nomi generici (intesa, aruba sono anche parole comuni): se salta fuori un falso positivo su un mittente preciso, un welcomelist_auth lo mette al riparo.

Sempre nello stesso file, una whitelist e una blacklist "manuali": la prima mette al riparo i mittenti fidati dai falsi positivi, la seconda fulmina chi non si vuole proprio vedere.

Occhio a un tranello grosso: **whitelist_from guarda solo il From, che è banalmente falsificabile.** Uno spammer scrive From: qualcuno@tuodominio (o un qualsiasi indirizzo che hai in whitelist) e si becca il -100 di USER_IN_WELCOMELIST, scavalcando ogni altro controllo. Per questo conviene welcomelist_auth: identico, ma il bonus vale **solo se il mittente è autenticato* (SPF pass* o DKIM valido e allineati col dominio). La posta forgiata fallisce l'auth e non prende niente.

welcomelist_auth *@dominio.com          # bonus solo se SPF/DKIM autenticano
welcomelist_auth mario@altrodominio.it

blacklist_from spam@example.com
blacklist_from *@example.org

Un'idea comoda è auto-generare la whitelist dai propri corrispondenti: gli indirizzi a cui hai scritto sono quasi sempre gente vera. Un mini-script che pesca i destinatari dalle cartelle .Sent:

grep -rhEi '^(To|cc|bcc):' /home/*/Maildir/.Sent/*/* |
  grep -oEi '[a-z0-9_.+-]+@([a-z0-9-]+\.)+[a-z]{2,}' |
  tr 'A-Z' 'a-z' | sort -u |
  xargs -n1 echo welcomelist_auth > /etc/spamassassin/whitelist.generator.cf

Nota bene: usa welcomelist_auth, non whitelist_from — altrimenti ognuno di quegli indirizzi diventa un buco, basta che uno spammer lo forgi nel From per auto-whitelistarsi.

Dopo ogni modifica conviene controllare la sintassi con --lint (se non dice nulla, è tutto a posto) e riavviare:

spamassassin --lint
systemctl restart spamd

A questo punto SpamAssassin marchia lo spam, ma la mail finisce comunque nella Posta in arrivo. Per spostarla in automatico nella cartella Junk uso Sieve, che filtra al momento della consegna:

aptitude install dovecot-sieve dovecot-managesieved

Abilitare il plugin sieve sia in consegna locale (lda) sia via lmtp, in /etc/dovecot/conf.d/15-lda.conf

protocol lda {
  mail_plugins {
     sieve = yes
  }
}

e in /etc/dovecot/conf.d/20-lmtp.conf

protocol lmtp {
  mail_plugins {
    sieve = yes
  }
}

In /etc/dovecot/conf.d/90-sieve.conf si registra uno script "globale" che gira prima degli eventuali filtri dell'utente (type = before):

sieve_script move2spam {
  type = before
  path = /var/mail/SpamToJunk.sieve
}

E lo script vero e proprio, /var/mail/SpamToJunk.sieve: se SpamAssassin ha marcato la mail (X-Spam-Flag: YES), spostala in Junk e ferma l'elaborazione:

require "fileinto";

if header :contains "X-Spam-Flag" "YES"
{
   fileinto "Junk";
   stop;
}

Gli script sieve vanno compilati con sievec, poi si riavvia Dovecot:

sievec /var/mail/SpamToJunk.sieve
systemctl restart dovecot

Attenzione a un cambiamento di Dovecot 2.4: gli script dei singoli utenti non sono abilitati di default, busogna impostare anche la posizione dello * script personale*, Basta decommentare in 90-sieve.conf:

sieve_script personal {
  driver = file
  path = ~/sieve
  active_path = ~/.dovecot.sieve
}

Un modo veloce per verificare che un filtro personale funzione basta eseguire sieve-test, che simula la consegna senza toccare la mailbox:

sieve-test -u utente /home/utente/.dovecot.sieve messaggio.eml

Occhio anche alla differenza tra header :is "from" "tizio@example.com" e address :is "from" "tizio@example.com": il primo pretende che l'header From sia esattamente quella stringa e quindi non filtra "Nome" <tizio@example.com> Per confrontare il solo indirizzo (ignorando il nome) va usato address. In Roundcube questo si fa nelle impostazioni della regola (l'ingranaggio a destra) apparirà un sottomenu in cui si può cambiare il modificatore, impostandolo ad "indirizzo" (address) invece di "nessuno" (header). Anche qui bestemmie e tempo perso a capire perché i filtri non funzionavano...

Già che ci siamo, un po' di pulizia sulle porte di Dovecot. Se non usi POP3, disattiva i suoi listener invece di lasciarli in ascolto: in /etc/dovecot/conf.d/10-master.conf metti port = 0:

service pop3-login {
  inet_listener pop3  { port = 0 }
  inet_listener pop3s { port = 0 }
}

E ManageSieve (porta 4190) di solito serve solo alla webmail sulla stessa macchina: conviene legarlo a localhost invece di esporlo su tutte le interfacce. In /etc/dovecot/conf.d/20-managesieve.conf:

service managesieve-login {
  listen = 127.0.0.1, ::1     # solo locale (la webmail ci arriva da localhost)
  inet_listener sieve {
    port = 4190
  }
  inet_listener sieve_deprecated {
    port = 0                   # vecchia porta 2000: spenta
  }
}

Ovviamente resta sempre valida la regola d'aver un firewall davanti che lascia aperte solo le porte che servono davvero, a scanso di equivoci...

Un'ultima accortezza: nascondere gli header X-Spam-* in uscita, così non sveliamo al mondo come è tarato il nostro antispam. In /etc/postfix/smtp_header_checks (IGNORE toglie l'header solo dalla posta inviata, lasciandolo su quella in arrivo):

/^X-Spam-Status:/             IGNORE
/^X-Spam-Checker-Version:/    IGNORE
postmap /etc/postfix/smtp_header_checks
systemctl reload postfix

Come ciliegina, Spamhaus offre un set di regole DQS (Data Query Service) gratuito per uso personale: bisogna registrarsi sul loro sito per ottenere una chiave, e in cambio si hanno blacklist molto più reattive di quelle pubbliche.

git clone https://github.com/spamhaus/spamassassin-dqs
cd spamassassin-dqs/4.0.0+
sh hbltest.sh    # verifica che le query DNS verso Spamhaus funzionino
# Inserisce la propria chiave DQS al posto del placeholder
sed -i -e 's/your_DQS_key/chiave_generata_da_spamhaus/g' sh.cf
nano sh.pre
>> loadplugin      Mail::SpamAssassin::Plugin::SH /etc/spamassassin/SH.pm
# Copia plugin, regole e punteggi tra le configurazioni di SpamAssassin
cp SH.pm /etc/spamassassin/
cp sh.cf /etc/spamassassin/
cp sh_scores.cf /etc/spamassassin/
cp sh.pre /etc/spamassassin/
spamassassin --lint

Attivare i Bayes

Una delle cose più efficaci di SpamAssassin è il filtro bayesiano: invece di regole fisse, impara statisticamente dai messaggi che gli dai in pasto e riconosce così anche lo spam "nuovo" (i phishing freschi che non sono ancora in nessuna blacklist). C'è un però: all'inizio è disabilitato. Finché non ha imparato almeno ~200 email di spam e ~200 email validei (bayes_min_spam_num/bayes_min_ham_num) non assegna alcun punteggio.

Il tranello del database condiviso. Qui si perde un pomeriggio se non lo si sa: spamd scansiona la posta girando come root (a livello di sistema), mentre l'addestramento (sa-learn, lanciato da voi o dalla webmail) gira con un altro utente. Di default ognuno usa il proprio database in ~/.spamassassin/bayes: risultato, addestri un DB e ne scansioni un altro, e il training non serve a niente. La soluzione mia è un unico DB condiviso su cui convergono tutti (mi fido dei miei utenti). In /etc/spamassassin/local.cf:

bayes_path       /var/lib/spamassassin/bayes/bayes   # DB in posizione fissa e condivisa
bayes_file_mode  0660                                # leggibile/scrivibile dal gruppo

e si prepara la cartella con un gruppo comune (qui users) per chi scansiona e per chi addestra, con lo setgid così i file nuovi ereditano il gruppo giusto:

mkdir -p /var/lib/spamassassin/bayes
chgrp users /var/lib/spamassassin/bayes     # gruppo condiviso: spamd + chi addestra
chmod 2770  /var/lib/spamassassin/bayes     # il "2" e' setgid: i nuovi file ereditano il gruppo

Adesso lo si deve addestrare. Come? Dandogli in pasto lo spam dalla cartella Junk e la posta valida dalla Inbox. Più esempi si danno in pasto (e più sono significativi) meglio funziona:

# come root (o un utente del gruppo condiviso), così scrive nel DB condiviso
sa-learn --spam /home/*/Maildir/.Junk/{cur,new}
sa-learn --ham  /home/*/Maildir/{cur,new}
# verifica: nspam e nham devono superare 200 perché il Bayes si attivi
sa-learn --dump magic

Da qui in poi il Bayes contribuisce con le regole BAYES_* (da BAYES_00, "quasi certamente buono" e con punteggio negativo, fino a BAYES_99, sicuramente spam).

L'opzione bayes_auto_learn (attiva di default) continua poi ad auto-allenarlo sui messaggi che gli altri controlli giudicano nettamente spam o ham.

Per allenarlo manualmente marcando "spam / non spam" dalla webmail vedremo markasjunk nella guida qui sotto di RoundCube.

le regole KAM

Le regole che arrivano con SpamAssassin sono poche e generiche. Si possono aggiungere canali di regole di terze parti, aggiornati via sa-update con firma GPG. Il più usato e ben mantenuto è KAM della McGrail Foundation: gratis, senza registrazione.

Per impostarle per prima cosa si scarica la chiave GPG del canale e si importa con sa-update:

wget https://mcgrail.com/downloads/kam.sa-channels.mcgrail.com.key
sa-update --import kam.sa-channels.mcgrail.com.key
sa-update --gpgkey 24C063D8 --channel kam.sa-channels.mcgrail.com

Su Debian l'aggiornamento automatico gira come debian-spamd con un keyring dedicato in /var/lib/spamassassin/sa-update-keys. Se importate la chiave come root finisce nel keyring sbagliato e il cron giornaliero fallirà. Meglio fare tutto in quel keyring condiviso, eseguendo come debian-spamd:

runuser -u debian-spamd -- sa-update --gpghomedir /var/lib/spamassassin/sa-update-keys --import kam.sa-channels.mcgrail.com.key
runuser -u debian-spamd -- sa-update --gpghomedir /var/lib/spamassassin/sa-update-keys --gpgkey 24C063D8 --channel kam.sa-channels.mcgrail.com
spamassassin --lint      # controlla che tutte le regole (KAM incluse) siano valide
systemctl reload spamd  

Il timer standard di Debian (spamassassin-maintenance.timer) lancia sa-update senza --channel, quindi aggiorna solo il canale di default: le regole KAM resterebbero non aggiornate. Serve un service+timer dedicato, creaundo un file /etc/systemd/system/sa-update-kam.service:

[Unit]
Description = Aggiorna il canale KAM di SpamAssassin (McGrail Foundation)
After = network-online.target
Wants = network-online.target
ConditionACPower = true

[Service]
Type = oneshot
# sa-update ritorna 0 = aggiornato, 1 = nessun update, >1 = errore.
# Solo su 0 rifacciamo lint e ricarichiamo spamd; su 1 usciamo puliti.
ExecStart = /bin/sh -c 'runuser -u debian-spamd -- sa-update --gpghomedir /var/lib/spamassassin/sa-update-keys --gpgkey 24C063D8 --channel kam.sa-channels.mcgrail.com; rc=$?; if [ "$rc" -eq 0 ]; then runuser -u debian-spamd -- spamassassin --lint && systemctl reload spamd; elif [ "$rc" -eq 1 ]; then exit 0; else exit "$rc"; fi'

E in /etc/systemd/system/sa-update-kam.timer:

[Unit]
Description = Aggiornamento giornaliero del canale KAM di SpamAssassin

[Timer]
OnCalendar = daily
RandomizedDelaySec = 1h
Persistent = true

[Install]
WantedBy = timers.target
systemctl daemon-reload
systemctl enable --now sa-update-kam.timer
systemctl start sa-update-kam.service   # test: deve dare  Result=success

KAM può essere un filo aggressivo: se una singola regola vi dà falsi positivi, non serve toccare il canale, basta abbassarne il punteggio come visto sopra in /etc/spamassassin/local.cf con score KAM_NOME_REGOLA <valore>.

Server DNS

Se riceverete errori di URIBL_BLOCKED e RCVD_IN_DNSWL_BLOCKED in spamassassin vuol dire che state utilizzanod DNS pubblici, che vengono bloccati per evitare utilizzi massicci da parte degli utenti.

Per ovviare al problema è succiciente installare un server DNS locale come unbound. Ciò rallenta un po' le richieste dns ma si guadagna aanche in privacy :)

aptitude install unbound

# Attivazione servizio 
systemctl enable --now unbound

# Modificarei dns 
nano /etc/systemd/resolved.conf
# aggiungere/modificare
# DNS=127.0.0.1

# e anche in opedkim (opzionale, solo per privacy)
nano /etc/opendkim.conf
# aggiungere/modificare
# nameservers 127.0.0.1

# Ricaricare il gestore DNS:
systemctl restart systemd-resolved

Un errore frequente: impostare DNS=127.0.0.1 in resolved.conf non basta se la scheda di rete continua a impostare dei DNS come fallback (spesso ce li mette netplan/cloud-init o il DHCP). Con systemd-resolved quei resolver restano in lista e se una richiesta passa a quelli, Spamhaus & co. tornano a dare errore (URIBL_BLOCKED). I DNS vanno tolti alla fonte, ovvero nel netplan si lascia come unico nameserver dell'interfaccia il resolver locale:

# /etc/netplan/*.yaml - dentro la sezione dell'interfaccia
nameservers:
  addresses:
    - 127.0.0.1

E si impedisce a cloud-init di rigenerare il file al reboot (reinserendo i DNS del provider):

echo 'network: {config: disabled}' > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg
netplan apply

Per controllare: resolvectl status deve mostrare solo 127.0.0.1 come DNS. Da qui ogni query passa esclusivamente per unbound, che è ricorsivo e non dipende da nessun dns esterno.

Testare il tutto

Per testare il tutto accedete con quello che volete alla vostra casella di posta (thunderbird, app sul telefono, RoundCube, ecc) ed inviate una email all'indirizzo che vi fornisce mail-tester.com. Vi restituirà un punteggio da 1 a 10 con il dettaglio di cosa va e cosa no: SPF, DKIM, DMARC, reverse DNS, presenza in blacklist e via dicendo. L'obiettivo è il fatidico 10/10, ma anche un onesto 9 fa arrivare la posta a destinazione.

Qualche altra verifica utile:

Se il primo invio finisce in spam non disperate: spesso è solo questione di "reputazione" del vostro ip, che migliora col tempo mandando posta legittima. E mi raccomando... non madare email con solo "ok" o poche parole... Sicuramente vanno in spam. Trucco utile: Aggiungete una firma automatica ai messaggi di uscita con qualche dato utile, non è necessaria, non aiuta molto, ma male non fa.

Extra: fail2ban

Fail2Ban guarda i log dei servizi e, quando vede troppi tentativi falliti dallo stesso ip (login SSH sbagliati, bot che cercano pagine inesistenti, ecc.), lo banna a livello di firewall per un po'. Da solo non ferma uno spammer determinato, ma toglie di mezzo il rumore di fondo dei bot, che è già tantissimo. Così poi è possibile visualizzare i log con effettivamente quello che serve per migliorare i filtri

aptitude install fail2ban
nano /etc/fail2ban/jail.d/chalda.conf

Ogni [sezione] è una "jail": un servizio da sorvegliare con le sue soglie. I valori chiave sono sempre gli stessi: maxretry quanti errori tollerare, findtime in quanto tempo devono accadere perché scatti il ban, bantime quanto dura il ban. La sezione [DEFAULT] fissa i valori di default (ma va) per tutte le jail (chee ovviamnete si possono sovrassrivere)

[DEFAULT]
ignoreip = 127.0.0.1/8 dominio.com    # ip da non bannare mai (te stesso!)
bantime = 5d                          # ban di 5 giorni di default
findtime = 1d                         # finestra entro cui contare i tentativi
maxretry = 3                          # 3 errori e sei fuori

# La jail "recidive" banna più a lungo chi è già finito in una jail più volte:
[recidive]
enabled = true
bantime = 4w                          # un mese di ban per i recidivi
findtime = 2w                         # se colleziona più ban in 2 settimane

[sshd]
enabled = true
port = XX                          # la porta SSH (di default 22, cambiatela!)
maxretry = 2
findtime = 7d

# Chi prova a loggarsi con utenti inesistenti: tolleranza zero, basta 1 colpo
[sshd-invaliduser]
enabled = true
maxretry = 1
port = XX                    # Inserire la porta ssh (di default 22, cambiala!)
logpath = %(sshd_log)s       # path del log SSH, preso dai default distro
backend = %(sshd_backend)s
bantime = 5d

# Tentativi di login falliti sulla webmail RoundCube
[roundcube-auth]
enabled = true
logpath = /var/dir_di_roundcube/public/logs/errors.log
maxretry = 5

# Abusi su Postfix: protocollo SMTP malformato, relay tentati, ecc.
[postfix]
enabled = true
port = smtp,465,submission

# Login SMTP (SASL) sbagliati: chi prova a indovinare le password di invio
[postfix-sasl]
enabled = true
port = smtp,465,submission

# Login IMAP/POP3 falliti su Dovecot
[dovecot]
enabled = true
port = pop3,pop3s,imap,imaps,submission,465,sieve

Extra: RoudCube

Per vedere la propria posta online io installo RoundCuhe, ha tutto ciò che occorre e sinceramente è abbastanza fatto bene.

Per installarlo basta scaricare il pacchetto da roundcube.net ed estraetelo in una cartella:

version=1.7.2
wget -q --show-progress -O "roundcubemail-${version}-complete.tar.gz" "https://github.com/roundcube/roundcubemail/releases/download/$version/roundcubemail-$version-complete.tar.gz"
tar -xf "roundcubemail-${version}-complete.tar.gz"

Ora c'è una cartella roundcubemail-1.7.x/. Per installarlo (ed eventualmente aggiornarlo) basta eseguire lo script bin/installTo.sh, specificando tutti i dati necessari:

Poi la configurazione vera e propria sta in config/config.inc.php, le voci essenziali:

class="c_keyword"><?php
// IMAP e SMTP: puntano al NOSTRO server di posta, con TLS
$config['imap_host'] = 'ssl://mail.dominio.com:993';   // TLS implicito su 993
$config['smtp_host'] = 'tls://mail.dominio.com:587';   // submission con STARTTLS

// Database. Attenzione: i caratteri speciali della password vanno URL-encoded
// nella stringa DSN (es. '$' -> %24, spazio -> %20, '@' -> %40)
$config['db_dsnw'] = 'mysql://roundcube:password_segreta@localhost/roundcubemail';

// Chiave per cifrare la password IMAP in sessione: 32 caratteri casuali.
// L'installer ne genera una; se la scrivi a mano, CAMBIALA (non lasciare questa)
$config['des_key'] = 'metti_qui_32_caratteri_casuali__';

$config['product_name'] = 'Webmail';

// Plugin attivi (nella cartella plugins/)
$config['plugins'] = [
    'managesieve',             // filtri/regole sieve dall'interfaccia (sposta lo spam, ecc.)
    'password',                // cambio password dalla webmail
    'markasjunk',              // pulsante "spam / non spam": ri-addestra il Bayes (vedi bayes sorpra)
    'zipdownload',             // scarica più allegati in un unico zip
    'contextmenu',             // opzioni sulla posta con il tasto destro
    'show_additional_headers',
];

Da Roundcube 1.7 in poi il document root del web server va puntato alla sottocartella public_html/ (non più alla radice): quindi il virtualhost Apache/Nginx deve servire .../roundcubemail-1.7.x/public_html/.

Commenti

Nessun commento ancora. Sii il primo!

Lascia un commento