Makale

Exim 4.100 Güncellemesi Sonrası MailScanner Mail Kuyruğu Sorunu: Split Spool Hatası ve Kesin Çözüm

21.09.2026 20:2725 dk okuma5 görüntülenme
Exim 4.100 Güncellemesi Sonrası MailScanner Mail Kuyruğu Sorunu: Split Spool Hatası ve Kesin Çözüm

Exim 4.100 Güncellemesi Sonrası MailScanner Split Spool Sorunu ve Kesin Çözümü

21 Eylül 2026 tarihinde cPanel sunucularımızdan birinde yapılan ana sistem güncellemesinin ardından e-posta kuyruğunun olağan dışı biçimde büyüdüğünü fark ettik.

Sunucuda aşağıdaki yapı çalışıyordu:

CentOS 7.9
cPanel 11.110.0.144
Exim 4.100
MailScanner 5.4.4

Sunucunun CPU, RAM, disk ve inode değerlerinde herhangi bir problem bulunmuyordu. Exim, Dovecot ve MailScanner servisleri de aktif görünüyordu.

Ancak mail kuyruğu kısa sürede yüzlerce mesaja ulaştı.

İlk bakışta sorun aşağıdakilerden biri gibi görünüyordu:

  • Spam saldırısı
  • Disk doluluğu
  • Inode tükenmesi
  • Exim servisinin durmuş olması
  • Dovecot problemi
  • MailScanner servisinin durmuş olması

Gerçek neden bunların hiçbiri değildi.

Belirti: Spool File Not Found Hatası

Mail kuyruğunda bulunan mesajlardan biri manuel olarak teslim edilmeye çalışıldığında Exim aşağıdakine benzer bir hata veriyordu:

Spool file 1x8enJ-000000005pQ-2hdX-D not found

Fakat dosyayı sunucuda aradığımızda gerçekten vardı:

find /var/spool/exim/input -name '1x8enJ-000000005pQ-2hdX-*'

Sonuç:

/var/spool/exim/input/0/1x8enJ-000000005pQ-2hdX-D
/var/spool/exim/input/0/1x8enJ-000000005pQ-2hdX-H

Yani Exim “dosya yok” diyordu ancak dosya fiziksel olarak diskte bulunuyordu.

Burada sorun dosyanın kendisi değil, dosyanın bulunduğu dizindi.

Exim Split Spool Nedir?

Exim büyük mail kuyruklarında bütün mesajları tek klasörde tutmak yerine mesajları alt klasörlere dağıtabilir.

Örneğin:

/var/spool/exim/input/A/
/var/spool/exim/input/B/
/var/spool/exim/input/C/
/var/spool/exim/input/J/
/var/spool/exim/input/N/

Bizim örnek mesaj ID’miz:

1x8enJ-000000005pQ-2hdX

Mesaj ID’sinin ilk bölümü:

1x8enJ

Bu yapıdaki split spool karakteri:

J

olmalıdır.

Dolayısıyla doğru yol:

/var/spool/exim/input/J/1x8enJ-000000005pQ-2hdX-H

olmalıydı.

Fakat MailScanner mesajı şuraya yazıyordu:

/var/spool/exim/input/0/

Bu nedenle Exim mesajı bulamıyordu.

Problemi strace ile Doğrulama

Exim’in dosyayı gerçekte hangi dizinde aradığını görmek için strace kullandık.

strace -f -e trace=openat,stat,lstat,access \
/usr/sbin/exim \
-C /etc/exim_outgoing.conf \
-Mvb 1x8enJ-000000005pQ-2hdX 2>&1 | grep 1x8enJ

Exim’in aradığı yol:

/var/spool/exim/input/J/1x8enJ-000000005pQ-2hdX-D

olmasına rağmen MailScanner dosyayı:

/var/spool/exim/input/0/

dizinine bırakmıştı.

Böylece sorunun split spool hesaplamasında olduğu kesinleşti.

Sunucunun Genel Sağlığını Kontrol Etme

Önce disk, inode ve servislerin düzgün çalışıp çalışmadığını kontrol ettik.

Disk Kullanımı

df -hT / /var /home

Örnek sağlıklı çıktı:

Filesystem              Type  Size  Used Avail Use% Mounted on
/dev/mapper/centos-root xfs   200G   44G  157G  22% /
/dev/mapper/centos-home xfs   541G  315G  227G  59% /home

Inode Kullanımı

df -i / /var /home

Örnek sağlıklı değer:

IUse% 1%

Load Kontrolü

uptime

Örneğin:

load average: 0.21, 0.22, 0.23

Exim Servisi

/scripts/restartsrv_exim --status

Dovecot Servisi

/scripts/restartsrv_dovecot --status

MailScanner Servisi

systemctl status MailScanner --no-pager -l

Sunucuda disk, inode, CPU load ve servis tarafında temel bir problem yoktu.

Güncelleme Sonrası Exim Sürüm Değişikliği

Sunucuda daha önce Exim 4.99.5 çalışıyordu.

cPanel ana güncellemesi sonrasında Exim sürümü:

Exim version 4.100

oldu.

Kontrol:

exim -bV | head -15

Örnek çıktı:

Exim version 4.100 #2 built 10-Sep-2026 11:24:25

Exim binary değişim zamanını görmek için:

stat /usr/sbin/exim

Exim config dosyalarının yeniden oluşturulma zamanını görmek için:

stat /etc/exim.conf
stat /etc/exim_outgoing.conf

Bizim örneğimizde Exim güncellemesi ve config yeniden oluşturma işlemi ile mail kuyruğu sorununun başlaması zaman olarak birebir örtüşüyordu.

MailScanner Sürümünü Kontrol Etme

Bizim kurulumumuzda gerçek MailScanner executable yolu:

/usr/mailscanner/usr/sbin/MailScanner

Sürüm kontrolü:

/usr/mailscanner/usr/sbin/MailScanner --version

Sonuç:

This is MailScanner version 5.4.4

MailScanner Split Exim Spool Ayarını Kontrol Etme

Aktif yapılandırmayı görmek için:

grep -nE '^(Incoming Queue Dir|Outgoing Queue Dir|Split Exim Spool)' \
/usr/mailscanner/etc/MailScanner.conf

Örnek:

Incoming Queue Dir = /var/spool/exim_incoming/input/*
Outgoing Queue Dir = /var/spool/exim/input
Split Exim Spool = yes

Yani problem Split Exim Spool = no değildi.

Ayar zaten doğru şekilde yes durumundaydı.

Sorunlu MailScanner Kodu

İlgili dosyalar:

/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStore.pm

/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStoreNew.pm

Kodu görmek için:

sed -n '195,230p' \
/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStore.pm

Eski kod:

sub OutQDir {
  my $name = shift;

  if (MailScanner::Config::Value('spliteximspool')) {
    return '/' . substr($name,-13,1) . '/';
  } else {
    return '/';
  }
}

Diğer dosyada:

sub OutQDir {
  my $name = shift;

  if (MailScanner::Config::Value('spliteximspool')) {
    return '/' . substr($name,-20,1) . '/';
  } else {
    return '/';
  }
}

Asıl Sorun Neydi?

Buradaki problem:

substr($name,-13,1)

ve:

substr($name,-20,1)

ifadelerinin mesaj ID uzunluğunun sabit olduğunu varsaymasıdır.

Exim 4.100 ile kullanılan yeni mesaj ID yapısında bu hesaplama yanlış karakteri seçmeye başladı.

Örnek mesaj:

1x8enJ-000000005pQ-2hdX

Doğru split spool karakteri:

J

olması gerekirken MailScanner:

0

karakterini seçiyordu.

Bu nedenle yüzlerce mesaj:

/var/spool/exim/input/0/

dizinine yazılmıştı.

Acil ve Hızlı Çözüm

Önce mutlaka yedek alınmalıdır.

cp -a \
/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStore.pm \
/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStore.pm.bak-20260921

cp -a \
/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStoreNew.pm \
/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStoreNew.pm.bak-20260921

Bizim Exim 4.100 yapımızda doğru karakter mesaj ID’nin 6. karakteriydi.

Bu nedenle:

sed -i 's/substr(\$name,-13,1)/substr(\$name,5,1)/' \
/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStore.pm

sed -i 's/substr(\$name,-20,1)/substr(\$name,5,1)/' \
/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStoreNew.pm

Kontrol:

grep -nA8 'sub OutQDir' \
/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStore.pm \
/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStoreNew.pm

Beklenen:

return '/' . substr($name,5,1) . '/';

Daha Sağlam ve Geleceğe Dayanıklı Çözüm

Dünya genelinde kullanılabilecek daha güvenli yaklaşım sabit karakter pozisyonuna bağlı kalmamaktır.

Mesaj ID’sinin ilk bölümünün son karakteri dinamik olarak alınabilir.

sub OutQDir {
  my $name = shift;

  if (MailScanner::Config::Value('spliteximspool')) {

    my ($firstpart) = split(/-/, $name, 2);

    return '/' . substr($firstpart, -1, 1) . '/';

  } else {
    return '/';
  }
}

Örnek:

1x8enJ-000000005pQ-2hdX

İlk bölüm:

1x8enJ

Son karakter:

J

Bu yaklaşım mesaj ID’sinin geri kalan bölümünün uzunluğundan bağımsızdır.

MailScanner Servisini Yeniden Başlatma

Servis adını kontrol etmek için:

systemctl list-unit-files | grep -i mailscanner

Örnek:

MailScanner.service enabled

Restart:

systemctl restart MailScanner

Kontrol:

systemctl status MailScanner --no-pager -l

Yeni Mail ile Düzeltmeyi Test Etme

MailScanner restart işleminden sonra dışarıdan bir test e-postası gönderin.

Ardından:

find /var/spool/exim/input -type f -name '*-H' \
-newermt '5 minutes ago' \
-printf '%TY-%Tm-%Td %TH:%TM:%TS %h/%f\n' \
| sort | tail -20

Bizim test mesajımız:

1x8gzN-000000003ZG-2Jl6

şu dizine düştü:

/var/spool/exim/input/N/1x8gzN-000000003ZG-2Jl6-H

Mesaj ID’sinin ilk bölümü:

1x8gzN

olduğu için N doğru split spool klasörüdür.

Yanlış Klasördeki Eski Mesajları Sayma

count=0

for h in $(find /var/spool/exim/input -type f -name '*-H'); do

    id=$(basename "$h" -H)

    actual=$(basename "$(dirname "$h")")

    firstpart="${id%%-*}"

    expected="${firstpart: -1}"

    if [ "$actual" != "$expected" ]; then
        count=$((count+1))
    fi

done

echo "Yanlış klasörde mesaj: $count"

Bizim sunucumuzda sonuç:

769

mesajdı.

H ve D Dosyalarını Kontrol Etme

Exim spool mesajlarında genellikle:

-H
-D

dosya çifti bulunur.

Taşımadan önce eşleşme kontrol edilmelidir.

both=0
honly=0

while IFS= read -r h; do

    id=$(basename "$h" -H)

    dir=$(dirname "$h")

    actual=$(basename "$dir")

    firstpart="${id%%-*}"

    expected="${firstpart: -1}"

    [ "$actual" = "$expected" ] && continue

    if [ -f "$dir/${id}-D" ]; then

        both=$((both+1))

    else

        honly=$((honly+1))

        echo "D dosyası eksik: $id"

    fi

done < <(
    find /var/spool/exim/input \
    -type f \
    -name '*-H'
)

echo "H+D tam: $both"
echo "D eksik: $honly"

Bizde sonuç:

H+D TAM: 769
SADECE H / D EKSIK: 0

Outgoing Queue Runner'ı Geçici Olarak Durdurma

Dosyaları taşırken outgoing Exim queue runner’ın aynı anda spool dosyalarına erişmesini istemeyiz.

PID:

cat /var/spool/exim/exim-outgoing.pid

Örnek:

5517

Geçici durdurma:

kill -STOP 5517

Kontrol:

ps -o pid,stat,cmd -p 5517

STAT alanında:

Ts

görülmelidir.

Bu işlem prosesi öldürmez. Sadece geçici olarak askıya alır.

Yanlış Spool Dosyalarını Doğru Dizinlere Taşıma

Aşağıdaki script yalnızca yanlış klasörde bulunan mesajları taşır.

BASE=/var/spool/exim/input

moved=0
collision=0
failed=0

while IFS= read -r h; do

    id=$(basename "$h" -H)

    source_dir=$(dirname "$h")

    actual=$(basename "$source_dir")

    firstpart="${id%%-*}"

    expected="${firstpart: -1}"

    [ "$actual" = "$expected" ] && continue

    d="$source_dir/${id}-D"

    target="$BASE/$expected"

    if [ ! -d "$target" ]; then

        mkdir -p "$target"

        chown --reference="$BASE/0" "$target"

        chmod --reference="$BASE/0" "$target"

    fi

    if [ -e "$target/${id}-H" ] || \
       [ -e "$target/${id}-D" ]; then

        echo "ÇAKIŞMA: $id"

        collision=$((collision+1))

        continue

    fi

    if mv -- "$d" "$h" "$target/"; then

        moved=$((moved+1))

    else

        echo "TAŞIMA HATASI: $id"

        failed=$((failed+1))

    fi

done < <(
    find "$BASE" \
    -type f \
    -name '*-H'
)

echo "Taşınan : $moved"
echo "Çakışma : $collision"
echo "Hata    : $failed"

Bizim sonuç:

TAŞINAN MESAJ : 769
CAKISMA       : 0
HATA          : 0

Yanlış Mesaj Kalmış mı?

count=0

for h in $(find /var/spool/exim/input -type f -name '*-H'); do

    id=$(basename "$h" -H)

    actual=$(basename "$(dirname "$h")")

    firstpart="${id%%-*}"

    expected="${firstpart: -1}"

    if [ "$actual" != "$expected" ]; then

        echo "$id actual=$actual expected=$expected"

        count=$((count+1))

    fi

done

echo "YANLIS KALAN: $count"

Bizim sonuç:

YANLIS KALAN: 0

Queue Runner'ı Yeniden Çalıştırma

kill -CONT 5517

Kontrol:

ps -o pid,stat,cmd -p 5517

STAT artık:

Ss

olmalıdır.

Gerçek Outgoing Kuyruğu Kontrol Etme

MailScanner kullanılan yapılarda standart:

exim -bpc

her zaman gerçek outgoing kuyruğunu göstermeyebilir.

Bizim yapımızda outgoing Exim ayrı config ile çalışıyordu:

/etc/exim_outgoing.conf

Dolayısıyla:

/usr/sbin/exim -C /etc/exim_outgoing.conf -bpc

kullanılmalıdır.

Bizim düzeltme sonrası kuyruk:

767

mesajdı.

Queue run:

/usr/sbin/exim -C /etc/exim_outgoing.conf -qff

sonrası kuyruk:

35

mesaja düştü.

Yani yüzlerce mesaj tekrar işlenmeye başladı.

Kalan Mesajların Hepsi Hata Değildir

Kuyruk sıfır olmak zorunda değildir.

Örneğin:

Connection timed out

uzak SMTP sunucusunun cevap vermemesi olabilir.

Veya:

550 5.1.1
The email account that you tried to reach does not exist

geçersiz alıcı olabilir.

Dolayısıyla MailScanner split spool problemi çözüldükten sonra kalan mesajlar ayrıca incelenmelidir.

Yıldız Bilişim MailScanner Update Sistemi

MailScanner bakım işlemlerini WHM içerisinden tek butonla çalıştırabilmek için ayrı bir güncelleme sistemi oluşturulabilir.

Mantık şu adımlardan oluşur:

  1. ClamAV veritabanını güncelle
  2. MailScanner phishing listelerini güncelle
  3. Bad sites listesini güncelle
  4. Safe sites listesini güncelle
  5. Bad email ve ScamNailer listelerini güncelle
  6. SpamAssassin rules update çalıştır
  7. MailScanner - Exim split spool uyumluluğunu kontrol et
  8. Gerekirse patch uygula
  9. MailScanner ve Exim servislerini yeniden başlat
  10. Tüm işlemleri logla

WHM Force MailScanner Update Butonu

<input type="submit" class="btn btn-default" value="Force MailScanner Update">

Form örneği:

<form method="post" action="">

    <input
        type="hidden"
        name="action"
        value="force_update"
    >

    <input
        type="submit"
        class="btn btn-default"
        value="Force MailScanner Update"
    >

</form>

Yıldız MailScanner Update Scripti

Örnek script yolu:

/usr/local/sbin/yildiz-mailscanner-update

Oluşturma:

nano /usr/local/sbin/yildiz-mailscanner-update

İçerik:

#!/bin/bash

set -u

LOG="/var/log/yildiz-mailscanner-update.log"

DATE="$(date '+%Y-%m-%d %H:%M:%S')"

log()
{
    echo "$*" | tee -a "$LOG"
}

log "===== $DATE Yildiz MSFE update started ====="

log "[1] ClamAV freshclam"

if command -v freshclam >/dev/null 2>&1; then

    freshclam >>"$LOG" 2>&1

    log "freshclam exit=$?"

else

    log "freshclam not found"

fi


log "[2] MailScanner list updates"

for CMD in \
    ms-update-phishing \
    ms-update-bad-sites \
    ms-update-safe-sites \
    ms-update-bad-emails

do

    if command -v "$CMD" >/dev/null 2>&1; then

        "$CMD" >>"$LOG" 2>&1

        RC=$?

        log "$CMD exit=$RC"

    else

        log "$CMD not found"

    fi

done


log "[3] SpamAssassin update"

if command -v sa-update >/dev/null 2>&1; then

    sa-update >>"$LOG" 2>&1

    log "sa-update exit=$?"

fi


if command -v ms-update-sa >/dev/null 2>&1; then

    ms-update-sa >>"$LOG" 2>&1

    log "ms-update-sa exit=$?"

fi


if command -v ms-update-vs >/dev/null 2>&1; then

    ms-update-vs >>"$LOG" 2>&1

    log "ms-update-vs exit=$?"

fi


log "[4] Exim MailScanner compatibility check"

FIX="/usr/local/sbin/fix-mailscanner-exim-splitspool"

if [ -x "$FIX" ]; then

    "$FIX" >>"$LOG" 2>&1

    log "split-spool compatibility check exit=$?"

else

    log "split-spool compatibility script not installed"

fi


log "[5] Restart services"

if command -v /scripts/restartsrv_exim >/dev/null 2>&1; then

    /scripts/restartsrv_exim >>"$LOG" 2>&1

    log "restartsrv_exim exit=$?"

fi


systemctl restart MailScanner >>"$LOG" 2>&1

log "MailScanner restart exit=$?"


DATE="$(date '+%Y-%m-%d %H:%M:%S')"

log "===== $DATE Yildiz MSFE update finished ====="

Yetkiler:

chown root:root /usr/local/sbin/yildiz-mailscanner-update
chmod 700 /usr/local/sbin/yildiz-mailscanner-update

Log dosyası:

/var/log/yildiz-mailscanner-update.log

Otomatik Split Spool Patch Scripti

Örnek dosya:

/usr/local/sbin/fix-mailscanner-exim-splitspool

İçerik:

#!/bin/bash

set -e

BASE="/usr/mailscanner/usr/share/MailScanner/perl/MailScanner"

FILES=(
"$BASE/EximDiskStore.pm"
"$BASE/EximDiskStoreNew.pm"
)

for FILE in "${FILES[@]}"
do

    [ -f "$FILE" ] || continue

    if grep -q 'substr($firstpart, -1, 1)' "$FILE"; then

        echo "$FILE already patched"

        continue

    fi

    cp -a \
    "$FILE" \
    "$FILE.pre-exim-split-fix-$(date +%Y%m%d-%H%M%S)"

    perl -0777 -i -pe '
    s{
      return\s+
      [\x27"]\/[\x27"]\s*\.\s*
      substr\(\$name,\s*-\d+,\s*1\)
      \s*\.\s*
      [\x27"]\/[\x27"];
    }{
      my (\$firstpart) = split(/-/, \$name, 2);
      return "/" . substr(\$firstpart, -1, 1) . "/";
    }gx
    ' "$FILE"

done

Yetki:

chmod 700 /usr/local/sbin/fix-mailscanner-exim-splitspool

WHM CGI Tarafı

Buton yalnızca sabit bir root-owned scripti çalıştırmalıdır.

Kullanıcıdan shell komutu alınmamalıdır.

Örnek Perl:

if (
    defined $FORM{'action'}
    &&
    $FORM{'action'} eq 'force_update'
) {

    my $script =
        '/usr/local/sbin/yildiz-mailscanner-update';

    if (-x $script) {

        system($script);

    }
}

Aşağıdaki gibi kullanıcıdan komut alıp çalıştırmak güvenli değildir:

system($FORM{'command'});

veya:

shell_exec($_POST['command']);

Güncellemeyi Arka Planda Çalıştırma

CGI isteğini uzun süre açık bırakmamak için child process kullanılabilir.

my $pid = fork();

if (!defined $pid) {

    die "fork failed";

}

if ($pid == 0) {

    open STDIN,  '<', '/dev/null';

    open STDOUT, '>>',
        '/var/log/yildiz-mailscanner-update.log';

    open STDERR, '>&STDOUT';

    exec(
        '/usr/local/sbin/yildiz-mailscanner-update'
    );

    exit 1;
}

Son Update Logunu WHM'de Gösterme

my $log =
    '/var/log/yildiz-mailscanner-update.log';

if (-f $log) {

    open my $fh, '<', $log;

    my @lines = <$fh>;

    close $fh;

    my $start =
        @lines > 200
        ? @lines - 200
        : 0;

    print "<pre>";

    for my $i ($start .. $#lines) {

        my $line = $lines[$i];

        $line =~ s/&/&amp;/g;
        $line =~ s/</&lt;/g;
        $line =~ s/>/&gt;/g;

        print $line;

    }

    print "</pre>";
}

Log içeriğini doğrudan HTML'e basmak yerine HTML escape yapılması önemlidir.

Güncelleme Sonrası Otomatik Sağlık Testi

echo "===== EXIM ====="

exim -bV | head -3


echo "===== MAILSCANNER ====="

/usr/mailscanner/usr/sbin/MailScanner \
--version 2>&1 | \
grep 'MailScanner version'


echo "===== SPLIT SPOOL CONFIG ====="

grep -n \
'Split Exim Spool' \
/usr/mailscanner/etc/MailScanner.conf


echo "===== WRONG SPOOL CHECK ====="

wrong=0

while IFS= read -r h
do

    id=$(basename "$h" -H)

    actual=$(basename "$(dirname "$h")")

    firstpart="${id%%-*}"

    expected="${firstpart: -1}"

    if [ "$actual" != "$expected" ]; then

        wrong=$((wrong+1))

    fi

done < <(
    find /var/spool/exim/input \
    -type f \
    -name '*-H'
)

echo "Wrong spool messages: $wrong"

Normal sonuç:

Wrong spool messages: 0

Otomatik Alarm Sistemi

Kontrol scripti:

nano /usr/local/sbin/check-exim-split-spool

İçerik:

#!/bin/bash

BASE="/var/spool/exim/input"

WRONG=0

while IFS= read -r h
do

    id=$(basename "$h" -H)

    actual=$(basename "$(dirname "$h")")

    firstpart="${id%%-*}"

    expected="${firstpart: -1}"

    if [ "$actual" != "$expected" ]; then

        WRONG=$((WRONG+1))

    fi

done < <(
    find "$BASE" \
    -type f \
    -name '*-H'
)

if [ "$WRONG" -gt 0 ]; then

    echo "WARNING: $WRONG Exim spool messages are in incorrect directories."

    exit 1

fi

exit 0

Yetki:

chmod 700 /usr/local/sbin/check-exim-split-spool

Cron:

*/10 * * * * root /usr/local/sbin/check-exim-split-spool >> /var/log/exim-split-check.log 2>&1

Önemli Güvenlik Notları

  • Spool dosyalarını doğrudan silmeyin.
  • Taşıma öncesinde mutlaka H ve D dosya çiftlerini kontrol edin.
  • MailScanner kaynak dosyalarını değiştirmeden önce yedek alın.
  • WHM üzerinden root seviyesinde script çalıştırıyorsanız kullanıcıdan shell komutu almayın.
  • Web üzerinden çalışan update sistemini yalnızca yetkili WHM oturumuna açık tutun.
  • Doğrudan internete açık update.php benzeri dosyalar oluşturmayın.
  • Gerçek WHM session ID değerlerini blog yazısında paylaşmayın.

WHM Session URL Kullanımı

Gerçek URL:

https://SERVER-IP:2087/cpsessXXXXXXXXXX/cgi/configserver/mailscanner.cgi

şeklinde maskelenmelidir.

Gerçek cpsess değerini veya özel yönetim IP'nizi açık şekilde yayınlamayın.

Sonuç

Bu olayda sorun disk, inode, CPU, Dovecot veya klasik bir Exim servis arızası değildi.

Asıl problem:

Exim 4.100
+
MailScanner 5.4.4
+
eski split spool karakter hesaplama yöntemi

kombinasyonuydu.

MailScanner mesajları yanlışlıkla:

/var/spool/exim/input/0/

altına yazıyor, Exim ise mesaj ID’ye göre:

/var/spool/exim/input/J/
/var/spool/exim/input/N/
/var/spool/exim/input/P/

gibi doğru split spool klasörlerini arıyordu.

MailScanner kodu düzeltildikten sonra yeni mesajlar doğru dizinlere düşmeye başladı.

Ardından yanlış konumdaki:

769

mesaj güvenli şekilde doğru dizinlere taşındı.

Kuyruk:

767

mesajdan:

35

mesaja düştü.

Kalan mesajların ise bir kısmı normal SMTP sebepleriyle bekleyen veya frozen durumdaki mesajlardı.

Teknik Özet

Problem:
MailScanner yanlış split spool klasörüne yazıyor.

Yanlış:
input/0/

Doğru örnek:
input/J/

Eski MailScanner:
substr($name,-13,1)
substr($name,-20,1)

Geçici fix:
substr($name,5,1)

Daha sağlam fix:
my ($firstpart) = split(/-/, $name, 2);
substr($firstpart, -1, 1)

Sonuç:
Yeni mailler doğru spool klasörüne yazıldı.
Eski 769 mesaj doğru dizinlere taşındı.
Mail kuyruğu büyük ölçüde temizlendi.

Yıldız Bilişim Hizmetleri - Sistem Yönetimi Teknik Notu

Bu çözüm gerçek bir cPanel, Exim ve MailScanner üretim sunucusunda teşhis edilmiş; spool dosyaları, servis logları ve gerçek e-posta teslimleri ile doğrulanmıştır.

Uyarı: Bu işlemler doğrudan Exim spool dosyaları ve MailScanner Perl kaynak kodları üzerinde değişiklik yapmaktadır. Üretim sunucusunda işlem yapmadan önce mutlaka yedek alın. Mail kuyruğundaki dosyaları bilinçsiz şekilde silmeyin veya taşımayın.

Yıldız Bilişim Otomatik Exim 4.100 / MailScanner Split Spool Koruma Sistemi

Manuel olarak uygulanan split spool düzeltmesinin gelecekte yapılacak MailScanner veya cPanel güncellemeleri sırasında kaybolmaması için ayrıca otomatik bir koruma sistemi oluşturduk.

Bu sistemin amacı yalnızca mevcut sorunu çözmek değil, gelecekte MailScanner kaynak dosyaları yeniden eski hale dönerse problemi otomatik olarak tespit edip tekrar düzeltmektir.

Sistem iki parçadan oluşur:

  1. Split spool compatibility / patch scripti
  2. Yıldız MailScanner Update sistemi içerisine otomatik kontrol entegrasyonu

Buna ek olarak her güncellemenin sonunda gerçek mail kuyruğu taranarak yanlış split spool dizininde mesaj bulunup bulunmadığı da otomatik kontrol edilir.

1. Split Spool Otomatik Patch Scripti

Öncelikle aşağıdaki dosyayı oluşturuyoruz:

/usr/local/sbin/yildiz-msfe-splitspool-fix

Dosyayı oluşturmak için:

nano /usr/local/sbin/yildiz-msfe-splitspool-fix

İçeriği:

#!/bin/bash

set -u

LOG="/var/log/yildiz-msfe-update.log"
BASE="/usr/mailscanner/usr/share/MailScanner/perl/MailScanner"

FILES=(
  "$BASE/EximDiskStore.pm"
  "$BASE/EximDiskStoreNew.pm"
)

STAMP="$(date '+%Y%m%d-%H%M%S')"

log() {
  echo "$*" | tee -a "$LOG"
}

log "[SplitSpool] Compatibility check started"

CHANGED=0
ERRORS=0

for FILE in "${FILES[@]}"; do

  if [ ! -f "$FILE" ]; then
    log "[SplitSpool] ERROR: file not found: $FILE"
    ERRORS=$((ERRORS+1))
    continue
  fi

  # Dinamik fix zaten varsa hiçbir değişiklik yapma
  if grep -Fq 'substr($firstpart,-1,1)' "$FILE" || \
     grep -Fq 'substr($firstpart, -1, 1)' "$FILE"; then

    log "[SplitSpool] OK: already patched: $FILE"
    continue
  fi

  # Sadece bildiğimiz eski veya geçici kalıplara müdahale et
  if ! grep -Eq "return '/' \. substr\\(\\\$name,(-13|-20|5),1\\) \. '/';" "$FILE"; then

    log "[SplitSpool] WARNING: unknown OutQDir format, not modifying: $FILE"
    ERRORS=$((ERRORS+1))
    continue
  fi

  BACKUP="${FILE}.pre-splitspool-${STAMP}"

  if ! cp -a "$FILE" "$BACKUP"; then

    log "[SplitSpool] ERROR: backup failed: $FILE"
    ERRORS=$((ERRORS+1))
    continue
  fi

  log "[SplitSpool] Backup: $BACKUP"

  sed -i \
    -e "s@return '/' \. substr(\\\$name,-13,1) \. '/';@my (\\\$firstpart) = split(/-/, \\\$name, 2);\\n    return '/' . substr(\\\$firstpart,-1,1) . '/';@" \
    -e "s@return '/' \. substr(\\\$name,-20,1) \. '/';@my (\\\$firstpart) = split(/-/, \\\$name, 2);\\n    return '/' . substr(\\\$firstpart,-1,1) . '/';@" \
    -e "s@return '/' \. substr(\\\$name,5,1) \. '/';@my (\\\$firstpart) = split(/-/, \\\$name, 2);\\n    return '/' . substr(\\\$firstpart,-1,1) . '/';@" \
    "$FILE"

  if grep -Fq 'substr($firstpart,-1,1)' "$FILE"; then

    log "[SplitSpool] PATCHED: $FILE"
    CHANGED=$((CHANGED+1))

  else

    log "[SplitSpool] ERROR: verification failed, restoring: $FILE"

    cp -a "$BACKUP" "$FILE"

    ERRORS=$((ERRORS+1))
  fi

done

log "[SplitSpool] Changed=$CHANGED Errors=$ERRORS"

if [ "$ERRORS" -gt 0 ]; then

  log "[SplitSpool] RESULT=ERROR"
  exit 1

fi

if [ "$CHANGED" -gt 0 ]; then

  log "[SplitSpool] RESULT=PATCHED"
  exit 10

fi

log "[SplitSpool] RESULT=OK"
exit 0

Script Yetkileri

chown root:root /usr/local/sbin/yildiz-msfe-splitspool-fix
chmod 700 /usr/local/sbin/yildiz-msfe-splitspool-fix

Shell syntax kontrolü:

bash -n /usr/local/sbin/yildiz-msfe-splitspool-fix && echo "SYNTAX OK"

Beklenen:

SYNTAX OK

Script Nasıl Çalışır?

Script iki MailScanner dosyasını kontrol eder:

/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStore.pm

/usr/mailscanner/usr/share/MailScanner/perl/MailScanner/EximDiskStoreNew.pm

Eğer aşağıdaki dinamik yapı zaten mevcutsa dosyalara dokunmaz:

my ($firstpart) = split(/-/, $name, 2);
return '/' . substr($firstpart,-1,1) . '/';

Bu durumda log:

[SplitSpool] OK: already patched
[SplitSpool] Changed=0 Errors=0
[SplitSpool] RESULT=OK

şeklinde olur.

Eğer MailScanner güncellemesi eski kodu geri getirmişse:

substr($name,-13,1)

veya:

substr($name,-20,1)

ya da daha önce kullanılan geçici:

substr($name,5,1)

kalıplarından biri tespit edilir.

Script önce otomatik yedek alır:

EximDiskStore.pm.pre-splitspool-YYYYMMDD-HHMMSS

ardından kodu şu dinamik yapıya dönüştürür:

my ($firstpart) = split(/-/, $name, 2);
return '/' . substr($firstpart,-1,1) . '/';

Özel Exit Code Kullanımı

Script üç farklı sonuç kodu döndürür:

0  = Patch zaten doğru, değişiklik gerekmedi
10 = Patch uygulandı
1  = Hata veya bilinmeyen kod yapısı

Buradaki 10 bir hata değildir. Özellikle patch uygulanmış olduğunu ana updater scriptine bildirmek için kullanılır.

2. Yıldız MailScanner Update Sistemine Otomatik Entegrasyon

Bizim ana updater dosyamız:

/usr/local/sbin/yildiz-msfe-update

Bu dosya ClamAV, MailScanner listeleri, SpamAssassin ve Exim restart işlemlerini gerçekleştirir.

Split spool kontrolü update sistemine aşağıdaki bölüm ile eklenmiştir:

echo "[4] Exim / MailScanner SplitSpool compatibility" >> "$LOG"

if [ -x /usr/local/sbin/yildiz-msfe-splitspool-fix ]; then

  /usr/local/sbin/yildiz-msfe-splitspool-fix >/dev/null 2>&1
  SPLIT_RC=$?

  if [ "$SPLIT_RC" -eq 0 ]; then

    echo "splitspool check exit=0 (OK)" >> "$LOG"

  elif [ "$SPLIT_RC" -eq 10 ]; then

    echo "splitspool check exit=10 (PATCHED)" >> "$LOG"

    systemctl restart MailScanner >> "$LOG" 2>&1

    echo "MailScanner restart after patch exit=$?" >> "$LOG"

  else

    echo "splitspool check exit=$SPLIT_RC (ERROR)" >> "$LOG"

  fi

else

  echo "splitspool fix script not found" >> "$LOG"

fi

Neden MailScanner Sadece Patch Uygulandığında Restart Ediliyor?

Patch zaten mevcutsa gereksiz yere MailScanner restart edilmez.

Ancak script:

exit 10

döndürürse bu, MailScanner Perl kaynak dosyasının değiştirildiği anlamına gelir.

Çalışan MailScanner proseslerinin yeni kodu yükleyebilmesi için:

systemctl restart MailScanner

otomatik çalıştırılır.

3. Exim Restart

Daha sonra normal update akışı devam eder:

echo "[5] Restart services" >> "$LOG"

echo "spamd disabled or not managed here, skipped" >> "$LOG"

/scripts/restartsrv_exim >> "$LOG" 2>&1

echo "restartsrv_exim exit=$?" >> "$LOG"

4. Güncelleme Sonrası Otomatik Split Spool Sağlık Kontrolü

Sadece patch dosyasının mevcut olması yeterli değildir.

Güncellemeden sonra gerçek mail kuyruğu da taranmalıdır.

Bu nedenle updater'ın sonunda aşağıdaki kontrol çalıştırılır:

echo "[6] SplitSpool health check" >> "$LOG"

WRONG=0

while IFS= read -r HFILE; do

  ID=$(basename "$HFILE" -H)

  ACTUAL=$(basename "$(dirname "$HFILE")")

  FIRSTPART="${ID%%-*}"

  EXPECTED="${FIRSTPART: -1}"

  if [ "$ACTUAL" != "$EXPECTED" ]; then

    WRONG=$((WRONG+1))

  fi

done < <(

  find /var/spool/exim/input \
    -type f \
    -name '*-H' \
    2>/dev/null

)

echo "splitspool wrong messages=$WRONG" >> "$LOG"

if [ "$WRONG" -eq 0 ]; then

  echo "splitspool health=OK" >> "$LOG"

else

  echo "splitspool health=WARNING" >> "$LOG"

fi

echo "MailScanner status=$(systemctl is-active MailScanner 2>/dev/null)" >> "$LOG"

OUTCOUNT=$(/usr/sbin/exim -C /etc/exim_outgoing.conf -bpc 2>/dev/null)

echo "outgoing queue=$OUTCOUNT" >> "$LOG"

Sağlıklı Güncelleme Sonrası Beklenen Log

Başarılı bir güncelleme sonunda log aşağıdaki yapıda olmalıdır:

[4] Exim / MailScanner SplitSpool compatibility

[SplitSpool] Compatibility check started

[SplitSpool] OK: already patched:
EximDiskStore.pm

[SplitSpool] OK: already patched:
EximDiskStoreNew.pm

[SplitSpool] Changed=0 Errors=0

[SplitSpool] RESULT=OK

splitspool check exit=0 (OK)

[5] Restart services

restartsrv_exim exit=0

[6] SplitSpool health check

splitspool wrong messages=0

splitspool health=OK

MailScanner status=active

outgoing queue=35

Yildiz MSFE update finished

En Önemli Sağlık Satırları

Aşağıdaki dört satır sağlıklı sistemi özetler:

splitspool check exit=0 (OK)

splitspool wrong messages=0

splitspool health=OK

MailScanner status=active

Özellikle:

splitspool wrong messages=0

değeri, Exim spool mesajlarının yanlış alt dizinlere düşmediğini gösterir.

MailScanner Güncellemesi Patch'i Silse Ne Olur?

Örneğin gelecekte MailScanner güncellemesi aşağıdaki eski satırı geri getirirse:

return '/' . substr($name,-13,1) . '/';

veya:

return '/' . substr($name,-20,1) . '/';

Yıldız MailScanner Update sistemi bunu otomatik algılar.

Log:

[SplitSpool] PATCHED:
EximDiskStore.pm

[SplitSpool] PATCHED:
EximDiskStoreNew.pm

[SplitSpool] Changed=2 Errors=0

[SplitSpool] RESULT=PATCHED

splitspool check exit=10 (PATCHED)

şeklinde görünür.

Daha sonra sistem otomatik olarak:

systemctl restart MailScanner

çalıştırır.

Böylece yeni kod hemen aktif hale gelir.

Bilinmeyen MailScanner Kod Yapısı Tespit Edilirse

Script bilinmeyen bir kaynak kod yapısı gördüğünde körlemesine değişiklik yapmaz.

Örneğin:

[SplitSpool] WARNING: unknown OutQDir format, not modifying

ve:

[SplitSpool] RESULT=ERROR

üretir.

Bu özellikle önemlidir. MailScanner gelecekte OutQDir fonksiyonunu tamamen değiştirirse eski patch mantığının yeni kaynak kodunu bozmasının önüne geçilmiş olur.

Force MailScanner Update Butonu ile Kullanım

WHM MailScanner Front-End içerisinde kullanılan:

<input
  type="submit"
  class="btn btn-default"
  value="Force MailScanner Update"
>

butonuna basıldığında artık yalnızca phishing, spam ve antivirüs listeleri güncellenmez.

Sistem aynı zamanda:

  • Exim / MailScanner uyumluluğunu kontrol eder.
  • Split spool patch kaybolmuşsa otomatik uygular.
  • Gerekirse MailScanner'ı restart eder.
  • Exim'i restart eder.
  • Gerçek outgoing mail kuyruğunu kontrol eder.
  • Yanlış split spool dizinindeki mesajları sayar.
  • MailScanner servis durumunu loglar.
  • Sonucu OK, PATCHED veya WARNING olarak kayıt altına alır.

Manuel Kontrol

Updater manuel olarak da çalıştırılabilir:

/usr/local/sbin/yildiz-msfe-update

Son log kayıtları:

tail -70 /var/log/yildiz-msfe-update.log

Kontrol edilmesi gereken en önemli bölüm:

[6] SplitSpool health check
splitspool wrong messages=0
splitspool health=OK
MailScanner status=active

Neden Bu Yapı Daha Güvenli?

Klasik bir otomatik patch sistemi her güncellemede dosyanın içine körlemesine müdahale edebilir.

Buradaki yapı ise önce mevcut kodu kontrol eder.

  • Patch mevcutsa hiçbir değişiklik yapmaz.
  • Bilinen eski yapı varsa önce yedek alır ve sonra patch uygular.
  • Patch sonrası sonucu doğrular.
  • Doğrulama başarısızsa otomatik olarak yedeği geri yükler.
  • Bilinmeyen kod yapısında hiçbir değişiklik yapmaz.

Dolayısıyla sistem idempotent çalışır.

Aynı script defalarca çalıştırılsa bile doğru dosyalara tekrar tekrar müdahale etmez.

Canlı Sunucuda Doğrulanan Sonuç

Kurulum tamamlandıktan sonra updater gerçek üretim sunucusunda tekrar çalıştırıldı.

Son sağlık kontrolü:

splitspool check exit=0 (OK)
splitspool wrong messages=0
splitspool health=OK
MailScanner status=active
outgoing queue=35

şeklinde sonuçlandı.

Bu durum:

  • MailScanner patch'inin aktif olduğunu,
  • Exim 4.100 ile doğru split spool klasörlerinin kullanıldığını,
  • Yanlış dizinde mail bulunmadığını,
  • MailScanner servisinin aktif olduğunu,
  • Outgoing queue runner'ın çalıştığını

doğrulamaktadır.

Önemli: Otomatik patch sistemi gelecekte yapılacak tüm MailScanner veya Exim değişikliklerini garanti altında tutmaz. MailScanner kaynak kod yapısı tamamen değişirse script bilinmeyen yapıyı tespit ederek müdahale etmeyecek ve logda hata/uyarı oluşturacaktır. Bu davranış kasıtlıdır ve üretim sunucusunda yanlış otomatik değişiklik yapılmasını önlemek amacıyla tasarlanmıştır.


Yıldız Bilişim Hizmetleri - Otomatik MailScanner / Exim Uyumluluk Sistemi

Bu yapı gerçek cPanel, Exim 4.100 ve MailScanner 5.4.4 üretim ortamında uygulanmış ve güncelleme sonrası otomatik sağlık kontrolü ile doğrulanmıştır.

OKUYUCU YORUMLARI

Yorumlar

0

Henüz onaylanmış yorum yok. İlk yorumu sen bırakabilirsin.

Yorum Yaz

Yorum yayınlanmadan önce e-posta adresiniz 120 saniyelik kodla doğrulanır ve yorum yönetici onayına gönderilir.