From caa8dd7d90ae541fea89137450016ca6a6bec229 Mon Sep 17 00:00:00 2001 From: Brad Arant Date: Sun, 28 Jul 2019 19:47:50 -0700 Subject: [PATCH] Created initial SMTP handlers. --- BMAMail.project | 41 ++++++++---- __SMTP_AUTH.h | 98 ++++++++++++++++++++++++++++ __SMTP_DATA.h | 164 +++++++++++++++++++++++++++++++++++++++++++++++ __SMTP_EHLO.h | 25 ++++++++ __SMTP_HELO.h | 19 ++++++ __SMTP_MAIL.h | 25 ++++++++ __SMTP_NOOP.h | 21 ++++++ __SMTP_QUIT.h | 23 +++++++ __SMTP_RCPT.h | 165 ++++++++++++++++++++++++++++++++++++++++++++++++ __SMTP_RSET.h | 20 ++++++ __SMTP_VRFY.h | 19 ++++++ 11 files changed, 608 insertions(+), 12 deletions(-) create mode 100644 __SMTP_AUTH.h create mode 100644 __SMTP_DATA.h create mode 100644 __SMTP_EHLO.h create mode 100644 __SMTP_HELO.h create mode 100644 __SMTP_MAIL.h create mode 100644 __SMTP_NOOP.h create mode 100644 __SMTP_QUIT.h create mode 100644 __SMTP_RCPT.h create mode 100644 __SMTP_RSET.h create mode 100644 __SMTP_VRFY.h diff --git a/BMAMail.project b/BMAMail.project index 86b7586..4b8795b 100644 --- a/BMAMail.project +++ b/BMAMail.project @@ -2,6 +2,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -15,8 +36,15 @@ + + - + + + + + + @@ -92,15 +120,4 @@ - - - - - - - - - - - diff --git a/__SMTP_AUTH.h b/__SMTP_AUTH.h new file mode 100644 index 0000000..a606280 --- /dev/null +++ b/__SMTP_AUTH.h @@ -0,0 +1,98 @@ +#ifndef ____SMTP_AUTH_h__ +#define ____SMTP_AUTH_h__ + +#include "Command.h" + +namespace http { + + class __SMTP_AUTH : public core::Command { + + int processCommand(std::string request, Session *session, std::stringstream &data); + + data << "" << std::endl; + + + //--------------------------------------------------------------------------- + // AUTH command request handling. + //--------------------------------------------------------------------------- + + else if(command(input) == "AUTH") { + + if(input.length() > 5) { + string method = input.substr(5); + + string userName; + string password; + + //-------------------------------- + // Check for AUTH LOGIN method. + //-------------------------------- + + if(method == "LOGIN") { + + cout << "334 VXNlcm5hbWU6" << CRLF; + + alarm(10); + + if(!getline(cin, userName)) { + return -1; + } + + alarm(0); + + if(userName[userName.length() - 1] == '\r') + userName.erase(userName.length() - 1); + + cout << "334 UGFzc3dvcmQ6" << CRLF; + + alarm(10); + + if(!getline(cin, password)) { + return -1; + } + alarm(0); + + if(password[password.length() - 1] == '\r') + password.erase(password.length() - 1); + + BASE64 base64; + + log.message("Logging in with user '" + base64.decode(userName) + "' using password '" + base64.decode(password) + "'."); + + if(authLogin(base64.decode(userName), base64.decode(password))) { + cout << "235 Authentication successful" << CRLF; + log.message("Response: 235 Authentication successful."); + relay = true; + } + + else { + cout << "530 Login was unsuccessful." << CRLF; + log.message("Response: 530 Login was unsuccessful."); + } + } + + else { + cout << "530 AUTH method not supported." << CRLF; + log.message("Response: 530 AUTH method not supported."); + } + + } + + else { + + + } + + } + + + httpRequest.response.addHeader("Content-Type", "script/javascript"); + + return 0; + } + + }; + +} + +#endif diff --git a/__SMTP_DATA.h b/__SMTP_DATA.h new file mode 100644 index 0000000..a5e9c11 --- /dev/null +++ b/__SMTP_DATA.h @@ -0,0 +1,164 @@ + //--------------------------------------------------------------------------- + // DATA command request handling. + //--------------------------------------------------------------------------- + + else if(command(input) == "DATA") { + + //-------------------------------------------------------------- + // We must have recipients before we can send data. + //-------------------------------------------------------------- + + if(state == "RCPT") { + + //--------------------------------------------------------- + // Prompt for client to begin entering mail message data. + //--------------------------------------------------------- + + cout << "354 Enter the mail message terminated by ." << CRLF; + + mailData = ""; + + //----------------------------------------------------------- + // Receive mail message one line at a time and keep an eye + // out for the terminating period character. + //----------------------------------------------------------- + + do { + + alarm(120); + + if(!getline(cin, input)) { + return -1; + } + + if(input[input.length() - 1] == '\r') + input.erase(input.length() - 1); + + alarm(0); + if(input != ".") { + + //-------------------------------------------------------------- + // If there was a period for the first character but it wasn't + // the only character then remove and ignore the first period. + // This is the transparency mode capability. + //-------------------------------------------------------------- + + if(input[0] == '.') { + mailData += input.substr(1) + CRLF; + } + + else { + mailData += input + CRLF; + } + + } + + } while(input != "."); + + //------------------------------------------------------------------ + // Run the received message through an external filter program if + // one is configured into the system settings. + //------------------------------------------------------------------ + + string ID; + + if(filterMessage(mailData)) { + + if(recipientList != "") { + + if(mailData.length() > 0) { + + //------------------------------------------------------------------ + // We have the message and we have a list of recipients. Send the + // message to the queue since we know everyone in the recipient + // list has passed the test. + //------------------------------------------------------------------ + + ID = queueMail(sender, recipientList, mailData, clientIP, log, sql, "N"); + + if(ID != "") { + + log.message("Response: 250 OK Queued message " + ID); + + //------------------------------ + // Tell the client we sent it. + //------------------------------ + + cout << "250 OK Queued message " << ID << CRLF; + } + + else { + + log.message("Response: 550 Mail message too big."); + + //--------------------------------- + // Tell the client it was too big. + //--------------------------------- + + cout << "550 Mail message too big" << CRLF; + } + + } + + //------------------------------------------------------------------------ + // The mail message is empty so we are going to error out. We don't like + // getting empty messages. + //------------------------------------------------------------------------ + + else { + + log.message("Response: 550 Mail message was empty."); + cout << "550 Mail message was empty" << CRLF; + + } + + } + + else { + + log.message("Response: 250 OK Queued message. (We actually discarded it due to empty recipient list)"); + + //------------------------------ + // Tell the client we sent it. + //------------------------------ + + cout << "250 OK Queued message " << ID << CRLF; + + } + + } + + //---------------------------------------------------- + // Error out cause it did not pass the filter test. + //---------------------------------------------------- + + else { + + cout << "550 Message is probably spam" << CRLF; + log.message("Response: 550 Message is probably spam."); + } + + //---------------------------- + // Return to the READY state. + //---------------------------- + + state = "READY"; + } + //-------------------------------------------------------- + // Generate an error cause we are not in the right state. + //-------------------------------------------------------- + + else { + + if(state == "MAIL") { + cout << "503 Please use RCPT first" << CRLF; + log.message("Response: 503 Please use RCPT first."); + } + + else { + cout << "503 Please use MAIL first" << CRLF; + log.message("Response: 503 Please use MAIL first."); + } + + } + } diff --git a/__SMTP_EHLO.h b/__SMTP_EHLO.h new file mode 100644 index 0000000..3d51ef0 --- /dev/null +++ b/__SMTP_EHLO.h @@ -0,0 +1,25 @@ + //--------------------------------------------------------------------------- + // EHLO command request handling. + //--------------------------------------------------------------------------- + + else if(command(input) == "EHLO") { + + if(input.length() > 5) { + string hostName = input.substr(5); + } + + else { + + // TODO: Provide error message demanding identity here. + // + } + + cout << "250-" << getHostName() << CRLF; + // cout << "250-STARTTLS" << CRLF; + // cout << "250-PIPELINING" << CRLF; + // cout << "250-8BITMIME" << CRLF; + cout << "250-AUTH LOGIN" << CRLF; + cout << "250 HELP" << CRLF; + state = "READY"; + } + \ No newline at end of file diff --git a/__SMTP_HELO.h b/__SMTP_HELO.h new file mode 100644 index 0000000..6f8dd8f --- /dev/null +++ b/__SMTP_HELO.h @@ -0,0 +1,19 @@ + //--------------------------------------------------------------------------- + // HELO command request handling. + //--------------------------------------------------------------------------- + + if(command(input) == "HELO") { + + if(input.length() > 5) { + string hostName = input.substr(5); + } + + else { + + // TODO: Provide error message demanding identity here. + // + } + + cout << "250 " << getHostName() << CRLF; + state = "READY"; + } \ No newline at end of file diff --git a/__SMTP_MAIL.h b/__SMTP_MAIL.h new file mode 100644 index 0000000..6cc816d --- /dev/null +++ b/__SMTP_MAIL.h @@ -0,0 +1,25 @@ + //--------------------------------------------------------------------------- + // MAIL command request handling. + //--------------------------------------------------------------------------- + + else if(command(input) == "MAIL") { + + sender = cleanEMail(input.substr(5)); + + //------------------------------------------------------------- + // Verify that the senders domain name resolves to an address + // with an MX record. If not and there is a bounce then we + // will not be able to return the bounce message so it is + // probably a spammer anyway. + //------------------------------------------------------------- + + if(verifyDomainMX(domainOnly(sender))) { + + cout << "250 OK" << CRLF; + log.message("Response: 250 OK."); + recipientList = ""; + state = "MAIL"; + } + + + } diff --git a/__SMTP_NOOP.h b/__SMTP_NOOP.h new file mode 100644 index 0000000..df17538 --- /dev/null +++ b/__SMTP_NOOP.h @@ -0,0 +1,21 @@ +#ifndef ____SMTP_AUTH_h__ +#define ____SMTP_AUTH_h__ + +#include "Command.h" + +namespace http { + + class __SMTP_AUTH : public core::Command { + + int processCommand(std::string request, Session *session, std::stringstream &data); + + data << "" << std::endl; + + //--------------------------------------------------------------------------- + // NOOP command request handling. + //--------------------------------------------------------------------------- + + else if(command(input) == "NOOP") { + cout << "250 OK" << CRLF; + log.message("Response: 250 OK."); + } diff --git a/__SMTP_QUIT.h b/__SMTP_QUIT.h new file mode 100644 index 0000000..24c3619 --- /dev/null +++ b/__SMTP_QUIT.h @@ -0,0 +1,23 @@ +#ifndef ____SMTP_AUTH_h__ +#define ____SMTP_AUTH_h__ + +#include "Command.h" + +namespace http { + + class __SMTP_AUTH : public core::Command { + + int processCommand(std::string request, Session *session, std::stringstream &data); + + data << "" << std::endl; + + //--------------------------------------------------------------------------- + // QUIT command request handling. + //--------------------------------------------------------------------------- + + else if(command(input) == "QUIT") { + cout << "221 " << getHostName() << CRLF; + log.message("Response: 221 " + getHostName() + "."); + state = "QUIT"; + break; + } \ No newline at end of file diff --git a/__SMTP_RCPT.h b/__SMTP_RCPT.h new file mode 100644 index 0000000..de6ae35 --- /dev/null +++ b/__SMTP_RCPT.h @@ -0,0 +1,165 @@ +#ifndef ____SMTP_AUTH_h__ +#define ____SMTP_AUTH_h__ + +#include "Command.h" + +namespace http { + + class __SMTP_AUTH : public core::Command { + + int processCommand(std::string request, Session *session, std::stringstream &data); + + data << "" << std::endl; + + //--------------------------------------------------------------------------- + // RCPT command request handling. + //--------------------------------------------------------------------------- + + else if(command(input) == "RCPT") { + + if((state == "MAIL") || (state == "RCPT")) { + + bool done = false; + + //-------------------------------------- + // Obtain the recipient's email address. + //-------------------------------------- + + string recipient = input.substr(5); + + //----------------------------------------------- + // Fetch the recipient list for the alias. + //----------------------------------------------- + + string aliasRecipients; + + if(getAliasList(cleanEMail(recipient), aliasRecipients)) { + + //----------------------------------------------- + // If the list is not blank then add the list + // to the recipient list and accept the request. + //----------------------------------------------- + + recipientList += aliasRecipients; + cout << "250 OK" << CRLF; + log.message("Response: 250 OK."); + state = "RCPT"; + done = true; + } + //------------------------------------------------- + // No alias entry for the mailbox so check if it + // is a hard mailbox. + //------------------------------------------------- + + if(!done) { + + //-------------------------------------- + // Check to see if the user is a local + // address to deliver to. + //-------------------------------------- + + if(localUser(cleanEMail(recipient))) { + + //------------------------------------------ + // They're local so lets queue it for them. + //------------------------------------------ + + recipientList += cleanEMail(recipient) + " "; + cout << "250 OK" << CRLF; + log.message("Response: 250 OK."); + state = "RCPT"; + done = true; + } + + } + + //-------------------------------------------------- + // There is no mailbox either so do domain check. + //-------------------------------------------------- + + if(!done) { + + //--------------------------------------------------- + // Check to see if the domain is good at least and + // if there is a default mailbox address.. + //--------------------------------------------------- + string defaultMailbox; + + if(localDomain(domainOnly(cleanEMail(recipient)), defaultMailbox)) { + + //-------------------------------------------------- + // The domain is local so if there is a default + // mailbox then let's send it to the queue with the + // default address attached instead. + //-------------------------------------------------- + + if(defaultMailbox != "*NONE") { + + //--------------------------------------------------------- + // If the default mailbox is *DISCARD then we do not want + // to add the mailbox to the recipient list but we still + // want to say we have a mailbox. + //--------------------------------------------------------- + + if(defaultMailbox != "*DISCARD") + recipientList += defaultMailbox + " "; + + cout << "250 OK" << CRLF; + log.message("Response: 250 OK."); + + state = "RCPT"; + done = true; + } + + //-------------------------------------------------- + // If there is no default address then we will tell + // the client that the mailbox does not exist. + //-------------------------------------------------- + + else { + + cout << "553 Mailbox does not exist" << CRLF; + log.message("Response: 553 Mailbox does not exist."); + + done = true; + } + + } + + } + + //-------------------------------------------------------------- + // If we are not done then this is a relay request. + //-------------------------------------------------------------- + + if (!done) { + + //--------------------------------------------------------- + // If relaying is enabled for this conversation then + // queue the mail. + //--------------------------------------------------------- + + if(relay) { + recipientList += cleanEMail(recipient) + " "; + cout << "250 OK" << CRLF; + log.message("Response: 250 OK."); + state = "RCPT"; + } + + //---------------------------------------- + // Otherwise send an error. + //---------------------------------------- + + else { + cout << "553 Server requires authentication to relay" << CRLF; + log.message("Response: 553 Server requires authentication to relay."); + } + + } + + } + else { + cout << "503 Please use MAIL first" << CRLF; + log.message("Response: 503 Please use MAIL first."); + } + } \ No newline at end of file diff --git a/__SMTP_RSET.h b/__SMTP_RSET.h new file mode 100644 index 0000000..5f2a014 --- /dev/null +++ b/__SMTP_RSET.h @@ -0,0 +1,20 @@ +#ifndef ____SMTP_RSET_h__ +#define ____SMTP_RSET_h__ + +#include "Command.h" + +namespace http { + + class __SMTP_RSET : public core::Command { + + int processCommand(std::string request, Session *session, std::stringstream &data); + state = "READY"; + data << "250 OK" << CRLF; + return 0; + } + + }; + +} + +#endif diff --git a/__SMTP_VRFY.h b/__SMTP_VRFY.h new file mode 100644 index 0000000..453b670 --- /dev/null +++ b/__SMTP_VRFY.h @@ -0,0 +1,19 @@ +#ifndef ____SMTP_VRFY_h__ +#define ____SMTP_VRFY_h__ + +#include "Command.h" + +namespace http { + + class __SMTP_VRFY : public core::Command { + + int processCommand(std::string request, Session *session, std::stringstream &data) { + data << "252 You must know who the mail is for" << CRLF; + return 0; + } + + }; + +} + +#endif