Perl Scripts to manage srs forward and bounce in postfix.
Sistema di forward email per Postfix con supporto completo SRS (Sender Rewriting Scheme), gestione automatica dei bounce e notifiche agli utenti.
- Problema
- Soluzione
- Caratteristiche
- Architettura
- Requisiti
- Installazione
- Configurazione
- Customizzazione
- Troubleshooting
- Caso d'Uso
- Licenza
Quando un server di posta inoltra email verso destinazioni esterne, puΓ² violare le policy SPF/DKIM/DMARC del mittente originale, causando il rifiuto delle email.
1. Mittente esterno (es: facebook.com) β Tuo server β Casella locale (user@tuodominio.it)
2. Casella locale ha forward configurato β Destinazione esterna (user@gmail.com)
3. Gmail RIFIUTA l'email perchΓ© il tuo server si "spaccia" per facebook.com
4. Violazione SPF/DMARC del dominio originale
Risultato: Email perse, bounce non gestiti, utenti non informati.
Questo sistema implementa SRS (Sender Rewriting Scheme) per riscrivere il mittente durante il forward, rispettando le policy anti-spam e gestendo automaticamente i bounce.
1. Email arriva: sender@example.com β user@tuodominio.it
2. Sistema rileva forward attivo
3. Riscrive mittente: SRS0=hash=timestamp=domain=ID=sender@bounce.tuodominio.it
4. Inoltra con mittente riscritto β Gmail ACCETTA β
5. In caso di bounce:
- Decodifica SRS
- Registra nel database
- Invia notifica all'utente proprietario del forward
- Auto-disabilita forward problematici
- β ConformitΓ SPF/DKIM/DMARC: Forward senza violare policy
- β Gestione automatica bounce: Tracking completo e notifiche
- β Protezione anti-loop: Previene loop infiniti di notifiche
- β Auto-disabilitazione: Forward problematici disabilitati automaticamente
- β Database MySQL: Tracking completo di forward e bounce
- β Notifiche utente: Email informative in caso di problemi
- β Chiavi rotabili: Sistema di chiavi HMAC per maggiore sicurezza
- β Compatibile: Integrazione trasparente con sistemi esistenti (vacation, alias, ecc.)
βββββββββββββββββββ
β Email In β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β Postfix β
β Virtual Alias β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ ββββββββββββββββ
β MySQL Check ββββββΆβ Forward DB β
ββββββββββ¬βββββββββ ββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Transport Map β
ββββββββββ¬βββββββββ
β
ββββββ΄βββββ
β β
βΌ βΌ
ββββββββββ ββββββββββ
βForward β βBounce β
βScript β βScript β
βββββ¬βββββ βββββ¬βββββ
β β
βΌ βΌ
ββββββββββ ββββββββββ
βExternalβ βUser β
βDeliveryβ βNotify β
ββββββββββ ββββββββββ
- srsforward.pl: Gestisce il forward con riscrittura SRS
- srsbounce.pl: Processa i bounce e notifica gli utenti
- Database MySQL: Tracking forward, bounce log, chiavi SRS
- Postfix: Transport maps e virtual alias integration
- Postfix (testato su 3.5+)
- MySQL/MariaDB (testato su MySQL 5.5+)
- Perl 5.x con moduli:
DBIDBD::mysqlMIME::ParserMIME::EntityMail::MessageMail::Transport::SMTPDigest::HMAC_SHA1Sys::Syslog
# CentOS/RHEL
yum install -y perl-DBI perl-DBD-MySQL perl-MIME-tools perl-MailTools perl-Digest-HMAC
# Debian/Ubuntu
apt-get install -y libdbi-perl libdbd-mysql-perl libmime-tools-perl libmailtools-perl libdigest-hmac-perl-- Crea database (se non esiste giΓ )
CREATE DATABASE IF NOT EXISTS postfix CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Tabella forward
CREATE TABLE `forward` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`goto` varchar(255) NOT NULL,
`bounce_domain` varchar(255) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT 1,
`bounce_count` int(11) NOT NULL DEFAULT 0,
`last_bounce` datetime DEFAULT NULL,
`disabled_reason` varchar(255) DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
KEY `idx_active` (`active`),
KEY `idx_bounce_domain` (`bounce_domain`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tabella chiavi SRS
CREATE TABLE `srs_keys` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`secret` varchar(64) NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT 1,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_active` (`active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tabella log bounce
CREATE TABLE `bounce_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`forward_id` int(11) NOT NULL,
`bounce_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`original_sender` varchar(255) NOT NULL,
`bounce_reason` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_forward_id` (`forward_id`),
KEY `idx_bounce_time` (`bounce_time`),
CONSTRAINT `fk_forward_id` FOREIGN KEY (`forward_id`) REFERENCES `forward` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Genera chiave SRS
INSERT INTO srs_keys (secret, active) VALUES (SHA2(CONCAT(RAND(), NOW()), 256), 1);# Crea directory per gli script
mkdir -p /var/spool/forward
# Copia gli script
cp srsforward.pl /var/spool/forward/
cp srsbounce.pl /var/spool/forward/
# Crea utente di sistema
useradd -r -d /var/spool/forward -s /bin/false forward
# Imposta permessi
chown forward:forward /var/spool/forward/*.pl
chmod 755 /var/spool/forward/*.pl
# Verifica sintassi
perl -c /var/spool/forward/srsforward.pl
perl -c /var/spool/forward/srsbounce.plCrea /etc/postfix/hash/transport_srs.cf:
# Dominio per trigger forward SRS
myforward.yourdomain.com myforward:
# Dominio per gestione bounce SRS
srs.yourdomain.com srsbounce:
Compila e configura:
postmap /etc/postfix/hash/transport_srs.cf
# Aggiungi a main.cf
postconf -e "transport_maps = hash:/etc/postfix/hash/transport_srs.cf"Aggiungi a /etc/postfix/master.cf:
# Servizio forward SRS
myforward unix - n n - - pipe
flags=Rq user=forward argv=/var/spool/forward/srsforward.pl -f ${sender} -- ${recipient}
# Servizio bounce SRS
srsbounce unix - n n - - pipe
flags=Rq user=forward argv=/var/spool/forward/srsbounce.pl -f ${sender} -- ${recipient}
Crea /etc/postfix/mysql/mysql-virtual-alias.cf:
user = postfix_user
password = your_password
hosts = mysql_host
dbname = postfix
query = SELECT CONCAT(id, '@myforward.yourdomain.com') as destination
FROM forward
WHERE username = '%s'
AND active = 1
Aggiungi a main.cf:
postconf -e "virtual_alias_maps = mysql:/etc/postfix/mysql/mysql-virtual-alias.cf"postfix check
postfix reloadModifica le variabili di configurazione in entrambi gli script:
# === CONFIGURAZIONE ===
my $DB_HOST = 'your-mysql-host';
my $DB_PORT = 3306;
my $DB_NAME = 'postfix';
my $DB_USER = 'postfix_user';
my $DB_PASS = 'your_password';
my $SRS_TIMEOUT = 21; # Giorni validitΓ SRS
my $SMTP_HOST = 'your-relay-host';
my $SMTP_PORT = 25;# === CONFIGURAZIONE ===
my $DB_HOST = 'your-mysql-host';
my $DB_PORT = 3306;
my $DB_NAME = 'postfix';
my $DB_USER = 'postfix_user';
my $DB_PASS = 'your_password';
my $SRS_TIMEOUT = 21;
my $MAX_BOUNCE_COUNT = 5; # Bounce prima di disabilitare
my $SMTP_HOST = 'your-mail-server'; # Per inviare notifiche
my $SMTP_PORT = 25;Configura i record DNS per il dominio bounce:
srs.yourdomain.com. IN MX 10 mail.yourdomain.com.
srs.yourdomain.com. IN A YOUR_SERVER_IP
Il sistema Γ¨ progettato per essere flessibile. Ecco come adattarlo:
Se hai giΓ una tabella alias in Postfix, puoi far generare automaticamente i record forward tramite trigger MySQL:
DELIMITER $$
CREATE TRIGGER auto_create_srs_forward
AFTER INSERT ON alias
FOR EACH ROW
BEGIN
DECLARE dest_domain VARCHAR(255);
DECLARE is_internal INT DEFAULT 0;
-- Estrai dominio destinazione
SET dest_domain = SUBSTRING_INDEX(SUBSTRING_INDEX(NEW.goto, '@', -1), '>', 1);
-- Verifica se Γ¨ interno
SELECT COUNT(*) INTO is_internal
FROM domain
WHERE domain = dest_domain AND active = 1;
-- Se esterno, crea forward SRS
IF is_internal = 0 AND NEW.goto LIKE '%@%' THEN
INSERT INTO forward (username, goto, bounce_domain, active)
VALUES (NEW.address, NEW.goto, 'srs.yourdomain.com', 1)
ON DUPLICATE KEY UPDATE goto = NEW.goto, active = 1;
END IF;
END$$
DELIMITER ;Modifica la funzione send_bounce_notification in srsbounce.pl per personalizzare il messaggio:
my $body_text = <<EOF;
Gentile utente,
Il tuo forward email ha riscontrato un problema nella consegna.
Dettagli:
- Mittente: $original_sender
- Destinazione: $failed_destination
- Motivo: $bounce_reason
[Personalizza il messaggio qui]
Cordiali saluti,
Il Team di Sistema
EOFSe usi PostfixAdmin, puoi aggiungere funzionalitΓ per gestire i forward SRS dall'interfaccia web. Esempio PHP:
<?php
function createExternalForward($username, $external_email) {
// Verifica se destinazione Γ¨ esterna
$dest_domain = substr(strrchr($external_email, '@'), 1);
$stmt = $pdo->prepare("SELECT 1 FROM domain WHERE domain = ?");
$stmt->execute([$dest_domain]);
if (!$stmt->fetch()) {
// Destinazione esterna - crea forward SRS
$stmt = $pdo->prepare("
INSERT INTO forward (username, goto, bounce_domain, active)
VALUES (?, ?, 'srs.yourdomain.com', 1)
");
$stmt->execute([$username, $external_email]);
return $pdo->lastInsertId();
}
return false;
}
?>Se hai piΓΉ server MDA dietro load balancer:
# In srsbounce.pl, usa il VIP del load balancer
my $SMTP_HOST = 'mail-mda-vip.yourdomain.com'; # HAProxy VIPCambia il numero di bounce prima della disabilitazione automatica:
# In srsbounce.pl
my $MAX_BOUNCE_COUNT = 10; # Default: 5Modifica il livello di logging in syslog:
# Debug verboso
syslog(LOG_DEBUG, "Messaggio di debug dettagliato");
# Solo errori
syslog(LOG_ERR, "Errore critico");# 1. Test connessione database
mysql -u postfix_user -p postfix -e "SELECT * FROM forward LIMIT 1"
# 2. Test sintassi script
perl -c /var/spool/forward/srsforward.pl
perl -c /var/spool/forward/srsbounce.pl
# 3. Test lookup Postfix
postmap -q "user@domain.com" mysql:/etc/postfix/mysql/mysql-virtual-alias.cf
# 4. Test transport
postmap -q "myforward.yourdomain.com" hash:/etc/postfix/hash/transport_srs
# 5. Monitor log in tempo reale
tail -f /var/log/maillog | grep -E "(srsforward|srsbounce)"-- Verifica forward attivo
SELECT * FROM forward WHERE username = 'user@domain.com' AND active = 1;
-- Verifica chiave SRS
SELECT * FROM srs_keys WHERE active = 1;# Verifica DNS
dig srs.yourdomain.com MX
# Verifica transport
postmap -q "srs.yourdomain.com" hash:/etc/postfix/hash/transport_srs# Test SMTP
telnet your-smtp-host 25
# Verifica log
grep "Notifica bounce" /var/log/maillog-- Status generale forward
SELECT
id, username, goto,
bounce_count, active,
last_bounce, disabled_reason
FROM forward
ORDER BY bounce_count DESC;
-- Bounce recenti
SELECT
f.username, f.goto,
b.bounce_time, b.bounce_reason
FROM bounce_log b
JOIN forward f ON b.forward_id = f.id
WHERE b.bounce_time > DATE_SUB(NOW(), INTERVAL 24 HOUR)
ORDER BY b.bounce_time DESC;
-- Reset bounce counter
UPDATE forward SET bounce_count = 0, last_bounce = NULL WHERE id = X;
-- Riattiva forward
UPDATE forward SET active = 1, disabled_reason = NULL WHERE id = X;Sistema di posta aziendale basato su:
- 3 server MDA (Mail Delivery Agent) dietro HAProxy
- Postfix con backend MySQL per virtual domains
- Migliaia di caselle email con necessitΓ di forward esterni
- Integrazione con sistema vacation esistente per risposte automatiche
- Dominio forward fittizio:
myforward.semplify.cloud - Dominio bounce unificato:
srs.semplify.cloud(unico per tutti i clienti) - Database condiviso: Cluster MySQL per alta disponibilitΓ
- Load balancing: HAProxy distribuisce su 3 MDA
Email: sender@external.com β user@cliente.it
β
1. MDA riceve via HAProxy (VIP: mailcl-mda.mailcl.semplify.net)
2. Postfix query MySQL β trova forward attivo (ID 65)
3. Alias rewrite: user@cliente.it β 65@myforward.semplify.cloud
4. Transport map β servizio myforward
5. srsforward.pl:
- Genera SRS: SRS0=hash=timestamp=domain=65=sender@srs.semplify.cloud
- Inoltra a destinazione@gmail.com
6. Se bounce:
- Gmail β bounce a SRS0=...@srs.semplify.cloud
- DNS MX β ritorna su MDA
- Transport β servizio srsbounce
- srsbounce.pl:
* Decodifica ID=65
* Query DB β trova user@cliente.it
* Invia notifica a user@cliente.it
* Registra in bounce_log
* Auto-disabilita se troppi bounce
Il sistema convive perfettamente con:
- Vacation/Autoreply: Script vacation riconosce header anti-loop
- Alias multipli: Supporta consegna a piΓΉ destinatari simultanei
- Domain aliasing: Gestisce alias di dominio
- Quota management: Non interferisce con limiti di storage
-- KPI Forward
SELECT
COUNT(*) as total_forward,
SUM(CASE WHEN active = 1 THEN 1 ELSE 0 END) as active,
SUM(CASE WHEN active = 0 THEN 1 ELSE 0 END) as disabled,
AVG(bounce_count) as avg_bounces
FROM forward;
-- Top bounce domains
SELECT
SUBSTRING_INDEX(goto, '@', -1) as domain,
COUNT(*) as forward_count,
SUM(bounce_count) as total_bounces
FROM forward
GROUP BY domain
ORDER BY total_bounces DESC
LIMIT 10;
-- Bounce trend
SELECT
DATE(bounce_time) as date,
COUNT(*) as bounce_count,
COUNT(DISTINCT forward_id) as affected_forwards
FROM bounce_log
WHERE bounce_time > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY DATE(bounce_time)
ORDER BY date;- Un processo Perl per ogni email forward (overhead minimo ma presente)
- Indirizzo SRS valido per 21 giorni (configurabile)
- Log bounce memorizzati 30 giorni (configurabile)
- Backup regolare del database
forwardebounce_log - Monitoring dei bounce rate per dominio
- Rotazione chiavi SRS periodica (opzionale ma consigliato)
- Cleanup dei log vecchi automatico
- Alert su forward disabilitati automaticamente
- Testato con 10.000+ forward attivi
- Overhead medio: < 50ms per email
- Database load: < 5 query per forward
Contributi, issue e feature request sono benvenuti!
- Fork del progetto
- Crea un branch per la feature (
git checkout -b feature/AmazingFeature) - Commit delle modifiche (
git commit -m 'Add some AmazingFeature') - Push sul branch (
git push origin feature/AmazingFeature) - Apri una Pull Request
Questo progetto Γ¨ rilasciato sotto licenza MIT. Vedi il file LICENSE per i dettagli.
Io, e ausilio di Claude Code. Sviluppato per gestire forward email complessi in ambienti Postfix multi-server con alta disponibilitΓ .
Per domande o supporto:
- Apri una Issue su GitHub
- Consulta la sezione Troubleshooting
- Verifica i log in
/var/log/maillog
Versione: 2.0
Ultimo aggiornamento: Ottobre 2025
MIT License
Copyright (c) 2025 [Your Name/Organization]
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.