Postfix'te catch-all, çoklu yönlendirme ve takma ad zincirleri: döngüye girmeden
evilmail.pro'ya bir satır catch-all ekledik, ertesi gün destek@ ve fatura@ kutularına mail düşmedi. Sebep tek bir kelime: sıra. virtual_alias_maps'in lookup mantığını, çoklu fan-out'u, zincirleri ve 5.4.6 forwarding loop bounce'unu çalıştırılabilir postmap komutlarıyla baştan sona kuruyoruz.
EvilMail Team10 Temmuz 202614 dk okuma
Salı günü evilmail.pro'ya tek satırlık bir catch-all ekledik: domaine gelen ne varsa admin@'e düşsün, hiçbir mail kaybolmasın. Çarşamba sabahı destek ekibi aradı — [email protected] ve [email protected] kutularına 18 saattir hiçbir mail düşmüyordu. Kutular boştu, log'da bounce yoktu, mesajlar sessizce admin@'e yutulmuştu.
Sebep egzotik bir bug değil, tek bir kelime: sıra. virtual_alias_maps, virtual_mailbox_maps'ten *önce* çalışır. Yani Postfix, [email protected] adresini gerçek mailbox'a teslim etmeyi düşünmeden önce alias tablosuna bakar, orada @evilmail.pro → [email protected] wildcard'ını görür, adresi yeniden yazar ve mailbox aşamasına hiç gelmez. Catch-all bir "kolaylık" değil; yanlış yere koyduğunuzda ya bütün spam'i tek kutuya yığan ya da geçerli kullanıcıları sessizce öldüren bir ayak kapanıdır.
Bu yazı o kapanı ve peşinden gelen zincir/döngü tuzaklarını çözer. Her bölümde çalıştırabileceğiniz postmap/postconf komutu, gerçek main.cf satırı ve prod MySQL örneği var.
virtual_alias_maps aslında nasıl okunur
Postfix bir zarf alıcısını çözmek için tabloya tek bir string sormaz — spesifikten genele doğru bir anahtar dizisi üretir ve ilk eşleşeni alır. [email protected] için sorgu sırası şudur:
[email protected] — uzantı soyulmuş hali (recipient_delimiter = + ise)
@evilmail.pro — domain wildcard, yani catch-all
En spesifik eşleşme kazanır ve arama orada durur. [email protected] için bir satır varsa @evilmail.pro catch-all'ına hiç bakılmaz. Bütün mesele bu: catch-all listenin *en altındaki* güvenlik ağıdır, üstündeki her explicit satır ondan önce yakalar.
İki noktayı kafaya kazımak gerekiyor. Birincisi, birden çok map verirseniz (hash: ve pcre: yan yana) dış döngü map sırası değil, anahtar önceliğidir: Postfix önce en spesifik anahtarı listedeki tüm map'lerde sırayla arar, bulamazsa bir sonraki anahtara geçer. Yani hash'te olmayan spesifik bir adres pcre'de bulunursa, alttaki @domain catch-all'ına düşmeden onu yakalar — daha spesifik anahtar, hangi map'te olursa olsun kazanır. İkincisi ve kritik olanı: bu çözüm cleanup daemon aşamasında, yani mesaj kuyruğa girerken olur — mailbox teslimatından çok önce.
Catch-all'ı kurmak — ve mailbox-domain tuzağı
Domain sadece bir alias domain'iyse, yani hiçbir gerçek mailbox barındırmıyorsa, catch-all zararsızdır. main.cf'te:
Tuzak, domain aynı zamanda mailbox domain olduğunda kapanır — ki evilmail.pro'da durum tam olarak buydu. virtual_mailbox_domains'te listelenen bir domaine catch-all koyarsanız, @domain wildcard'ı destek@ ve fatura@ dahil *her adresi* yakalar; çünkü Diyagram 1'de gördüğünüz gibi alias çözümü mailbox'tan önce çalışır ve o adresler için daha spesifik bir alias satırı yoktur.
İki çözüm var. Temizi: catch-all'ı asla mailbox domain'e koymayın.virtual_alias_domains ile virtual_mailbox_domains kesişmemeli — bu Postfix'te teslimatı kıran klasik yanlış konfigürasyondur. İkincisi, mimari gereği aynı domainde hem gerçek kutu hem catch-all istiyorsanız, geçerli kullanıcılar için explicit passthrough satırları ekleyin:
[email protected] exact-match olduğu için anahtar dizisinde @evilmail.pro'dan önce gelir ve adresi kendisine map'ler — yani "değiştirme, mailbox'a bırak" demiş olursunuz. Catch-all sadece geriye kalanları yakalar. Derleyip doğrulayalım:
Sol taraftaki [email protected] ile sağ listedeki [email protected] aynı görünse de sonsuz döngü olmaz — Postfix bir adresi kendi genişleme sonucunda ikinci kez görürse tekrar açmaz, o dalı mailbox'a bırakır. Yani mesaj hem veli'nin yerel kutusuna düşer hem [email protected]'a gider.
Bir uyarı: `postmaster` ve `abuse`'u asla catch-all'a yem etmeyin. RFC 2142 bu rol adreslerinin, RFC 5321 ise özellikle postmaster'ın erişilebilir olmasını zorunlu kılar. Bunları explicit, gerçek bir kutuya işaret eden satırlar olarak tutun; yoksa domaininizin şikâyet ve reputation trafiği catch-all'ın karanlığında kaybolur. Ayrıca virtual_alias_expansion_limit (varsayılan 1000) tek bir mesajın açılabileceği toplam adres sayısını sınırlar — büyük dağıtım listeleri için bunu bilerek yönetin.
Zincirler ve özyineleme: A → B → C
Bir alias'ın hedefi başka bir alias olabilir; Postfix zinciri recursion ile çözer. Cross-domain bir örnek — evilmail.pro'daki bir adres evilmail.cloud'a, oradan da gerçek kutuya:
virtual_alias_recursion_limit = 1000 — bir adresin kaç kat *derine* açılabileceği (A→B→C→... zincir uzunluğu)
virtual_alias_expansion_limit = 1000 — tek mesaj için üretilen toplam *genişlik*, yani nihai adres sayısı
Varsayılanlar neredeyse hep doğrudur. Recursion limitini düşürmek, kendinizi çok uzun zincirlerden korumak isterseniz mantıklı; artırmak yalnızca gerçekten derin, meşru bir yönlendirme hiyerarşiniz varsa. Kritik ayrım şu: terminal (sonlanan) zincir bir mailbox'ta biter; sonsuz zincir kendine geri döner. Limit ikincisini yakalar ama tasarım hatasını çözmez.
Döngüler: neden olur, Postfix nasıl yakalar
Klasik senaryo: birisi info@'yu support@'a, sonra support@'u geri info@'ya yönlendirir. Ya da iki domain karşılıklı catch-all'la birbirine bağlanır. Postfix expansion sayacını artıra artıra limite dayanır ve mesajı `5.4.6` "mail forwarding loop for <addr>" hatasıyla bounce eder. Sessiz kayıp değil — log'da görürsünüz — ama yine de teslim edilememiş bir mail ve rahatsız bir kullanıcı demek.
Önleme birkaç basit tasarım kuralına iner. Her zincir mutlaka bir mailbox'ta sonlansın — hiçbir zincirin son halkası başka bir alias olmasın. İki catch-all'ı birbirine bağlamayın; iki domain arası çapraz yönlendirme yapıyorsanız en az bir uçta zincir gerçek bir kutuya toprağa insin. expansion_limit'in koruma katmanı olduğuna güvenin ama tasarımda döngü bırakmayın — sayaç sizi bounce'tan kurtarmaz, sadece felaketin sessiz olmamasını sağlar.
Kural tabanlı catch-all: PCRE maps
hash: map'i sabit anahtarlar içindir. Rol adreslerini korurken gerisini regex'le yönetmek isterseniz pcre: devreye girer. Kritik fark: PCRE'de ilk eşleşen desen kazanır, en spesifik değil. Yani sıralama tamamen sizin elinizde ve /.*/ her zaman en altta olmalı.
İlk desen rol adreslerini gerçek bir kutuya sabitler; ikinci desen kalan her şeyi tek noktaya toplar. Sırayı ters çevirseydiniz /.+/ her şeyi önce yakalar ve rol adreslerini de yutardı. PCRE map'i postmap ile derlemez, doğrudan test edersiniz:
Gerisini tamamen çöpe atmak isterseniz hedef yerine Postfix'in discard transport'unu ya da bir smtpd_recipient_restrictions REJECT'ini kullanın; alias tablosunda "hiçbir yere" diye bir hedef yoktur.
Prod kurulum: MySQL ile
evilmail mimarisi alias'ları düz dosyada değil mailserver veritabanında tutuyor. Postfix'e bir mysql: map dosyası veriyoruz:
ini
# /etc/postfix/mysql-virtual-alias-maps.cf
user = mailuser
password = your_strong_db_password
hosts = 127.0.0.1
dbname = mailserver
query = SELECT destination FROM virtual_aliases WHERE source='%s'
İşin özü: Postfix %s'i Diyagram 1'deki anahtar dizisinin her adımı için ayrı ayrı çağırır — yani sorgu hem [email protected] hem @evilmail.pro ile çalışır. Dolayısıyla catch-all için ekstra bir query gerekmez; virtual_aliases tablosuna source = '@evilmail.pro' satırı eklemeniz yeter. Explicit passthrough'lar için de aynı tabloda source = '[email protected]', destination = '[email protected]' satırları.
Prod ipuçları: source kolonuna unique index koyun (lookup her mesajda çalışır), bir active/enabled boolean kolonu ekleyip query'yi ... WHERE source='%s' AND active=1 yapın ki bir alias'ı silmeden devre dışı bırakabilesiniz. En önemlisi — tablo değişiklikleri anında etki eder, postfix reload gerekmez, çünkü Postfix her lookup'ta canlı sorgu atar. Sadece main.cf veya .cf map dosyasını değiştirdiğinizde reload gerekir.
Devreye alma checklist'i
postconf -n | grep alias — virtual_alias_maps ve virtual_alias_domains beklediğiniz gibi mi
virtual_alias_domains ile virtual_mailbox_domainskesişmiyor — aynı domain ikisinde birden varsa teslimat kırılır
Catch-all'ın *üstünde* her geçerli mailbox için explicit passthrough satırı var (mailbox domain'de catch-all kullanıyorsanız)
postconf virtual_alias_expansion_limit ve virtual_alias_recursion_limit — varsayılan 1000, dağıtım listeleriniz altında mı
postmaster ve abuse catch-all'a değil, gerçek bir kutuya gidiyor (RFC 2142 / 5321)
Canlıya almadan test mesajı atın; alıcıdaki Received: zincirini ve tekrarlayan Delivered-To: başlıklarını sayın — döngü buradan sırıtır
grep 5.4.6 /var/log/mail.log ve mailq temiz — forwarding loop bounce'u yok
Bu sekiz maddeyi geçen bir catch-all yapısı, ne gerçek kutuları yutar ne de bir döngüye takılıp 5.4.6 yağdırır. Kural her zaman aynı: en spesifik satır üstte, catch-all en altta, ve her zincir bir mailbox'ta toprağa insin.