Python SMTP রিলে সাবমিশন: 587-এ STARTTLS নাকি 465-এ TLS?
587-এ STARTTLS আর 465-এ অন্তর্নিহিত TLS — দুটোই Python সাবমিশন বহন করে, কিন্তু আপনার রিলের সঙ্গে মেলে কেবল একটি। হ্যান্ডশেক ও ব্যর্থতার ধরন তুলনা করুন।
দুটি সংযোগ মোডই একই বার্তা একই রিলে অ্যাকাউন্টে পৌঁছে দেয়, আর পার্থক্য হলো TLS কোথায় শুরু হয়, কী পাঠানো হয় তা নয়। একটি প্লেইনটেক্সটে খোলে এবং ব্যান্ডের ভেতরেই আপগ্রেড হয়; অন্যটি প্রথম বাইট থেকেই এনক্রিপ্টেড। এই একটিমাত্র পার্থক্যই ঠিক করে কঠোর রিলে নীতি কী গ্রহণ করবে, একটি মিডলবক্স কী দেখতে পাবে, এবং কোন ত্রুটিবার্তা নিয়ে আপনি এক বিকেল কাটাবেন। প্রাইভেট নেটওয়ার্কের একটি অ্যাপ্লিকেশন যখন অভ্যন্তরীণ সাবমিশন সার্ভিসে বার্তা দেয়, তখন আমি এ দুটির মধ্যে যেভাবে বেছে নিই তা এখানে।
দুটি মোড আসলে কোথায় আলাদা হয়
কাজটি দুই ক্ষেত্রেই একই: অনুমোদিত অ্যাকাউন্ট হিসেবে প্রমাণীকরণ, অনুমোদিত প্রেরকের নাম উল্লেখ, প্রাপকের তালিকা, এবং রিলে দায়িত্ব নেওয়ার জন্য অপেক্ষা। যা বদলায় তা হলো ক্রিয়াকলাপের ক্রম। পোর্ট 587 প্লেইনটেক্সট SMTP সেশন হিসেবে শুরু হয়, EHLO উত্তরে STARTTLS ঘোষণা করে, এবং ক্লায়েন্ট চাইলে তবেই আপগ্রেড হয়। পোর্ট 465 যেকোনো SMTP কমান্ড বলার আগেই TLS হ্যান্ডশেক প্রত্যাশা করে।
সাবমিশন আর রিলে আলাদা কাজ, তাই পোর্ট নম্বরগুলো বিনিময়যোগ্য অভ্যাস নয়। RFC 6409 বার্তা সাবমিশনকে স্বতন্ত্র সার্ভিস হিসেবে বর্ণনা করে, আর RFC 8314 আধুনিক রিলেগুলো 465-এ যে অন্তর্নিহিত-TLS রূপ দেয় তা কভার করে। আপনার রিলে যদি দুটির একটির নথিভুক্ত করে, তবে সেই নথি পূর্ববর্তী নিয়োগকর্তার কাছ থেকে পাওয়া যেকোনো পছন্দের চেয়ে বেশি গুরুত্বপূর্ণ।
587-এ STARTTLS আপনাকে যা দেয়
আপগ্রেডটি SMTP কথোপকথনের ভেতরেই ঘটে, তাই রিলে এটি ঘোষণা করতে পারে এবং ক্লায়েন্ট এটি ছাড়া এগোতে অস্বীকার করতে পারে। Python-এ starttls() ব্যতিক্রম উৎক্ষেপণ করে যখন সার্ভার এক্সটেনশনটি প্রস্তাব করে না, যা ডাউনগ্রেডের চেষ্টাকে নীরব প্লেইনটেক্সট লগইনের বদলে দৃশ্যমান ব্যর্থতায় পরিণত করে। এই আচরণই মোডটির মূল যুক্তি, আর এটির উপর দুর্ঘটনাক্রমে নয়, সচেতনভাবেই নির্ভর করা উচিত।
প্রকাশের সময়সীমা ছোট কিন্তু বাস্তব: প্রাথমিক EHLO বিনিময় পাঠযোগ্য, এবং আপগ্রেডের আগে কিছুই শংসাপত্র বহন করে না। অ্যাপ্লিকেশন সাবনেট আর অভ্যন্তরীণ রিলের মধ্যবর্তী প্রাইভেট পথে সেই সময়সীমা সাধারণত গ্রহণযোগ্য। যে পথ আপনি নিয়ন্ত্রণ করেন না, সেখানে এটাই কিছু দলকে অন্য মোডে জোর দেওয়ার কারণ।
যে ইন্টারসেপশন ডিভাইস STARTTLS সক্ষমতা সরিয়ে দেয়, তা ব্যর্থ সাবমিশন তৈরি করে, ফলব্যাক নয়। এটাই আপনি চান, এবং পথে কিছু SMTP পুনর্লিখন করছে তা আবিষ্কারের এটাই দ্রুততম উপায়।
465-এ অন্তর্নিহিত TLS কী বদলায়
কোনো প্লেইনটেক্সট পর্যায়ই নেই। সকেট খোলার সঙ্গে সঙ্গেই ক্লায়েন্ট TLS হ্যান্ডশেক করে, তার ট্রাস্ট স্টোরের বিপরীতে সার্টিফিকেট যাচাই করে, এবং কেবল তারপর EHLO, AUTH ও বার্তা বিনিময় করে। কিছুই সেশন ডাউনগ্রেড করতে পারে না কারণ দরকষাকষির কোনো প্লেইনটেক্সট অবস্থা নেই, আর এতে ইন্টারসেপশন ব্যর্থতার একটি সম্পূর্ণ শ্রেণি দূর হয়।
বিনিময়টি হলো রোগনির্ণয়যোগ্যতা। যে রিলে 465-এ প্লেইনটেক্সট SMTP দিয়ে উত্তর দেয়, বা যে লোড ব্যালেন্সার ভুল সার্টিফিকেট নামে TLS সমাপ্ত করে, তা হ্যান্ডশেকে ব্যর্থ হয় এবং মেইল ত্রুটির বদলে আপনাকে SSL ত্রুটি দেয়। যখন তা ঘটে, সমস্যা অ্যাপ্লিকেশনের নিচে, এবং যতই বার্তা নির্মাণ করুন তা ঠিক হবে না।
একটি সাবমিশন ফাংশন, দুটি ট্রান্সপোর্ট
কোড মোড দুটির মধ্যে সামান্যই আলাদা, তাই পছন্দটি লাইব্রেরির সুবিধা নয়, রিলে নীতির ভিত্তিতে হওয়া উচিত। আগে সেটিংস এক্সপোর্ট করুন, তারপর একটি স্ক্রিপ্ট চালান যা কনফিগার করা পোর্ট থেকে ট্রান্সপোর্ট বেছে নেয়।
export SMTP_HOST=smtp.relay.internal export SMTP_PORT=587 export SMTP_USER=svc-notify export [email protected] export [email protected] export SMTP_PASSWORD="$(cat /run/secrets/smtp_password)"
import os, smtplib, ssl
from email.message import EmailMessage
ctx = ssl.create_default_context()
msg = EmailMessage()
msg["From"] = os.environ["MAIL_FROM"]
msg["To"] = os.environ["MAIL_TEST_TO"]
msg["Subject"] = "relay submission test"
msg.set_content("One authorized test message from the app.")
host = os.environ["SMTP_HOST"]
port = int(os.environ.get("SMTP_PORT", "587"))
if port == 465:
client = smtplib.SMTP_SSL(host, port, context=ctx, timeout=15)
else:
client = smtplib.SMTP(host, port, timeout=15)
client.ehlo()
client.starttls(context=ctx)
client.ehlo()
with client:
client.login(os.environ["SMTP_USER"], os.environ["SMTP_PASSWORD"])
refused = client.send_message(msg)
print("refused:", refused)
create_default_context() চেইন ও হোস্টনাম যাচাই করে, তাই ব্যক্তিগত CA ট্রাস্ট স্টোরে ইনস্টল করতে হবে, অযাচাইকৃত কনটেক্সট দিয়ে এড়িয়ে যাওয়া যাবে না। send_message() রিলে যে প্রাপকদের প্রত্যাখ্যান করেছে তাদের অভিধান ফেরত দেয়; খালি অভিধান মানে এটি সবাইকে গ্রহণ করেছে। পনেরো সেকেন্ডের টাইমআউট একটি সূচনা বিন্দু, আপনার নেটওয়ার্কের জন্য টিউন করা মান নয়।
পাসওয়ার্ড ইমেজের বাইরে ও লগের বাইরে রাখুন। রানটাইমে মাউন্ট করা এবং পরিবেশে পড়া গোপন তথ্য ঠিক আছে; পরিবেশকে লগ লাইনে প্রতিধ্বনিত করা ঠিক নয়, AUTH বিনিময়ের ডিবাগ ট্রেসও নয়।
অভ্যাস নয়, রিলে নীতি ও পথ দেখে বাছুন
তিনটি শর্ত এটি নিষ্পত্তি করে। প্রথমত, রিলে আসলে কী ঘোষণা করে: যে সার্ভিস কেবল 587-এ STARTTLS দিয়ে শোনে, তাতে 465-এ পৌঁছানো যায় না, আপনি যতই অন্তর্নিহিত TLS পছন্দ করুন না কেন। দ্বিতীয়ত, অ্যাপ্লিকেশন ও রিলের মধ্যবর্তী পথটি পাঠযোগ্য EHLO বিনিময় গ্রহণ করার মতো বিশ্বস্ত কি না। তৃতীয়ত, মাঝখানে কিছু SMTP পুনর্লিখন বা প্রক্সি করে কি না, কারণ এটাই ঠিক করে ডাউনগ্রেড ঝুঁকি তাত্ত্বিক না বাস্তব।
রিলে দুটোই দিলে, আমি 465 নিই এমন সার্ভিসের জন্য যা তত্ত্বাবধানহীন চলে, আর 587 নিই এমন সার্ভিসের জন্য যেখানে প্রথম স্থাপনায় একজন মানুষ নজর রাখে, শুধু এই কারণে যে ব্যর্থতার বার্তাগুলো ভিন্ন স্তরের দিকে ইঙ্গিত করে। এই পছন্দটি আমার, আর এটি কোনো নীতি নয়।
যা-ই বাছুন, পোর্ট, ট্রাস্ট উপাদান ও প্রেরকের ডোমেইন স্থাপনার সঙ্গে লিপিবদ্ধ করুন। তারপর একটি অনুমোদিত পরীক্ষামূলক বার্তা পাঠান এবং রিলে তা গ্রহণ করেছে নিশ্চিত করুন, সাবমিশন ফলাফল ও পরবর্তী যেকোনো ডেলিভারি রিপোর্ট আলাদা রেকর্ড হিসেবে রাখুন। রিলেতে সফল হস্তান্তরই অ্যাপ্লিকেশনের দায়িত্বের শেষ, বার্তাটি ইনবক্সে পৌঁছেছে তার প্রমাণ নয়।