summaryrefslogtreecommitdiff
path: root/src/operator-authenticate.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/operator-authenticate.cc')
-rw-r--r--src/operator-authenticate.cc315
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;
+}