Exim Router ve Transport Zinciri: Bir Mesajın Teslim Kararı Adım Adım
Exim'i "başka bir main.cf" sanıp ayarlara körlemesine dokunmak en pahalı hatadır. Bir e-postanın adres çözümlemeden diske ya da uzak MX'e teslime kadar geçtiği router → transport karar zincirini, gerçek -bt ve -d çıktıları üzerinden teşhis odağıyla okumayı öğrenin.
EvilMail Team11 Temmuz 202612 dk okuma
Postfix'ten Exim'e geçen çoğu yönetici ilk gün aynı refleksi gösterir: bir main.cf benzeri bulup değer değiştirmeye çalışır. Exim'in main bölümü vardır, evet, ama sunucunun asıl karar mekanizması orada değildir. Bir mesajın kime, nasıl ve nereye teslim edileceği router → transport zincirinde belirlenir. Bu zinciri anlamadan yapılan her müdahale, "config'i kurcaladım, şimdi hiçbir şey teslim olmuyor" ile sonuçlanır.
Bu yazının derdi teori değil teşhis. "Şu router neden atlandı, mesaj neden bu transport'a düştü, adres neden unrouteable?" sorularına exim -bt ve -d çıktılarına bakarak kesin cevap verebilecek bir zihinsel model kuracağız.
Router ve transport: iki farklı soruya iki farklı cevap
Tek cümlede: router "bu adres kime ait ve hangi transport işlesin?" sorusunu yanıtlar; transport ise "fiilen nasıl teslim edilir?" işini yapar. Postfix'ten gelenlerin en sık yaptığı kategori hatası bu ikisini karıştırmaktır.
Exim Router ve Transport Yapılandırması Nasıl Çalışır? | evilmail — EvilMail Blog
Bir router karar verir: adres yerel mi, uzak mı, bir alias'a mı yönlensin, .forward dosyası mı var? Sonunda ya adresi bir transport'a bağlar (accept), ya reddeder, ya erteler.
Bir transport mekanizmadır: SMTP ile uzak host'a bağlanmak (smtp), Dovecot'a socket üzerinden vermek (lmtp), maildir'e yazmak (appendfile), harici komuta boru ile aktarmak (pipe).
Config'de bu iki dünya iki ayrı blokta yaşar:
text
begin routers
# adres çözümleme kararları burada, SIRA ÖNEMLİ
...
begin transports
# teslimat mekanizmaları burada, sıra önemsiz (isimle referanslanır)
...
Kritik ayrım: router'lar dosyadaki yazılı sıraya göre denenir. Postfix'teki gibi bir öncelik puanı yoktur, sıralama = mantık. Transport'lar ise sadece bir isim havuzudur; hangisinin çalışacağını router söyler (transport = remote_smtp), bu yüzden aralarındaki sıra hiçbir şey ifade etmez.
Zincir nasıl yürür: sıra, precondition'lar ve verdict'ler
Her alıcı adresi router listesinin en üstünden girer ve aşağı doğru akar. Her router önce precondition'larını kontrol eder — bunlar tutmazsa router hiç çalıştırılmadan atlanır (dikkat: bu "declined" değil, "skipped"). Başlıca precondition'lar:
domains — adresin domain'i bu listede mi?
local_parts — @ öncesi kısım eşleşiyor mu?
senders — gönderen kim?
require_files — belli bir dosya var mı (ör. ~/.forward)?
condition — herhangi bir string expansion true dönüyor mu?
local_part_prefix / local_part_suffix — user-foo gibi ekler.
Precondition'lar geçtiyse router çalışır ve bir verdict üretir:
decline — "bu benim işim değil", sıradaki router'a geç.
pass — bir sonraki (veya pass_router ile belirtilen) router'a atla.
defer — geçici sorun (4xx), mesaj kuyrukta kalır, retry kuralına göre tekrar denenir.
fail — kalıcı hata (5xx), bounce üretilir.
error — config hatası; genelde paniclog'a düşer.
İki değiştirici çok işe yarar: no_more bir router decline ederse *sonraki hiçbir router'ın denenmemesini* zorlar (adres orada ölür). unseen ise router'ın adresi işlerken bir kopya üretmesini sağlar — orijinal adres akmaya devam eder, arşiv/yönlendirme senaryolarında birebir. Bir de redirect router'ı vardır: adres üretir (.forward, alias) ve üretilen yeni adres(ler) zincirin başına geri döner, baştan router'lardan geçer.
Adresin izini sürmek: exim -bt ve -d ile canlı teşhis
Exim'in en güçlü tarafı, teslimatı gerçekten yapmadan kararı çalıştırıp gösterebilmesidir. -bt (address test) bunun için var:
Bu iki satır, "hangi router kabul etti, hangi transport işleyecek" sorusunun tam cevabıdır. unrouteable address görüyorsanız hiçbir router adresi kabul etmemiştir — genelde domains yanlış ya da router sırası bozuktur.
Kararın *neden* böyle olduğunu görmek için debug çıktısını route kategorisine daraltın:
Bu çıktı her router için tek tek "precondition tuttu mu, tutmadıysa neden atlandı, çalıştıysa ne döndü" bilgisini satır satır verir. Şuna benzer satırlar okursunuz:
text
--------> dnslookup router <--------
local_part=jane domain=example.com
checking domains
example.com in "! +local_domains"? ... yes (end of list)
calling dnslookup router
dnslookup router called for [email protected]
routed by dnslookup router
checking domains ... no gördüğünüz yer, o router'ın neden atlandığını söyler. Bu, "sıra yanlış" ve "precondition unuttum" hatalarının çoğunu tek başına çözer.
Config'i çalıştırmadan doğrulamak ve efektif ayarları dökmek için:
Akışın mantığı: önce alias'lara bakılır (postmaster → gerçek kişi), sonra kullanıcının `.forward` dosyası, ardından adres yerel değilse dnslookup ile uzak MX'e yönlenir, en sonda kalan yerel kullanıcı localuser ile Dovecot'a teslim edilir.
Sıra neden bu kadar kritik? dnslookup router'ındaki domains = ! +local_domains, "yerel olmayan her şeyi ben alırım" demektir. system_aliases'ı dnslookup'tan sonraya alsaydınız, yerel bir alias uzak alan sanılıp yanlış yönlenebilirdi. no_more ise ince bir nokta ve burada tehlikeli değildir: yerel bir adres dnslookup'a geldiğinde domains precondition'ı tutmaz ve router çalıştırılmadan atlanır. no_more yalnızca router gerçekten çalışıp *decline* ettiğinde devreye girer, atlanan router'da değil — bu yüzden yerel adres sorunsuzca localuser'a iner. Buna karşılık MX'i bulunamayan uzak bir adreste dnslookup fiilen çalışıp decline eder; işte o an no_more doğru olanı yapıp adresi unrouteable olarak sonlandırır ve mesajın yanlışlıkla localuser'a düşüp yerel teslim edilmeye çalışılmasını engeller.
Sanal alan (virtual domain) senaryosunda araya bir router daha girer — genelde alan/kutu eşlemesini bir lookup'tan çözüp doğrudan LMTP transport'una bağlayan bir router. evilmail yığınında yerel kutular Dovecot'a LMTP ile teslim edilir; kutular /var/mail/vhosts/{domain}/{username}/ altında 5000:5000 sahipliğiyle tutulur.
Transport tarafı: teslimatın fiziği
Router "kim" ve "hangi transport" dedikten sonra iş transport'a geçer. Üç yaygın transport:
remote_smtp — uzak alanlara SMTP. Deliverability'nin çoğu burada ayarlanır:
Burada DKIM imzalama transport seviyesindedir — router değil transport imzalar, çünkü imza fiili giden bağlantıda eklenir. hosts_try_dane = *, DNSSEC'li bir alanda TLSA kaydı varsa oportünistik TLS'i, sertifikası DANE ile doğrulanan zorunlu TLS'e yükseltir; TLSA kaydı yoksa teslimat normal şekilde sürer.
LMTP tercih edilir çünkü sieve filtreleme, quota ve indeksleme Dovecot tarafında tek noktadan yönetilir; Exim'in dosyaya yazıp Dovecot'un sonradan görmesini beklemek yerine teslimatı doğrudan Dovecot'a devreder.
appendfile — Dovecot LMTP kullanmıyorsanız maildir'e doğrudan yazar:
pipe ise mesajı harici bir komuta (spam eğitimi, ticket sistemi, script) borular. Hangisinin seçileceğini her zaman router'ın transport = satırı ya da redirect'in ürettiği özel adres belirler.
Sık yapılan hatalar ve hızlı teşhis checklist'i
Exim hata ayıklamada tekrar tekrar karşılaşılan tuzaklar:
`unrouteable address` — hiçbir router adresi kabul etmedi. Önce exim -d-all+route -bt <adres> ile hangi router'ın nerede takıldığına bakın; genelde domains listesi yanlış ya da router yanlış sırada.
"no transport set" — bir accept router'ına transport = bağlamayı unuttunuz. Router adresi kabul ediyor ama nasıl teslim edileceğini bilmiyor.
`no_more` yanlış yerde — fiilen çalışıp decline eden bir router'da no_more varsa, altındaki tüm teslimat ölür. Yerel teslimatı yakalayan router'ları no_more'lu bir router'ın *altında* bırakmayın.
Precondition unutmak → retry döngüsü — domains filtresi olmayan bir dnslookup router'ı yerel adresleri de dışarı yollamaya çalışıp defer'de takılır.
`self = local` döngüsü — MX kendini gösteriyorsa ve self doğru ayarlı değilse mesaj kendine teslim edilmeye çalışır.
`paniclog` dolu — bu dosya boş olmalı. İçinde bir şey varsa config ya da çalışma zamanı hatası var demektir, önce oraya bakın.
Sorun anında koşturulacak komutlar:
bash
exim -bV # config syntax + sürüm sağlam mı
exim -bt user@domain # teslimat kararını çalıştırmadan gör
exim -d-all+route -bt user@dom # kararın NEDENİ satır satır
exim -bpc # kuyrukta kaç mesaj var
exim -bp # kuyruk listesi
exiqgrep -z -i # sadece donmuş (frozen) mesaj id'leri
exim -Mvl <msgid> # tek mesajın tüm log geçmişi
exim -Mrm <msgid> # mesajı kuyruktan sil
tail -f /var/log/exim4/mainlog /var/log/exim4/paniclog
Debian/Ubuntu'nun split config'inde router ve transport'lar /etc/exim4/conf.d/router/ ve /etc/exim4/conf.d/transport/ altında numaralı dosyalarda durur ve update-exim4.conf ile tek dosyaya derlenir; monolitik kurulumlarda hepsi /etc/exim4/exim4.conf.template (ya da /etc/exim/exim.conf) içindedir. Hangi modda olduğunuzu bilmeden dosya düzenlemek, değişikliğin hiç uygulanmaması demektir — düzenledikten sonra mutlaka exim -bP router <isim> ile efektif değeri doğrulayın.
Exim'i "postfix gibi" değil, "adresin sırayla router'lardan geçip bir transport'a bağlandığı bir karar makinesi" olarak gördüğünüz an, her unrouteable ve her beklenmedik teslimat tek bir -bt çıktısıyla açıklanabilir hale gelir. Zihinsel model doğru olduğunda config zaten okunur.