}  // 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