OpenSMTPD ile Tek Dosyada Üretime Hazır Posta Sunucusu: smtpd.conf'u Satır Satır Sökmek
Postfix'in main.cf/master.cf labirentine karşı OpenSMTPD tüm posta akışını tek bir smtpd.conf içinde, okunabilir bir gramerle toplar. Bu yazı o gramerin zihinsel modelini kurar, üretim conf'unu satır satır söker ve PTR/SPF/DKIM/DMARC ile Gmail'in 550'sinden kaçmayı anlatır.
EvilMail Team10 Temmuz 202613 dk okuma
Postfix'te tek bir relay kuralı eklemek için kaç dosyaya dokunuyorsun? main.cf'te transport haritasını gösterir, master.cf'te servisi tanımlar, transport tablosunu yazar, postmap ile hash'ler, sonra virtual ile virtual_alias_maps arasında hangisinin hangi postayı tuttuğunu wiki'den kontrol edersin. Exim daha da beter: yapılandırma yavaş yavaş kendi programlama diline dönüşen bir ACL yığınına evrilir.
OpenSMTPD bu dağınıklığı reddeder. Dinleme, TLS, filtre, teslim ve relay — posta yolunun tamamı tek bir /etc/smtpd/smtpd.conf içinde, neredeyse düz İngilizce gibi okunan bir gramerle ifade edilir. Ama asıl kazanç "az satır" değil, declarative gramer modelidir: listen, match, action
,
table
,
pki
,
filter
bloklarının nasıl birbirine bağlandığını bir kez kavradığında, 25 satırlık bir conf ile DKIM imzalayan, TLS zorunlu tutan, rspamd'den geçen üretime hazır bir sunucu ayağa kaldırırsın.
Bir uyarı: OpenSMTPD 6.4 (2018) ile "v2" gramerine geçti. İnternette hâlâ dolaşan accept ... deliver to ... biçimindeki eski örnekler yeni gramerle çalışmaz. Buradaki her şey v2'dir ve güncel sürümde doğrudan çalışır.
smtpd.conf gramerini bir zihinsel modele oturtmak
Altı yapı taşı var ve hepsi birbirine referans veriyor:
`pki` — bir hostname'i sertifika ve özel anahtar dosyalarına bağlar. TLS'in kimlik katmanı.
`table` — veri kaynağı. Alias'lar, SMTP kimlik bilgileri, virtual eşlemeler burada tanımlanır; bir dosya, passwd formatı veya bir veritabanı olabilir.
`filter` — postayı işleyen harici süreçler. DKIM imzalama ve rspamd taraması buradan geçer.
`listen` — giriş kapıları. Hangi arayüzde, hangi portta, hangi TLS moduyla dinlediğini söyler; bir pki'ye ve bir filter'a referans verir.
`action` — teslim eylemi. Maildir'e yaz ya da relay ile dışarı gönder. Bir table'a referans verebilir.
`match` — kural. from/for/auth koşullarını bir action'a bağlar.
Kritik nokta şu: `match` kuralları yukarıdan aşağı değerlendirilir ve ilk eşleşen kazanır. Bu yüzden sıralama bir stil tercihi değil, güvenlik meselesidir — yanlış sıra open relay demektir. Aşağıdaki diyagram altı bloğun bağ grafiğini tek bakışta veriyor.
Kurulum ve dizin düzeni
OpenBSD'de smtpd taban sistemle gelir; yapman gereken tek şey servisi açmak:
Yol farkına dikkat: OpenBSD conf'u /etc/mail/smtpd.conf'ta, Linux portable sürümü /etc/smtpd/smtpd.conf'ta arar. Alias, credentials ve virtual tabloları geleneksel olarak /etc/mail/ altında durur.
Virtual teslim için ayrı bir sistem kullanıcısı aç ve maildir kökünü hazırla:
Kuruluma başlamadan önce port çakışmasını kontrol et — sunucuda hâlâ Postfix dinliyorsa 25/587 çakışır:
bash
ss -tlnp | grep -E ':25|:465|:587'
Üretim conf'u — satır satır
İşte üretimde çalışan tam bir smtpd.conf. Oyuncak değil: implicit TLS, submission auth, DKIM imzalama ve doğru sıralanmış match kuralları içeriyor.
conf
pki mail.example.com cert "/etc/ssl/mail.example.com.crt"
pki mail.example.com key "/etc/ssl/private/mail.example.com.key"
table aliases file:/etc/mail/aliases
table credentials file:/etc/mail/credentials
table virtuals file:/etc/mail/virtuals
filter dkimsign proc-exec \
"filter-dkimsign -d example.com -s mail -k /etc/mail/dkim/mail.key" \
user _dkimsign group _dkimsign
listen on eth0 port 25 tls pki mail.example.com \
hostname mail.example.com
listen on eth0 port 465 smtps pki mail.example.com \
hostname mail.example.com auth <credentials> filter dkimsign
listen on eth0 port submission tls-require pki mail.example.com \
hostname mail.example.com auth <credentials> filter dkimsign
action "local" maildir alias <aliases>
action "domain" maildir "/var/vmail/%{dest.domain}/%{dest.user}" virtual <virtuals>
action "outbound" relay helo mail.example.com
match from any for domain "example.com" action "domain"
match from local for local action "local"
match auth from any for any action "outbound"
match from local for any action "outbound"
Şimdi mantık gruplarını açalım.
`filter dkimsign` bloğu postayı mail selector'ıyla imzalar. Bunu yalnızca submission listener'larına (465/587) bağladık, çünkü DKIM imzası giden mailde anlamlıdır: kimlik doğrulaması yapmış kullanıcı postayı gönderirken filtreden geçer ve imzalanır. Port 25'ten gelen posta ise dışarıdan sana ulaşan inbound trafiktir; onu imzalamanın bir anlamı yok. Anahtar dosyasını _dkimsign kullanıcısı okuduğundan, dosyanın izinleri o kullanıcıya ait olmalı.
Üç `listen` satırı üç farklı görev için:
Port 25 tls (fırsatçı TLS) — MTA'dan MTA'ya gelen postayı kabul eder, auth istemez.
Port submission (587) tls-require — STARTTLS başarısız olursa bağlantı reddedilir, plaintext auth asla mümkün olmaz.
Match sıralaması hayati.match auth from any for any satırı, from local for any outbound kuralından önce gelir ki yalnızca kimlik doğrulaması yapmış istemciler dışarıya mail relay'leyebilsin. Bu iki satırın sırasını bozar ya da auth koşulunu düşürürsen, sunucun herkesin spam pompaladığı bir open relay'e döner. %{dest.domain}/%{dest.user} genişletmesi gelen maili alıcının domain ve kullanıcı adına göre ayrı maildir'lere ayrıştırır.
Credentials tablosunu düz metin parolayla doldurma. smtpctl ile bcrypt hash üret:
pki bloğu sadece cert ve key yollarını gösterir; şifreleme çubuklarını çevirmen gerekmez. OpenSMTPD, LibreSSL/OpenSSL'in makul cipher ve protokol setini kullanır. TLS'i elle sertleştirmeye çalışıp cipher listesi yazma tuzağına düşme — modern LibreSSL zaten güvenli varsayılanlarla gelir ve elle müdahale genelde uyumluluğu bozar.
OpenBSD'de sertifikayı acme-client ile al, /etc/acme-client.conf içinde domain'i tanımla. Linux'ta certbot kullan ve yenileme hook'una reload ekle:
Port semantiğini net tut: 465 implicit TLS, 587 STARTTLS submission'dır. Submission'da tls-require şarttır; kullanıcı istemcileri her zaman şifreli auth yapmalı. Port 25'te ise tls-requirekullanma — gönderen MTA'ların bir kısmı hâlâ TLS konuşamaz ve bağlantıyı reddedersen o postaları hiç almazsın. 25'te fırsatçı tls doğru seçimdir: mümkünse şifrele, değilse düz kabul et.
Deliverable olmak: PTR, SPF, DKIM, DMARC
Conf'un çalışması postayı göndermeni sağlar; Gmail'in postayı inbox'a koyması ayrı bir savaştır ve büyük ölçüde DNS'te kazanılır. Aşağıdaki diyagram giden yolu ve her aşamanın hangi DNS kaydıyla doğrulandığını eşliyor.
Önce DKIM anahtarını üret, sonra public kısmını çıkarıp DNS'e koy:
PTR (reverse DNS) — IP → mail.example.com, sağlayıcının panelinden set edilir
En sık atlanan kayıt PTR'dir ve en sert cezalandırılan da odur. Gmail, çıkış IP'sinin reverse DNS'i HELO adınla tutarlı değilse postayı 550-5.7.1 ile geri çevirir — SPF ve DKIM kusursuz olsa bile. Ayrıca çoğu cloud sağlayıcı (AWS, GCP, DigitalOcean) 25/tcp giden portu varsayılan olarak bloklar; kendi IP'nden doğrudan gönderim yapacaksan hem bu bloğun kaldırılmasını hem de rDNS'in senin domain'ine ayarlanmasını talep etmen gerekir.
DMARC'ı p=reject ile başlatma. p=quarantine ile aç, rua raporlarını 2-4 hafta izle, meşru akışların (özellikle forward'lanan postalar) SPF/DKIM hizalamasının bozulmadığından emin ol, sonra p=reject'e çık.
rspamd ile gelen postayı filtrelemek
İmzalayıp doğrulamamak yarım iştir. Gelen tarafta rspamd'yi devreye al; filter-rspamd'yi port 25 listener'ına bağla:
conf
filter rspamd proc-exec "filter-rspamd"
listen on eth0 port 25 tls pki mail.example.com \
hostname mail.example.com filter rspamd
Bir listener'a birden fazla filtre bağlamak istersen — inline virgül listesi çalışmaz — önce bir zincir tanımlar, sonra o zinciri referans verirsin:
conf
filter "gelen" chain { "rspamd", "diger_filtre" }
listen on eth0 port 25 tls pki mail.example.com \
hostname mail.example.com filter "gelen"
rspamd'yi ayrı servis olarak çalıştır, Redis backend'ini bağla:
rspamd gelen postanın SPF, DKIM ve DMARC sonuçlarını okur, greylisting uygular ve topladığı sinyallerden bir skor üretir. Eşiklerle skoru aksiyona çevirir: düşük skor add-header (X-Spam işaretle), orta skor greylist (geçici reddet, tekrar denemesini bekle), yüksek skor reject. Varsayılan eşikler makuldür; kendi trafiğini birkaç gün izleyip ayarla.
Test, log ve kuyruk yönetimi
Her reload'dan önce refleks olarak syntax kontrolü yap — bozuk bir conf ile smtpd'yi yeniden başlatmak servisi düşürür:
bash
smtpd -n
Kuyruğu ve teslim durumunu incelemek için smtpctl:
bash
smtpctl show queue # bekleyen zarflar
smtpctl show envelope <id> # tek zarfın detayı
smtpctl schedule all # tüm bekleyenleri hemen dene
smtpctl remove <id> # takılı postayı sil
smtpctl trace smtp # canlı SMTP oturumu izle
tail -f /var/log/maillog # ham loglar
Dış doğrulama olmadan "çalışıyor" deme. Üç bağımsız kontrol yap:
mail-tester.com — verilen adrese bir mail gönder, 10/10 hedefle. 8'in altındaysa rapor tam olarak neyin eksik olduğunu söyler.
dig txt mail._domainkey.example.com — DKIM kaydının gerçekten yayında ve doğru olduğunu teyit et.
TLS sertifikası geçerli ve certbot/acme-client ile otomatik yenileniyor, hook smtpctl reload çağırıyor
smtpctl show queue boşalıyor, postalar takılıp kalmıyor
rspamd ve Redis çalışıyor, gelen posta skorlanıyor
Maildir izinleri vmail:vmail, DKIM anahtarı _dkimsign tarafından okunabiliyor
/var/log/maillog için logrotate aktif
Bu on maddeyi geçtiysen elinde tek dosyada okunabilir, DKIM imzalayan, TLS zorunlu tutan ve Gmail'e teslim eden bir posta sunucusu var. OpenSMTPD basit olduğu için değil — gramerini anladığın için.