في مقال بوابات أمن البريد المفتوحة قارنا بين البوابات واخترنا Proxmox Mail Gateway لمن يريد حجراً Quarantine وتقريراً يومياً للمستخدم ومركز تتبع جاهزة من أول يوم، وفي هذا الدليل سوف نشغله فعلاً أمام خادم بريد Postfix، ونمرر عليه رسائل حقيقية ونرى ماذا يفعل بكل واحدة، ثم نكسر إعداده عمداً ثلاث مرات لنرى الأخطاء التي تقع فيها المؤسسات عادة. وإن أردت أن تفهم أولاً كيف يعطي المحرك الرسالة درجة، فابدأ بمقال كيف تعمل بوابة أمن البريد:
دليلبوابات أمن البريد المفتوحة: أيها تضع أمام خادمك؟مقارنة بين بوابات أمن البريد المفتوحة والمستضافة ذاتياً، لتعرف أيها يناسب مؤسستك وأين تضعها أمام خادم البريد.
دليلكيف تعمل بوابة أمن البريد وتكشف التصيد؟كيف تفحص بوابة أمن البريد كل رسالة وتكشف التصيد والانتحال، مع تشغيل Rspamd وClamAV على رسائل اختبار.
دليلشرح SPF وDKIM وDMARC للمبتدئين: كيف تثبت أن رسائلك منك فعلاًلماذا يستطيع أي شخص أن يرسل باسم نطاقك، وكيف يسد كل سجل من هذه السجلات الثلاثة ثغرة تركها الذي قبله.وسوف نناقش في هذا المقال ما يلي:
- مختبر محلي كامل: إنترنت تجريبي فيه مرسل ومرسل مزعج وDNS، وشبكة داخلية فيها خادم البريد، والبوابة بينهما.
- تثبيت Proxmox Mail Gateway 9.1 بإصدار مثبت داخل Container يعمل بـ systemd، وثلاث مشكلات تواجهها في هذه الطريقة وحلها.
- تسليم البريد للخادم الداخلي، ثم نتائج SPF وDKIM وDMARC على رسائل اختبار، ولماذا لم يعمل DMARC من أول مرة.
- الحجر والتقرير اليومي الذي يصل للموظف، وإفراجه عن رسالة بنفسه.
- قواعد المرفقات: ملف تنفيذي، ومستند ماكرو Macro غير ضار، وملف EICAR التجريبي.
- الفلترة الصادرة وتوقيع DKIM، ثم كسر الإعداد: شبكة موثوقة خاطئة، وانقطاع DNS عن قائمة الحظر.
المختبر: ماذا سنبني؟
لا يخرج أي بريد من هذا المختبر إلى الإنترنت، فكل الأطراف Containers على جهازك في شبكتين من Docker: الأولى lab-seg-inet تمثل الإنترنت، وفيها مرسل خارجي لنطاق example.net يوقع رسائله بـ DKIM، ومرسل مزعج عنوانه مدرج في قائمة حظر تجريبية، وسيرفر DNS يجيب عن سجلات SPF وDKIM وDMARC، وMailpit يمثل سيرفر شريك نرسل إليه البريد الصادر. والثانية lab-seg-lan شبكة داخلية دون إنترنت، فيها خادم البريد الخاص بنطاقنا example.com. والبوابة متصلة بالشبكتين. والصورة التالية تبين ذلك:

ولاحظ أننا استعملنا النطاقات المحجوزة للأمثلة example.com وexample.net وexample.org، ولم نستعمل .test، والسبب أن مكتبة Mail::DMARC التي يعتمد عليها فحص DMARC في PMG تتحقق من النطاق في قائمة اللواحق العامة Public Suffix List، فرفضت النطاق .test برسالة invalid header_from في تجربتنا الأولى ولم تفحص DMARC أصلاً. وسيرفر DNS هو CoreDNS بملف منطقة Zone لكل نطاق، وهذا مثلاً ملف example.net:
@ IN MX 10 mail.example.net.
mail IN A 10.77.10.20
@ IN TXT "v=spf1 ip4:10.77.10.20 -all"
_dmarc IN TXT "v=DMARC1; p=reject"
s1._domainkey IN TXT ( "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOC..." )
partner IN MX 10 mx.partner.example.net.
mx.partner IN A 10.77.10.30ونطاق example.org يمثل بنكاً ينتحل المهاجم اسمه، وسجل SPF فيه يسمح لعنوان آخر فقط مع p=reject، وقائمة الحظر bl.lab.test فيها عنوان المرسل المزعج 10.77.10.21.
تثبيت Proxmox Mail Gateway 9.1
قد يتساءل البعض: أين الـ Docker Image الرسمية؟ والإجابة أنه لا توجد، فالطرق التي يذكرها دليل الإدارة هي ملف ISO على سيرفر أو جهاز افتراضي، أو حزم فوق Debian، أو داخل LXC بالحزمة proxmox-mailgateway-container التي لا تحتاج نواة ولا محمل إقلاع. وPMG يحتاج systemd يدير قرابة عشر خدمات معاً، وهذا ما لا يقدمه Container عادي، لذلك بنينا Docker Image من Debian 13 يعمل فيه systemd كعملية أولى PID 1، ثم ثبتنا الحزمة كما يثبتها LXC. وهذه طريقة مختبر فقط، أما في الإنتاج فاستعمل ملف ISO في جهاز افتراضي أو قالب LXC في Proxmox VE، والسبب أن الـ Container هنا يعمل بصلاحيات --privileged ويرى موارد الجهاز كله.
الـ Dockerfile كما يلي، ولاحظ أن مفتاح المستودع ومكانه من الدليل الرسمي:
# Debian 13 with systemd as PID 1, ready for proxmox-mailgateway-container
FROM debian:13.1
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
systemd systemd-sysv ca-certificates wget curl ifupdown iproute2 dnsutils swaks procps \
&& wget -q https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg \
-O /usr/share/keyrings/proxmox-archive-keyring.gpg \
&& printf 'Types: deb\nURIs: http://download.proxmox.com/debian/pmg\nSuites: trixie\nComponents: pmg-no-subscription\nSigned-By: /usr/share/keyrings/proxmox-archive-keyring.gpg\n' \
> /etc/apt/sources.list.d/proxmox.sources \
&& rm -rf /var/lib/apt/lists/*
STOPSIGNAL SIGRTMIN+3
CMD ["/sbin/init"]ثم ننشئ الشبكتين ونشغل البوابة، ولاحظ أن لوحة الإدارة منشورة على 127.0.0.1 فقط:
docker build -t lab-seg-pmg-base:1 pmg
docker network create --subnet 10.77.10.0/24 lab-seg-inet
docker network create --internal --subnet 10.77.20.0/24 lab-seg-lan
docker run -d --name lab-seg-pmg --hostname mx.example.com --privileged --cgroupns=host \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw --tmpfs /run --tmpfs /run/lock --memory 3g \
--network lab-seg-inet --ip 10.77.10.10 --dns 10.77.10.53 -p 127.0.0.1:18006:8006 lab-seg-pmg-base:1
docker network connect --ip 10.77.20.10 lab-seg-lan lab-seg-pmg
docker exec lab-seg-pmg bash -c 'apt-get update && apt-get install -y proxmox-mailgateway-container=9.1'ثبتنا الإصدار بـ =9.1 حتى تحصل على ما حصلنا عليه نفسه، وبعد حل المشكلات في القسم التالي أخرج الأمر pmgversion -v ما يلي (اختصرنا القائمة):
proxmox-mailgateway-container: 9.1 (API: 9.1.4/9931cdff460a, running kernel: 5.15.167.4-microsoft-standard-WSL2)
pmg-api: 9.1.4
pmg-gui: 5.2.3
clamav-daemon: 1.4.6+dfsg-1~deb13u1+pmg1
pmg-docs: 9.1.1
pmg-log-tracker: 3.1.2ومعه Postfix 3.10.13 وSpamAssassin 4.0.2 وPostgreSQL 17.11، واستهلك الـ Container قرابة 1.9 GiB من الذاكرة RAM وهو يعمل، وهذا متوافق مع ما يطلبه الدليل: 2.5 GiB حداً أدنى و4 GiB موصى بها.
ثلاث مشكلات في التثبيت داخل Container
انتهى أمر التثبيت دون خطأ، ولكن البوابة لم تعمل، وفي السجل وجدنا ما يلي:
/usr/sbin/policy-rc.d returned 101, not running 'start pmgdaemon.service'
psql: error: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directory
postgres_admin_cmd failed: command 'psql -U postgres --list --quiet --tuples-only' failed: exit code 2والسبب أن Docker Image الرسمية لـ Debian تأتي بملف /usr/sbin/policy-rc.d يرجع 101 ليمنع تشغيل الخدمات أثناء docker build، فلم يبدأ PostgreSQL ولم تنشأ قاعدة القواعد. والحل أن تحذف الملف ثم تعيد تشغيل الـ Container ليبدأ systemd كل الخدمات:
docker exec lab-seg-pmg rm -f /usr/sbin/policy-rc.d
docker restart lab-seg-pmgوالمشكلة الثانية أن خدمة تحديث توقيعات الفيروسات فشلت:
freshclam[202]: ERROR: Can't save PID to file /var/run/clamav/freshclam.pid: No such file or directoryوالسبب أننا جعلنا /run نظام ملفات في الذاكرة tmpfs، فيبدأ فارغاً عند كل تشغيل والمجلد /run/clamav غير موجود، والحل ملف tmpfiles ينشئه عند الإقلاع:
docker exec lab-seg-pmg sh -c 'echo "d /run/clamav 0755 clamav clamav -" > /etc/tmpfiles.d/clamav-run.conf \
&& systemd-tmpfiles --create /etc/tmpfiles.d/clamav-run.conf && systemctl restart clamav-freshclam'والمشكلة الثالثة لا تخص Docker، وإنما ستراها على أي سيرفر: نزل freshclam قواعد التوقيعات في أول تشغيل، ثم أعدنا تشغيله فرفض الخادم طلبه وأخرج ما يلي:
WARNING: You are still on cool-down until after: 2026-10-12 04:05:38وهذا حد المعدل Rate Limit في شبكة توزيع ClamAV، فلا تقم بإعادة تشغيل freshclam في حلقة ولا تثبت عشر بوابات خلف عنوان واحد دون مرآة داخلية، والسبب أن العنوان كله يدخل فترة انتظار. والقواعد التي نزلت أولاً كانت كافية، فبدأ clamav-daemon وكشف الفيروس التجريبي كما سنرى.
ربط البوابة بخادم البريد الداخلي
الآن نخبر البوابة بثلاثة أشياء: النطاق الذي تقبل البريد له Relay Domain، والخادم الذي تسلم له البريد Default Relay، ونمنعها من البحث عن سجل MX لهذا الخادم لأن عنوانه داخلي. ويمكن فعل ذلك من اللوحة أو بأداة pmgsh التي تستدعي الواجهة البرمجية نفسها:
docker exec lab-seg-pmg pmgsh create /config/domains --domain example.com
docker exec lab-seg-pmg pmgsh set /config/mail --relay 10.77.20.30 --relaynomx 1والصورة التالية تبين النتيجة في اللوحة على https://127.0.0.1:18006 بعد الدخول بالمستخدم root:

وفي الجهة الأخرى نجعل خادم البريد الداخلي Postfix يرسل كل بريده الخارج عبر المنفذ الداخلي للبوابة، وهذا هو الجزء المهم من main.cf فيه:
myhostname = backend.example.com
mydestination = example.com, localhost
mynetworks = 127.0.0.0/8 10.77.20.0/24
smtpd_recipient_restrictions = permit_mynetworks, reject
# all outgoing mail goes through the gateway's internal port 26
relayhost = [10.77.20.10]:26
home_mailbox = Maildir/وفي الإعداد أعلاه لاحظ التالي:
- المنفذ 25 في PMG للوارد من الإنترنت، والمنفذ 26 للصادر من خوادمك، وهذا التقسيم في قسم المنافذ، وكل ما يدخل من 26 تطبق عليه القواعد ذات الاتجاه Out.
- الأقواس حول العنوان في
relayhostتمنع Postfix من البحث عن سجل MX له. - الخادم الداخلي يقبل الاتصال من شبكته الداخلية فقط، فلا يستطيع أحد من الإنترنت أن يتجاوز البوابة ويكلمه مباشرة.
وهناك أمر رابع لم يظهر في اللوحة ولكنه الأخطر في الإعداد كله، وهو الشبكة الموثوقة mynetworks. فـ PMG يضيف إليها تلقائياً الشبكة التي يقع فيها عنوان السيرفر نفسه، كما في الدالة get_template_vars في PMG/Config.pm، وعنوان السيرفر هو العنوان المكتوب لاسمه في /etc/hosts. ولأن Docker كتب في البداية العنوان 10.77.10.10 من شبكة الإنترنت التجريبية، صارت الشبكة الموثوقة كما يلي:
mynetworks = 10.77.10.0/24 10.77.20.30/32 127.0.0.0/8 [::1]/128أي أن كل الإنترنت التجريبي موثوق، وسوف نرى نتيجة ذلك في قسم الكسر. والحل أن يكون عنوان السيرفر في /etc/hosts هو عنوانه الداخلي، وأن يكون نطاق البحث في /etc/resolv.conf هو نطاقك، وإلا قدمت البوابة نفسها باسم mx.localdomain كما حدث معنا أول مرة:
docker exec lab-seg-pmg sh -c 'printf "127.0.0.1 localhost\n10.77.20.10 mx.example.com mx\n" > /etc/hosts; pmgconfig sync --restart 1; postconf mynetworks'mynetworks = 10.77.20.0/24 10.77.20.30/32 127.0.0.0/8 [::1]/128طريق الرسالة داخل البوابة
قبل أن نرسل أول رسالة، لنعرف أين يمكن أن تتوقف، لأن كل مخرج في الأقسام التالية يأتي من مرحلة مختلفة. والصورة التالية تبين ذلك:

ولاحظ أن الرفض في المرحلتين الأوليين يحدث أثناء حوار SMTP، فيعرف المرسل الحقيقي أن رسالته رفضت، أما ما بعد ذلك فالبوابة قبلت الرسالة ثم تقرر بنظام القواعد، وهذا ما يسميه الدليل الفلترة بعد الطابور After Queue، وهي الإعداد الافتراضي.
الوارد: رسالة نظيفة تصل الصندوق
نبدأ برسالة عادية من [email protected] موقعة بـ DKIM إلى [email protected]، ونرسلها بأداة swaks من Container المرسل إلى المنفذ 25 في البوابة:
docker exec lab-seg-sender swaks --server 10.77.10.10 --port 25 \
--from [email protected] --to [email protected] --data @/mail/clean.emlوالمخرج من البوابة ثم من سجلها سوف يكون كما يلي:
<- 250 2.0.0 Ok: queued as CA8532E13BF
pmgpolicy[2129]: SPF says pass
pmg-smtp-filter[2363]: 2E18CA6ACC10A0E41D4: SA score=0/5 time=1.428 bayes=undefined autolearn=disabled hits=DKIM_SIGNED(0.1),DKIM_VALID(-0.1),DKIM_VALID_AU(-0.1),DKIM_VALID_EF(-0.1),DMARC_PASS(-0.1),SPF_HELO_NONE(0.001),SPF_PASS(-0.001)
pmg-smtp-filter[2363]: 2E18CA6ACC10A0E41D4: accept mail to <[email protected]> (821BA2E18C7) (rule: default-accept)وهذه ترويسات الرسالة كما وصلت صندوق bob في الخادم الداخلي (اختصرنا التوقيع):
Received: from mx.example.com (lab-seg-pmg.lab-seg-lan [10.77.20.10])
by backend.example.com (Postfix) with ESMTP id E78DE2D6FD0
Received-SPF: pass (example.net: 10.77.10.20 is authorized to use '[email protected]' in 'mfrom' identity (mechanism 'ip4:10.77.10.20' matched)) receiver=mx.example.com; identity=mailfrom; envelope-from="[email protected]"; helo=sender.example.net; client-ip=10.77.10.20
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=example.net; s=s1; ...
From: Alice <[email protected]>
Subject: Meeting tomorrow
X-SPAM-LEVEL: Spam detection results: 0
DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain
DMARC_PASS -0.1 DMARC pass policy
SPF_PASS -0.001 SPF: sender matches SPF recordفي المخرج أعلاه لاحظ التالي:
- فحص SPF تم مرتين بطريقتين: الخدمة
pmgpolicyفحصته أثناء الحوار وأضافت الترويسةReceived-SPF، ثم استعمل SpamAssassin النتيجة نفسها في الدرجة. - الدرجة 0 من 5، والقاعدة التي طبقت هي
default-accept، أي لم تطابق أي قاعدة منع أو حجر فسلمت الرسالة. - الترويسة
X-SPAM-LEVELأضافتها القاعدة الافتراضية Modify Header، وهي ما يقرؤه فريق الدعم عندما يسأل الموظف لماذا وصلته رسالة ما.
نتائج SPF وDKIM وDMARC على رسائل اختبار
شرحنا هذه الفحوص الثلاثة في مقال SPF وDKIM وDMARC، وهنا نرى كيف يتعامل معها PMG تحديداً، والفرق بينها في المكان الذي يحدث فيه الفحص.
SPF: رفض قبل استلام الرسالة
نرسل رسالة «من المدير» بعنوان مرسل في الظرف Envelope من example.org، من عنوان لا يسمح به سجل SPF لهذا النطاق:
-> MAIL FROM:<[email protected]>
-> RCPT TO:<[email protected]>
<** 554 5.7.1 <[email protected]>: Recipient address rejected: Rejected by SPF: 10.77.10.20 is not a designated mailserver for ceo%40example.org (context mfrom, on mx.example.com)رفضت الرسالة قبل أن تصل محتواها أصلاً، فلا يوجد ما يحجر ولا ما يفحص. وقرأنا كود pmgpolicy في pmg-api 9.1.4 لنعرف القاعدة كاملة: نتيجة fail ترفض فوراً، ونتيجة pass تخرج من الفحص قبل الوصول إلى التأجيل الرمادي Greylisting، أما بقية النتائج مثل عدم وجود سجل فتمر إلى Greylisting، ولهذا لم تتأخر أي رسالة نظيفة في تجربتنا مع أن Greylisting مفعل افتراضياً.
DKIM: من الدرجة لا من الرفض
التحقق من توقيع DKIM للوارد يحدث داخل SpamAssassin كما يقول قسم DKIM في الدليل، فالتوقيع الصحيح يعطي الرموز DKIM_VALID وDKIM_VALID_AU كما رأينا في الرسالة النظيفة، والتوقيع المكسور يرفع الدرجة قليلاً ولا يرفض الرسالة وحده.
DMARC: لماذا لم يعمل من أول مرة؟
هذه هي الحالة الأخطر في البريد الحقيقي: المهاجم يرسل من نطاقه هو فينجح SPF، ولكنه يكتب في الحقل From الذي يراه الموظف اسم البنك [email protected]. وسجل DMARC للبنك يقول p=reject، فالمتوقع أن تحجز البوابة الرسالة. ولكن هذا ما حدث في أول تجربة:
pmgpolicy[218]: SPF says pass
pmg-smtp-filter[404]: 2E04AF6ACC0C8B0CC8B: SA score=0/5 time=1.138 bayes=undefined autolearn=disabled hits=ALL_TRUSTED(-1),HEADER_FROM_DIFFERENT_DOMAINS(0.249),KAM_DMARC_STATUS(0.01),SPF_PASS(-0.001)
pmg-smtp-filter[404]: 2E04AF6ACC0C8B0CC8B: accept mail to <[email protected]> (4917A2E04B0) (rule: default-accept)وصلت الرسالة المنتحلة إلى bob بدرجة صفر. والرمز المهم هنا هو ALL_TRUSTED، ومعناه أن SpamAssassin اعتبر كل السيرفرات التي مرت بها الرسالة داخلية موثوقة. وعندما شغلنا spamassassin -D dmarc على الرسالة لم يطبع أي سطر عن DMARC، فقرأنا كود الإضافة في Mail/SpamAssassin/Plugin/DMARC.pm فوجدنا أنها تخرج قبل الفحص إن لم تجد سيرفراً خارجياً في مسار الرسالة:
dbg("no external relay found, skipping DMARC check");والسبب أن SpamAssassin حين لا تحدد له شبكتك يستنتجها، فيعتبر العناوين الخاصة Private Addresses مثل 10.77.10.20 داخلية، وإضافة DMARC لا تفحص رسالة لم تأت من الخارج. وفي الإنتاج سترى الشيء نفسه إذا وصلت الرسائل إلى البوابة من عنوان خاص، كأن تكون خلف موزع حمل أو NAT يغير عنوان المصدر. والحل كما يقول قسم إعداد SpamAssassin المخصص ملف custom.cf لا يكتب فوقه PMG، نحدد فيه شبكتنا الحقيقية:
docker exec lab-seg-pmg sh -c 'cat >> /etc/mail/spamassassin/custom.cf <<EOF
trusted_networks 10.77.20.0/24
internal_networks 10.77.20.0/24
EOF
spamassassin --lint && systemctl restart pmg-smtp-filter'ثم أرسلنا الرسالة المنتحلة نفسها مرة أخرى، والمخرج سوف يكون كما يلي:
pmgpolicy[2129]: SPF says pass
pmg-smtp-filter[2362]: 2E18E16ACC10AFF3745: SA score=7/5 time=1.727 bayes=undefined autolearn=disabled hits=DMARC_REJECT(0.1),HEADER_FROM_DIFFERENT_DOMAINS(0.249),KAM_DMARC_REJECT(7),KAM_DMARC_STATUS(0.01),SPF_HELO_NONE(0.001),SPF_PASS(-0.001)
pmg-smtp-filter[2362]: 2E18E16ACC10AFF3745: moved mail for <[email protected]> to spam quarantine - 2E18F16ACC10B1EFAA3 (rule: Quarantine/Mark Spam (Level 3))في المخرج أعلاه لاحظ التالي:
- اختفى
ALL_TRUSTED، وظهرDMARC_REJECTمن إضافة DMARC في SpamAssassin 4.0، والرمزKAM_DMARC_REJECTمن مجموعة قواعد KAM التي يضيفها PMG هو الذي أعطى 7 نقاط. - الرسالة لم ترفض وإنما حجزت، والسبب أن PMG لا يطبق سياسة DMARC كقرار مستقل، وإنما تصبح نقاطاً في الدرجة، والقاعدة الافتراضية Quarantine/Mark Spam (Level 3) تحجز كل ما بلغ 3 نقاط.
- أضاف PMG أيضاً
SPAM:إلى العنوان، وسنراه في الحجر بعد قليل.
الحجر والتقرير اليومي للمستخدم
أرسلنا بعد ذلك رسالة فيها نص GTUBE، وهو نص اختبار متفق عليه تعتبره كل فلاتر SpamAssassin بريداً مزعجاً بالتأكيد، كما أن EICAR ملف اختبار للفيروسات:
pmg-smtp-filter[2363]: 2E18F76ACC10B73074C: SA score=999/5 time=0.805 bayes=undefined autolearn=disabled hits=DKIM_SIGNED(0.1),DKIM_VALID(-0.1),DKIM_VALID_AU(-0.1),DKIM_VALID_EF(-0.1),DMARC_PASS(-0.1),GTUBE(1000),SPF_HELO_NONE(0.001),SPF_PASS(-0.001),TVD_SPACE_RATIO(0.001)
pmg-smtp-filter[2363]: 2E18F76ACC10B73074C: moved mail for <[email protected]> to spam quarantine - 2E18F86ACC10B817B6B (rule: Quarantine/Mark Spam (Level 3))والآن في حجر البريد المزعج رسالتان لـ bob، والصورة التالية تبينهما من لوحة المدير، ولاحظ تفصيل الدرجة لرسالة الانتحال في الأسفل:

ولكن المدير لا يريد أن يراجع حجر كل موظف، ولهذا يرسل PMG كل يوم تقريراً لكل مستخدم في حجره رسائل جديدة، والخدمة pmgspamreport.timer تشغله الساعة 00:05 كما في قسم الحجر في الدليل. ولن ننتظر منتصف الليل، فنرسله الآن بأداة pmgqm:
docker exec lab-seg-pmg pmgqm send --receiver [email protected] --timespan todayووصل التقرير إلى صندوق bob في الخادم الداخلي، وهذا جزؤه النصي (اختصرنا الرابط):
From: Proxmox Mail Gateway <[email protected]>
Subject: Daily Spam Report for '[email protected]' - 2026-10-11
There are 2 messages for [email protected] in the spam quarantine.
Manage your spam quarantine:
https://mx.example.com:8006/quarantine?ticket=PMGQUAR%3Abob%40example.com%3A6ACC1121%3A%3AGEfNv0kS3S...
Quarantined message(s):
- 2026-10-11 22:41:51 from: CEO <[email protected]>
Subject: SPAM: Urgent transfer
- 2026-10-11 22:41:59 from: Offers <[email protected]>
Subject: SPAM: Special offerوالصورة التالية تبين الجزء HTML من التقرير نفسه، ولاحظ الروابط الصغيرة بجانب كل رسالة:

والرابط في التقرير فيه تذكرة Ticket تدخل الموظف إلى حجره هو فقط دون كلمة مرور، وهذا هو وضع الدخول الافتراضي ticket، ويمكن تغييره إلى LDAP أو الاثنين. والصورة التالية تبين ما يراه bob بعد أن فتح الرابط:

ولاحظ أن الموظف رأى عند الدخول نافذة «No valid subscription» قبل هذه الصفحة، فالتنبيه بعدم وجود اشتراك يظهر للمستخدمين أيضاً لا للمدير فقط، وهذا سبب عملي لشراء أقل اشتراك قبل أن ترسل التقارير لكل الموظفين. ثم أفرجنا عن رسالة من الحجر بالأمر الذي يستدعيه زر Deliver:
docker exec lab-seg-pmg pmgsh create /quarantine/content --id C0R7T2383947 --action deliver200 OK
postfix/smtp[2871]: F38D52B80FF: to=<[email protected]>, relay=10.77.20.30[10.77.20.30]:25, delay=0.22, delays=0.08/0.02/0.05/0.07, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 28FD42B84B5)ووصلت إلى الصندوق بعنوانها المعدل subject: SPAM: Special offer وترويسة X-SPAM-LEVEL: Spam detection results: 999، أي أن الإفراج لا يعيد الرسالة إلى حالتها الأصلية، والسبب أن التعديل حدث قبل الحجر.
قواعد المرفقات
كل الملفات في هذا القسم غير ضارة: الملف «التنفيذي» نص يبدأ بالحرفين MZ، ومستند الماكرو ملف zip فيه ملف vbaProject.bin فارغ لا يشغل شيئاً، وEICAR نص اختبار من 68 بايت تتفق برامج الحماية على كشفه كما تشرح صفحة EICAR.
ملف تنفيذي: حذف المرفق وتسليم الرسالة
في القواعد الافتراضية قاعدة Block Dangerous Files، ومجموعتها Dangerous Content تطابق أنواع محتوى وأسماء ملفات، وهذه عناصرها كما أخرجها pmgsh:
ContentType Filter | content-type=application/javascript
ContentType Filter | content-type=application/vnd\.microsoft\.portable-executable
ContentType Filter | content-type=application/x-executable
ContentType Filter | content-type=application/x-java
ContentType Filter | content-type=application/x-msdownload
ContentType Filter | content-type=message/partial
Match Filename | filename=.*\.(vbs|pif|lnk|shs|shb)
Match Filename | filename=.*\.\{.+\}وأرسلنا رسالة فيها invoice.exe، والمخرج سوف يكون كما يلي:
pmg-smtp-filter[2362]: 2E18F76ACC10BE512FD: virus detected: Heuristics.Broken.Executable (clamav)
pmg-smtp-filter[2362]: 2E18F76ACC10BE512FD: SA score=2/5 ... hits=ClamAVHeuristics(3),DKIM_SIGNED(0.1),...
pmg-smtp-filter[2362]: 2E18F76ACC10BE512FD: removed attachment 3 ('invoice.exe', rule: Block Dangerous Files)
pmg-smtp-filter[2362]: 2E18F76ACC10BE512FD: accept mail to <[email protected]> (251D62E18FE) (rule: default-accept)ووصلت الرسالة إلى bob وفيها بدل المرفق ملف نصي:
Content-Type: text/plain; charset="UTF-8"; name="REMOVED_ATTACHMENT_3.txt"
This attachment was removed: invoice.exeولاحظ أن ClamAV رأى الملف «تنفيذياً مكسوراً» بالكشف الاستدلالي Heuristics، وهذا لم يحجز الرسالة كفيروس وإنما أضاف 3 نقاط للدرجة، أما الذي حذف المرفق فقاعدة نوع المحتوى، والسبب أن PMG يحدد النوع من محتوى الملف ومن ترويسة الجزء ومن امتداد الاسم معاً.
مستند ماكرو: قاعدة نضيفها بأنفسنا
أرسلنا report.docm، وهو امتداد مستندات Word التي فيها ماكرو، فمر بالقواعد الافتراضية ووصل كما هو، والسبب أن مجموعة Office Files الافتراضية لا تشمل النوع application/vnd.ms-word.document.macroEnabled.12، وقاعدتها Quarantine Office Files معطلة افتراضياً أصلاً. لذلك أضفنا مجموعة وقاعدة جديدتين بالواجهة البرمجية:
docker exec lab-seg-pmg pmgsh create /config/ruledb/what --name "Macro Documents" # returned 25
docker exec lab-seg-pmg pmgsh create /config/ruledb/what/25/filenamefilter --filename '.*\.(docm|dotm|xlsm|xltm|pptm|potm)'
docker exec lab-seg-pmg pmgsh create /config/ruledb/what/25/archivefilenamefilter --filename '.*vbaProject\.bin'
docker exec lab-seg-pmg pmgsh create /config/ruledb/rules --name "Quarantine Macro Documents" --priority 92 --direction 0 --active 1 # returned 14
docker exec lab-seg-pmg pmgsh create /config/ruledb/rules/14/what --ogroup 25
docker exec lab-seg-pmg pmgsh create /config/ruledb/rules/14/action --ogroup 23 # Attachment Quarantine (remove matching)والصورة التالية تبين القاعدة في اللوحة بين القواعد الافتراضية، ولاحظ أولويتها 92 فهي تعمل بعد منع الفيروسات وقبل تعديل الترويسات:

وأعدنا إرسال المستند نفسه، والمخرج سوف يكون كما يلي:
pmg-smtp-filter[2363]: 2E19046ACC10C537B20: moved mail for <[email protected]> to attachment quarantine - 2E19066ACC10C6111C4 (rule: Quarantine Macro Documents)
pmg-smtp-filter[2363]: 2E19046ACC10C537B20: removed attachment 3 ('report.docm', rule: Quarantine Macro Documents)
pmg-smtp-filter[2363]: 2E19046ACC10C537B20: accept mail to <[email protected]> (1AF172E1908) (rule: default-accept)أي أن bob استلم الرسالة دون المرفق، والنسخة الأصلية بمرفقها في حجر المرفقات، والمدير يستطيع أن يسلمها كاملة إن كانت مشروعة، كما في الصورة التالية:

ولكن ماذا لو أعاد المهاجم تسمية الملف نفسه إلى report.docx؟ أرسلناه، فمر:
pmg-smtp-filter[2362]: 2E190B6ACC10CC419EF: SA score=0/5 ... hits=DKIM_SIGNED(0.1),DKIM_VALID(-0.1),DKIM_VALID_AU(-0.1),DKIM_VALID_EF(-0.1),DMARC_PASS(-0.1),SPF_HELO_NONE(0.001),SPF_PASS(-0.001)
pmg-smtp-filter[2362]: 2E190B6ACC10CC419EF: accept mail to <[email protected]> (1E8F22E190C) (rule: default-accept)وقد يتساءل البعض: أليس المستند ملف zip، وقد أضفنا شرطاً على vbaProject.bin داخل الأرشيف؟ والإجابة أن PMG حدد نوعه application/vnd.openxmlformats-officedocument.wordprocessingml.document، وقائمة الأنواع التي يفتحها كأرشيف في الملف PMG/Unpack.pm فيها zip و7z وrar وtar وcab وغيرها، وليس فيها أنواع Office، فلم ينظر داخل المستند أصلاً. وهذا يعني أن قاعدة الماكرو بالاسم تمسك المستند الذي يحمل امتداده الصحيح فقط، فإن كان الماكرو خطراً حقيقياً عندك ففعل قاعدة Quarantine Office Files المعطلة وراجع ما تحجزه أسبوعاً قبل أن تعتمدها، فهي تحجز كل مستندات Office لا الماكرو فقط.
ملف EICAR: حجر الفيروسات
pmg-smtp-filter[2363]: 2E190E6ACC10D3ABD2D: virus detected: Eicar-Test-Signature (clamav)
pmg-smtp-filter[2363]: 2E190E6ACC10D3ABD2D: moved mail for <[email protected]> to virus quarantine - 2E190C6ACC10D4A9B6E (rule: Block Viruses)
pmg-smtp-filter[2363]: 2E190E6ACC10D3ABD2D: notify <[email protected]> (rule: Block Viruses, 958892E190B)ولاحظ أن الرسالة ذهبت إلى حجر الفيروسات، وأن bob لم يستلم شيئاً، وأن المدير وصله إشعار. وعنوان المدير هنا [email protected] لأننا ضبطناه بالأمر pmgsh set /config/admin --email [email protected]، أما قبل ذلك فكانت الإشعارات تذهب إلى العنوان الافتراضي [email protected] كما ظهر في مركز التتبع، فلا تنس هذا الإعداد.
الصادر: توقيع DKIM ومنع الفيروس
البريد الصادر من خوادمك يمر بالبوابة على المنفذ 26، فنفعل توقيع DKIM أولاً بمفتاح RSA بطول 2048 بت، ونجعل البوابة تختار نطاق التوقيع من الحقل From لا من الظرف، والسبب أن DMARC يطابق نطاق التوقيع مع From كما يشرح قسم DKIM:
docker exec lab-seg-pmg pmgsh create /config/dkim/selector --selector pmg2026 --keysize 2048
docker exec lab-seg-pmg pmgsh set /config/admin --dkim_sign 1 --dkim_selector pmg2026 --dkim-use-domain header
docker exec lab-seg-pmg pmgsh get /config/dkim/selectorوالأمر الأخير يخرج السجل الذي تنشره في DNS لنطاقك، ونشرناه في منطقة example.com:
pmg2026._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmkcGQIi8+yZx04PMWhj36GvFNHPiiRa8r..." )ثم أرسلت alice من الخادم الداخلي رسالة عادية ورسالة فيها EICAR إلى شريك خارجي:
docker exec lab-seg-backend swaks --server 127.0.0.1 --from [email protected] --to [email protected] \
--header "Subject: Quarterly report" --body "Hello Carol, the report is ready."
docker exec lab-seg-backend swaks --server 127.0.0.1 --from [email protected] --to [email protected] \
--header "Subject: file" --body "see attached" --attach @/tmp/eicar.comوالمخرج من سجل البوابة سوف يكون كما يلي:
pmg-smtp-filter[2362]: 2DFF456ACC10FA85B1B: accept mail to <[email protected]> (C0E1B2DFF30) (rule: default-accept)
postfix/smtp[2381]: C0E1B2DFF30: to=<[email protected]>, relay=mx.partner.example.net[10.77.10.30]:25, delay=0.46, delays=0.34/0.01/0.02/0.1, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as UPrvQU8SiQgDLqEsfihmLx)
pmg-smtp-filter[2363]: 2D47BA6ACC10FB6B1C5: virus detected: Eicar-Test-Signature (clamav)
pmg-smtp-filter[2363]: 2D47BA6ACC10FB6B1C5: notify <[email protected]> (rule: Virus Alert, 9281C2E0471)
pmg-smtp-filter[2363]: 2D47BA6ACC10FB6B1C5: notify <[email protected]> (rule: Virus Alert, BD43D2E0472)
pmg-smtp-filter[2363]: 2D47BA6ACC10FB6B1C5: block mail to <[email protected]> (rule: Virus Alert)والرسالة العادية وصلت Mailpit عند الشريك بهذه الترويسات (اختصرنا التوقيع)، وتحققنا منها بأداة dkimverify من حزمة dkimpy فأخرجت signature ok:
Received: from mx.example.com (lab-seg-pmg.lab-seg-inet. [10.77.10.10])
by 9a3511fc0d57 (Mailpit) with SMTP
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; h=
cc:date:from:from:message-id:reply-to:subject:subject:to:to; s=
pmg2026; bh=xfVwLcHhUKxKKKtzDtbtcSx/kW8jqknTrSPXwjtDmSM=; b=KWDv...
Received: from backend.example.com (lab-seg-backend.lab-seg-lan [10.77.20.30])
by mx.example.com (Proxmox) with ESMTP id 47CE22E06BB
From: "Alice" <[email protected]>
Subject: Quarterly reportفي المخرج أعلاه لاحظ التالي:
- القاعدة التي منعت الفيروس الصادر هي Virus Alert ذات الاتجاه Out، وهي تمنع وتخطر المدير والمرسل معاً، فـ alice عرفت أن جهازها أرسل ملفاً مصاباً، وهذه فائدة فلترة الصادر التي يذكرها الدليل.
- الحقول
fromوsubjectوtoمكررة فيh=، وهذا ما يسميه الدليل التوقيع المزدوج Oversigning، والسبب أن من يضيف حقلFromثانياً بعد التوقيع يكسره. - قاعدة Block outgoing Spam معطلة افتراضياً كما رأينا في صورة القواعد، ففعلها إن كان عندك خوادم أو تطبيقات قد تخترق فترسل بريداً مزعجاً باسمك.
وكل ما حدث منذ بداية التجربة يظهر في مركز التتبع، وهو أول مكان تفتحه عندما يسأل موظف عن رسالة لم تصله:

اكسرها لتفهمها
شبكة موثوقة خاطئة: البوابة مرحل مفتوح
أعدنا عنوان السيرفر في /etc/hosts إلى العنوان الذي على شبكة الإنترنت التجريبية كما كتبه Docker أول مرة، ثم حاول المرسل المزعج أن يستعمل البوابة لإرسال بريد إلى نطاق ليس لنا، مرة على المنفذ 25 ومرة على المنفذ 26:
##### wrong node IP: 10.77.10.10 (internet side) in /etc/hosts
mynetworks = 10.77.10.0/24 10.77.20.30/32 127.0.0.0/8 [::1]/128
##### spammer -> port 25
-> RCPT TO:<[email protected]>
<** 454 4.7.1 <[email protected]>: Relay access denied
##### spammer -> port 26
-> RCPT TO:<[email protected]>
<- 250 2.0.0 Ok: queued as BEDA72B7764
pmg-smtp-filter[2362]: 2B78066ACC16C828A9E: accept mail to <[email protected]> (3DCF12B7808) (rule: default-accept)
postfix/smtp[2952]: 3DCF12B7808: to=<[email protected]>, relay=mx.partner.example.net[10.77.10.30]:25, ... status=sent (250 2.0.0 Ok: queued as iZvMsmjbcknamQtsWDZ255)المنفذ 25 رفض، والسبب أن PMG يكتب في master.cf لخدمة المنفذ 25 شبكة موثوقة خاصة فيها 127.0.0.0/8 وعنوان السيرفر فقط. أما المنفذ 26 فقبل الرسالة وسلمها للخارج، فالبوابة صارت مرحلاً مفتوحاً Open Relay لكل من يقع في تلك الشبكة. وعندما أعدنا العنوان الداخلي:
mynetworks = 10.77.20.0/24 10.77.20.30/32 127.0.0.0/8 [::1]/128
##### spammer -> port 26 again
-> RCPT TO:<[email protected]>
<** 454 4.7.1 <[email protected]>: Relay access deniedوالدرس هنا أمران: لا تضع البوابة في شبكة تثق بها كلها، كشبكة ضيوف أو شبكة فيها سيرفرات لا تديرها، والسبب أن الدليل يقول صراحة إن الأجهزة في الشبكة نفسها تستطيع الترحيل دون إضافتها، ثم لا تقم بنشر المنفذ 26 على الإنترنت أبداً، فهو للخوادم الداخلية فقط.
قائمة حظر بلا DNS: البوابة تفتح الباب
فعلنا قائمة الحظر التجريبية bl.lab.test بالأمر pmgsh set /config/mail --dnsbl_sites bl.lab.test، ثم أرسل المرسل المزعج ثلاث مرات: والـ DNS يعمل، ثم بعد أن أوقفنا Container الـ DNS، ثم بعد أن أعدناه:
##### 1) DNS up
<** 550 5.7.1 Service unavailable; client [10.77.10.21] blocked using bl.lab.test
postfix/postscreen[3151]: DNSBL rank 1 for [10.77.10.21]:52070
##### 2) DNS down
<** 450 4.7.1 <[email protected]>: Recipient address rejected: Service is unavailable (try later)
postfix/postscreen[3151]: PASS NEW [10.77.10.21]:40680
postfix/dnsblog[3152]: warning: dnsblog_query: lookup error for DNS query 21.10.77.10.bl.lab.test: Host or domain name not found. Name service error for name=21.10.77.10.bl.lab.test type=A: Host not found, try again
pmgpolicy[2784]: defer greylisted mail
##### 3) DNS back
<** 450 4.7.1 <[email protected]>: Recipient address rejected: Service is unavailable (try later)
postfix/postscreen[3151]: PASS OLD [10.77.10.21]:45142
pmgpolicy[2784]: defer greylisted mailفي المخرج أعلاه لاحظ التالي:
- عندما فشل استعلام DNS لم يرفض postscreen المرسل وإنما سمح له بالمرور
PASS NEW، أي أن القائمة تفشل مفتوحة Fail Open، وهذا تصميم مقصود حتى لا يتوقف بريدك كله بسبب سيرفر DNS، ولكن نتيجته أن المرسل المزعج مر. - بعد عودة DNS لم يعد postscreen الاستعلام وإنما قال
PASS OLD، والسبب أنه يخزن نتيجة المرور في ذاكرته المؤقتة Cache لمدة، فالمرسل المزعج ما زال يمر حتى تنتهي. - الذي أوقف المرسل في المرتين هو Greylisting، لأن عنوان المرسل في الظرف من نطاق دون سجل SPF، وهذا سبب لتبقي أكثر من طبقة مفعلة بدل الاعتماد على قائمة حظر واحدة.
وفي الإنتاج يحدث هذا إذا كان DNS الذي تستعمله البوابة بطيئاً أو محجوباً عن مزود القائمة، فاستعمل سيرفر DNS محلياً للبوابة، وراقب السطر dnsblog_query: lookup error في السجل، وراجع شروط مزود القائمة قبل أن تضيفها، فبعض المزودين لا يجيبون الاستعلامات القادمة من خوادم DNS العامة.
قبل الإنتاج
- طريقة التثبيت: ملف ISO في جهاز افتراضي أو قالب LXC في Proxmox VE، لا Docker، مع 4 GiB ذاكرة على الأقل كما يوصي الدليل.
- المستودع: اشتراك Community على الأقل للمستودع المختبر، أو مستودع pmg-no-subscription مع قبول أنه أقل اختباراً.
- النسخ الاحتياطي: الأمر
pmgbackup backupينسخ الإعداد وقاعدة القواعد إلى/var/lib/pmg/backup/، وأخرج عندناbackup finishedوملفاً بحجم 10331 بايت، وانقله إلى خارج السيرفر أو استعمل النسخ إلى Proxmox Backup Server. - اللوحة: لا تنشر المنفذ 8006 على الإنترنت، وفعل الدخول بعاملين للمدير، وضع شهادة TLS حقيقية عبر ACME.
- الشبكة: تأكد من
postconf mynetworksبعد كل تغيير في العناوين، ومن أن المنفذ 26 لا يصل إليه إلا خوادمك، ومن أن المنفذ 25 في خادم البريد لا يصل إليه إلا البوابة. - العنوان والاسم: سجل PTR لعنوان البوابة العام يطابق اسمها، وعنوانها في سجل SPF لنطاقك، كما شرحنا في مقال SPF وDKIM وDMARC.
وأخيراً حذفنا كل ما أنشأناه بأسمائه بالضبط:
docker rm -f lab-seg-pmg lab-seg-backend lab-seg-sender lab-seg-spammer lab-seg-partner lab-seg-dns
docker network rm lab-seg-inet lab-seg-lan
docker image rm lab-seg-pmg-base:1 lab-seg-backend:1 lab-seg-sender:1أخطاء شائعة
- ترك عنوان السيرفر في شبكة واسعة موثوقة، فيصبح المنفذ 26 مرحلاً مفتوحاً لكل من فيها.
- افتراض أن DMARC يعمل لأن الدليل يذكره، والصحيح أن تختبره برسالة منتحلة وتتأكد أن
ALL_TRUSTEDلا يظهر في رسائل الإنترنت. - الاعتماد على قاعدة الماكرو بالاسم وحده، والمستند المعاد تسميته يمر.
- نسيان عنوان المدير، فتذهب إشعارات الفيروسات إلى
[email protected]. - إعادة تشغيل freshclam مرات كثيرة أو تشغيل بوابات كثيرة خلف عنوان واحد، فيحظر عنوانك مؤقتاً من شبكة توزيع ClamAV.
- الاعتماد على قائمة حظر واحدة دون مراقبة أخطاء DNS، وهي تفشل مفتوحة بصمت.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- لا توجد Docker Image رسمية لـ PMG، وفي المختبر عمل بحزمة
proxmox-mailgateway-containerفي Debian 13 مع systemd بعد حذفpolicy-rc.dوإنشاء/run/clamav. - المنفذ 25 للوارد والمنفذ 26 للصادر، وعنوان السيرفر في
/etc/hostsيحدد الشبكة الموثوقة، وخطؤه جعل البوابة مرحلاً مفتوحاً. - SPF fail يرفض فوراً بـ 554، وDKIM يدخل الدرجة، وDMARC عبر SpamAssassin لم يعمل حتى حددنا الشبكة الداخلية، ثم حجز الانتحال بسبع نقاط.
- الحجر والتقرير اليومي برابط دخول جاهزان، والموظف يفرج عن رسالته بنفسه.
- الملف التنفيذي حذف، وEICAR حجز، ومستند الماكرو احتاج قاعدة أضفناها، والمستند المعاد تسميته مر.
- الصادر وقع بـ DKIM ووصل صحيحاً، والفيروس الصادر منع مع إشعار المرسل، وقائمة الحظر تفشل مفتوحة عند انقطاع DNS.
سجل التحديثات
- أكتوبر 2026: كتابة المقال، والتجربة على Docker Engine 29.8.0 مع Proxmox Mail Gateway 9.1 (pmg-api 9.1.4 وpmg-gui 5.2.3) على Debian 13 وPostfix 3.10.13 وSpamAssassin 4.0.2 وClamAV 1.4.6 وPostgreSQL 17.11، وخادم داخلي Postfix 3.10.13، وMailpit v1.29.3 وCoreDNS 1.14.7.