Postfix ve Dovecot'u Tek SQL Şemasında Buluşturmak: PostfixAdmin ile Sanal Kullanıcı Yönetimi
Flat dosyalarla başlayan sanal kullanıcı kurulumları er ya da geç çatlar. Hesap, alan adı ve alias verisini tek bir PostfixAdmin SQL şemasında toplayıp Postfix ile Dovecot'u aynı tabloların ortak okuyucusu yapmanın doğru yolu — ve geçişte düşülen tuzaklar.
EvilMail Team12 Temmuz 202612 dk okuma
Bir kullanıcı eklemek için dört dosyaya dokunup iki servisi reload ettiğiniz an, flat-file sanal kullanıcı kurulumunuzun ömrünü doldurduğunu anlarsınız. /etc/postfix/vmailbox'a mailbox satırı, /etc/postfix/valias'a alias satırı, ayrı bir Dovecot passwd dosyasına parola hash'i, ardından iki postmap çalıştırıp systemctl reload postfix dovecot. Bir kişi için katlanılır. Elli alan adı ve birkaç yüz kutu için felaket.
Asıl sorun tekrar değil, tutarsızlık. Parolayı Dovecot dosyasında değiştirirsiniz ama quota Postfix tarafında ayrı yaşar; bir hesabı kapattığınızı sanırsınız ama alias hâlâ mail akıtır, çünkü valias'ı unutmuşsunuzdur. Tek bir gerçeğin dört ayrı yerde saklandığı her sistem, eninde sonunda bu kopyaların birbirinden ayrışmasıyla çöker.
Bu yazının açısı yaygın "önce hash map, bir gün belki SQL" rehberlerinin tersi: hesap, alan adı ve alias verisini tek bir SQL şemasında — PostfixAdmin şemasında — toplayıp Postfix ile Dovecot'u bu tabloların ortak tüketicisi yapmak. Tek yazma noktası, iki bağımsız okuyucu. PostfixAdmin'i burada bir "panel" olarak değil, hazır bir şema artı web tabanlı CRUD katmanı olarak konumlandırıyoruz; mail yolunda çalışan asıl motor, Postfix ve Dovecot'un aynı satırları okumasıdır.
Postfix + Dovecot SQL Sanal Kullanıcı Şeması ve PostfixAdmin Kurulumu — EvilMail Blog
Sorun: dört ayrı yerde tutulan tek gerçek
Flat-file dünyasında bir "kullanıcı" fiziksel olarak dağınıktır. Mailbox eşlemesi Postfix'in hash veritabanında, alias yönlendirmesi başka bir hash'te, kimlik doğrulama parolası Dovecot'un passwd-file backend'inde, quota ise ya Dovecot maildir'inde ya da hiç. Bu dört parçanın senkron kalmasını sağlayan tek şey sizin disiplininiz — ve disiplin ölçeklenmez.
SQL'e geçişin gerçek gerekçesi "SQL daha havalı" değil; yazmayı tek noktaya indirmek. PostfixAdmin bir satır yazar (INSERT INTO mailbox ...), Postfix o satırı virtual_mailbox_maps ile okur, Dovecot aynı satırı password_query ile okur. Üçü de aynı mailbox tablosunun aynı active sütununa bakar. Bir hesabı active=0 yaptığınızda hem SMTP teslimatı hem IMAP oturumu aynı anda kesilir, çünkü ikisi de aynı WHERE koşulunu paylaşır. Flat-file'da bunu garanti edemezsiniz.
PostfixAdmin şeması: hangi tablolar gerçekten işi yapıyor
PostfixAdmin 3.3.13+ şeması bir düzine tablo kurar, ama mail yolunda Postfix ve Dovecot'un fiilen dokunduğu tablolar birkaç tanedir. Panel-only tabloları (admin, domain_admins, log, vacation) teslimatı ilgilendirmez; onları görmezden gelin.
`domain` — hangi alan adlarına yerel teslimat yapılacağı. Postfix virtual_mailbox_domains ile buraya bakar. backupmx='0' filtresi önemli: yedek MX olarak eklenmiş bir alan adına mailbox teslim etmek istemezsiniz.
`mailbox` — asıl kutu. Postfix'in okuduğu maildir ve active, Dovecot'un okuduğu password (Dovecot hash formatında) ve yine active. İki servisin buluştuğu tablo budur.
`alias` — yönlendirme. address gelen adres, goto ise virgülle ayrılmış hedefler. Postfix virtual_alias_maps ile okur. Bir adresin hem mailbox'ta hem alias'ta bulunması döngü riskidir; buna döneceğiz.
`alias_domain` — target_domain'e aynalanan alan adı takma adları. alias.example → mail.example gibi tüm bir alanı başka alana yansıtır.
`quota2` — kullanıcı başına byte/mesaj sınırı. Dovecot user_query ile buradan quota_rule üretebilir.
Buradaki tek en kritik operasyonel kural: `active` sütunu her sorguda WHERE koşulu olmalı. PostfixAdmin bir hesabı devre dışı bıraktığınızda satırı silmez, active=0 yapar. Sorgularınızda active filtresi yoksa "kapattığınız" hesap mail almaya ve kimlik doğrulamaya devam eder. Geçiş yapan ekiplerin en sık ısırıldığı yer burasıdır.
Parola tarafında not: PostfixAdmin 3.2'den itibaren ARGON2ID destekler ve modern kurulumlarda mailbox.password alanı {ARGON2ID}$argon2id$... prefix'i taşır. Dovecot'un bu prefix'i doğru yorumlaması aşağıdaki default_pass_scheme ayarına bağlıdır.
Postfix tarafı: dört SQL map ve sıranın önemi
Postfix tarafında dört ayrı sorgu var, her biri kendi .cf dosyasında. main.cf bunları map olarak bağlar:
# /etc/postfix/sql/virtual_mailbox_domains.cf
user = postfix_ro
password = read_only_secret
hosts = 127.0.0.1
dbname = postfixadmin
query = SELECT domain FROM domain WHERE domain='%s' AND active='1' AND backupmx='0'
# /etc/postfix/sql/virtual_mailbox_maps.cf (aynı user/password/host/dbname bloğu)
query = SELECT maildir FROM mailbox WHERE username='%s' AND active='1'
# /etc/postfix/sql/virtual_alias_maps.cf
query = SELECT goto FROM alias WHERE address='%s' AND active='1'
# /etc/postfix/sql/virtual_alias_domain_maps.cf
query = SELECT goto FROM alias, alias_domain
WHERE alias_domain.alias_domain='%d'
AND alias.address=CONCAT('%u','@',alias_domain.target_domain)
AND alias.active=1 AND alias_domain.active=1
İki nokta önemli. Birincisi `proxy:mysql`: Postfix'te her SQL map ayrı bir veritabanı bağlantısı açar ve smtpd/cleanup/trivial-rewrite gibi birden çok süreç bunları çoğaltır. proxymap servisi araya girince tüm map'ler tek proxymap süreci üzerinden paylaşımlı bağlantı kullanır; onlarca bağlantı yerine bir avuç. Yoğun sunucuda MySQL max_connections'ı bununla korursunuz.
İkincisi `virtual_alias_maps` içindeki sıra. Önce doğrudan alias tablosu, sonra alias_domain aynalaması. Postfix map listesini soldan sağa dener; ilk eşleşen kazanır. Sırayı ters çevirirseniz doğrudan tanımlı bir alias'a sahip adres, önce aynalanmaya çalışılır ve %u@target_domain karşılığı mailbox'ta yoksa adres kendini çözemez.
Dovecot tarafı: auth, userdb ve quota aynı satırdan
Dovecot iki sorgu çalıştırır: kimlik için password_query, kullanıcı meta verisi için user_query. İkisi de aynı mailbox tablosuna, aynı active='1' filtresiyle bakmalı — Postfix ile birebir aynı gerçeği görmelerinin tek yolu bu.
ini
# /etc/dovecot/dovecot-sql.conf.ext (Dovecot 2.3 sözdizimi)
driver = mysql
connect = host=127.0.0.1 dbname=postfixadmin user=dovecot_ro password=read_only_secret
default_pass_scheme = ARGON2ID
password_query = \
SELECT username AS user, password \
FROM mailbox WHERE username='%u' AND active='1'
user_query = \
SELECT CONCAT('/var/mail/vhosts/', maildir) AS home, \
5000 AS uid, 5000 AS gid, \
CONCAT('*:bytes=', quota) AS quota_rule \
FROM mailbox WHERE username='%u' AND active='1'
uid/gid sabit 5000 — maildir'lerin sahibi de 5000:5000 olmalı, yoksa Dovecot LMTP teslimatta yazamaz. quota_rule, mailbox.quota alanından üretiliyor; kullanıcı başına birden çok kural gereken kurulumlarda quota2 tablosunu user_query'ye join edersiniz, ama tek kutu-tek sınır senaryosunda mailbox.quota yeterlidir.
Dovecot 2.4 uyarısı. 2.4 ile konfigürasyon sözdizimi değişti. Eski driver = mysql ve connect = ... satırları artık geçerli değil; yerlerine sql_driver ve yapılandırılmış bir blok gelir:
ini
# Dovecot 2.4 — connect string artık yapılandırılmış blok
sql_driver = mysql
# passdb / userdb tanımları içinde:
sql {
driver = mysql
parameters {
host = 127.0.0.1
dbname = postfixadmin
user = dovecot_ro
password = read_only_secret
}
}
2.3'ten 2.4'e yükseltip eski connect= satırını olduğu gibi bırakırsanız Dovecot backend'i bağlamaz; sonuç her oturumda auth failed. Yükseltme sonrası ilk iş doveadm auth test çalıştırmaktır.
Üç bileşenin parola konusunda anlaşması gerekir, yoksa panelde oluşturduğunuz kullanıcı IMAP'a giriş yapamaz. İşin sırrı, PostfixAdmin'in hash'i kendisi üretmeyip Dovecot'un doveadm pw komutuna delege etmesi:
encrypt = 'dovecot:ARGON2ID' sayesinde panel bir parola aldığında doveadm pw -s ARGON2ID çağırır. Böylece mailbox.password alanına yazılan hash, Dovecot'un aynı algoritmayla doğrulayacağı formatta olur. Panel ve doğrulayıcı aynı ikiliyi (doveadm) kullandığından prefix ve parametreler otomatik uyumludur.
Bir istisna: CRAM-MD5 gibi challenge-response mekanizmaları geri döndürülebilir parola saklama gerektirir; ARGON2ID gibi tek yönlü hash'lerle CRAM-MD5 yapılamaz. Pratikte doğru taviz, istemcilere AUTH PLAIN/LOGIN'i yalnızca TLS altında sunup hash'i güçlü tutmaktır — bu, CRAM-MD5 uğruna düz metin saklamaktan çok daha sağlıklıdır. Challenge-response'a mecbur olmadıkça ARGON2ID + zorunlu TLS doğru varsayılandır.
Alias çözümleme ve döngü tuzağı
Sanal alias mimarisinin en sinsi arızası "mail loops back to myself (554)". Bir adres hem mailbox tablosunda hem de goto içinde kendini gösteren bir alias satırındaysa, Postfix adresi sonsuz yeniden yazma döngüsüne sokar ve reddeder. PostfixAdmin bir mailbox oluştururken normalde kendine dönen bir alias satırı da ekler; bu tasarım gereğidir ve sorun çıkarmaz, çünkü Postfix virtual_alias_maps'ten dönen sonuç mailbox'a giden yolla çakışmaz. Sorun, elle eklediğiniz bir alias'ın hedefine yanlışlıkla kaynak adresi de koyduğunuzda başlar.
Bir catch-all alias (@domain.com → hedef) tanımladığınızda başka bir tuzak devreye girer. Catch-all, aynı alanda tanımlı meşru mailbox'ları gölgelemez, çünkü Postfix önce spesifik address eşleşmesini dener; catch-all yalnızca hiçbir spesifik alias veya mailbox tutmadığında çalışır. Ama goto'sunu yanlış kurarsanız domain'in tüm postasını tek kutuya boşaltırsınız. Catch-all'ı ekledikten sonra bilinen bir mailbox'a test maili atıp doğru kutuya düştüğünü doğrulayın.
Doğrulama: canlıda ne kırılır, nasıl test edilir
Reload'dan önce her map'i tek tek sorgulayın. postmap -q map'i Postfix'in gördüğü şekilde sorgular (proxymap servisi postmap ile sorgulanamadığı için burada map'leri doğrudan mysql: ile açarsınız):
bash
# Alan adı tanınıyor mu?
postmap -q evilmail.pro mysql:/etc/postfix/sql/virtual_mailbox_domains.cf
# -> evilmail.pro (boş dönerse domain.active veya backupmx'e bak)
# Mailbox maildir'e çözümleniyor mu?
postmap -q [email protected] mysql:/etc/postfix/sql/virtual_mailbox_maps.cf
# -> evilmail.pro/info/
# Alias hedefe gidiyor mu?
postmap -q [email protected] mysql:/etc/postfix/sql/virtual_alias_maps.cf
# -> [email protected]
# Dovecot kimlik doğrulaması
doveadm auth test [email protected] 'parola'
# -> passdb: [email protected] auth succeeded
# Kullanıcı meta verisi (uid/gid/home/quota)
doveadm user [email protected]
# Aktif Postfix sanal ayarlarını gör
postconf -n | grep virtual
En önemli test, active sözünün tutulduğunu doğrulamaktır: PostfixAdmin'de bir hesabı devre dışı bırakın, ardından hem postmap -q hem doveadm auth test çalıştırın. İkisi de anında boş/başarısız dönmeli. Biri hâlâ hesabı görüyorsa o servisin sorgusunda active='1' filtresi eksiktir.
Operasyon checklist'i
Salt-okunur DB kullanıcıları. Postfix ve Dovecot için GRANT SELECT yeten ayrı kullanıcılar (postfix_ro, dovecot_ro); yazma yetkisi yalnızca PostfixAdmin'in kullanıcısında olsun. Mail yolundaki bir açık, veritabanına yazamamalı.
`proxy:mysql` kullanın. Map başına bağlantı yerine proxymap üzerinden paylaşımlı bağlantı; MySQL max_connections'ı korur.
Index'lere güvenin ama doğrulayın.mailbox(username), alias(address), domain(domain) PostfixAdmin şemasında zaten PRIMARY/UNIQUE. Elle kolon eklediyseniz sorgunuzun index kullandığını EXPLAIN ile teyit edin.
`active` her sorguda WHERE koşulu. Domain, mailbox ve alias sorgularının hepsinde. Tek eksik filtre, "kapalı" hesabın mail almasına yol açar.
alias_domain döngü kontrolü. Bir adresi hem mailbox hem kendine dönen alias yapmayın; postmap -q ile teslimat yolunu test edip 554 loop'unu önceden yakalayın.
Şema uyumu tek elden. PostfixAdmin dovecot:ARGON2ID + doveadm pw üzerinden hash üretsin ki panel ve Dovecot aynı algoritmayı kullansın.
2.3 → 2.4 geçişinde connect bloğunu güncelleyin. Eski driver=/connect= satırları 2.4'te çalışmayı bırakır; yükseltme sonrası ilk iş doveadm auth test.
Yedekleme.mysqldump postfixadmin günlük; şema tek gerçek kaynağı olduğu için onu kaybetmek dört flat dosyayı birden kaybetmek demek.
Quota tutarlılığı.mailbox.quota/quota2 ile Dovecot'un quota_rule'unun aynı birimde (byte) olduğunu doğrulayın; MB/byte karışıklığı sınırları 1024 kat şaşırtır.