#include "__SMTP_AUTH.h" #include "Base64.h" #include "SMTPServer.h" namespace mail { int __SMTP_AUTH::processCommand(coreutils::ZString &request, SMTPSession &session, SMTPServer &server) { coreutils::Base64 base64; switch(session.authState) { case USER_UNKNOWN: // SMTPServer's CommandList splits the line only once ("AUTH", // depth=1 - see its own constructor), so request[1] here is // "LOGIN" alone for the classic multi-step form, but "LOGIN // " as ONE token when a client supplies RFC 4954 4's // optional initial-response inline to save a round trip - // confirmed live against Python's smtplib, which does exactly // that. The old request[1].equals("LOGIN") exact-match rejected // every such client with a spurious 504, never reaching the // inline response at all. if(request[1].ifNextIgnoreCase("LOGIN")) { request[1].skipWhitespace(); if(!request[1].eod()) { session.userName = base64.decode(request[1].unparsed()); session.out << "334 UGFzc3dvcmQ6" << CRLF; session.authState = USER_SECRET_QUERY; grabInput(session); } else { session.out << "334 VXNlcm5hbWU6" << CRLF; // setTimer(10.0f); session.authState = USER_QUERY; grabInput(session); } } else { session.out << "504 AUTH method not supported." << CRLF; } return 1; case USER_QUERY: // RFC 4954 4: a bare "*" cancels the exchange rather than being // treated as (still-encoded) credential data. if (request.equals("*")) { session.out << "501 5.7.0 Authentication cancelled" << CRLF; session.authState = USER_UNKNOWN; clearGrab(session); return 1; } // grabInput() above means this line never goes through // CommandList's own .split() (see CommandList::processRequest's // grab branch) - request[0] here was actually an out-of-bounds // read on an always-empty list, not "the first word". The // whole line IS the client's response: a single base64-encoded // token with no embedded whitespace, exactly what the unsplit // request already holds. It was never decoded either - AUTH // LOGIN's wire format sends the username/password base64 // encoded (that's what the "334 VXNlcm5hbWU6"/"334 // UGFzc3dvcmQ6" challenges - "Username:"/"Password:" - signal) // - comparing the raw, still-encoded text against a plaintext // secret file could never match for any real client. session.userName = base64.decode(request); // setTimer(0.0f); session.out << "334 UGFzc3dvcmQ6" << CRLF; // setTimer(10.0f); session.authState = USER_SECRET_QUERY; return 1; case USER_SECRET_QUERY: if (request.equals("*")) { session.out << "501 5.7.0 Authentication cancelled" << CRLF; session.authState = USER_UNKNOWN; clearGrab(session); return 1; } session.password = base64.decode(request); // setTimer(0.0f); if(authLogin(session.userName, session.password, server)) { session.out << "235 2.7.0 Authentication successful" << CRLF; session.relay = true; session.authState = USER_KNOWN; clearGrab(session); } else { // Reset back to a clean, addressable state - without this, // the session stayed grabbed and stuck expecting yet another // "password" line forever after one wrong guess, since // nothing else ever set authState back to USER_UNKNOWN or // released the grab. A client that mistypes a password needs // to be able to retry AUTH on the same connection, not // reconnect entirely. session.out << "530 Login was unsuccessful." << CRLF; session.authState = USER_UNKNOWN; clearGrab(session); } return 1; } return 0; } bool __SMTP_AUTH::authLogin(coreutils::ZString &userName, coreutils::ZString &password, SMTPServer &server) { // The actual check now lives in MailFileSystem::checkPassword(), // shared with IMAP LOGIN and POP3 USER/PASS - see its own doc // comment. return server.mailFileSystem.checkPassword(userName, password); } }