Repository navigation
fix(plugin): negotiate SMTP AUTH so LOGIN-only servers work - #630
Conversation
send_email hard-coded PLAIN. Azure Communication Services (and some Exchange relays) advertise only LOGIN/XOAUTH2, so every send failed with "504 5.7.4 Unrecognized authentication type" even though the credential was valid — net/smtp's PlainAuth refuses to transmit when PLAIN is not offered. Read the server's advertised mechanisms after STARTTLS, prefer PLAIN, fall back to AUTH LOGIN, and fail with the advertised list when nothing supported is on offer. The LOGIN challenge handler answers only the two prompts it expects, so a server that asks again cannot harvest the password twice. Verified against smtp.azurecomm.net:587 with a real ACS credential (LOGIN accepted, message accepted for delivery) and by unit tests covering PLAIN+LOGIN, LOGIN only, XOAUTH2 only, and no AUTH advertisement.
There was a problem hiding this comment.
Important
The new AUTH LOGIN path drops the unencrypted-connection guard that smtp.PlainAuth gave us, so credentials can now be transmitted in cleartext on a connection where TLS was never negotiated. Details inline.
Reviewed changes
- SMTP AUTH negotiation —
dialAndSendSMTPnow reads the mechanisms fromclient.Extension("AUTH")after the implicit-TLS/STARTTLS step and picks PLAIN or LOGIN viasmtpAuthFor, instead of always assuming PLAIN; when neither is offered it errors naming the advertised set. loginAuth— newsmtp.Authimplementing the AUTH LOGIN username/password challenge handshake, failing closed on an unexpected challenge so the password is never echoed twice.- Tests — table test for mechanism selection (
PLAINpreferred, LOGIN-only, XOAUTH2-only and empty both error) plus a unit test for the LOGIN challenge order including the unexpected-challenge case.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Review caught that loginAuth.Start ignored ServerInfo, while smtp.PlainAuth refuses to send credentials when the connection was never encrypted and the host is not loopback. dialAndSendSMTP only upgrades opportunistically, so a LOGIN-only server without STARTTLS would have received the password in the clear. Mirror the guard. Also: scan PLAIN across all advertised mechanisms before LOGIN (the loop returned whichever came first, so "LOGIN PLAIN" picked LOGIN), and correct two comments that claimed PlainAuth refuses to send when PLAIN is unadvertised — it sends AUTH PLAIN regardless and the server rejects it with 504.
|
All three review points addressed in 7ea0d0f:
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
This run reviewed the delta since the prior pullfrog review (84f494b), which resolves every item that review raised.
- Cleartext guard restored on AUTH LOGIN —
loginAuth.Startnow returns"unencrypted connection"when!server.TLS && !isLocalhost(server.Name), mirroringplainAuth.Startso opportunistic STARTTLS failing can no longer leak the password. - Comment corrected — the inline comment now states that
PlainAuthsendsAUTH PLAINunconditionally and the server rejected it, instead of claiming a client-side refusal. - PLAIN-first selection implemented —
smtpAuthFornow scansPLAINbeforeLOGINregardless of the server's advertisement order, matching its doc comment. - Tests extended — the LOGIN test now asserts the unencrypted-connection refusal and the loopback exception.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
pikann
left a comment
There was a problem hiding this comment.
LGTM! Thank you for the contribution! 🚀

Problem
paca.send_emailhard-codessmtp.PlainAuth, which sendsAUTH PLAINunconditionally. Servers that accept onlyAUTH LOGIN— Azure Communication Services is one, so are several Exchange relays — reject that handshake with:The credential is valid; the mechanism is not one the server offers. SMTP email is therefore impossible against ACS from a Paca plugin today.
Change
smtp.Client.Extension("AUTH")after STARTTLS.net/smtpships no LOGIN auth), and fail naming the advertised set when nothing supported is on offer — instead of the opaque 504.loginAuth.StartmirrorsPlainAuth's protection: it refuses ("unencrypted connection") unless the connection is already encrypted or the host is loopback, so opportunistic STARTTLS failing cannot leak the password.No config or plugin-manifest change: existing
host/port/username/password/use_tlsfields are unchanged, so PLAIN servers behave exactly as before.Verification
go test ./internal/platform/plugin/passes. New tests cover PLAIN+LOGIN (PLAIN chosen even when LOGIN is advertised first), LOGIN only, XOAUTH2 only (error), no AUTH advertisement (error), the LOGIN challenge order including an unexpected challenge, and the unencrypted-connection refusal with its loopback exception.smtp.azurecomm.net:587with a real ACS credential and a plugin-shaped message: AUTH LOGIN accepted, message accepted for delivery (250). Before the change the same server/credential pair returned the 504 above.Scoped to
services/api/internal/platform/plugin/email.goand its test.