private void HandleMessageEvent(Object mailItem)
        {
            try
            {
                SignMailItem((MailItem)mailItem);
            }
            catch (Exception ex)
            {
                Logger.LogError("Signing a mail item according to DKIM failed with an exception. Check the logged exception for details.\n" + ex);
            }
            finally
            {
#if !EX_2007_SP3 //not supported in Exchange 2007
                // This allows Transport poison detection to correclty handle this message
                // if there is a crash on this thread.
                agentAsyncContext.Resume();
#endif
                agentAsyncContext.Complete();
                agentAsyncContext = null;
            }
        }
        private void OnOnSubmittedMessage(SubmittedMessageEventSource source, QueuedMessageEventArgs queuedMessageEventArgs)
        {
            lock (_fileLock)
            {
                AgentAsyncContext agentAsyncContext = null;
                try
                {
                    var mailItem = queuedMessageEventArgs.MailItem;
                    agentAsyncContext = GetAgentAsyncContext();

                    // check the sender whitelist
                    if (_exchangeAttachmentFilterConfig.SendersWhitelist.Any(
                            f => Regex.IsMatch(mailItem.FromAddress.ToString(), WildcardToRegex(f))))
                    {
                        return;
                    }

                    // maybe we will need list of recipients on single line...
                    var recipientList = new StringBuilder();
                    for (var i = 0; i < mailItem.Recipients.Count; i++)
                    {
                        recipientList.Append(i == 0 ? mailItem.Recipients[i].Address.ToString() : "; " + mailItem.Recipients[i].Address);
                    }


                    var removedAttachments  = new List <Attachment>();
                    var strippedAttachments = new List <Attachment>();
                    var messageRejected     = false;

                    var messageLogStringBuilder = new SysLogBuilder();

                    var mailItemStatusText =
                        $"[from: {mailItem.FromAddress}] [to: {recipientList}] [method: {mailItem.InboundDeliveryMethod}] [subject: {mailItem.Message.Subject}] [size: {mailItem.MimeStreamLength.ToString("N0")}]";
                    messageLogStringBuilder.Log(mailItemStatusText);

                    if (mailItem.Message.Attachments.Count == 0 && _exchangeAttachmentFilterConfig.LogAccepted)
                    {
                        messageLogStringBuilder.LogPadded(
                            "ACCEPTED: [reason: no attachments]");
                    }
                    else
                    {
                        foreach (var attachment in mailItem.Message.Attachments)
                        {
                            // It would be great idea to process only attachments with size greater
                            // than some threshold, 'cos infected files are always quite small (only few kB)
                            // But I am not sure how to get the attachment size here, ...

                            // if (any previous) attachment rejected the message then break the processing now
                            if (messageRejected)
                            {
                                break;
                            }

                            AttachmentFilterStatus attachmentStatus = null;

                            if (_exchangeAttachmentFilterConfig.DsnStripOriginalMessage)
                            {
                                // DSN has InboundDeliveryMethod equal to DeliveryMethod.File and FromAddress is equal to <>
                                // and DSN's original message is included as message/rfc822 attachment
                                if (mailItem.InboundDeliveryMethod == DeliveryMethod.File &&
                                    mailItem.FromAddress.ToString() == "<>" &&
                                    attachment.ContentType.ToLower().Equals("message/rfc822"))
                                {
                                    attachmentStatus =
                                        new AttachmentFilterStatus(AttachmentFilterStatusEnum.StripAttachment,
                                                                   "DSN original message");
                                }
                            }

                            if (attachmentStatus == null)
                            {
                                // default file status (by extension)
                                attachmentStatus = FilenameFilterStatus(attachment.FileName);

                                // is it archive?
                                if (_exchangeAttachmentFilterConfig.ScanArchives && IsArchive(attachment.FileName))
                                {
                                    var archiveStatus = ProcessArchiveStream(attachment.GetContentReadStream());
                                    if (archiveStatus.Status > attachmentStatus.Status)
                                    {
                                        attachmentStatus = archiveStatus;
                                    }
                                }

                                // is it OpenXml document?
                                if (_exchangeAttachmentFilterConfig.ScanOpenXmlDocuments &&
                                    IsOpenXmlDocument(attachment.FileName))
                                {
                                    var openXmlDocumentStatus =
                                        ProcessOpenXmlDocumentStream(attachment.GetContentReadStream());
                                    if (openXmlDocumentStatus.Status > attachmentStatus.Status)
                                    {
                                        attachmentStatus = openXmlDocumentStatus;
                                    }
                                }
                            }

                            var attachmentStatusText =
                                $"[file: {attachment.FileName}] [type: {attachment.AttachmentType}] [content type:{attachment.ContentType}] [reason: {attachmentStatus.Reason}]";

                            switch (attachmentStatus.Status)
                            {
                            case AttachmentFilterStatusEnum.Accept:
                                if (_exchangeAttachmentFilterConfig.LogAccepted)
                                {
                                    messageLogStringBuilder.LogPadded($"ACCEPTED: {attachmentStatusText}");
                                }
                                break;

                            case AttachmentFilterStatusEnum.RemoveAttachment:
                                // just mark this attachment for removement, but do not touch attachments collection now
                                // (we are in foreach loop and need to process them all)
                                removedAttachments.Add(attachment);
                                if (_exchangeAttachmentFilterConfig.LogRejectedOrRemoved)
                                {
                                    messageLogStringBuilder.LogPadded($"REMOVED: {attachmentStatusText}");
                                }
                                break;

                            case AttachmentFilterStatusEnum.StripAttachment:
                                // just mark this attachment for removement, but do not touch attachments collection now
                                // (we are in foreach loop and need to process them all)
                                strippedAttachments.Add(attachment);
                                if (_exchangeAttachmentFilterConfig.LogRejectedOrRemoved)
                                {
                                    messageLogStringBuilder.LogPadded($"STRIPPED: {attachmentStatusText}");
                                }
                                break;

                            case AttachmentFilterStatusEnum.RejectMessage:
                                // reject whole message
                                if (_exchangeAttachmentFilterConfig.LogRejectedOrRemoved)
                                {
                                    messageLogStringBuilder.LogPadded($"REJECTED: {attachmentStatusText}");
                                }
                                messageRejected = true;
                                break;
                            }
                        }
                    }

                    if (messageLogStringBuilder.MessageCount > 1)
                    {
                        SysLog.Log(messageLogStringBuilder);
                    }

                    // reject the message?
                    if (messageRejected)
                    {
                        // delete the source message and do nothing more (we do not send DSN)...
                        source.Delete();
                        return;
                    }

                    // for every attachment we marked as being removed create new txt attachment with some info why it happened...
                    foreach (var removedAttachment in removedAttachments)
                    {
                        // new attachment filename
                        var newFileName =
                            $"{_exchangeAttachmentFilterConfig.RemovedAttachmentPrefix}{removedAttachment.FileName}.txt";
                        // add new attachment to the message...
                        var newAttachment = mailItem.Message.Attachments.Add(newFileName);
                        // ...and write content into it (info message)
                        var newAttachmentWriter = new StreamWriter(newAttachment.GetContentWriteStream());
                        newAttachmentWriter.WriteLine(removedAttachment.FileName);
                        newAttachmentWriter.WriteLine();
                        newAttachmentWriter.WriteLine(_exchangeAttachmentFilterConfig.RemovedAttachmentNewContent);
                        newAttachmentWriter.Flush();
                        newAttachmentWriter.Close();
                    }

                    // finally remove all attachments marked for removal
                    foreach (var removedAttachment in removedAttachments)
                    {
                        mailItem.Message.Attachments.Remove(removedAttachment);
                    }

                    // ...and stripped attachments, too
                    foreach (var strippedAttachment in strippedAttachments)
                    {
                        mailItem.Message.Attachments.Remove(strippedAttachment);
                    }
                }
                catch (IOException ex)
                {
                    SysLog.Log("IOException: " + ex.Message);
                }
                catch (Exception ex)
                {
                    SysLog.Log("Exception: " + ex.Message);
                }
                finally
                {
                    agentAsyncContext?.Complete();
                }
            }
        }
        void RvScopiaMeeting_OnResolvedMessage(ResolvedMessageEventSource source, QueuedMessageEventArgs args){
            this.emailMessage = args.MailItem.Message;

            if (this.emailMessage == null || this.emailMessage.TnefPart == null) {
                return;
            }

            long now = DateTime.UtcNow.Ticks;
            SchedulingInfo schedulingInfo = new SchedulingInfo();
            schedulingInfo.subject = args.MailItem.Message.Subject;
            schedulingInfo.delegatorEmailAddr = args.MailItem.Message.From.NativeAddress;
            RvLogger.DebugWrite("Enter transport agent, from: " + schedulingInfo.delegatorEmailAddr + ", subject: " + schedulingInfo.subject);

            try
            {
                this.agentAsyncContext = this.GetAgentAsyncContext();
                schedulingInfo.requestType = this.getRequestType(this.emailMessage.MapiMessageClass);
                //Reject all meeting type except request and cancel.
                if (RequestType.Other == schedulingInfo.requestType)
                {
                    RvLogger.DebugWrite("Reject other request type: " + this.emailMessage.MapiMessageClass);
                    return;
                }
                RvMailParser parser = new RvMailParser(this);
                try
                {
                    parser.parseTnefSimple(args.MailItem, schedulingInfo);
                }
                catch (Exception exceptionParseMail)
                {
                    RvLogger.DebugWrite("Fail to parse mail.");
                    RvLogger.DebugWrite(exceptionParseMail.Message);
                    RvLogger.DebugWrite(exceptionParseMail.StackTrace);
                    return;
                }
                
                //Reject forwarded appointment
                if (!string.IsNullOrEmpty(schedulingInfo.subjectPrefix) && "FW:".Equals(schedulingInfo.subjectPrefix))
                {
                    RvLogger.DebugWrite("Reject forward request type");
                    return;
                }

                if (schedulingInfo.recurrencePattern != null){
                    schedulingInfo.recurrenceHashInfo = RvScopiaMeeting.getHashString4Str(schedulingInfo.recurrencePattern.getStringForHash());
                    schedulingInfo.recurrencePattern.startDate = schedulingInfo.startDate;
                    schedulingInfo.recurrencePattern.endDate = schedulingInfo.endDate;
                }

                if (null == schedulingInfo.emailMessage) {
                    RvLogger.DebugWrite("null == schedulingInfo.emailMessage================================================");
                    return; 
                }

                if (!isScopia(schedulingInfo))
                {
                    RvLogger.DebugWrite("This is not a SCOPIA meeting");
                    return;
                }

                parseRecipentsChanged(schedulingInfo);
                if (schedulingInfo.isRecipentsChanged)
                    if (schedulingInfo.requestType == RequestType.CancelMeeting)
                    {
                        schedulingInfo.requestType = RequestType.CreateMeeting;
                        schedulingInfo.isAddRecipents = false;
                        schedulingInfo.subject = schedulingInfo.subject.Substring(schedulingInfo.subjectPrefix.Length + 1);
                        Thread.Sleep(HOW_MANY_SECONDS_WAIT_FOR_FOLLOWING_REQUEST * 1000);
                    }else
                        schedulingInfo.isAddRecipents = true;

                if (RvScopiaMeeting.SERVER_ACCOUNT.Equals(schedulingInfo.senderEmailAddr))
                {
                    RvLogger.DebugWrite("Send a email back to notify the sender this mail is failed to send out.");
                    return;
                }

                //when modify a recurrence, to make sure the modified ocurrence request is later than the recurrence request.
                if (schedulingInfo.meetingType == MeetingType.Ocurrence)
                    Thread.Sleep(HOW_MANY_SECONDS_WAIT_FOR_FOLLOWING_REQUEST * 1000);

                icm.XmlApi.scheduleReportType result = changeMail(source, args, schedulingInfo, now);
                if (null != result && result.Success && isCreateMeetingRequest(schedulingInfo))
                {
                    Dictionary<string, byte[]> attachmentsdata = null;
                    if (this.emailMessage.Attachments.Count > 0)
                    {
                        attachmentsdata = new Dictionary<string, byte[]>(this.emailMessage.Attachments.Count);
                        for (int i = 0; i < this.emailMessage.Attachments.Count; i++)
                        {
                            Attachment attachment = this.emailMessage.Attachments[i];
                            Stream readStream = attachment.GetContentReadStream();
                            byte[] bytes = null;
                            if (readStream.Length > 0) {
                                bytes = new byte[readStream.Length];
                                readStream.Read(bytes, 0, bytes.Length);
                            } else
                                bytes = Encoding.ASCII.GetBytes(" ");
                            attachmentsdata.Add(attachment.FileName, bytes);
                        }
                    }
                    
                    parser.changeBodyOfTnef(args.MailItem.Message.TnefPart, schedulingInfo);

                    if (attachmentsdata != null)
                    {
                        foreach (KeyValuePair<string, byte[]> attachmentdata in attachmentsdata)
                        {
                            Attachment attachment = this.emailMessage.Attachments.Add(attachmentdata.Key);
                            Stream attachmentStream = attachment.GetContentWriteStream();
                            attachmentStream.Write(attachmentdata.Value, 0, attachmentdata.Value.Length);
                            attachmentStream.Flush();
                            attachmentStream.Close();
                        }
                    }
                }

            }
            catch (Exception ex)
            {
                RvLogger.DebugWrite(ex.Message);
                RvLogger.DebugWrite(ex.StackTrace);
                string baseFailCode = ex.Message;
                sendBackAMail(source, schedulingInfo, baseFailCode);
            }
            finally
            {
                RvLogger.DebugWrite("Start to agentAsyncContext.Complete()================================================");
                agentAsyncContext.Complete();
                RvLogger.DebugWrite("Complete agentAsyncContext.Complete()================================================");
                RvLogger.DebugWrite("Leave transport agent, from: " + schedulingInfo.delegatorEmailAddr + ", subject: " + schedulingInfo.subject);
            }
        }