Oba tryby połączenia przenoszą tę samą wiadomość na to samo konto relaya, a różnica polega na tym, gdzie zaczyna się TLS, a nie na tym, co jest wysyłane. Jeden otwiera się w postaci czystego tekstu i jest podnoszony w ramach połączenia; drugi jest szyfrowany od pierwszego bajtu. Ta jedna różnica decyduje o tym, co zaakceptuje surowa polityka relaya, co może zaobserwować urządzenie pośredniczące i który komunikat błędu spędzisz całe popołudnie, czytając. Oto jak wybieram między nimi, gdy aplikacja w sieci prywatnej przekazuje wiadomość wewnętrznej usłudze wysyłkowej.

Gdzie oba tryby faktycznie się rozchodzą

Zadanie jest identyczne w obu przypadkach: uwierzytelnić się jako zatwierdzone konto, wskazać dozwolonego nadawcę, wymienić odbiorców i poczekać, aż relay przyjmie odpowiedzialność. Zmienia się kolejność operacji. Port 587 zaczyna jako sesja SMTP w czystym tekście, ogłasza STARTTLS w odpowiedzi EHLO i jest podnoszony tylko wtedy, gdy klient o to poprosi. Port 465 oczekuje uzgadniania TLS, zanim zostanie wypowiedziane jakiekolwiek polecenie SMTP.

Wysyłka i relay to różne zadania, dlatego numery portów nie są wymiennymi nawykami. RFC 6409 opisuje wysyłkę wiadomości jako odrębną usługę, a RFC 8314 obejmuje wariant niejawnego TLS, który większość nowoczesnych relayów udostępnia na porcie 465. Jeśli twój relay dokumentuje jeden z tych dwóch, ta dokumentacja ma pierwszeństwo przed wszelkimi preferencjami wyniesionymi od poprzedniego pracodawcy.

Co daje ci STARTTLS na porcie 587

Podniesienie odbywa się wewnątrz rozmowy SMTP, więc relay może je ogłosić, a klient może odmówić kontynuowania bez niego. W Pythonie starttls() zgłasza wyjątek, gdy serwer nie oferuje tego rozszerzenia, co zamienia próbę obniżenia poziomu w widoczny błąd zamiast cichego logowania czystym tekstem. To zachowanie jest głównym argumentem za tym trybem i warto polegać na nim świadomie, a nie przypadkiem.

Okno ekspozycji jest małe, ale realne: początkowa wymiana EHLO jest czytelna i nic przed podniesieniem nie przenosi poświadczeń. Na prywatnej ścieżce między podsiecią aplikacji a wewnętrznym relayem to okno jest zwykle akceptowalne. Na ścieżce, której nie kontrolujesz, właśnie to jest powodem, dla którego niektóre zespoły nalegają na drugi tryb.

Urządzenie przechwytujące, które usuwa możliwość STARTTLS, powoduje nieudaną wysyłkę, a nie powrót do starszego trybu. To jest wynik, którego chcesz, i jednocześnie najszybszy sposób odkrycia, że coś przepisuje SMTP na ścieżce.

Co zmienia niejawne TLS na porcie 465

Nie ma w ogóle fazy czystego tekstu. Klient wykonuje uzgadnianie TLS zaraz po otwarciu gniazda, weryfikuje certyfikat w swoim magazynie zaufania i dopiero potem wymienia EHLO, AUTH i wiadomość. Nic nie może obniżyć poziomu sesji, ponieważ nie istnieje stan czystego tekstu, z którego można by negocjować, i to usuwa całą klasę błędów przechwycenia.

Ceną jest diagnostyka. Relay, który odpowiada na porcie 465 czystym SMTP, albo równoważnik obciążenia, który kończy TLS z niewłaściwą nazwą certyfikatu, zawodzi na uzgadnianiu i daje błąd SSL zamiast błędu poczty. Gdy tak się stanie, problem leży poniżej aplikacji i żadne budowanie wiadomości go nie naprawi.

Jedna funkcja wysyłki, dwa transporty

Kod prawie nie różni się między trybami, dlatego wybór powinien wynikać z polityki relaya, a nie z wygody biblioteki. Najpierw wyeksportuj ustawienia, a następnie uruchom jeden skrypt, który wybiera transport na podstawie skonfigurowanego portu.

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() weryfikuje łańcuch i nazwę hosta, więc prywatny CA trzeba zainstalować w magazynie zaufania, a nie omijać niezweryfikowanym kontekstem. send_message() zwraca słownik odbiorców, których relay odmówił; pusty słownik oznacza, że przyjął wszystkich. Limit piętnastu sekund to punkt wyjścia, a nie wartość dostrojona do twojej sieci.

Trzymaj hasło poza obrazem i poza logami. Sekret montowany w czasie działania i odczytywany do środowiska jest w porządku; wypisywanie środowiska w linii logu już nie, podobnie jak ślad debugowania wymiany AUTH.

Wybieraj według polityki relaya i ścieżki, a nie według nawyku

Trzy warunki rozstrzygają. Po pierwsze, co relay faktycznie ogłasza: usługa nasłuchująca tylko na porcie 587 z STARTTLS nie jest osiągalna na porcie 465, niezależnie od tego, jak bardzo wolisz niejawne TLS. Po drugie, czy ścieżka między aplikacją a relayem jest wystarczająco zaufana, by zaakceptować czytelną wymianę EHLO. Po trzecie, czy coś pośrodku przepisuje lub proxuje SMTP, bo to decyduje, czy ryzyko obniżenia poziomu jest teoretyczne, czy realne.

Gdy relay oferuje oba, biorę 465 dla usługi działającej bez nadzoru, a 587 dla tej, przy której człowiek obserwuje pierwsze wdrożenie, wyłącznie dlatego, że komunikaty błędów wskazują na różne warstwy. Ta preferencja jest moja i nie jest polityką.

Cokolwiek wybierzesz, zapisz port, materiał zaufania i domenę nadawcy wraz z wdrożeniem. Następnie wyślij jedną autoryzowaną wiadomość testową i potwierdź, że relay ją przyjął, przechowując wynik wysyłki i każdy późniejszy raport dostarczenia jako osobne zapisy. Udane przekazanie do relaya to koniec odpowiedzialności aplikacji, a nie dowód, że wiadomość dotarła do skrzynki odbiorczej.

Powiązane artykuły