خادم البريد الإلكتروني: Postfix وDovecot وRspamd
خروج

الدرس 1 من 12 · أساسيات SMTP

رحلة رسالة بريد إلكتروني: SMTP وsubmission وIMAP

40 د Python 3.12 (Debian, git, pip)

الهدف

فهم ما يحدث بين النقر على « إرسال » ووصول الرسالة إلى صندوق البريد، ومعرفة كيفية التحدّث بـ SMTP يدويًا مع خادم، وقراءة تاريخ الرسالة في ترويساتها.

الأساسيات

  • أربعة أدوار: MUA (Thunderbird، Outlook، هاتف) يكتب الرسالة؛ وMSA يستقبل الإرسال الموثَّق (المنفذ 587 أو 465)؛ وMTA (Postfix) ينقل الرسالة من خادم إلى خادم (المنفذ 25)؛ وMDA (Dovecot) يضعها في صندوق البريد، الذي يقرؤه العميل عبر IMAP (المنفذ 993).
  • للعثور على خادم المستلِم، يسأل MTA نظام DNS عن سجل MX الخاص بالنطاق.
  • تتكوّن جلسة SMTP من خمسة أوامر: EHLO، MAIL FROM، RCPT TO، DATA، QUIT.
  • الظرف (envelope) (MAIL FROM، RCPT TO) يُستعمل للنقل؛ والترويسات (headers) (From:، To:، Subject:) هي ما يراه المستخدم. ويمكن أن يختلفا.
  • كل خادم تمرّ به الرسالة يضيف سطر Received: في الأعلى: لذلك نقرأ التاريخ من الأسفل إلى الأعلى.

1. الفاعلون والمنافذ

 poste de l'agence de Fès                      Internet                       ml-mail-01.masterlinux.lab
┌───────────────┐  587 (STARTTLS)   ┌──────────────┐   25 (SMTP)   ┌─────────────────────────────┐
│ MUA           │ ── + mot de passe ─► │ MSA/MTA      │ ─────────────► │ MTA Postfix ─► MDA Dovecot │
│ Thunderbird   │                   │ smtp.gmail…  │  (MX du domaine) │        boîte Maildir          │
└───────────────┘                   └──────────────┘                  └──────────────┬──────────────┘
                                                                         IMAP 993 (TLS) │
                                                                  téléphone du support ◄┘

أي: حاسوب وكالة فاس (MUA) يرسل عبر المنفذ 587 مع كلمة المرور إلى MSA/MTA، الذي ينقل الرسالة عبر المنفذ 25 إلى MX النطاق (Postfix ثم Dovecot وصندوق Maildir)، ثم يقرؤها هاتف فريق الدعم عبر IMAP 993.

المنفذ الاسم (/etc/services) الدور التشفير
25 smtp من خادم إلى خادم (الترحيل نحو MX) STARTTLS اختياري
587 submission إرسال المستخدمين، مع التوثيق STARTTLS إلزامي
465 submissions إرسال المستخدمين، TLS منذ بداية الاتصال TLS ضمني
143 imap قراءة صناديق البريد STARTTLS
993 imaps قراءة صناديق البريد TLS ضمني
110 / 995 pop3 / pop3s بروتوكول قديم لجلب البريد —

المنفذ 25 يُستعمل بين الخوادم. كثير من المشغّلين (في المغرب كما في غيره) يحجبون المنفذ 25 الصادر من الاتصالات المنزلية: على المستخدم الذي يرسل أن يمرّ عبر 587 أو 465، بعد التوثيق.

2. العثور على خادم المستلِم: سجل MX

dig MX masterlinux.lab +short
# 10 mail.masterlinux.lab.          ← الأولوية 10 (الأصغر = المفضَّل)
# 20 mail2.masterlinux.lab.         ← خادم احتياطي
dig A mail.masterlinux.lab +short
# 203.0.113.25

في غياب MX، يجرّب المُرسِل سجل A الخاص بالنطاق نفسه (قاعدة الرجوع في RFC 5321). ويمكن لنطاق لا يستقبل البريد أبدًا أن ينشر MX فارغًا (null MX): @ IN MX 0 ..

3. جلسة SMTP يدويًا

على خادم حقيقي، نختبر بـ swaks (الحزمة swaks) أو يدويًا بـ nc أو openssl s_client:

swaks --to support@masterlinux.lab --from ops@masterlinux.lab --server 127.0.0.1 --header "Subject: Test"
openssl s_client -connect mail.masterlinux.lab:25 -starttls smtp -crlf -quiet

الحوار سطرًا بسطر (C = العميل، S = الخادم):

S: 220 mail.masterlinux.lab ESMTP Postfix              ← رسالة الترحيب (banner)
C: EHLO ml-poste-01
S: 250-mail.masterlinux.lab
S: 250-SIZE 10240000                                    ← الحجم الأقصى المقبول
S: 250-STARTTLS                                         ← التشفير ممكن
S: 250 8BITMIME
C: MAIL FROM:<ops@masterlinux.lab>                      ← الظرف: المُرسِل (عنوان الرجوع)
S: 250 2.1.0 Ok
C: RCPT TO:<support@masterlinux.lab>                    ← الظرف: المستلِم
S: 250 2.1.5 Ok
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: From: ops@masterlinux.lab                            ← الترويسات (ما يراه المستخدم)
C: Subject: Premier envoi
C:                                                      ← سطر فارغ: نهاية الترويسات
C: Bonjour.
C: .                                                    ← نقطة وحدها: نهاية الرسالة
S: 250 2.0.0 Ok: queued as 3F2A61C0D2                   ← المعرّف في طابور الرسائل
C: QUIT
S: 221 2.0.0 Bye

تُقرأ رموز الردّ من رقمها الأول: 2xx نجاح، 3xx تابِع، 4xx فشل مؤقّت (سيعيد المُرسِل المحاولة)، 5xx فشل نهائي (إرجاع إلى المُرسِل). ويحدّد الرمز الموسَّع (5.1.1، 4.7.1…) السبب: 5.1.1 = صندوق بريد غير معروف، 5.7.1 = مرفوض بقاعدة أمنية.

في مختبرات هذا التكوين

آلة المختبر غير متصلة بالشبكة. تشغّل ml-smtpd (خادم SMTP التدريبي في المختبر)، وهو يقرأ الملف الحقيقي /etc/postfix/main.cf ويردّ برسائل Postfix. يستمع على 127.0.0.1:25 و:587، ويسلّم رسائل النطاقات المحلية بصيغة Maildir، ويكتب /var/log/mail.log.

# 1. مثل sendmail -bs: جلسة SMTP على الإدخال القياسي
printf 'EHLO lab\r\nMAIL FROM:<ops@masterlinux.lab>\r\nRCPT TO:<support@masterlinux.lab>\r\nQUIT\r\n' | ml-smtpd --stdio

# 2. عبر الشبكة المحلية، باستعمال Python (يعرض set_debuglevel الحوار كاملًا)
python3 -c 'import smtplib; s=smtplib.SMTP("127.0.0.1", 25); s.set_debuglevel(1); s.noop(); s.quit()'

# 3. يدويًا باستعمال bash (إعادة التوجيه /dev/tcp)
exec 3<>/dev/tcp/127.0.0.1/25; head -1 <&3; printf 'QUIT\r\n' >&3; cat <&3

4. الظرف مقابل الترويسات

يشبه الظرف العنوانَ المكتوب على رسالة بريدية ورقية؛ أما الترويسات فهي رأس الرسالة في الداخل. ساعي البريد لا يقرأ إلا الظرف.

الظرف (SMTP) الترويسات (المحتوى)
المُرسِل MAIL FROM:<bounces@masterlinux.lab> From: Support <support@masterlinux.lab>
المستلِم RCPT TO:<admin@masterlinux.lab> To:، Cc: (ولا شيء بالنسبة لـ Bcc)
من يستعمله الخوادم، SPF، إشعارات الأخطاء المستخدم، DMARC، المرشّحات
الأثر عند الوصول Return-Path: يضيفه الخادم الأخير دون تغيير

هذا ما يسمح لمنصة إرسال باستقبال إشعارات الأخطاء (bounces@…) مع عرض عنوان المتجر. وهو أيضًا ما يستغلّه المنتحلون: ومن هنا جاءت SPF وDKIM وDMARC (المستوى 4).

5. قراءة أسطر Received

Received: from mail-ed1-f45.google.com (mail-ed1-f45.google.com [209.85.208.45])   ← 3. استلمه خادمنا
        by mail.masterlinux.lab (Postfix) with ESMTPS id 4B7F21C0A2 …
Received: by mail-ed1-f45.google.com with SMTP id …                                  ← 2. نقل داخلي لدى Google
Received: from [192.168.1.23] ([41.250.88.17]) by smtp.gmail.com with ESMTPSA …        ← 1. إرسال العميل
  • ESMTPS = مشفَّر (TLS)، ESMTPA = موثَّق، ESMTPSA = الاثنان معًا.
  • في from X ([IP])، الاسم X يصرّح به العميل (وقد يكذب)؛ أما عنوان IP بين المعقوفين فيراه الخادم (موثوق إذا كان هذا الخادم موثوقًا).
  • 192.168.1.23 هو العنوان الخاص للحاسوب خلف جهاز الراوتر؛ و41.250.88.17 هو العنوان العمومي الذي رآه Gmail.
grep -n '^Received:' /root/recu.eml            # أسطر البداية
awk '/^Received:/,/;/' /root/recu.eml          # كل سطر Received كاملًا (حتى التاريخ)

حالة واقعية

صباح يوم اثنين، يتلقّى فريق الدعم في متجر MasterLinux شكوى: « لم أتلقَّ ردّكم أبدًا ». عند إعادة قراءة ترويسات الرسالة المرسَلة، يلاحظ المسؤول أن حاسوب الدعم كان يرسل بـ ESMTP (بدون A) نحو المنفذ 25 لخادم قديم: كانت الرسالة تخرج دون توثيق فيصنّفها Gmail رسالة مزعجة (spam). ضبط برنامج البريد على 587 + STARTTLS + التوثيق حلّ المشكلة. قراءة أسطر Received قبل لمس الإعدادات توفّر ساعات من العمل.

الأخطاء الشائعة

  • الخلط بين 25 و587: برنامج بريد مضبوط على 25 يعمل في المكتب ثم يفشل عبر 4G (المنفذ محجوب لدى المشغّل).
  • تصديق الترويسة From:: يكتبها المُرسِل، مثل الاسم الذي يلي from في Received. وحدها عناوين IP التي أضافتها خوادمك موثوقة.
  • نسيان \r\n في SMTP اليدوي: بعض الخوادم ترفض نهايات الأسطر المنفردة (\n) — وهذا بالذات أساس هجوم « SMTP smuggling » في 2023-2024، الذي صُحِّح في Postfix بواسطة smtpd_forbid_bare_newline.
  • قراءة أسطر Received من الأعلى إلى الأسفل: السطر الأول هو الأحدث.

المختبر

على آلة المختبر، يؤدّي ml-smtpd دور ml-mail-01 للنطاق masterlinux.lab. ستقوم بما يلي: إرسال رسالة أولى عبر SMTP، وإرسال رسالة يختلف ظرفها عن ترويستها، وإحداث رفض 550 5.1.1، والعثور على عنوان IP الأصلي لرسالة حقيقية، وتلخيص منافذ البريد.

اختبار قصير

  1. ما سجل DNS الذي يحدّد الخادم الذي يستقبل بريد نطاق ما؟
  2. ما الفرق بين MAIL FROM والترويسة From:؟
  3. ماذا يعني رمز ردّ يبدأ بالرقم 4؟
  4. لماذا يرسل المستخدم عبر 587 وليس عبر 25؟
  5. بأي ترتيب نقرأ أسطر Received:؟
الأجوبة
  1. سجل MX (مع أولويته؛ الأصغر = المفضَّل).
  2. MAIL FROM جزء من ظرف SMTP (عنوان إرجاع الأخطاء، يستعمله SPF)؛ أما From: فترويسة من المحتوى تُعرض للمستخدم.
  3. فشل مؤقّت: يحتفظ الخادم المُرسِل بالرسالة في طابور الرسائل ويعيد المحاولة لاحقًا.
  4. المنفذ 587 (submission) يشترط التوثيق والتشفير؛ أما المنفذ 25 فمخصّص للتبادل بين الخوادم وغالبًا ما يحجبه المشغّلون في الاتجاه الصادر.
  5. من الأسفل إلى الأعلى: كل خادم يضيف سطره فوق الأسطر السابقة.

تمارين تطبيقية

تُنجَز على Python 3.12 (Debian, git, pip)

أنجز هذا التطبيق العملي على جهازك

الطرفية طرفيتك: آلة افتراضية بنظام لينكس (VirtualBox أو VMware أو Hyper-V أو WSL…) أو Docker/Podman. أداة ml تحضّر التمرين وتصحّح عملك، ويُؤكَّد الدرس هنا تلقائيًا.

أنشئ حسابًا مجانيًا للحصول على أمرك الشخصي وتصحيح تطبيقاتك العملية تلقائيًا.

إنشاء حسابي

المهام المطلوبة

  1. عبر SMTP على 127.0.0.1 المنفذ 25، أرسل رسالة من ops@masterlinux.lab إلى support@masterlinux.lab بالموضوع « Premier envoi ». يجب أن تصل إلى صندوق البريد Maildir /var/mail/vhosts/masterlinux.lab/support/.
    عرض تلميحpython3 -c 'import smtplib; s=smtplib.SMTP("127.0.0.1", 25); s.set_debuglevel(1); s.sendmail("ops@masterlinux.lab", ["support@masterlinux.lab"], "Subject: Premier envoi\r\n\r\nBonjour"); s.quit()'
    عرض الحل
    printf 'EHLO ml-poste-01\r\nMAIL FROM:<ops@masterlinux.lab>\r\nRCPT TO:<support@masterlinux.lab>\r\nDATA\r\nFrom: ops@masterlinux.lab\r\nTo: support@masterlinux.lab\r\nSubject: Premier envoi\r\n\r\nBonjour depuis le lab.\r\n.\r\nQUIT\r\n' | ml-smtpd --stdio

    التحقق : grep -l "^Subject: Premier envoi" /var/mail/vhosts/masterlinux.lab/support/new/* 2>/dev/null | xargs -r grep -l "^Return-Path: <ops@masterlinux.lab>" | grep -q .

  2. أرسل إلى admin@masterlinux.lab رسالة يكون مُرسِل الظرف فيها (MAIL FROM) هو bounces@masterlinux.lab بينما تعرض الترويسة From: العنوان support@masterlinux.lab، بالموضوع « Enveloppe ».
    عرض تلميحالظرف هو MAIL FROM؛ أما الترويسة From: فتُكتب بعد DATA. يسجّل الخادم الظرف في Return-Path.
    عرض الحل
    printf 'EHLO ml-poste-01\r\nMAIL FROM:<bounces@masterlinux.lab>\r\nRCPT TO:<admin@masterlinux.lab>\r\nDATA\r\nFrom: Support MasterLinux <support@masterlinux.lab>\r\nTo: admin@masterlinux.lab\r\nSubject: Enveloppe\r\n\r\nTest enveloppe.\r\n.\r\nQUIT\r\n' | ml-smtpd --stdio

    التحقق : f=$(grep -l "^Subject: Enveloppe" /var/mail/vhosts/masterlinux.lab/admin/new/* 2>/dev/null | head -1); [ -n "$f" ] && grep -q "^Return-Path: <bounces@masterlinux.lab>" "$f" && grep -Eq "^From:.*support@masterlinux\.lab" "$f"

  3. حاول الكتابة إلى stagiaire@masterlinux.lab (صندوق بريد غير موجود) واحفظ ردّ الخادم الكامل على RCPT TO في /root/code-rejet.txt.
    عرض تلميحمرّر الجلسة إلى ml-smtpd --stdio ورشّح السطر الذي يبدأ بـ 550.
    عرض الحل
    printf 'EHLO ml-poste-01\r\nMAIL FROM:<ops@masterlinux.lab>\r\nRCPT TO:<stagiaire@masterlinux.lab>\r\nQUIT\r\n' | ml-smtpd --stdio | grep "^550" > /root/code-rejet.txt

    التحقق : grep -q "^550 5\.1\.1 <stagiaire@masterlinux\.lab>" /root/code-rejet.txt && grep -q "NOQUEUE: reject: RCPT.*to=<stagiaire@masterlinux.lab>" /var/log/mail.log

  4. الرسالة /root/recu.eml وصلت من أحد العملاء. اكتب في /root/origine.txt عنوان IP العمومي للحاسوب الذي كُتبت عليه (سطر واحد فقط).
    عرض تلميحتُقرأ أسطر Received من الأسفل إلى الأعلى؛ و192.168.x.x عنوان خاص.
    عرض الحل
    echo 41.250.88.17 > /root/origine.txt

    التحقق : grep -qx "41\.250\.88\.17" /root/origine.txt

  5. في /root/ports.txt، اكتب سطرًا « port service » لكل خدمة من هذه الخدمات: 25 smtp و465 submissions و587 submission و143 imap و993 imaps.
    عرض تلميحقارن مع grep -Ew '25|465|587|143|993' /etc/services إذا كان الملف موجودًا.
    عرض الحل
    printf '25 smtp\n465 submissions\n587 submission\n143 imap\n993 imaps\n' > /root/ports.txt

    التحقق : f=/root/ports.txt; grep -Eqx "25[[:space:]]+smtp" $f && grep -Eqx "465[[:space:]]+submissions" $f && grep -Eqx "587[[:space:]]+submission" $f && grep -Eqx "143[[:space:]]+imap" $f && grep -Eqx "993[[:space:]]+imaps" $f