/// <summary> /// Handles processing the message once the end of the DATA SMTP stream is sent. /// </summary> /// <param name="source"></param> /// <param name="eodArgs"></param> public void OnEndOfDataHandler(ReceiveEventSource source, EndOfDataEventArgs eodArgs) { Byte[] newlineNeedle = Encoding.ASCII.GetBytes("\n"); Byte[] scoreNeedle = Encoding.ASCII.GetBytes("X-Spam-Score: "); Byte[] discardFlag = Encoding.ASCII.GetBytes("X-Spam-Discard: YES\n"); Byte[] recievedNeedle = Encoding.ASCII.GetBytes("Received: "); Byte[] indentedLineNeedle = Encoding.ASCII.GetBytes(" "); Nullable <Double> score = null; this.logger.debug("OnEndOfDataHandler Called"); // Wrap everything in a Try. This makes sure that even if something fails the message still passes through try { this.logger.info("OnEndOfDataHandler Info: FROM=" + eodArgs.MailItem.FromAddress.ToString() + ", REMOTE=" + eodArgs.SmtpSession.RemoteEndPoint.Address.ToString()); // Check to make sure SpamAssassin exists at the path it's supposed to if (!System.IO.File.Exists(this.settings.SpamassassinPath)) { this.logger.fatal("Spamassassin does not exist at path: '" + this.settings.SpamassassinPath + "'. Bypassing."); this.logger.flush(); return; } // Check to see if this is an internal message if (!eodArgs.SmtpSession.IsExternalConnection) { foreach (EnvelopeRecipient recipient in eodArgs.MailItem.Recipients) { if (recipient.Address.LocalPart.IndexOf("HealthMailbox") == 0) { this.logger.info("Health Mailbox found in recipients. Bypassing."); this.logger.flush(); } } this.logger.info("Internal Connection Found. Bypassing."); this.logger.flush(); } // Is the message too big? if (eodArgs.MailItem.MimeStreamLength > this.settings.MaxMessageSize) { this.logger.warning("Message is too large. Increase MaxMessageSize to scan larger messages. MAXSIZE=" + this.settings.MaxMessageSize.ToString() + ", MESSAGESIZE=" + eodArgs.MailItem.MimeStreamLength.ToString()); this.logger.flush(); } // Get the message stream Stream message = eodArgs.MailItem.GetMimeReadStream(); List <Byte> messageBytes = new List <Byte>(ReadFully(message)); byte[] messageByteArray = messageBytes.ToArray(); message.Close(); this.logger.debug("Message Stream Retrieved. BYTES=" + messageByteArray.Length.ToString()); // Skip the top number of recieved lines int linestart = 0; int lineend = 0; int linestartmatch = 0; for (int i = 0; i < this.settings.SkipRecieved; i++) { this.logger.debug("Skipping #" + i.ToString() + " Recieved line"); // Find the first instance of the Recieved header linestart = ByteSearch.Locate(messageByteArray, recievedNeedle, 0); // Loop until we find a line that doesn't start with a space. do { lineend = ByteSearch.Locate(messageByteArray, newlineNeedle, linestart); this.logger.debug("Located Line End: " + lineend.ToString()); linestartmatch = ByteSearch.Locate(messageByteArray, indentedLineNeedle, lineend); this.logger.debug("Located LineStartMatch:" + linestartmatch.ToString()); messageBytes.RemoveRange(linestart, lineend - linestart + 1); messageByteArray = messageBytes.ToArray(); } while (linestartmatch == lineend + 1); this.logger.debug("Finished Skipping #" + i.ToString()); } // Run spamassassin while piping stdin/stdout this.logger.debug("Starting SpamAssassin Process. PATH='" + this.settings.SpamassassinPath + "', ARGS='" + this.settings.SpamassassinArgs + "'"); Process spamassassin = new Process(); spamassassin.StartInfo.UseShellExecute = false; spamassassin.StartInfo.FileName = this.settings.SpamassassinPath; spamassassin.StartInfo.WorkingDirectory = Path.GetDirectoryName(spamassassin.StartInfo.FileName); spamassassin.StartInfo.Arguments = ("-s " + this.settings.MaxMessageSize + " " + this.settings.SpamassassinArgs).Trim(); spamassassin.StartInfo.RedirectStandardInput = true; spamassassin.StartInfo.RedirectStandardOutput = true; spamassassin.StartInfo.RedirectStandardError = true; spamassassin.Start(); this.logger.debug("Started SpamAssassin Process. PID='" + spamassassin.Id.ToString() + "'"); // Copy the message into stdio using a byte for byte copy this.logger.debug("Copying message to Spamassassin. BYTES=" + messageByteArray.Length.ToString()); spamassassin.StandardInput.BaseStream.Write(messageByteArray, 0, messageByteArray.Length); spamassassin.StandardInput.BaseStream.Flush(); spamassassin.StandardInput.BaseStream.Close(); // Read the entire output buffer, put it into a list for easy manipulation List <Byte> outBytes = new List <Byte>(ReadFully(spamassassin.StandardOutput.BaseStream)); this.logger.debug("Read STDOUT from spamassassin. BYTES=" + outBytes.Count.ToString()); spamassassin.StandardOutput.BaseStream.Close(); // Read the entire stderr buffer, place it in a file if there's anything to it Byte[] outErrorBytes = ReadFully(spamassassin.StandardError.BaseStream); this.logger.debug("Read STDERR from spamassassin. BYTES=" + outErrorBytes.Length.ToString()); if (outErrorBytes.Length > 0) { this.logger.error("Error From SpamAssassin: " + outErrorBytes.ToString()); } spamassassin.StandardError.BaseStream.Close(); // Wait for process to exit spamassassin.WaitForExit(); // Find a header int flagStart = ByteSearch.Locate(outBytes.ToArray(), scoreNeedle, 0); int scoreEnd = -1; int scoreStart = 0; // Check if we found the start of the header if (flagStart > -1) { scoreStart = flagStart + scoreNeedle.Length; scoreEnd = ByteSearch.Locate(outBytes.ToArray(), newlineNeedle, scoreStart); } else { this.logger.warning("Could not find start of score in message."); } // Check if we found the end of the header if (scoreEnd > -1) { Byte[] scoreBytes = outBytes.GetRange(scoreStart, scoreEnd - scoreStart).ToArray(); try { score = Double.Parse(new String(scoreBytes.Select(b => (Char)b).ToArray()).Trim()); } catch (Exception e) { this.logger.warning("Could not parse score. Exception:" + e.Message); } } else { this.logger.warning("Could not find end of score in message."); } // If we found a score check it and process it if (score != null) { if (score >= this.settings.RejectThreshold) { this.logger.info("Score(" + score.ToString() + ") above threshold(" + this.settings.RejectThreshold.ToString() + "), flagging for discard."); outBytes.InsertRange(flagStart, discardFlag); } else { this.logger.info("Score(" + score.ToString() + ") below threshold(" + this.settings.RejectThreshold.ToString() + "), passing."); } } // Write the message back to SpamAssassin Byte[] writeBytes = outBytes.ToArray(); Stream messageOut = eodArgs.MailItem.GetMimeWriteStream(); this.logger.debug("Writing message to MailItem. BYTES=" + writeBytes.Length.ToString()); messageOut.Write(writeBytes, 0, writeBytes.Length); messageOut.Close(); } catch (Exception e) { this.logger.fatal("Exception Detected"); this.logger.fatal(e.ToString()); } finally { this.logger.flush(); } }
/// <summary> /// Handles the "RCPT TO:" SMTP command /// </summary> /// <param name="source">The event source.</param> /// <param name="eodArgs">The event arguments.</param> public void RcptToHandler(ReceiveEventSource source, RcptCommandEventArgs rcptArgs) { RoutingAddress catchAllAddress; // Check whether to handle the recipient's domain //if (this.catchAllConfig.AddressMap.TryGetValue( // rcptArgs.RecipientAddress.DomainPart.ToLower(), // out catchAllAddress)) foreach (Regex r in this.catchAllConfig.AddressMap.Keys) { if(r.IsMatch(rcptArgs.RecipientAddress.ToString())) { // DO THE DIRTY WORK HERE } } if (false) { // Rewrite the (envelope) recipient address if 'not found' if ((this.addressBook != null) && (this.addressBook.Find(rcptArgs.RecipientAddress) == null)) { rcptArgs.RecipientAddress = catchAllAddress; } } return; }
/// <summary> /// Handles processing the message once the end of the DATA SMTP stream is sent. /// </summary> /// <param name="source"></param> /// <param name="eodArgs"></param> public void OnEndOfDataHandler(ReceiveEventSource source, EndOfDataEventArgs eodArgs) { Byte[] newlineNeedle = Encoding.ASCII.GetBytes("\n"); Byte[] scoreNeedle = Encoding.ASCII.GetBytes("X-Spam-Score: "); Byte[] discardFlag = Encoding.ASCII.GetBytes("X-Spam-Discard: YES\n"); Byte[] recievedNeedle = Encoding.ASCII.GetBytes("Received: "); Byte[] indentedLineNeedle = Encoding.ASCII.GetBytes(" "); Nullable<Double> score = null; this.logger.debug("OnEndOfDataHandler Called"); // Wrap everything in a Try. This makes sure that even if something fails the message still passes through try { this.logger.info("OnEndOfDataHandler Info: FROM=" + eodArgs.MailItem.FromAddress.ToString() + ", REMOTE=" + eodArgs.SmtpSession.RemoteEndPoint.Address.ToString()); // Check to make sure SpamAssassin exists at the path it's supposed to if (!System.IO.File.Exists(this.settings.SpamassassinPath)) { this.logger.fatal("Spamassassin does not exist at path: '" + this.settings.SpamassassinPath + "'. Bypassing."); this.logger.flush(); return; } // Check to see if this is an internal message if (!eodArgs.SmtpSession.IsExternalConnection) { foreach (EnvelopeRecipient recipient in eodArgs.MailItem.Recipients) { if (recipient.Address.LocalPart.IndexOf("HealthMailbox") == 0) { this.logger.info("Health Mailbox found in recipients. Bypassing."); this.logger.flush(); } } this.logger.info("Internal Connection Found. Bypassing."); this.logger.flush(); } // Is the message too big? if (eodArgs.MailItem.MimeStreamLength > this.settings.MaxMessageSize) { this.logger.warning("Message is too large. Increase MaxMessageSize to scan larger messages. MAXSIZE=" + this.settings.MaxMessageSize.ToString() + ", MESSAGESIZE=" + eodArgs.MailItem.MimeStreamLength.ToString()); this.logger.flush(); } // Get the message stream Stream message = eodArgs.MailItem.GetMimeReadStream(); List<Byte> messageBytes = new List<Byte>(ReadFully(message)); byte[] messageByteArray = messageBytes.ToArray(); message.Close(); this.logger.debug("Message Stream Retrieved. BYTES=" + messageByteArray.Length.ToString()); // Skip the top number of recieved lines int linestart = 0; int lineend = 0; int linestartmatch = 0; for (int i = 0; i < this.settings.SkipRecieved; i++) { this.logger.debug("Skipping #" + i.ToString() + " Recieved line"); // Find the first instance of the Recieved header linestart = ByteSearch.Locate(messageByteArray, recievedNeedle, 0); // Loop until we find a line that doesn't start with a space. do { lineend = ByteSearch.Locate(messageByteArray, newlineNeedle, linestart); this.logger.debug("Located Line End: " + lineend.ToString()); linestartmatch = ByteSearch.Locate(messageByteArray, indentedLineNeedle, lineend); this.logger.debug("Located LineStartMatch:" + linestartmatch.ToString()); messageBytes.RemoveRange(linestart, lineend - linestart + 1); messageByteArray = messageBytes.ToArray(); } while (linestartmatch == lineend + 1); this.logger.debug("Finished Skipping #" + i.ToString()); } // Run spamassassin while piping stdin/stdout this.logger.debug("Starting SpamAssassin Process. PATH='" + this.settings.SpamassassinPath + "', ARGS='" + this.settings.SpamassassinArgs + "'"); Process spamassassin = new Process(); spamassassin.StartInfo.UseShellExecute = false; spamassassin.StartInfo.FileName = this.settings.SpamassassinPath; spamassassin.StartInfo.WorkingDirectory = Path.GetDirectoryName(spamassassin.StartInfo.FileName); spamassassin.StartInfo.Arguments = ("-s " + this.settings.MaxMessageSize + " " + this.settings.SpamassassinArgs).Trim(); spamassassin.StartInfo.RedirectStandardInput = true; spamassassin.StartInfo.RedirectStandardOutput = true; spamassassin.StartInfo.RedirectStandardError = true; spamassassin.Start(); this.logger.debug("Started SpamAssassin Process. PID='" + spamassassin.Id.ToString() + "'"); // Copy the message into stdio using a byte for byte copy this.logger.debug("Copying message to Spamassassin. BYTES=" + messageByteArray.Length.ToString()); spamassassin.StandardInput.BaseStream.Write(messageByteArray, 0, messageByteArray.Length); spamassassin.StandardInput.BaseStream.Flush(); spamassassin.StandardInput.BaseStream.Close(); // Read the entire output buffer, put it into a list for easy manipulation List<Byte> outBytes = new List<Byte>(ReadFully(spamassassin.StandardOutput.BaseStream)); this.logger.debug("Read STDOUT from spamassassin. BYTES=" + outBytes.Count.ToString()); spamassassin.StandardOutput.BaseStream.Close(); // Read the entire stderr buffer, place it in a file if there's anything to it Byte[] outErrorBytes = ReadFully(spamassassin.StandardError.BaseStream); this.logger.debug("Read STDERR from spamassassin. BYTES=" + outErrorBytes.Length.ToString()); if (outErrorBytes.Length > 0) { this.logger.error("Error From SpamAssassin: " + outErrorBytes.ToString()); } spamassassin.StandardError.BaseStream.Close(); // Wait for process to exit spamassassin.WaitForExit(); // Find a header int flagStart = ByteSearch.Locate(outBytes.ToArray(), scoreNeedle, 0); int scoreEnd = -1; int scoreStart = 0; // Check if we found the start of the header if (flagStart > -1) { scoreStart = flagStart + scoreNeedle.Length; scoreEnd = ByteSearch.Locate(outBytes.ToArray(), newlineNeedle, scoreStart); } else { this.logger.warning("Could not find start of score in message."); } // Check if we found the end of the header if (scoreEnd > -1) { Byte[] scoreBytes = outBytes.GetRange(scoreStart, scoreEnd - scoreStart).ToArray(); try { score = Double.Parse(new String(scoreBytes.Select(b => (Char)b).ToArray()).Trim()); } catch(Exception e) { this.logger.warning("Could not parse score. Exception:" + e.Message); } } else { this.logger.warning("Could not find end of score in message."); } // If we found a score check it and process it if(score != null) { if (score >= this.settings.RejectThreshold) { this.logger.info("Score(" + score.ToString() + ") above threshold(" + this.settings.RejectThreshold.ToString() + "), flagging for discard."); outBytes.InsertRange(flagStart, discardFlag); } else { this.logger.info("Score(" + score.ToString() + ") below threshold(" + this.settings.RejectThreshold.ToString() + "), passing."); } } // Write the message back to SpamAssassin Byte[] writeBytes = outBytes.ToArray(); Stream messageOut = eodArgs.MailItem.GetMimeWriteStream(); this.logger.debug("Writing message to MailItem. BYTES=" + writeBytes.Length.ToString()); messageOut.Write(writeBytes, 0, writeBytes.Length); messageOut.Close(); } catch (Exception e) { this.logger.fatal("Exception Detected"); this.logger.fatal(e.ToString()); } finally { this.logger.flush(); } }