public async Task <bool> TrySendSingleMailAsync( IMailReference mail, StreamWriter writer, StreamReader reader, CancellationToken token) { _log.Information($"Sending mail {mail.Id}"); IMailReadReference readMail = await _queue.OpenReadAsync(mail, token); _log.Information($"Sender: {readMail.Sender}, Recipients: {string.Join(",", readMail.Recipients)}"); SmtpResponse response = await ExecuteRemoteCommandAsync(writer, reader, $"MAIL FROM:<{readMail.Sender}>"); if (response.Code != SmtpReplyCode.Okay) { _log.Warning("Failed MAIL FROM, aborting"); return(false); } foreach (string recipient in readMail.Recipients) { response = await ExecuteRemoteCommandAsync(writer, reader, $"RCPT TO:<{recipient}>"); if (response.Code != SmtpReplyCode.Okay) { _log.Warning("Failed RCPT TO, aborting"); return(false); } } response = await ExecuteRemoteCommandAsync(writer, reader, "DATA"); if (response.Code != SmtpReplyCode.StartMail) { _log.Warning("Failed DATA, aborting"); return(false); } using (var mailReader = new StreamReader(readMail.BodyStream)) { string line = null; while (await mailReader.TryReadLineAsync(l => line = l, token)) { await writer.WriteLineAsync(line); } } await writer.WriteLineAsync("."); response = await ReadResponseAsync(reader); if (response.Code != SmtpReplyCode.Okay) { _log.Warning("Failed RCPT TO, aborting"); return(false); } await _queue.DeleteAsync(mail); return(true); }
public Stream GetTemporaryMailStream(IMailReadReference reference) { if (reference == null) { throw new ArgumentNullException(nameof(reference)); } if (!(reference is ReadReference concrete)) { throw new ArgumentException($"Reference must be returned from {GetType().Name}.", nameof(reference)); } return(File.Create(Path.Combine(GetRootPath("tmp"), $"{concrete.Id}-{DateTime.UtcNow:yyyy-MM-ddTHH-mm-ss-ffff}"), 1024, FileOptions.DeleteOnClose)); }
private async Task DispatchSingleMailReferenceAsync( IMailReadReference readReference, Stream bodyStream, CancellationToken token) { string sender = readReference.Sender; IDictionary <string, IEnumerable <string> > headers = await MailUtilities.ParseHeadersAsync(bodyStream); ISet <string> recipients = AugmentRecipients(sender, readReference.Recipients, headers); if (!recipients.Any()) { _log.Warning($"{readReference.Id} had no recipients"); } (bodyStream, sender) = await ReplaceSenderAsync(headers, bodyStream, sender, token); IWritable[] dispatchReferences = await CreateDispatchesAsync( readReference.Id, recipients, sender, token); if (!dispatchReferences.Any()) { _log.Warning($"Failed to locate any processor for {readReference.Id}"); } using (var targetStream = new MultiStream(dispatchReferences.Select(r => r.BodyStream))) { await bodyStream.CopyToAsync(targetStream, token); } await Task.WhenAll(dispatchReferences.Select(r => r.Store.SaveAsync(r, token))); }
public Stream GetTemporaryMailStream(IMailReadReference reference) { return(new MemoryStream()); }
public async Task <Stream> ScanAsync(IMailReadReference mailReference, Stream stream) { var spamcPath = _settings.IncomingScan?.SpamAssassin?.ClientPath; if (String.IsNullOrEmpty(spamcPath)) { return(stream); } var position = stream.Position; ProcessStartInfo info = new ProcessStartInfo(spamcPath) { CreateNoWindow = true, RedirectStandardError = true, RedirectStandardInput = true, RedirectStandardOutput = true, UseShellExecute = false, }; var proc = new Process { StartInfo = info, EnableRaisingEvents = true, }; TaskCompletionSource <int> exitSource = new TaskCompletionSource <int>(); proc.Exited += (p, e) => exitSource.SetResult(((Process)p).ExitCode); proc.Start(); Stream tempStream = _mailQueue.GetTemporaryMailStream(mailReference); await Task.WhenAll( stream.CopyToAsync(proc.StandardInput.BaseStream), exitSource.Task, proc.StandardOutput.BaseStream.CopyToAsync(tempStream) ); if ((await exitSource.Task) != 0) { // spam assassin returns non-zero, abort and just return the original // we might not have anything to report, so just return the original stream stream.Position = position; return(stream); } // Unfortunately, we need the score from the spam assassin to just the mail // And there is no way to both get the score AND adjust the message body at the same time // So we have to scan for a header we know about... double score = 0; string targetHeader = _settings.IncomingScan.SpamAssassin.ScoreHeader; using (StreamReader reader = new StreamReader(tempStream, Encoding.ASCII, false, 1024, true)) { var line = await reader.ReadLineAsync(); while (!String.IsNullOrEmpty(line)) { if (line.StartsWith(targetHeader)) { if (Double.TryParse(line.AsSpan(0, targetHeader.Length), out double dScore)) { // We found a score-y line, that's the one! score = dScore; _logger.Verbose($"Mail {mailReference.Id} has spam score of {score}"); } else { _logger.Warning($"Mail {mailReference.Id} has invalid spam assassin header: {line}"); } // We found the header, whether it was the score or not, break; } line = await reader.ReadLineAsync(); } } if (score > _settings.IncomingScan.SpamAssassin.DeleteThreshold) { _logger.Information($"Mail {mailReference.Id} has spam score of {score}, which exceeds threshold of {_settings.IncomingScan.SpamAssassin.DeleteThreshold}, discarding."); // This was so high, we are just supposed to delete it without forwarding tempStream.Dispose(); return(null); } tempStream.Seek(0, SeekOrigin.Begin); return(tempStream); }