diff options
Diffstat (limited to 'src/operator-authenticate.cc')
-rw-r--r-- | src/operator-authenticate.cc | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/src/operator-authenticate.cc b/src/operator-authenticate.cc new file mode 100644 index 0000000..03f994c --- /dev/null +++ b/src/operator-authenticate.cc @@ -0,0 +1,315 @@ +/** -------------------------------------------------------------------- + * @file operator-authenticate.cc + * @brief Implementation of the AUTHENTICATE command, incl. CRAM-MD5. + * @author Andreas Aardal Hanssen, Erwin Hoffmann + * @date 2002-2005, 2023 + * ----------------------------------------------------------------- **/ +#include <string> + +#include "authenticate.h" +#include "base64.h" +#include "convert.h" +#include "depot.h" +#include "iodevice.h" +#include "iofactory.h" +#include "globals.h" +#include "operators.h" +#include "recursivedescent.h" +#include "session.h" +#include <cstring> + +using namespace ::std; +using namespace Binc; + +//---------------------------------------------------------------------- +AuthenticateOperator::AuthenticateOperator(void) +{ +} + +//---------------------------------------------------------------------- +AuthenticateOperator::~AuthenticateOperator(void) +{ +} + +//---------------------------------------------------------------------- +const string AuthenticateOperator::getName(void) const +{ + return "AUTHENTICATE"; +} + +//---------------------------------------------------------------------- +int AuthenticateOperator::getState(void) const +{ + return Session::NONAUTHENTICATED; +} + +//------------------------------------------------------------------------ +Operator::ProcessResult AuthenticateOperator::Login(string& username, string& password) +{ + Session &session = Session::getInstance(); + + bincClient << "+ " << base64encode("User Name") << endl; + bincClient.flush(); + + // Read user name + string b64usr; + for (;;) { + char c; + if (!bincClient.readChar(&c)) { + session.setLastError("unexpected EOF"); + return BAD; + } + if (c == '\n') break; + b64usr += c; + } + + if (b64usr != "" && b64usr[0] == '*') { + session.setLastError("Authentication cancelled by user"); + return NO; + } + + bincClient << "+ " << base64encode("Password") << endl; + bincClient.flush(); + + // Read password + string b64pwd; + for (;;) { + char c; + if (!bincClient.readChar(&c)) { + session.setLastError("unexpected EOF"); + return BAD; + } + if (c == '\n') break; + b64pwd += c; + } + + if (b64pwd != "" && b64pwd[0] == '*') { + session.setLastError("Authentication cancelled by user"); + return NO; + } + + username = base64decode(b64usr); + password = base64decode(b64pwd); + + return OK; +} +//------------------------------------------------------------------------ +Operator::ProcessResult AuthenticateOperator::Plain(string& username, string& password) +{ + Session &session = Session::getInstance(); + + bincClient << "+ " << endl; + bincClient.flush(); + + string b64; + for (;;) { + char c; + if (!bincClient.readChar(&c)) { + session.setLastError("unexpected EOF"); + return BAD; + } + if (c == '\n') break; + + b64 += c; + } + + if (b64.size() >= 1 && b64[0] == '*') { + session.setLastError("Authentication cancelled by user"); + return NO; + } + + string plain = base64decode(b64); + string::size_type pos = 0; + + if ((pos = plain.find('\0')) == string::npos) { + session.setLastError("Authentication failed. In PLAIN mode, " + "there must be at least two null characters " + "in the input string, but none were found"); + return NO; + } + + plain = plain.substr(pos + 1); + if ((pos = plain.find('\0')) == string::npos) { + session.setLastError("Authentication failed. In PLAIN mode, " + "there must be at least two null characters " + "in the input string, but only one was found"); + return NO; + } + + username = plain.substr(0, pos); + password = plain.substr(pos + 1); + + return OK; +} +//------------------------------------------------------------------------ +Operator::ProcessResult AuthenticateOperator::Cram(string& username, string& password, + string& challenge) +{ + Session &session = Session::getInstance(); + + // generate challenge first: <pid.time@fqdn> and deploy it to authenticator + time_t timer; + struct tm y2k = {0}; + int timestamp; + y2k.tm_hour = 0; y2k.tm_min = 0; y2k.tm_sec = 0; + y2k.tm_year = 100; y2k.tm_mon = 0; y2k.tm_mday = 1; + + time(&timer); /* get current time; same as: timer = time(NULL) */ + timestamp = difftime(timer,mktime(&y2k)); + + challenge += "<"; + challenge += to_string(session.getPid()); + challenge += "."; + challenge += to_string(timestamp); + challenge += "@"; + challenge += session.getEnv("TCPLOCALHOST"); + challenge += ">"; + + bincClient << "+ " << base64encode(challenge) << endl; + bincClient.flush(); + + // Read response + string b64; + for (;;) { + char c; + if (!bincClient.readChar(&c)) return BAD; + if (c == '\n') break; + b64 += c; + } + + // Disentangle response + string response = base64decode(b64); + string::size_type pos = 0; + + if ((pos = response.find(' ')) == string::npos) { + session.setLastError("Authentication failed. In CRAM-MD5 mode, " + "there must be a white space in the " + "input string between username and digest"); + return NO; + } + + username = response.substr(0, pos); + password = response.substr(pos + 1); + + return OK; +} +//------------------------------------------------------------------------ +Operator::ProcessResult AuthenticateOperator::process(Depot &depot, + Request &command) +{ + Session &session = Session::getInstance(); + + string authtype = command.getAuthType(); + uppercase(authtype); + + string username; + string password; + string challenge; + ProcessResult r = NOTHING; + + if (authtype == "LOGIN") { + // we only allow this type of authentication over an unencryted connection + // if it is explicitely commanded + if (!session.command.ssl + && !session.hasEnv("ALLOW_NONSSL_PLAINTEXT_LOGINS")) { + session.setLastError("Plain text password authentication is disallowd. " + "Please enable StartTLS or TLS in your mail client."); + return NO; + } + if ((r = Login(username, password)) != OK) return r; + + } else if (authtype == "PLAIN") { + // we only allow this type of authentication over an TLS encrypted connection. + if (!session.command.ssl + && !session.hasEnv("ALLOW_NONSSL_PLAINTEXT_LOGINS")) { + session.setLastError("Plain text password authentication is disallowd. " + "Please enable StartTLS or TLS in your mail client."); + return NO; + } + if ((r = Plain(username, password)) != OK) return r; + + } else if (authtype == "CRAM-MD5" ) { + // this type can be used even over unencrypted connections + if ((r = Cram(username, password, challenge)) != OK) return r; + + + } else { // Any other disallowed + session.setLastError("The authentication method " + + toImapString(authtype) + " is not supported. " + "Please try again with a different method. " + "There is built in support for \"PLAIN\" " + "and \"LOGIN\"."); + return NO; + } + + putenv(strdup(("BINCIMAP_LOGIN=AUTHENTICATE+" + command.getTag()).c_str())); + + // put the username in the environment for logging purpose + + session.setEnv("USER", username.c_str()); + + // the authenticate function calls a stub which does the actual + // authentication. the function returns 0 (success), 1 (internal + // error) or 2 (failed) + + switch (authenticate(depot, username, password, challenge)) { + case 1: + session.setLastError("An internal error occurred when you attempted " + "to log in to the IMAP server. Please contact " + "your system administrator."); + return NO; + case 2: + session.setLastError("Login failed. Either your user name " + "or your password was wrong. Please try again, " + "and if the problem persists, please contact " + "your system administrator."); + return NO; + case 3: + bincClient << "* BYE Timeout after " << IDLE_TIMEOUT + << " seconds of inactivity." << endl; + break; + case -1: + bincClient << "* BYE The server died unexpectedly. Please contact " + "your system administrator for more information." << endl; + break; + default: +// bincLog << "<" << username.c_str() << "> authenticated" << endl; + break; + } + + // auth was ok. go to logout state + session.setState(Session::LOGOUT); + return NOTHING; +} + + +//---------------------------------------------------------------------- +Operator::ParseResult AuthenticateOperator::parse(Request &c_in) const +{ + Session &session = Session::getInstance(); + + if (c_in.getUidMode()) return REJECT; + + Operator::ParseResult res; + + if ((res = expectSPACE()) != ACCEPT) { + session.setLastError("Expected single SPACE after AUTHENTICATE"); + return res; + } + + string authtype; + if ((res = expectAtom(authtype)) != ACCEPT) { + session.setLastError("Expected auth_type after AUTHENTICATE SPACE"); + return ERROR; + } + + if ((res = expectCRLF()) != ACCEPT) { + session.setLastError("Expected CRLF after AUTHENTICATE SPACE auth_type"); + return res; + } + + c_in.setAuthType(authtype); + + c_in.setName("AUTHENTICATE"); + return ACCEPT; +} |