Beispiel #1
0
        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);
        }
Beispiel #2
0
        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));
        }
Beispiel #3
0
        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)));
        }
Beispiel #4
0
 public Stream GetTemporaryMailStream(IMailReadReference reference)
 {
     return(new MemoryStream());
 }
Beispiel #5
0
        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);
        }