private bool BDAT(string cmdText) { // RFC 5321 3.1. if(m_SessionRejected){ WriteLine("503 bad sequence of commands: Session rejected."); return true; } // RFC 5321 4.1.4. if(string.IsNullOrEmpty(m_EhloHost)){ WriteLine("503 Bad sequence of commands: send EHLO/HELO first."); return true; } // RFC 5321 4.1.4. if(m_pFrom == null){ WriteLine("503 Bad sequence of commands: send 'MAIL FROM:' first."); return true; } // RFC 5321 4.1.4. if(m_pTo.Count == 0){ WriteLine("503 Bad sequence of commands: send 'RCPT TO:' first."); return true; } /* RFC 3030 2 The BDAT verb takes two arguments.The first argument indicates the length, in octets, of the binary data chunk. The second optional argument indicates that the data chunk is the last. The message data is sent immediately after the trailing <CR> <LF> of the BDAT command line. Once the receiver-SMTP receives the specified number of octets, it will return a 250 reply code. The optional LAST parameter on the BDAT command indicates that this is the last chunk of message data to be sent. The last BDAT command MAY have a byte-count of zero indicating there is no additional data to be sent. Any BDAT command sent after the BDAT LAST is illegal and MUST be replied to with a 503 "Bad sequence of commands" reply code. The state resulting from this error is indeterminate. A RSET command MUST be sent to clear the transaction before continuing. A 250 response MUST be sent to each successful BDAT data block within a mail transaction. bdat-cmd ::= "BDAT" SP chunk-size [ SP end-marker ] CR LF chunk-size ::= 1*DIGIT end-marker ::= "LAST" */ DateTime startTime = DateTime.Now; int chunkSize = 0; bool last = false; string[] args = cmdText.Split(' '); if(cmdText == string.Empty || args.Length > 2){ WriteLine("501 Syntax error, syntax: \"BDAT\" SP chunk-size [SP \"LAST\"] CRLF"); return true; } if(!int.TryParse(args[0],out chunkSize)){ WriteLine("501 Syntax error(chunk-size must be integer), syntax: \"BDAT\" SP chunk-size [SP \"LAST\"] CRLF"); return true; } if(args.Length == 2){ if(args[1].ToUpperInvariant() != "LAST"){ WriteLine("501 Syntax error, syntax: \"BDAT\" SP chunk-size [SP \"LAST\"] CRLF"); return true; } last = true; } // First BDAT block in transaction. if(m_pMessageStream == null){ m_pMessageStream = OnGetMessageStream(); if(m_pMessageStream == null){ m_pMessageStream = new MemoryStreamEx(32000); } // RFC 5321.4.4 trace info. byte[] recevived = CreateReceivedHeader(); m_pMessageStream.Write(recevived,0,recevived.Length); } Stream storeStream = m_pMessageStream; // Maximum allowed message size exceeded. if((m_BDatReadedCount + chunkSize) > this.Server.MaxMessageSize){ storeStream = new JunkingStream(); } // Read data block. this.TcpStream.BeginReadFixedCount( storeStream, chunkSize, new AsyncCallback(delegate(IAsyncResult ar){ try{ this.TcpStream.EndReadFixedCount(ar); m_BDatReadedCount += chunkSize; // Maximum allowed message size exceeded. if(m_BDatReadedCount > this.Server.MaxMessageSize){ WriteLine("552 Too much mail data."); OnMessageStoringCanceled(); } else{ SMTP_Reply reply = new SMTP_Reply(250,chunkSize + " bytes received in " + (DateTime.Now - startTime).TotalSeconds.ToString("f2") + " seconds."); if(last){ reply = OnMessageStoringCompleted(reply); } WriteLine(reply.ToString()); } if(last){ // Accoring RFC 3030, client should send RSET and we must wait it and reject transaction commands. // If we reset internally, then all works as specified. Reset(); } } catch(Exception x){ OnError(x); } BeginReadCmd(); }), null ); return false; }
/// <summary> /// Completes DATA command. /// </summary> /// <param name="startTime">Time DATA command started.</param> /// <param name="op">Read period-terminated opeartion.</param> private void DATA_End(DateTime startTime,SmartStream.ReadPeriodTerminatedAsyncOP op) { try{ if(op.Error != null){ if(op.Error is LineSizeExceededException){ WriteLine("500 Line too long."); } else if(op.Error is DataSizeExceededException){ WriteLine("552 Too much mail data."); } else{ OnError(op.Error); } OnMessageStoringCanceled(); } else{ SMTP_Reply reply = new SMTP_Reply(250,"DATA completed in " + (DateTime.Now - startTime).TotalSeconds.ToString("f2") + " seconds."); reply = OnMessageStoringCompleted(reply); WriteLine(reply.ToString()); } } catch(Exception x){ OnError(x); } Reset(); BeginReadCmd(); }
private void RCPT(string cmdText) { // RFC 5321 3.1. if(m_SessionRejected){ WriteLine("503 bad sequence of commands: Session rejected."); return; } // RFC 5321 4.1.4. if(string.IsNullOrEmpty(m_EhloHost)){ WriteLine("503 Bad sequence of commands: send EHLO/HELO first."); return; } // RFC 5321 4.1.4. if(m_pFrom == null){ WriteLine("503 Bad sequence of commands: send 'MAIL FROM:' first."); return; } // RFC 3030 BDAT. if(m_pMessageStream != null){ WriteLine("503 Bad sequence of commands: BDAT command is pending."); return; } /* RFC 5321 4.1.1.3. rcpt = "RCPT TO:" ( "<Postmaster@" Domain ">" / "<Postmaster>" / Forward-path ) [SP Rcpt-parameters] CRLF Rcpt-parameters = esmtp-param *(SP esmtp-param) esmtp-param = esmtp-keyword ["=" esmtp-value] esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") esmtp-value = 1*(%d33-60 / %d62-126) ; any CHAR excluding "=", SP, and control ; characters. If this string is an email address, ; i.e., a Mailbox, then the "xtext" syntax [32] SHOULD be used. Note that, in a departure from the usual rules for local-parts, the "Postmaster" string shown above is treated as case-insensitive. Forward-path = Path Path = "<" [ A-d-l ":" ] Mailbox ">" 4.1.1.11. If the server SMTP does not recognize or cannot implement one or more of the parameters associated with a particular MAIL FROM or RCPT TO command, it will return code 555. */ if(cmdText.ToUpper().StartsWith("TO:")){ // Remove TO: from command text. cmdText = cmdText.Substring(3).Trim(); } else{ WriteLine("501 Syntax error, syntax: \"RCPT TO:\" \"<\" address \">\" [SP Rcpt-parameters] CRLF"); return; } string address = ""; SMTP_DSN_Notify notify = SMTP_DSN_Notify.NotSpecified; string orcpt = null; // Mailbox not between <>. if(!cmdText.StartsWith("<") || cmdText.IndexOf('>') == -1){ WriteLine("501 Syntax error, syntax: \"RCPT TO:\" \"<\" address \">\" [SP Rcpt-parameters] CRLF"); return; } // Parse mailbox. else{ address = cmdText.Substring(1,cmdText.IndexOf('>') - 1).Trim(); cmdText = cmdText.Substring(cmdText.IndexOf('>') + 1).Trim(); } if(address == string.Empty){ WriteLine("501 Syntax error('address' value must be specified), syntax: \"RCPT TO:\" \"<\" address \">\" [SP Rcpt-parameters] CRLF"); return; } #region Parse parameters string[] parameters = string.IsNullOrEmpty(cmdText) ? new string[0] : cmdText.Split(' '); foreach(string parameter in parameters){ string[] name_value = parameter.Split(new char[]{'='},2); // NOTIFY if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.DSN) && name_value[0].ToUpper() == "NOTIFY"){ /* RFC 1891 5.1. notify-esmtp-value = "NEVER" / 1#notify-list-element notify-list-element = "SUCCESS" / "FAILURE" / "DELAY" a. Multiple notify-list-elements, separated by commas, MAY appear in a NOTIFY parameter; however, the NEVER keyword MUST appear by itself. */ if(name_value.Length == 1){ WriteLine("501 Syntax error: NOTIFY parameter value must be specified."); return; } string[] notifyItems = name_value[1].ToUpper().Split(','); foreach(string notifyItem in notifyItems){ if(notifyItem.Trim().ToUpper() == "NEVER"){ notify |= SMTP_DSN_Notify.Never; } else if(notifyItem.Trim().ToUpper() == "SUCCESS"){ notify |= SMTP_DSN_Notify.Success; } else if(notifyItem.Trim().ToUpper() == "FAILURE"){ notify |= SMTP_DSN_Notify.Failure; } else if(notifyItem.Trim().ToUpper() == "DELAY"){ notify |= SMTP_DSN_Notify.Delay; } // Invalid or not supported notify item. else{ WriteLine("501 Syntax error: Not supported NOTIFY parameter value '" + notifyItem + "'."); return; } } } // ORCPT else if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.DSN) && name_value[0].ToUpper() == "ORCPT"){ if(name_value.Length == 1){ WriteLine("501 Syntax error: ORCPT parameter value must be specified."); return; } orcpt = name_value[1].ToUpper(); } // Unsupported parameter. else{ WriteLine("555 Unsupported parameter: " + parameter); } } #endregion // Maximum allowed recipients exceeded. if(m_pTo.Count >= this.Server.MaxRecipients){ WriteLine("452 Too many recipients"); return; } SMTP_RcptTo to = new SMTP_RcptTo(address,notify,orcpt); SMTP_Reply reply = new SMTP_Reply(250,"OK."); reply = OnRcptTo(to,reply); // RCPT accepted. if(reply.ReplyCode < 300){ if(!m_pTo.ContainsKey(address.ToLower())){ m_pTo.Add(address.ToLower(),to); } } WriteLine(reply.ToString()); }
private void MAIL(string cmdText) { // RFC 5321 3.1. if(m_SessionRejected){ WriteLine("503 bad sequence of commands: Session rejected."); return; } // RFC 5321 4.1.4. if(string.IsNullOrEmpty(m_EhloHost)){ WriteLine("503 Bad sequence of commands: send EHLO/HELO first."); return; } // RFC 5321 4.1.4. if(m_pFrom != null){ WriteLine("503 Bad sequence of commands: nested MAIL command."); return; } // RFC 3030 BDAT. if(m_pMessageStream != null){ WriteLine("503 Bad sequence of commands: BDAT command is pending."); return; } if(this.Server.MaxTransactions != 0 && m_Transactions >= this.Server.MaxTransactions){ WriteLine("503 Bad sequence of commands: Maximum allowed mail transactions exceeded."); return; } /* RFC 5321 4.1.1.2. mail = "MAIL FROM:" Reverse-path [SP Mail-parameters] CRLF Mail-parameters = esmtp-param *(SP esmtp-param) esmtp-param = esmtp-keyword ["=" esmtp-value] esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") esmtp-value = 1*(%d33-60 / %d62-126) ; any CHAR excluding "=", SP, and control ; characters. If this string is an email address, ; i.e., a Mailbox, then the "xtext" syntax [32] SHOULD be used. Reverse-path = Path / "<>" Path = "<" [ A-d-l ":" ] Mailbox ">" 4.1.1.11. If the server SMTP does not recognize or cannot implement one or more of the parameters associated with a particular MAIL FROM or RCPT TO command, it will return code 555. */ if(cmdText.ToUpper().StartsWith("FROM:")){ // Remove FROM: from command text. cmdText = cmdText.Substring(5).Trim(); } else{ WriteLine("501 Syntax error, syntax: \"MAIL FROM:\" \"<\" address \">\" / \"<>\" [SP Mail-parameters] CRLF"); return; } string address = ""; int size = -1; string body = null; SMTP_DSN_Ret ret = SMTP_DSN_Ret.NotSpecified; string envID = null; // Mailbox not between <>. if(!cmdText.StartsWith("<") || cmdText.IndexOf('>') == -1){ WriteLine("501 Syntax error, syntax: \"MAIL FROM:\" \"<\" address \">\" / \"<>\" [SP Mail-parameters] CRLF"); return; } // Parse mailbox. else{ address = cmdText.Substring(1,cmdText.IndexOf('>') - 1).Trim(); cmdText = cmdText.Substring(cmdText.IndexOf('>') + 1).Trim(); } #region Parse parameters string[] parameters = string.IsNullOrEmpty(cmdText) ? new string[0] : cmdText.Split(' '); foreach(string parameter in parameters){ string[] name_value = parameter.Split(new char[]{'='},2); // SIZE if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.SIZE) && name_value[0].ToUpper() == "SIZE"){ // RFC 1870. // size-value ::= 1*20DIGIT if(name_value.Length == 1){ WriteLine("501 Syntax error: SIZE parameter value must be specified."); return; } if(!int.TryParse(name_value[1],out size)){ WriteLine("501 Syntax error: SIZE parameter value must be integer."); return; } // Message size exceeds maximum allowed message size. if(size > this.Server.MaxMessageSize){ WriteLine("552 Message exceeds fixed maximum message size."); return; } } // BODY else if(this.Server.Extentions.Contains(SMTP_ServiceExtensions._8BITMIME) && name_value[0].ToUpper() == "BODY"){ // RFC 1652. // body-value ::= "7BIT" / "8BITMIME" / "BINARYMIME" // // BINARYMIME - defined in RFC 3030. if(name_value.Length == 1){ WriteLine("501 Syntax error: BODY parameter value must be specified."); return; } if(name_value[1].ToUpper() != "7BIT" && name_value[1].ToUpper() != "8BITMIME" && name_value[1].ToUpper() != "BINARYMIME"){ WriteLine("501 Syntax error: BODY parameter value must be \"7BIT\",\"8BITMIME\" or \"BINARYMIME\"."); return; } body = name_value[1].ToUpper(); } // RET else if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.DSN) && name_value[0].ToUpper() == "RET"){ // RFC 3461 4.3. // ret-value = "FULL" / "HDRS" if(name_value.Length == 1){ WriteLine("501 Syntax error: RET parameter value must be specified."); return; } else if(name_value[1].ToUpper() != "FULL"){ ret = SMTP_DSN_Ret.FullMessage; } else if(name_value[1].ToUpper() != "HDRS"){ ret = SMTP_DSN_Ret.Headers; } else{ WriteLine("501 Syntax error: RET parameter value must be \"FULL\" or \"HDRS\"."); return; } } // ENVID else if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.DSN) && name_value[0].ToUpper() == "ENVID"){ // RFC 3461 4.4. // envid-parameter = "ENVID=" xtext if(name_value.Length == 1){ WriteLine("501 Syntax error: ENVID parameter value must be specified."); return; } envID = name_value[1].ToUpper(); } // AUTH else if(name_value[0].ToUpper() == "AUTH"){ } // Unsupported parameter. else{ WriteLine("555 Unsupported parameter: " + parameter); return; } } #endregion SMTP_MailFrom from = new SMTP_MailFrom(address,size,body,ret,envID); SMTP_Reply reply = new SMTP_Reply(250,"OK."); reply = OnMailFrom(from,reply); // MAIL accepted. if(reply.ReplyCode < 300){ m_pFrom = from; m_Transactions++; } WriteLine(reply.ToString()); }
private void HELO(string cmdText) { // RFC 5321 3.1. if(m_SessionRejected){ WriteLine("503 bad sequence of commands: Session rejected."); return; } /* RFC 5321 4.1.1.1. helo = "HELO" SP Domain CRLF response = "250" SP Domain [ SP ehlo-greet ] CRLF */ if(string.IsNullOrEmpty(cmdText) || cmdText.Split(' ').Length != 1){ WriteLine("501 Syntax error, syntax: \"HELO\" SP hostname CRLF"); return; } SMTP_Reply reply = new SMTP_Reply(250,Net_Utils.GetLocalHostName(this.LocalHostName)); reply = OnEhlo(cmdText,reply); // HELO accepted. if(reply.ReplyCode < 300){ m_EhloHost = cmdText; /* RFC 5321 4.1.4. An EHLO command MAY be issued by a client later in the session. If it is issued after the session begins and the EHLO command is acceptable to the SMTP server, the SMTP server MUST clear all buffers and reset the state exactly as if a RSET command had been issued. In other words, the sequence of RSET followed immediately by EHLO is redundant, but not harmful other than in the performance cost of executing unnecessary commands. */ Reset(); } WriteLine(reply.ToString()); }
private void EHLO(string cmdText) { // RFC 5321 3.1. if(m_SessionRejected){ WriteLine("503 bad sequence of commands: Session rejected."); return; } /* RFC 5321 4.1.1.1. ehlo = "EHLO" SP ( Domain / address-literal ) CRLF ehlo-ok-rsp = ( "250" SP Domain [ SP ehlo-greet ] CRLF ) / ( "250-" Domain [ SP ehlo-greet ] CRLF *( "250-" ehlo-line CRLF ) "250" SP ehlo-line CRLF ) ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127) ; string of any characters other than CR or LF ehlo-line = ehlo-keyword *( SP ehlo-param ) ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") ; additional syntax of ehlo-params depends on ehlo-keyword ehlo-param = 1*(%d33-126) ; any CHAR excluding <SP> and all control characters (US-ASCII 0-31 and 127 inclusive) */ if(string.IsNullOrEmpty(cmdText) || cmdText.Split(' ').Length != 1){ WriteLine("501 Syntax error, syntax: \"EHLO\" SP hostname CRLF"); return; } List<string> ehloLines = new List<string>(); ehloLines.Add(Net_Utils.GetLocalHostName(this.LocalHostName)); if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.PIPELINING)){ ehloLines.Add(SMTP_ServiceExtensions.PIPELINING); } if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.SIZE)){ ehloLines.Add(SMTP_ServiceExtensions.SIZE + " " + this.Server.MaxMessageSize); } if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.STARTTLS) && !this.IsSecureConnection && this.Certificate != null){ ehloLines.Add(SMTP_ServiceExtensions.STARTTLS); } if(this.Server.Extentions.Contains(SMTP_ServiceExtensions._8BITMIME)){ ehloLines.Add(SMTP_ServiceExtensions._8BITMIME); } if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.BINARYMIME)){ ehloLines.Add(SMTP_ServiceExtensions.BINARYMIME); } if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.CHUNKING)){ ehloLines.Add(SMTP_ServiceExtensions.CHUNKING); } if(this.Server.Extentions.Contains(SMTP_ServiceExtensions.DSN)){ ehloLines.Add(SMTP_ServiceExtensions.DSN); } StringBuilder sasl = new StringBuilder(); foreach(AUTH_SASL_ServerMechanism authMechanism in this.Authentications.Values){ if(!authMechanism.RequireSSL || (authMechanism.RequireSSL && this.IsSecureConnection)){ sasl.Append(authMechanism.Name + " "); } } if(sasl.Length > 0){ ehloLines.Add(SMTP_ServiceExtensions.AUTH + " " + sasl.ToString().Trim()); } SMTP_Reply reply = new SMTP_Reply(250,ehloLines.ToArray()); reply = OnEhlo(cmdText,reply); // EHLO accepted. if(reply.ReplyCode < 300){ m_EhloHost = cmdText; /* RFC 5321 4.1.4. An EHLO command MAY be issued by a client later in the session. If it is issued after the session begins and the EHLO command is acceptable to the SMTP server, the SMTP server MUST clear all buffers and reset the state exactly as if a RSET command had been issued. In other words, the sequence of RSET followed immediately by EHLO is redundant, but not harmful other than in the performance cost of executing unnecessary commands. */ Reset(); } WriteLine(reply.ToString()); }