} // CheckForOversizeMessage private bool IsMARSMessage(ref SMTPMessage objMessage) { // If site callsign is MARS callsign or // if sender is MARS callsign or // if any recipient is MARS callsign // return true otherwise false if (Globals.IsMARSStation()) { return(true); } if (Globals.IsMARSCallsign(objMessage.Sender.RadioAddress)) { return(true); } return(objMessage.IsAnyRecipientMARS()); } // IsMARSMessage
} // OnSessionTimer private string Protocol(string strInputStream) { string ProtocolRet = default; // This function processes the incoming data stream for the SMTPSession object. // If the string contains a command then the command is processed to update // the SMTPstate and generate a reply string. An empty string value is returned // to signal that no response is required to the partial command or data string. // Data and command processing is a function of the current SMTPState. // Authorization for both PLAIN and LOGIN is implemented... string strTemporary; int intPointer; // Check for GetData state to see if CMD processing required... if (SMTPState != SessionState.GetData) { if (strInputStream == "NewConnection") { // The SMTPSession has just been initialized. // Acknowledge with Reply 220 with echo local IP Address... _log.Info("In from " + connection.RemoteEndPoint + ": " + "(New connection started)"); strCommandBuffer = ""; // Clear the command buffer return(Reply220); // Local IP address service ready } // This is for a complete command or the CRLF to complete the command... if ((strInputStream.Right(2) ?? "") == Globals.CRLF) { strInputStream = strCommandBuffer + strInputStream; strCommand = strInputStream.Left(4).ToUpper(); strCommandBuffer = ""; } else { // This accumulates a command which bridges multiple data in events... strCommandBuffer = strCommandBuffer + strInputStream; return(""); } } // Process the command... _log.Info("In from " + connection.RemoteEndPoint + ": " + strCommand); var switchExpr = SMTPState; switch (switchExpr) { case SessionState.Connected: // The inital state instantiated state... { var switchExpr1 = strCommand; switch (switchExpr1) { case "EHLO": // Reply to ELHO only extensions supported are AUTH... { SMTPState = SessionState.Ready; // Update the state // OK accept LOGIN and PLAIN authorization - Note both formats are // required below since there was some prior incompatibility in the spec... // Add in a Hello response to the return packet. return("250-smtp.winlink.org Hello " + strInputStream.Substring(5) + "250-AUTH LOGIN PLAIN" + Globals.CRLF + "250-AUTH=LOGIN PLAIN" + Globals.CRLF + "250 OK" + Globals.CRLF); } case "QUIT": // Send reply to shut down connection... { SMTPState = SessionState.Disconnected; // Update the state return(Reply221); // Closing service } case "VRFY": // Currently reply OK to all recipients { return("250 OK " + strInputStream + Globals.CRLF); // If command not "EHLO", "VRFY", or "QUIT" return error } default: { return(Reply504); // Command paramater not implemented } } break; } case SessionState.AuthPlain: { var switchExpr2 = strCommand; switch (switchExpr2) { case "RSET": // Restart the protocol... { SMTPState = SessionState.Ready; return(Reply250); // OK } default: { // This decodes the Base64 Client reply for PLAIN for UserName and Password... UserPasswordDecode(ref strAccountName, ref strPassword, strInputStream); // Return to the ready state on Authorization success or faiure... SMTPState = SessionState.Ready; authenticated = Authorize(strAccountName, strPassword); // Sucessful authorization if true if (authenticated) { return("235 OK Authenticated" + Globals.CRLF); } else { return("501 Authentication failed" + Globals.CRLF); } break; } } break; } case SessionState.AuthLoginID: { var switchExpr3 = strCommand; switch (switchExpr3) { case "RSET": // Restart the protocol... { SMTPState = SessionState.Ready; return(Reply250); // OK } default: { // Decode the Base64 Client reply for account name... strAccountName = Base64Decode(strInputStream).ToUpper(); // Change state to receive password... SMTPState = SessionState.AuthLoginPass; // Change state to receive password // Send the Base64 encoded request for password... return("334 " + Base64Encode("Password:"******"RSET": { SMTPState = SessionState.Ready; return(Reply250); // OK } default: { // Decode the Base64 Client reply for password... strPassword = Base64Decode(strInputStream).ToUpper(); // Return to the ready state on Authorization success or faiure... SMTPState = SessionState.Ready; authenticated = Authorize(strAccountName, strPassword); // Sucessful authorization if true is returned... if (authenticated) { Globals.queSMTPDisplay.Enqueue("BSMTP link from " + strAccountName + " at " + Globals.TimestampEx()); return("235 OK Authenticated" + Globals.CRLF); } else { return("501 Authentication failed" + Globals.CRLF); } break; } } break; } case SessionState.Ready: // The state after receiving "EHLO" and Authorization... { var switchExpr5 = strCommand; switch (switchExpr5) { case "MAIL": case "SOML": { strRecipients = ""; // clear out the recipients if (strInputStream.IndexOf("<") != -1) { if (strInputStream.ToLower().IndexOf("winlink.org") == -1) { return(Reply504); } } if (authenticated) { blnMessageErrorFlag = false; // Reset the message error flag SMTPState = SessionState.StartMail; sbdInboundMessage = null; return(Reply250); // OK } else { return("530 Authentication required" + Globals.CRLF); } break; } case "RSET": // Restart the protocol... { SMTPState = SessionState.Ready; return(Reply250); // OK } case "QUIT": { // send reply to shut down connection SMTPState = SessionState.Disconnected; ProtocolRet = Reply221; // closing service break; } case "AUTH": // Authorization request check for type { intPointer = strInputStream.ToUpper().IndexOf("PLAIN"); if (intPointer != -1 & strInputStream.Length < intPointer + 8) { // Initial AUTH did NOT contain the Base64 Encode SMTPState = SessionState.AuthPlain; return("334 " + Globals.CRLF); // Reply for a AUTH PLAIN request } else if (intPointer != -1) { // Base64 User/Password included in AUTH command (e.g Netscape)... strTemporary = strInputStream.Substring(intPointer + 5).Trim(); // This decodes the Base64 Client reply for PLAIN for UserName and Password... UserPasswordDecode(ref strAccountName, ref strPassword, strTemporary); // Return to the ready state on authorization success or failure... SMTPState = SessionState.Ready; // Check the user array for authorization: UserName and Password must match (case insensitive) authenticated = Authorize(strAccountName, strPassword); if (authenticated) // sucessful authorization if true is returned { return("235 OK Authenticated" + Globals.CRLF); } else { return("501 Authentication failed" + Globals.CRLF); } } else if (strInputStream.ToUpper().IndexOf("LOGIN") != -1) { intPointer = strInputStream.ToUpper().IndexOf("LOGIN"); if (strInputStream.Length < intPointer + 8) { // Initial AUTH did NOT contain the Base64 Encode SMTPState = SessionState.AuthLoginID; return("334 " + Base64Encode("Username:"******"334 " + Base64Encode("Password:"******"504 Unrecognized authentication type" + Globals.CRLF); } break; } default: { return(Reply500); // Command unrecognized } } break; } case SessionState.GetData: { // In the GetData state - only check is for End of data condition and handle <CRLF>.<CRLF> // The following should catch any <CRLF>.<CRLF> sequence even over multiple data in events... // Append data to the inbound string builder... InboundMessage(strInputStream); if ((strMessageBody.Right(4) + strInputStream).Right(5) == Globals.CRLF + "." + Globals.CRLF) { // This is the end of data... var objPaclinkMessage = new SMTPMessage(sbdInboundMessage.ToString(), true); if (objPaclinkMessage.IsAccepted) { // Added by RM Feb 25, 2008 check for compressed size if (objPaclinkMessage.SaveMessageToWinlink()) { if (CheckForOversizeMessage(objPaclinkMessage.MessageId)) { Globals.queSMTPDisplay.Enqueue("B" + objPaclinkMessage.MessageId + " received from " + strAccountName); Globals.queSMTPDisplay.Enqueue("B Subject: " + objPaclinkMessage.Subject); Globals.queSMTPDisplay.Enqueue("B Rejected! Exceeds 120KB compressed size limit."); return("554 Message exceeds WL2K's 120KB compressed size limit" + Globals.CRLF); // Transaction failed } } var intPtr = default(int); if (IsMARSMessage(ref objPaclinkMessage)) { for (int i = 0; i <= 4; i++) { intPtr = objPaclinkMessage.Subject.ToUpper().IndexOf("//MARS " + "ZOPRM".Substring(i, 1) + "/"); if (intPtr != -1) { break; } } if (intPtr != -1) { objPaclinkMessage.Subject = objPaclinkMessage.Subject.Substring(intPtr); // Drop off anything ahead of the "//MARS flag like Re: Fw: etc } else { // No MARS flag so add Routine Mars flag objPaclinkMessage.Subject = "//MARS R/ " + objPaclinkMessage.Subject; } } else { intPtr = objPaclinkMessage.Subject.ToUpper().IndexOf("//WL2K"); if (intPtr != -1) { objPaclinkMessage.Subject = objPaclinkMessage.Subject.Substring(intPtr); // Drop off anything ahead of the "//WL2K flag like Re: Fw: etc } else { // No WL2K flag so add Routine flag objPaclinkMessage.Subject = "//WL2K " + objPaclinkMessage.Subject; } } if (objPaclinkMessage.SaveMessageToWinlink()) { Globals.queSMTPDisplay.Enqueue("B" + objPaclinkMessage.MessageId + " received from " + strAccountName); Globals.queSMTPDisplay.Enqueue("B Subject: " + objPaclinkMessage.Subject); SMTPState = SessionState.Ready; // Exit to the ready state return(Reply250); // OK } else { SMTPState = SessionState.Failure; return("554 " + "Failure to save message" + Globals.CRLF); } } else { SMTPState = SessionState.Failure; return("554 " + objPaclinkMessage.ErrorDescription + Globals.CRLF); } } else { // Only need to buffer the last 4 Characters to be able catch end of data sequence above strMessageBody = strMessageBody + strInputStream.Right(4); return(""); } break; } case SessionState.StartMail: { var switchExpr6 = strCommand; switch (switchExpr6) { case "RSET": // Restart the protocol... { SMTPState = SessionState.Ready; return(Reply250); // OK } case "QUIT": { // Send reply to shut down connection SMTPState = SessionState.Disconnected; return(Reply221); // Closing service } case "RCPT": { strRecipients = strInputStream.Substring(1 + strInputStream.IndexOf(":")).Trim(); SMTPState = SessionState.GetRecipients; return(Reply250); // OK } case "VRFY": { return(Reply250); // OK } default: { // Send error response... return(Reply500); // Command unrecognized } } break; } case SessionState.GetRecipients: { var switchExpr7 = strCommand; switch (switchExpr7) { case "RSET": // Restart the protocol... { SMTPState = SessionState.Ready; return(Reply250); // OK } case "RCPT": { strRecipients = strRecipients + ";" + strInputStream.Substring(1 + strInputStream.IndexOf(":")).Trim(); SMTPState = SessionState.GetRecipients; return(Reply250); // OK } case "DATA": { // Change to GetData State... SMTPState = SessionState.GetData; strMessageBody = ""; // strMimeFilename = "" return(Reply354); // Start mail, end with <CRLF>.<CRLF> } case var @case when @case == "RSET": { // Send reply for going to ready state... SMTPState = SessionState.Ready; return(Reply250); // OK } case "QUIT": { // Send Reply to shut down connection... SMTPState = SessionState.Disconnected; return(Reply221); // Closing service } case "VRFY": { return(Reply250); // OK } default: { // Wrong command for GetRecipients state return(Reply500); // Bad syntax, command not recognized } } // sCommand break; } case SessionState.Failure: { var switchExpr8 = strCommand; switch (switchExpr8) { case "RSET": // Restart the protocol... { SMTPState = SessionState.Ready; return(Reply250); // OK } case "QUIT": { // Send reply to shut down connection... SMTPState = SessionState.Disconnected; return(Reply221); // Closing service } default: { return("451 Local error cannot process command" + Globals.CRLF); } } break; } default: { return(Reply503); } } // SMTPState return(ProtocolRet); } // Protocol
private void SMTPThread() { _abortSMTPThread = false; // Open SMTP/POP3 ports... try { // Clear and re establish the objSMTPPort if (Globals.objSMTPPort != null) { Globals.objSMTPPort.Close(); Globals.objSMTPPort = null; } Globals.objSMTPPort = new SMTPPort(); Globals.objSMTPPort.LocalPort = Globals.intSMTPPortNumber; Globals.objSMTPPort.Listen(true); // Clear and reestablish the objPOP3Port if (Globals.objPOP3Port != null) { Globals.objPOP3Port.Close(); Globals.objPOP3Port = null; } Globals.objPOP3Port = new POP3Port(); Globals.objPOP3Port.LocalPort = Globals.intPOP3PortNumber; Globals.objPOP3Port.Listen(true); } catch (Exception ex) { MessageBox.Show( ex.Message + Globals.CRLF + "There may be a SMTP/POP3 confilct due to another program/service listening on the POP3/SMTP Ports." + " Terminate that service or change POP3/SMTP ports in Paclink and your mail client." + " Check the Paclink Errors.log for details of the error.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); _log.Error("[SMTPThread] SMTP/POP3 Port Setup: " + ex.Message); } var messageStoreDatabase = new MessageStore(DatabaseFactory.Get()); do { Thread.Sleep(4000); if (Globals.blnProgramClosing) { break; } if (_intDay != DateTime.UtcNow.Day) { _intDay = DateTime.UtcNow.Day; messageStoreDatabase.PurgeMessageIdsSeen(); } // // Initiates processing of any messages received from Winlink. // try { var messageStore = new MessageStore(DatabaseFactory.Get()); foreach (var message in messageStore.GetFromWinlinkMessages()) { string strMime = UTF8Encoding.UTF8.GetString(message.Value); var objWinlinkMessage = new SMTPMessage(strMime, false); if (objWinlinkMessage.IsAccepted) { if (objWinlinkMessage.SaveMessageToAccounts() == false) { _log.Error("[PrimaryThreads.SMTPThread] Failure to save " + objWinlinkMessage.Mime + " to user account"); } } else { _log.Error("[PrimaryThreads.SMTPThread] Failure to decode " + objWinlinkMessage.Mime + " from Winlink"); } messageStore.DeleteFromWinlinkMessage(message.Key); } } catch (Exception ex) { _log.Error("[Main.PollSMTPSide A] " + ex.Message); } // // Updates the message pending counts. // try { var messageStore = new MessageStore(DatabaseFactory.Get()); var strFromMessageList = messageStore.GetToWinlinkMessages(); Globals.intPendingForWinlink = strFromMessageList.Count; Globals.intPendingForClients = messageStore.GetNumberOfAccountEmails(); // // Displays the message pending counts. // Globals.queSMTPStatus.Enqueue("To Clients: " + Globals.intPendingForClients.ToString() + " To Winlink: " + Globals.intPendingForWinlink.ToString()); } catch (Exception ex) { _log.Error("[Main.PollSMTPSide B] " + ex.Message); } }while (!_abortSMTPThread); } // SMTPThread