/// <summary> /// Executes specified actions. /// </summary> /// <param name="dvActions">Dataview what contains actions to be executed.</param> /// <param name="server">Reference to owner virtual server.</param> /// <param name="message">Recieved message.</param> /// <param name="sender">MAIL FROM: command value.</param> /// <param name="to">RCPT TO: commands values.</param> public GlobalMessageRuleActionResult DoActions(DataView dvActions, VirtualServer server, Stream message, string sender, string[] to) { // TODO: get rid of MemoryStream, move to Stream // bool messageChanged = false; bool deleteMessage = false; string storeFolder = null; string errorText = null; // Loop actions foreach (DataRowView drV in dvActions) { GlobalMessageRuleAction_enum action = (GlobalMessageRuleAction_enum)drV["ActionType"]; byte[] actionData = (byte[])drV["ActionData"]; // Reset stream position message.Position = 0; #region AutoResponse /* Description: Sends specified autoresponse message to sender. * Action data structure: * <ActionData> * <From></From> * <Message></Message> * </ActionData> */ if (action == GlobalMessageRuleAction_enum.AutoResponse) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); string smtp_from = table.GetValue("From"); string responseMsg = table.GetValue("Message"); // See if we have header field X-LS-MailServer-AutoResponse, never answer to auto response. MIME_h_Collection header = new MIME_h_Collection(new MIME_h_Provider()); header.Parse(new SmartStream(message, false)); if (header.Contains("X-LS-MailServer-AutoResponse")) { // Just skip } else { Mail_Message autoresponseMessage = Mail_Message.ParseFromByte(System.Text.Encoding.Default.GetBytes(responseMsg)); // Add header field 'X-LS-MailServer-AutoResponse:' autoresponseMessage.Header.Add(new MIME_h_Unstructured("X-LS-MailServer-AutoResponse", "")); // Update message date autoresponseMessage.Date = DateTime.Now; // Set To: if not explicity set if (autoresponseMessage.To == null || autoresponseMessage.To.Count == 0) { if (autoresponseMessage.To == null) { Mail_t_AddressList t = new Mail_t_AddressList(); t.Add(new Mail_t_Mailbox(null, sender)); autoresponseMessage.To = t; } else { autoresponseMessage.To.Add(new Mail_t_Mailbox(null, sender)); } } // Update Subject: variables, if any if (autoresponseMessage.Subject != null) { if (header.Contains("Subject")) { autoresponseMessage.Subject = autoresponseMessage.Subject.Replace("#SUBJECT", header.GetFirst("Subject").ValueToString().Trim()); } } // Sender missing, we can't send auto response. if (string.IsNullOrEmpty(sender)) { continue; } server.ProcessAndStoreMessage(smtp_from, new string[] { sender }, new MemoryStream(autoresponseMessage.ToByte(new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q, Encoding.UTF8), Encoding.UTF8)), null); } } #endregion #region Delete Message /* Description: Deletes message. * Action data structure: * <ActionData> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.DeleteMessage) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); deleteMessage = true; } #endregion #region ExecuteProgram /* Description: Executes specified program. * Action data structure: * <ActionData> * <Program></Program> * <Arguments></Arguments> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.ExecuteProgram) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); System.Diagnostics.ProcessStartInfo pInfo = new System.Diagnostics.ProcessStartInfo(); pInfo.FileName = table.GetValue("Program"); pInfo.Arguments = table.GetValue("Arguments"); pInfo.CreateNoWindow = true; System.Diagnostics.Process.Start(pInfo); } #endregion #region ForwardToEmail /* Description: Forwards email to specified email. * Action data structure: * <ActionData> * <Email></Email> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.ForwardToEmail) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); // See If message has X-LS-MailServer-ForwardedTo: and equals to "Email". // If so, then we have cross reference forward, don't forward that message MIME_h_Collection header = new MIME_h_Collection(new MIME_h_Provider()); header.Parse(new SmartStream(message, false)); bool forwardedAlready = false; if (header.Contains("X-LS-MailServer-ForwardedTo")) { foreach (MIME_h headerField in header["X-LS-MailServer-ForwardedTo"]) { if (headerField.ValueToString().Trim() == table.GetValue("Email")) { forwardedAlready = true; break; } } } // Reset stream position message.Position = 0; if (forwardedAlready) { // Just skip } else { // Add header field 'X-LS-MailServer-ForwardedTo:' MemoryStream msFwMessage = new MemoryStream(); byte[] fwField = System.Text.Encoding.Default.GetBytes("X-LS-MailServer-ForwardedTo: " + table.GetValue("Email") + "\r\n"); msFwMessage.Write(fwField, 0, fwField.Length); SCore.StreamCopy(message, msFwMessage); server.ProcessAndStoreMessage(sender, new string[] { table.GetValue("Email") }, msFwMessage, null); } } #endregion #region ForwardToHost /* Description: Forwards email to specified host. * All RCPT TO: recipients are preserved. * Action data structure: * <ActionData> * <Host></Host> * <Port></Port> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.ForwardToHost) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); foreach (string t in to) { message.Position = 0; server.RelayServer.StoreRelayMessage( Guid.NewGuid().ToString(), null, message, HostEndPoint.Parse(table.GetValue("Host") + ":" + table.GetValue("Port")), sender, t, null, SMTP_DSN_Notify.NotSpecified, SMTP_DSN_Ret.NotSpecified ); } message.Position = 0; } #endregion #region StoreToDiskFolder /* Description: Stores message to specified disk folder. * Action data structure: * <ActionData> * <Folder></Folder> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.StoreToDiskFolder) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); string folder = table.GetValue("Folder"); if (!folder.EndsWith("\\")) { folder += "\\"; } if (Directory.Exists(folder)) { using (FileStream fs = File.Create(folder + DateTime.Now.ToString("ddMMyyyyHHmmss") + "_" + Guid.NewGuid().ToString().Replace('-', '_').Substring(0, 8) + ".eml")){ SCore.StreamCopy(message, fs); } } else { // TODO: log error somewhere } } #endregion #region StoreToIMAPFolder /* Description: Stores message to specified IMAP folder. * Action data structure: * <ActionData> * <Folder></Folder> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.StoreToIMAPFolder) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); storeFolder = table.GetValue("Folder"); } #endregion #region AddHeaderField /* Description: Add specified header field to message main header. * Action data structure: * <ActionData> * <HeaderFieldName></HeaderFieldName> * <HeaderFieldValue></HeaderFieldValue> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.AddHeaderField) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); Mail_Message mime = Mail_Message.ParseFromStream(message); mime.Header.Add(new MIME_h_Unstructured(table.GetValue("HeaderFieldName"), table.GetValue("HeaderFieldValue"))); message.SetLength(0); mime.ToStream(message, new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q, Encoding.UTF8), Encoding.UTF8); // messageChanged = true; } #endregion #region RemoveHeaderField /* Description: Removes specified header field from message mian header. * Action data structure: * <ActionData> * <HeaderFieldName></HeaderFieldName> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.RemoveHeaderField) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); Mail_Message mime = Mail_Message.ParseFromStream(message); mime.Header.RemoveAll(table.GetValue("HeaderFieldName")); message.SetLength(0); mime.ToStream(message, new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q, Encoding.UTF8), Encoding.UTF8); // messageChanged = true; } #endregion #region SendErrorToClient /* Description: Sends error to currently connected client. NOTE: Error text may contain ASCII printable chars only and maximum length is 500. * Action data structure: * <ActionData> * <ErrorText></ErrorText> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.SendErrorToClient) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); errorText = table.GetValue("ErrorText"); } #endregion #region StoreToFTPFolder /* Description: Stores message to specified FTP server folder. * Action data structure: * <ActionData> * <Server></Server> * <Port></Server> * <User></User> * <Password></Password> * <Folder></Folder> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.StoreToFTPFolder) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); _MessageRuleAction_FTP_AsyncSend ftpSend = new _MessageRuleAction_FTP_AsyncSend( table.GetValue("Server"), Convert.ToInt32(table.GetValue("Port")), table.GetValue("User"), table.GetValue("Password"), table.GetValue("Folder"), message, DateTime.Now.ToString("ddMMyyyyHHmmss") + "_" + Guid.NewGuid().ToString().Replace('-', '_').Substring(0, 8) + ".eml" ); } #endregion #region PostToNNTPNewsGroup /* Description: Posts message to specified NNTP newsgroup. * Action data structure: * <ActionData> * <Server></Server> * <Port></Server> * <User></User> * <Password></Password> * <Newsgroup></Newsgroup> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.PostToNNTPNewsGroup) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); // Add header field "Newsgroups: newsgroup", NNTP server demands it. Mail_Message mime = Mail_Message.ParseFromStream(message); if (!mime.Header.Contains("Newsgroups:")) { mime.Header.Add(new MIME_h_Unstructured("Newsgroups:", table.GetValue("Newsgroup"))); } _MessageRuleAction_NNTP_Async nntp = new _MessageRuleAction_NNTP_Async( table.GetValue("Server"), Convert.ToInt32(table.GetValue("Port")), table.GetValue("Newsgroup"), new MemoryStream(mime.ToByte(new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q, Encoding.UTF8), Encoding.UTF8)) ); } #endregion #region PostToHTTP /* Description: Posts message to specified page via HTTP. * Action data structure: * <ActionData> * <URL></URL> * <FileName></FileName> * </ActionData> */ else if (action == GlobalMessageRuleAction_enum.PostToHTTP) { XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); _MessageRuleAction_HTTP_Async http = new _MessageRuleAction_HTTP_Async( table.GetValue("URL"), message ); } #endregion } return(new GlobalMessageRuleActionResult(deleteMessage, storeFolder, errorText)); }
/// <summary> /// /// </summary> /// <param name="sender"></param> /// <param name="recipient"></param> /// <param name="storeFolder">Message folder where message will be stored. For example 'Inbox'.</param> /// <param name="msgStream"></param> /// <param name="e">Event data.</param> private void ProcessRecipientMsg(string sender,string recipient,string storeFolder,Stream msgStream,MessageStoringCompleted_eArgs e) { msgStream.Position = 0; string eAddress = recipient; string mailbox = ""; bool relay = false; string forwardHost = null; // We have local user instead of emailaddress if(recipient.IndexOf('@') == -1){ mailbox = recipient; } else{ mailbox = m_pApi.MapUser(eAddress); } // If not valid local mailbox or relay message. if(mailbox == null){ #region Check Routing bool routingDone = false; DataView dv = m_pApi.GetRoutes(); foreach(DataRowView drV in dv){ // We have matching route if(Convert.ToBoolean(drV["Enabled"]) && SCore.IsAstericMatch(drV["Pattern"].ToString(),eAddress)){ RouteAction_enum action = (RouteAction_enum)Convert.ToInt32(drV["Action"]); byte[] actionData = (byte[])drV["ActionData"]; #region RouteToEmail if(action == RouteAction_enum.RouteToEmail){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); eAddress = table.GetValue("EmailAddress"); relay = true; } #endregion #region RouteToHost else if(action == RouteAction_enum.RouteToHost){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); forwardHost = table.GetValue("Host") + ":" + table.GetValue("Port"); relay = true; } #endregion #region RouteToMailbox else if(action == RouteAction_enum.RouteToMailbox){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); mailbox = table.GetValue("Mailbox"); } #endregion routingDone = true; break; } } // Routing not done, handle message normally. if(!routingDone){ // Local message, but won't match to any mailbox. We never should reach there, // this may happen if routing deleted between session, just skip that message. if(m_pApi.DomainExists(eAddress)){ return; } else{ relay = true; } } #endregion } if(relay){ this.RelayServer.StoreRelayMessage(msgStream,forwardHost,sender,eAddress); } else{ ProcessUserMsg(sender,eAddress,mailbox,storeFolder,msgStream,e); } }
/// <summary> /// Executes specified actions. /// </summary> /// <param name="dvActions">Dataview what contains actions to be executed.</param> /// <param name="server">Reference to owner virtual server.</param> /// <param name="message">Recieved message.</param> /// <param name="sender">MAIL FROM: command value.</param> /// <param name="to">RCPT TO: commands values.</param> public GlobalMessageRuleActionResult DoActions(DataView dvActions,VirtualServer server,Stream message,string sender,string[] to) { // TODO: get rid of MemoryStream, move to Stream // bool messageChanged = false; bool deleteMessage = false; string storeFolder = null; string errorText = null; // Loop actions foreach(DataRowView drV in dvActions){ GlobalMessageRuleAction_enum action = (GlobalMessageRuleAction_enum)drV["ActionType"]; byte[] actionData = (byte[])drV["ActionData"]; // Reset stream position message.Position = 0; #region AutoResponse /* Description: Sends specified autoresponse message to sender. Action data structure: <ActionData> <From></From> <Message></Message> </ActionData> */ if(action == GlobalMessageRuleAction_enum.AutoResponse){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); string smtp_from = table.GetValue("From"); string responseMsg = table.GetValue("Message"); // See if we have header field X-LS-MailServer-AutoResponse, never answer to auto response. MIME_h_Collection header = new MIME_h_Collection(new MIME_h_Provider()); header.Parse(new SmartStream(message,false)); if(header.Contains("X-LS-MailServer-AutoResponse")){ // Just skip } else{ Mail_Message autoresponseMessage = Mail_Message.ParseFromByte(System.Text.Encoding.Default.GetBytes(responseMsg)); // Add header field 'X-LS-MailServer-AutoResponse:' autoresponseMessage.Header.Add(new MIME_h_Unstructured("X-LS-MailServer-AutoResponse","")); // Update message date autoresponseMessage.Date = DateTime.Now; // Set To: if not explicity set if(autoresponseMessage.To == null || autoresponseMessage.To.Count == 0){ if(autoresponseMessage.To == null){ Mail_t_AddressList t = new Mail_t_AddressList(); t.Add(new Mail_t_Mailbox(null,sender)); autoresponseMessage.To = t; } else{ autoresponseMessage.To.Add(new Mail_t_Mailbox(null,sender)); } } // Update Subject: variables, if any if(autoresponseMessage.Subject != null){ if(header.Contains("Subject")){ autoresponseMessage.Subject = autoresponseMessage.Subject.Replace("#SUBJECT",header.GetFirst("Subject").ValueToString().Trim()); } } server.ProcessAndStoreMessage(smtp_from,new string[]{sender},new MemoryStream(autoresponseMessage.ToByte(new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q,Encoding.UTF8),Encoding.UTF8)),null); } } #endregion #region Delete Message /* Description: Deletes message. Action data structure: <ActionData> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.DeleteMessage){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); deleteMessage = true; } #endregion #region ExecuteProgram /* Description: Executes specified program. Action data structure: <ActionData> <Program></Program> <Arguments></Arguments> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.ExecuteProgram){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); System.Diagnostics.ProcessStartInfo pInfo = new System.Diagnostics.ProcessStartInfo(); pInfo.FileName = table.GetValue("Program"); pInfo.Arguments = table.GetValue("Arguments"); pInfo.CreateNoWindow = true; System.Diagnostics.Process.Start(pInfo); } #endregion #region ForwardToEmail /* Description: Forwards email to specified email. Action data structure: <ActionData> <Email></Email> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.ForwardToEmail){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); // See If message has X-LS-MailServer-ForwardedTo: and equals to "Email". // If so, then we have cross reference forward, don't forward that message MIME_h_Collection header = new MIME_h_Collection(new MIME_h_Provider()); header.Parse(new SmartStream(message,false)); bool forwardedAlready = false; if(header.Contains("X-LS-MailServer-ForwardedTo")){ foreach(MIME_h headerField in header["X-LS-MailServer-ForwardedTo"]){ if(headerField.ValueToString().Trim() == table.GetValue("Email")){ forwardedAlready = true; break; } } } // Reset stream position message.Position = 0; if(forwardedAlready){ // Just skip } else{ // Add header field 'X-LS-MailServer-ForwardedTo:' MemoryStream msFwMessage = new MemoryStream(); byte[] fwField = System.Text.Encoding.Default.GetBytes("X-LS-MailServer-ForwardedTo: " + table.GetValue("Email") + "\r\n"); msFwMessage.Write(fwField,0,fwField.Length); SCore.StreamCopy(message,msFwMessage); server.ProcessAndStoreMessage(sender,new string[]{table.GetValue("Email")},msFwMessage,null); } } #endregion #region ForwardToHost /* Description: Forwards email to specified host. All RCPT TO: recipients are preserved. Action data structure: <ActionData> <Host></Host> <Port></Port> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.ForwardToHost){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); foreach(string t in to){ message.Position = 0; server.RelayServer.StoreRelayMessage( null, Guid.NewGuid().ToString(), message, HostEndPoint.Parse(table.GetValue("Host") + ":" + table.GetValue("Port")), sender, t, null, SMTP_DSN_Notify.NotSpecified, SMTP_DSN_Ret.NotSpecified ); } message.Position = 0; // TODO: does it later that needed there, must do in called place instead ? } #endregion #region StoreToDiskFolder /* Description: Stores message to specified disk folder. Action data structure: <ActionData> <Folder></Folder> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.StoreToDiskFolder){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); string folder = table.GetValue("Folder"); if(!folder.EndsWith("\\")){ folder += "\\"; } if(Directory.Exists(folder)){ using(FileStream fs = File.Create(folder + DateTime.Now.ToString("ddMMyyyyHHmmss") + "_" + Guid.NewGuid().ToString().Replace('-','_').Substring(0,8) + ".eml")){ SCore.StreamCopy(message,fs); } } else{ // TODO: log error somewhere } } #endregion #region StoreToIMAPFolder /* Description: Stores message to specified IMAP folder. Action data structure: <ActionData> <Folder></Folder> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.StoreToIMAPFolder){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); storeFolder = table.GetValue("Folder"); } #endregion #region AddHeaderField /* Description: Add specified header field to message main header. Action data structure: <ActionData> <HeaderFieldName></HeaderFieldName> <HeaderFieldValue></HeaderFieldValue> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.AddHeaderField){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); Mail_Message mime = Mail_Message.ParseFromStream(message); mime.Header.Add(new MIME_h_Unstructured(table.GetValue("HeaderFieldName"),table.GetValue("HeaderFieldValue"))); message.SetLength(0); mime.ToStream(message,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q,Encoding.UTF8),Encoding.UTF8); // messageChanged = true; } #endregion #region RemoveHeaderField /* Description: Removes specified header field from message mian header. Action data structure: <ActionData> <HeaderFieldName></HeaderFieldName> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.RemoveHeaderField){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); Mail_Message mime = Mail_Message.ParseFromStream(message); mime.Header.RemoveAll(table.GetValue("HeaderFieldName")); message.SetLength(0); mime.ToStream(message,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q,Encoding.UTF8),Encoding.UTF8); // messageChanged = true; } #endregion #region SendErrorToClient /* Description: Sends error to currently connected client. NOTE: Error text may contain ASCII printable chars only and maximum length is 500. Action data structure: <ActionData> <ErrorText></ErrorText> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.SendErrorToClient){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); errorText = table.GetValue("ErrorText"); } #endregion #region StoreToFTPFolder /* Description: Stores message to specified FTP server folder. Action data structure: <ActionData> <Server></Server> <Port></Server> <User></User> <Password></Password> <Folder></Folder> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.StoreToFTPFolder){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); _MessageRuleAction_FTP_AsyncSend ftpSend = new _MessageRuleAction_FTP_AsyncSend( table.GetValue("Server"), Convert.ToInt32(table.GetValue("Port")), table.GetValue("User"), table.GetValue("Password"), table.GetValue("Folder"), message, DateTime.Now.ToString("ddMMyyyyHHmmss") + "_" + Guid.NewGuid().ToString().Replace('-','_').Substring(0,8) + ".eml" ); } #endregion #region PostToNNTPNewsGroup /* Description: Posts message to specified NNTP newsgroup. Action data structure: <ActionData> <Server></Server> <Port></Server> <User></User> <Password></Password> <Newsgroup></Newsgroup> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.PostToNNTPNewsGroup){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); // Add header field "Newsgroups: newsgroup", NNTP server demands it. Mail_Message mime = Mail_Message.ParseFromStream(message); if(!mime.Header.Contains("Newsgroups:")){ mime.Header.Add(new MIME_h_Unstructured("Newsgroups:",table.GetValue("Newsgroup"))); } _MessageRuleAction_NNTP_Async nntp = new _MessageRuleAction_NNTP_Async( table.GetValue("Server"), Convert.ToInt32(table.GetValue("Port")), table.GetValue("Newsgroup"), new MemoryStream(mime.ToByte(new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q,Encoding.UTF8),Encoding.UTF8)) ); } #endregion #region PostToHTTP /* Description: Posts message to specified page via HTTP. Action data structure: <ActionData> <URL></URL> <FileName></FileName> </ActionData> */ else if(action == GlobalMessageRuleAction_enum.PostToHTTP){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); _MessageRuleAction_HTTP_Async http = new _MessageRuleAction_HTTP_Async( table.GetValue("URL"), message ); } #endregion } return new GlobalMessageRuleActionResult(deleteMessage,storeFolder,errorText); }
/// <summary> /// Processes and stores message. /// </summary> /// <param name="envelopeID">Envelope ID_(MAIL FROM: ENVID).</param> /// <param name="sender">Mail from.</param> /// <param name="ret">Specifies what parts of message are returned in DSN report.</param> /// <param name="recipients">Message recipients.</param> /// <param name="msgStream">Message stream. Stream position must be there where message begins.</param> /// <param name="e">Event data.</param> /// <exception cref="ArgumentNullException">Is raised when <b>recipients</b> or <b>msgStream</b> is nulll reference.</exception> public void ProcessAndStoreMessage(string envelopeID,string sender,SMTP_DSN_Ret ret,SMTP_RcptTo[] recipients,Stream msgStream,SMTP_e_MessageStored e) { if(recipients == null){ throw new ArgumentNullException("recipients"); } if(msgStream == null){ throw new ArgumentNullException("msgStream"); } /* Message processing. *) Message filters. *) Global message rules. *) Process recipients. */ List<SMTP_RcptTo> dsn_Delivered = new List<SMTP_RcptTo>(); string[] to = new string[recipients.Length]; for(int i=0;i<to.Length;i++){ to[i] = recipients[i].Mailbox; } #region Global Filtering stuff //--- Filter message -----------------------------------------------// Stream filteredMsgStream = msgStream; DataView dvFilters = m_pApi.GetFilters(); dvFilters.RowFilter = "Enabled=true AND Type='ISmtpMessageFilter'"; dvFilters.Sort = "Cost"; foreach(DataRowView drViewFilter in dvFilters){ try{ filteredMsgStream.Position = 0; string assemblyFile = API_Utlis.PathFix(drViewFilter.Row["Assembly"].ToString()); // File is without path probably, try to load it from filters folder if(!File.Exists(assemblyFile)){ assemblyFile = API_Utlis.PathFix(m_pOwnerServer.StartupPath + "\\Filters\\" + assemblyFile); } Assembly ass = Assembly.LoadFrom(assemblyFile); Type tp = ass.GetType(drViewFilter.Row["ClassName"].ToString()); object filterInstance = Activator.CreateInstance(tp); ISmtpMessageFilter filter = (ISmtpMessageFilter)filterInstance; string errorText = ""; SMTP_Session session = null; if(e != null){ session = e.Session; } FilterResult result = filter.Filter(filteredMsgStream,out filteredMsgStream,sender,to,m_pApi,session,out errorText); if(result == FilterResult.DontStore){ // Just skip messge, act as message is stored e.Reply = new SMTP_Reply(552,"Requested mail action aborted: Message discarded by server filter."); return; } else if(result == FilterResult.Error){ if(e != null){ e.Reply = new SMTP_Reply(552,"Requested mail action aborted: " + errorText); } else{ // NOTE: 26.01.2006 - e maybe null if that method is called server internally and no smtp session. } return; } // Filter didn't return message stream if(filteredMsgStream == null){ e.Reply = new SMTP_Reply(552,"Requested mail action aborted: Message discarded by server filter."); return; } } catch(Exception x){ // Filtering failed, log error and allow message through. OnError(x); } } //---------------------------------------------------------------// #endregion #region Global Message Rules filteredMsgStream.Position = 0; Mail_Message mime = null; try{ mime = Mail_Message.ParseFromStream(filteredMsgStream); } // Invalid message syntax, block such message. catch{ e.Reply = new SMTP_Reply(552,"Requested mail action aborted: Message has invalid structure/syntax."); try{ if(!Directory.Exists(this.MailStorePath + "Unparseable")){ Directory.CreateDirectory(this.MailStorePath + "Unparseable"); } using(FileStream fs = File.Create(this.MailStorePath + "Unparseable\\" + Guid.NewGuid().ToString().Replace("-","") + ".eml")){ filteredMsgStream.Position = 0; Net_Utils.StreamCopy(filteredMsgStream,fs,32000); } } catch{ } return; } //--- Check Global Message Rules --------------------------------------------------------------// bool deleteMessage = false; string storeFolder = "Inbox"; string smtpErrorText = null; // Loop rules foreach(DataRowView drV_Rule in m_pApi.GetGlobalMessageRules()){ // Reset stream position filteredMsgStream.Position = 0; if(Convert.ToBoolean(drV_Rule["Enabled"])){ string ruleID = drV_Rule["RuleID"].ToString(); GlobalMessageRule_CheckNextRule_enum checkNextIf = (GlobalMessageRule_CheckNextRule_enum)(int)drV_Rule["CheckNextRuleIf"]; string matchExpression = drV_Rule["MatchExpression"].ToString(); // e may be null if server internal method call and no actual session ! SMTP_Session session = null; if(e != null){ session = e.Session; } GlobalMessageRuleProcessor ruleEngine = new GlobalMessageRuleProcessor(); bool matches = ruleEngine.Match(matchExpression,sender,to,session,mime,(int)filteredMsgStream.Length); if(matches){ // Do actions GlobalMessageRuleActionResult result = ruleEngine.DoActions( m_pApi.GetGlobalMessageRuleActions(ruleID), this, filteredMsgStream, sender, to ); if(result.DeleteMessage){ deleteMessage = true; } if(result.StoreFolder != null){ storeFolder = result.StoreFolder; } if(result.ErrorText != null){ smtpErrorText = result.ErrorText; } } //--- See if we must check next rule -------------------------------------------------// if(checkNextIf == GlobalMessageRule_CheckNextRule_enum.Always){ // Do nothing } else if(checkNextIf == GlobalMessageRule_CheckNextRule_enum.IfMatches && !matches){ break; } else if(checkNextIf == GlobalMessageRule_CheckNextRule_enum.IfNotMatches && matches){ break; } //------------------------------------------------------------------------------------// } } // Return error to connected client if(smtpErrorText != null){ e.Reply = new SMTP_Reply(552,"Requested mail action aborted: " + smtpErrorText); return; } // Just don't store message if(deleteMessage){ return; } // Reset stream position filteredMsgStream.Position = 0; //--- End of Global Rules -------------------------------------------------------------------// #endregion #region Process recipients HashSet<string> processedItems = new HashSet<string>(); Queue<SMTP_RcptTo> recipientsQueue = new Queue<SMTP_RcptTo>(); // Queue current recipients for processing. foreach(SMTP_RcptTo recipient in recipients){ recipientsQueue.Enqueue(recipient); } while(recipientsQueue.Count > 0){ /* Process order *) Local user *) Local address *) Local mailing list address *) Route *) Relay */ SMTP_RcptTo recipient = recipientsQueue.Dequeue(); // Check if we already have processed this item. Skip dublicate items. // This method also avoids loops when 2 recipients reference each other. if(processedItems.Contains(recipient.Mailbox)){ continue; } processedItems.Add(recipient.Mailbox); #region Local user if(recipient.Mailbox.IndexOf('@') == -1 && m_pApi.UserExists(recipient.Mailbox)){ // Add user to processed list. processedItems.Add(recipient.Mailbox); // Delivery status notification(DSN) requested to this user. if((recipient.Notify & SMTP_DSN_Notify.Success) != 0){ dsn_Delivered.Add(recipient); } ProcessUserMsg(sender,recipient.Mailbox,recipient.Mailbox,storeFolder,filteredMsgStream,e); continue; } #endregion #region Local address string localUser = m_pApi.MapUser(recipient.Mailbox); if(localUser != null){ // Add user to processed list. processedItems.Add(localUser); // Delivery status notification(DSN) requested to this user. if((recipient.Notify & SMTP_DSN_Notify.Success) != 0){ dsn_Delivered.Add(recipient); } ProcessUserMsg(sender,recipient.Mailbox,localUser,storeFolder,filteredMsgStream,e); } #endregion #region Mailing list address else if(m_pApi.MailingListExists(recipient.Mailbox)){ // Delivery status notification(DSN) requested to this user. if((recipient.Notify & SMTP_DSN_Notify.Success) != 0){ dsn_Delivered.Add(recipient); } Queue<string> processQueue = new Queue<string>(); processQueue.Enqueue(recipient.Mailbox); // Loop while there are mailing lists or nested mailing list available while(processQueue.Count > 0){ string mailingList = processQueue.Dequeue(); // Process mailing list members foreach(DataRowView drV in m_pApi.GetMailingListAddresses(mailingList)){ string member = drV["Address"].ToString(); // Member is asteric pattern matching server emails if(member.IndexOf('*') > -1){ DataView dvServerAddresses = m_pApi.GetUserAddresses(""); foreach(DataRowView drvServerAddress in dvServerAddresses){ string serverAddress = drvServerAddress["Address"].ToString(); if(SCore.IsAstericMatch(member,serverAddress)){ recipientsQueue.Enqueue(new SMTP_RcptTo(serverAddress,SMTP_DSN_Notify.NotSpecified,null)); } } } // Member is user or group, not email address else if(member.IndexOf('@') == -1){ // Member is group, replace with actual users if(m_pApi.GroupExists(member)){ foreach(string user in m_pApi.GetGroupUsers(member)){ recipientsQueue.Enqueue(new SMTP_RcptTo(user,SMTP_DSN_Notify.NotSpecified,null)); } } // Member is user else if(m_pApi.UserExists(member)){ recipientsQueue.Enqueue(new SMTP_RcptTo(member,SMTP_DSN_Notify.NotSpecified,null)); } // Unknown member, skip it. else{ } } // Member is nested mailing list else if(m_pApi.MailingListExists(member)){ processQueue.Enqueue(member); } // Member is normal email address else{ recipientsQueue.Enqueue(new SMTP_RcptTo(member,SMTP_DSN_Notify.NotSpecified,null)); } } } } #endregion else{ bool isRouted = false; #region Check Routing foreach(DataRowView drRoute in m_pApi.GetRoutes()){ // We have matching route if(Convert.ToBoolean(drRoute["Enabled"]) && SCore.IsAstericMatch(drRoute["Pattern"].ToString(),recipient.Mailbox)){ string description = drRoute["Action"].ToString(); RouteAction_enum action = (RouteAction_enum)Convert.ToInt32(drRoute["Action"]); byte[] actionData = (byte[])drRoute["ActionData"]; #region RouteToEmail if(action == RouteAction_enum.RouteToEmail){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); // Add email to process queue. recipientsQueue.Enqueue(new SMTP_RcptTo(table.GetValue("EmailAddress"),SMTP_DSN_Notify.NotSpecified,null)); // Log if(e != null){ e.Session.LogAddText("Route '[" + description + "]: " + drRoute["Pattern"].ToString() + "' routed to email '" + table.GetValue("EmailAddress") + "'."); } } #endregion #region RouteToHost else if(action == RouteAction_enum.RouteToHost){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); msgStream.Position = 0; // Route didn't match, so we have relay message. this.RelayServer.StoreRelayMessage( Guid.NewGuid().ToString(), envelopeID, msgStream, HostEndPoint.Parse(table.GetValue("Host") + ":" + table.GetValue("Port")), sender, recipient.Mailbox, recipient.ORCPT, recipient.Notify, ret ); // Log if(e != null){ e.Session.LogAddText("Route '[" + description + "]: " + drRoute["Pattern"].ToString() + "' routed to host '" + table.GetValue("Host") + ":" + table.GetValue("Port") + "'."); } } #endregion #region RouteToMailbox else if(action == RouteAction_enum.RouteToMailbox){ XmlTable table = new XmlTable("ActionData"); table.Parse(actionData); ProcessUserMsg(sender,recipient.Mailbox,table.GetValue("Mailbox"),storeFolder,filteredMsgStream,e); // Log if(e != null){ e.Session.LogAddText("Route '[" + description + "]: " + drRoute["Pattern"].ToString() + "' routed to user '" + table.GetValue("Mailbox") + "'."); } } #endregion isRouted = true; break; } } #endregion // Route didn't match, so we have relay message. if(!isRouted){ filteredMsgStream.Position = 0; this.RelayServer.StoreRelayMessage( Guid.NewGuid().ToString(), envelopeID, filteredMsgStream, null, sender, recipient.Mailbox, recipient.ORCPT, recipient.Notify, ret ); } } } #endregion #region DSN "delivered" // Send DSN for requested recipients. if(dsn_Delivered.Count > 0 && !string.IsNullOrEmpty(sender)){ try{ string dsn_to = ""; for(int i=0;i<dsn_Delivered.Count;i++){ if(i == (dsn_Delivered.Count - 1)){ dsn_to += dsn_Delivered[i].Mailbox; } else{ dsn_to += dsn_Delivered[i].Mailbox + "; "; } } string reportingMTA = ""; if(e != null && !string.IsNullOrEmpty(e.Session.LocalHostName)){ reportingMTA = e.Session.LocalHostName; } else{ reportingMTA = System.Net.Dns.GetHostName(); } ServerReturnMessage messageTemplate = null; if(messageTemplate == null){ string bodyRtf = "" + "{\\rtf1\\ansi\\ansicpg1257\\deff0\\deflang1061{\\fonttbl{\\f0\\froman\\fcharset0 Times New Roman;}{\\f1\froman\\fcharset186{\\*\\fname Times New Roman;}Times New Roman Baltic;}{\\f2\fswiss\\fcharset186{\\*\\fname Arial;}Arial Baltic;}}\r\n" + "{\\colortbl ;\\red0\\green128\\blue0;\\red128\\green128\\blue128;}\r\n" + "{\\*\\generator Msftedit 5.41.21.2508;}\\viewkind4\\uc1\\pard\\sb100\\sa100\\lang1033\\f0\\fs24\\par\r\n" + "Your message WAS SUCCESSFULLY DELIVERED to:\\line\\lang1061\\f1\\tab\\cf1\\lang1033\\b\\f0 " + dsn_to + "\\line\\cf0\\b0 and you explicitly requested a delivery status notification on success.\\par\\par\r\n" + "\\cf2 Your original message\\lang1061\\f1 /header\\lang1033\\f0 is attached to this e-mail\\lang1061\\f1 .\\lang1033\\f0\\par\\r\\n" + "\\cf0\\line\\par\r\n" + "\\pard\\lang1061\\f2\\fs20\\par\r\n" + "}\r\n"; messageTemplate = new ServerReturnMessage("DSN SUCCESSFULLY DELIVERED: " + mime.Subject,bodyRtf); } string rtf = messageTemplate.BodyTextRtf; Mail_Message dsnMsg = new Mail_Message(); dsnMsg.MimeVersion = "1.0"; dsnMsg.Date = DateTime.Now; dsnMsg.From = new Mail_t_MailboxList(); dsnMsg.From.Add(new Mail_t_Mailbox("Mail Delivery Subsystem","postmaster@local")); dsnMsg.To = new Mail_t_AddressList(); dsnMsg.To.Add(new Mail_t_Mailbox(null,sender)); dsnMsg.Subject = messageTemplate.Subject; //--- multipart/report ------------------------------------------------------------------------------------------------- MIME_h_ContentType contentType_multipartReport = new MIME_h_ContentType(MIME_MediaTypes.Multipart.report); contentType_multipartReport.Parameters["report-type"] = "delivery-status"; contentType_multipartReport.Param_Boundary = Guid.NewGuid().ToString().Replace('-','.'); MIME_b_MultipartReport multipartReport = new MIME_b_MultipartReport(contentType_multipartReport); dsnMsg.Body = multipartReport; //--- multipart/alternative ----------------------------------------------------------------------------------------- MIME_Entity entity_multipart_alternative = new MIME_Entity(); MIME_h_ContentType contentType_multipartAlternative = new MIME_h_ContentType(MIME_MediaTypes.Multipart.alternative); contentType_multipartAlternative.Param_Boundary = Guid.NewGuid().ToString().Replace('-','.'); MIME_b_MultipartAlternative multipartAlternative = new MIME_b_MultipartAlternative(contentType_multipartAlternative); entity_multipart_alternative.Body = multipartAlternative; multipartReport.BodyParts.Add(entity_multipart_alternative); //--- text/plain --------------------------------------------------------------------------------------------------- MIME_Entity entity_text_plain = new MIME_Entity(); MIME_b_Text text_plain = new MIME_b_Text(MIME_MediaTypes.Text.plain); entity_text_plain.Body = text_plain; text_plain.SetText(MIME_TransferEncodings.QuotedPrintable,Encoding.UTF8,SCore.RtfToText(rtf)); multipartAlternative.BodyParts.Add(entity_text_plain); //--- text/html ----------------------------------------------------------------------------------------------------- MIME_Entity entity_text_html = new MIME_Entity(); MIME_b_Text text_html = new MIME_b_Text(MIME_MediaTypes.Text.html); entity_text_html.Body = text_html; text_html.SetText(MIME_TransferEncodings.QuotedPrintable,Encoding.UTF8,SCore.RtfToHtml(rtf)); multipartAlternative.BodyParts.Add(entity_text_html); //--- message/delivery-status MIME_Entity entity_message_deliveryStatus = new MIME_Entity(); MIME_b_MessageDeliveryStatus body_message_deliveryStatus = new MIME_b_MessageDeliveryStatus(); entity_message_deliveryStatus.Body = body_message_deliveryStatus; multipartReport.BodyParts.Add(entity_message_deliveryStatus); //--- per-message-fields ---------------------------------------------------------------------------- MIME_h_Collection messageFields = body_message_deliveryStatus.MessageFields; if(!string.IsNullOrEmpty(envelopeID)){ messageFields.Add(new MIME_h_Unstructured("Original-Envelope-Id",envelopeID)); } messageFields.Add(new MIME_h_Unstructured("Arrival-Date",MIME_Utils.DateTimeToRfc2822(DateTime.Now))); if(e != null && !string.IsNullOrEmpty(e.Session.EhloHost)){ messageFields.Add(new MIME_h_Unstructured("Received-From-MTA","dns;" + e.Session.EhloHost)); } messageFields.Add(new MIME_h_Unstructured("Reporting-MTA","dns;" + reportingMTA)); //--------------------------------------------------------------------------------------------------- foreach(SMTP_RcptTo r in dsn_Delivered){ //--- per-recipient-fields -------------------------------------------------------------------------- MIME_h_Collection recipientFields = new MIME_h_Collection(new MIME_h_Provider()); if(r.ORCPT != null){ recipientFields.Add(new MIME_h_Unstructured("Original-Recipient",r.ORCPT)); } recipientFields.Add(new MIME_h_Unstructured("Final-Recipient","rfc822;" + r.Mailbox)); recipientFields.Add(new MIME_h_Unstructured("Action","delivered")); recipientFields.Add(new MIME_h_Unstructured("Status","2.0.0")); body_message_deliveryStatus.RecipientBlocks.Add(recipientFields); //--------------------------------------------------------------------------------------------------- } //--- message/rfc822 if(mime != null){ MIME_Entity entity_message_rfc822 = new MIME_Entity(); MIME_b_MessageRfc822 body_message_rfc822 = new MIME_b_MessageRfc822(); entity_message_rfc822.Body = body_message_rfc822; if(ret == SMTP_DSN_Ret.FullMessage){ body_message_rfc822.Message = mime; } else{ MemoryStream ms = new MemoryStream(); mime.Header.ToStream(ms,null,null); ms.Position = 0; body_message_rfc822.Message = Mail_Message.ParseFromStream(ms); } multipartReport.BodyParts.Add(entity_message_rfc822); } using(MemoryStream strm = new MemoryStream()){ dsnMsg.ToStream(strm,new MIME_Encoding_EncodedWord(MIME_EncodedWordEncoding.Q,Encoding.UTF8),Encoding.UTF8); ProcessAndStoreMessage("",new string[]{sender},strm,null); } } catch(Exception x){ Error.DumpError(this.Name,x); } } #endregion }