예제 #1
0
        public void Extract()
        {
            var utcnow = DateTime.UtcNow;

            // Obtain the mailbox token of the recipient
            var mbox = _conn.QuerySingleOrDefault <MailboxDTO>("select Id,Token,Address,ExpiresOn from MailBox where Address = @Recipient", _m);

            if (mbox == null)
            {
                Console.WriteLine(string.Format("Could not find mailbox for {0}", _m.Recipient));
                return;
            }

            // Create mail entry
            var mail = new MailDTO()
            {
                IdMailBox = mbox.Id, ReceivedOn = _m.ReceivedOn, Size = _m.ContentSize
            };

            mail.Id = _conn.QuerySingle <int>("insert into Mail (IdMailBox,ReceivedOn,Size) values (@IdMailBox,@ReceivedOn,@Size) returning Id;", mail);

            // Create mailbox directory structure
            var mailboxDir        = Path.Combine(Configuration.Instance.MailboxDirectory, mbox.Token.ToString());
            var mailDir           = Path.Combine(mailboxDir, mail.Id.ToString(CultureInfo.InvariantCulture));
            var mailInlineDir     = Path.Combine(mailDir, InlineSubDirectory);
            var mailAttachmentDir = Path.Combine(mailDir, AttachmentSubDirectory);

            Directory.CreateDirectory(mailboxDir);
            Directory.CreateDirectory(mailDir);
            Directory.CreateDirectory(mailInlineDir);
            Directory.CreateDirectory(mailAttachmentDir);

            var me           = new MailEntry();
            var mailFilename = _m.GetContentFileName();

            using (var mailStream = new FileStream(mailFilename, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                // Parse MIME message
                var msg = MimeMessage.Load(ParserOptions.Default, mailStream, true);

                me.Id         = mail.Id;
                me.ReceivedOn = _m.ReceivedOn;
                me.Sender     = _m.Sender;
                me.Subject    = msg.Subject;
                me.Size       = _m.ContentSize;

                // Generate HTML preview and save inline images
                var preview = new HtmlPreviewVisitor(mailInlineDir, InlineSubDirectory + '/');
                msg.Accept(preview);

                // Save sanitized mail content
                using (var fs = new FileStream(Path.Combine(mailDir, "mail.html"), FileMode.Create, FileAccess.Write, FileShare.None))
                    using (var sw = new StreamWriter(fs, Encoding.UTF8))
                    {
                        var html = preview.HtmlBody;

                        // Plug js auto sizer at end of <head>, end of <body> or just append it (sometimes, we only get HTML snippet withou body).
                        var js  = "<script src=\"/js/iframeResizer.contentWindow.min.js\" async></script>";
                        int idx = html.IndexOf("</head>", StringComparison.OrdinalIgnoreCase);
                        if (idx == -1)
                        {
                            idx = html.IndexOf("</body>", StringComparison.OrdinalIgnoreCase);
                        }
                        if (idx != -1)
                        {
                            html = html.Insert(idx, js);
                            sw.Write(html);
                        }
                        else
                        {
                            // sometimes all we get is an html snippet
                            sw.Write(js);
                            sw.Write(html);
                        }
                    }

                // Save attachments
                foreach (var attachment in preview.Attachments)
                {
                    // TODO: cleanup filename
                    var dirtyName = attachment.ContentDisposition.FileName;
                    var cleanName = CleanupFilename(dirtyName);
                    me.Attachments.Add(new MailEntry.Attachment()
                    {
                        OriginalFileName = dirtyName, FileName = cleanName
                    });
                    using (var attachmentStream = new FileStream(Path.Combine(mailAttachmentDir, cleanName), FileMode.Create, FileAccess.Write, FileShare.None))
                    {
                        if (attachment is MessagePart)
                        {
                            var part = (MessagePart)attachment;
                            part.Message.WriteTo(attachmentStream);
                        }
                        else
                        {
                            var part = (MimePart)attachment;
                            part.ContentObject.DecodeTo(attachmentStream);
                        }
                    }
                }
            }

            // Load existing mails to insert new mail
            var    mails    = new List <MailEntry>();
            string jsonFile = Path.Combine(mailboxDir, "mails.json");

            if (File.Exists(jsonFile))
            {
                using (var fs = new FileStream(jsonFile, FileMode.Open, FileAccess.Read, FileShare.Read))
                    using (var reader = new StreamReader(fs, Encoding.UTF8))
                    {
                        var content = reader.ReadToEnd();
                        mails = JsonConvert.DeserializeObject <List <MailEntry> >(content);
                    }
            }
            mails.Add(me);

            // mail.json & mail.json.gz
            var json = JsonConvert.SerializeObject(mails);

            using (var fs = new FileStream(jsonFile, FileMode.Create, FileAccess.Write, FileShare.None))
                using (var writer = new StreamWriter(fs, Encoding.UTF8))
                {
                    writer.Write(json);
                }
            Touch(jsonFile, utcnow);

            using (var fs = new FileStream(jsonFile + ".gz", FileMode.Create, FileAccess.Write, FileShare.None))
                using (var gzip = new GZipStream(fs, CompressionLevel.Fastest))
                    using (var writer = new StreamWriter(gzip, Encoding.UTF8))
                    {
                        writer.Write(json);
                    }
            Touch(jsonFile + ".gz", utcnow);

            Gzipify(mailDir, utcnow);
        }
예제 #2
0
        private void HandleCompleteLine(ArraySegment <byte> line)
        {
            var isHandled = false;
            var nextState = _state;

            byte[] response = null;

            ArraySegment <byte> cmd = _emptyBuffer;
            var tokens = new ArraySegment <byte>[] { };

            if (_state != SmtpState.WaitingForEndOfData)
            {
                tokens = Bytes.Split(line, Whitespace);
                if (tokens.Length > 0)
                {
                    Bytes.ToUpper(tokens[0]);
                    cmd = tokens[0];
                }
            }

            if (Bytes.IsSame(RSET, cmd))
            {
                isHandled = true;
                if (tokens.Length == 1)
                {
                    ResetMailInfo();
                    response  = ReplyOk;
                    nextState = SmtpState.WaitingForHelo;
                }
                else
                {
                    response = ReplySyntaxErrorRset;
                }
            }
            else if (_state == SmtpState.WaitingForHelo && Bytes.IsSame(EHLO, cmd))
            {
                isHandled = true;
                if (tokens.Length == 2)
                {
                    response  = ReplyEhlo;
                    nextState = SmtpState.WaitingForMailFrom;
                }
                else
                {
                    response = ReplySyntaxErrorEhlo;
                }
            }
            else if (_state == SmtpState.WaitingForHelo && Bytes.IsSame(HELO, cmd))
            {
                isHandled = true;
                if (tokens.Length == 2)
                {
                    response  = ReplyHelo;
                    nextState = SmtpState.WaitingForMailFrom;
                }
                else
                {
                    response = ReplySyntaxErrorHelo;
                }
            }
            else if (_state == SmtpState.WaitingForMailFrom && Bytes.IsSame(MAIL, cmd))
            {
                isHandled = true;

                // extract mail address between < > after :
                bool go = false;
                int  lt = -1, gt = -1;
                for (var i = line.Offset; i < line.Offset + line.Count; i++)
                {
                    byte ch = line.Array[i];
                    if (go)
                    {
                        if (lt == -1 && ch == LessThan)
                        {
                            lt = i;
                        }
                        if (lt != -1 && ch == GreaterThan)
                        {
                            gt = i;
                            break;
                        }
                    }
                    else if (ch == SemiColon)
                    {
                        go = true;
                    }
                }

                if (lt != -1 && gt != -1)
                {
                    // sender found,
                    _sender = Encoding.ASCII.GetString(line.Array, lt + 1, gt - lt - 1).Trim();

                    // check for optional SIZE=nnnnnnn extension after the sender
                    int sizeOffset = Bytes.OffsetOf(MessageSizeExtension, line.Array, gt + 1, line.Count - gt - 1);
                    if (sizeOffset != -1)
                    {
                        int valueBeginOffset = sizeOffset + MessageSizeExtension.Length;
                        int valueEndOffset   = -1;
                        for (var i = valueBeginOffset; i < line.Offset + line.Count; i++)
                        {
                            byte ch    = line.Array[i];
                            byte digit = Bytes.Digits[ch];
                            if (Whitespace.Contains(ch))
                            {
                                break;
                            }
                            else if (digit > 9 || digit < 0)
                            {
                                valueEndOffset = -1;
                                break;
                            }
                            valueEndOffset = i;
                        }

                        if (valueEndOffset >= valueBeginOffset)
                        {
                            int size = 0, scale = 1;
                            for (var i = valueEndOffset; i >= valueBeginOffset && size < MaximumMessageSize; i--, scale *= 10)
                            {
                                size += Bytes.Digits[line.Array[i]] * scale;
                            }

                            if (size > MaximumMessageSize)
                            {
                                response  = ReplyMessageSizeTooBig;
                                nextState = SmtpState.WaitingForRset;
                            }
                            else
                            {
                                _mailContentExpectedSize = size;
                                response  = ReplyOk;
                                nextState = SmtpState.WaitingForRcptTo;
                            }
                        }
                        else
                        {
                            response = ReplySyntaxErrorMail;
                        }
                    }
                    else
                    {
                        // no size extension
                        response  = ReplyOk;
                        nextState = SmtpState.WaitingForRcptTo;
                    }
                }
                else
                {
                    response = ReplySyntaxErrorMail;
                }
            }
            else if ((_state == SmtpState.WaitingForRcptTo || _state == SmtpState.WaitingForAdditionalRcptTo) && Bytes.IsSame(RCPT, cmd))
            {
                // standard deviation: send only to the last given recipient even if not pretending so.
                isHandled = true;

                // extract mail address between < > after :
                bool go = false;
                int  lt = -1, gt = -1;
                for (var i = line.Offset; i < line.Offset + line.Count; i++)
                {
                    byte ch = line.Array[i];
                    if (go)
                    {
                        if (lt == -1 && ch == LessThan)
                        {
                            lt = i;
                        }
                        if (lt != -1 && ch == GreaterThan)
                        {
                            gt = i;
                            break;
                        }
                    }
                    else if (ch == SemiColon)
                    {
                        go = true;
                    }
                }

                if (lt != -1 && gt != -1)
                {
                    var recipient = Encoding.ASCII.GetString(line.Array, lt + 1, gt - lt - 1).Trim().ToLowerInvariant();
                    if (_server.MailDispatcher.IsMailboxActive(recipient))
                    {
                        _lastRecipient = recipient;
                        response       = ReplyOk;
                        nextState      = SmtpState.WaitingForAdditionalRcptTo;
                    }
                    else
                    {
                        response = ReplyNoSuchUserHere;
                    }
                }
                else
                {
                    response = ReplySyntaxErrorRcpt;
                }
            }
            else if (_state == SmtpState.WaitingForAdditionalRcptTo && Bytes.IsSame(DATA, cmd))
            {
                isHandled = true;
                if (tokens.Length == 1)
                {
                    response = ReplyStartMail;
                    if (_mailContent == null)
                    {
                        // presize stream to avoid garbage & extra reallocation if expected size makes sense.
                        // add a bit of slack for size errors
                        var size = (_mailContentExpectedSize > 0 && _mailContentExpectedSize <= MaximumMessageSize) ? _mailContentExpectedSize + 1024 : 4096;
                        _mailContentId = Guid.NewGuid();
                        _mailContent   = new FileStream(IncomingMailDTO.GetContentFileName(_mailContentId), FileMode.CreateNew, FileAccess.Write, FileShare.None, 16384);
                    }
                    nextState = SmtpState.WaitingForEndOfData;
                }
                else
                {
                    response = ReplySyntaxErrorData;
                }
            }
            else if (_state == SmtpState.WaitingForEndOfData)
            {
                isHandled = true;
                if (line.Count == EndOfMail.Length && Bytes.StartsWith(EndOfMail, line))
                {
                    if (_mailContent.Length <= MaximumMessageSize)
                    {
                        response  = ReplyOk;
                        nextState = SmtpState.WaitingForMailFrom;
                        _mailContent.Flush();
                        _server.MailDispatcher.Enqueue(new IncomingMailDTO(DateTime.UtcNow, _lastRecipient, _sender, (int)_mailContent.Length, _mailContentId));
                        _mailContent.Dispose();
                        _mailContent = null;
                    }
                    else
                    {
                        response  = ReplyMessageSizeTooBig;
                        nextState = SmtpState.WaitingForRset;
                    }
                    ResetMailInfo();
                }
                else
                {
                    // accumulate mail body & handle dot unstuffing (while msg size doesn't exceed maximum size, since it will be discarded later anyway).
                    int offset = line.Offset;
                    int count  = line.Count;
                    if (line.Array[offset] == Dot)
                    {
                        offset++;
                        count--;
                    }

                    if (_mailContent != null && _mailContent.Length + count <= MaximumMessageSize)
                    {
                        _mailContent.Write(line.Array, offset, count);
                    }
                }
            }
            else if (Bytes.IsSame(QUIT, cmd))
            {
                isHandled = true;
                if (tokens.Length == 1)
                {
                    ResetMailInfo();
                    response  = ReplyBye;
                    nextState = SmtpState.Disconnect;
                }
                else
                {
                    response = ReplySyntaxErrorQuit;
                }
            }
            else if (Bytes.IsSame(VRFY, cmd) || Bytes.IsSame(EXPN, cmd))
            {
                isHandled = true;
                response  = ReplyCommandNotImplemented;
            }
            else if (Bytes.IsSame(HELP, cmd))
            {
                isHandled = true;
                response  = ReplyHelp;
            }
            else if (Bytes.IsSame(NOOP, cmd))
            {
                isHandled = true;
                response  = ReplyOk;
            }

            if (!isHandled)
            {
                var isKnownCommand = KnownCommands.Any((knownCmd) => Bytes.IsSame(knownCmd, cmd));
                response = isKnownCommand ? ReplyBadSequence : ReplyUnknownCommand;
            }

            _state = nextState;
            if (response != null && response.Length > 0)
            {
                var e = CreateArgs(SendCompleted, response);
                if (!_socket.SendAsync(e))
                {
                    SendCompleted(_socket, e);
                }
            }
        }