MsgProcessor.cs
author Thomas
Tue, 30 Jan 2018 09:59:14 +0100
changeset 1979 cf610012e16d
parent 1956 962f4c86b9af
child 1988 44840dd6a15d
permissions -rw-r--r--
Add sensitive data logging
     1 using pEpCOMServerAdapterLib;
     2 using System;
     3 using System.Collections.Generic;
     4 using System.ComponentModel;
     5 using System.Runtime.InteropServices;
     6 
     7 namespace pEp
     8 {
     9     /// <summary>
    10     /// Class containing processing methods for PEPMessages.
    11     /// </summary>
    12     internal class MsgProcessor : IDisposable
    13     {
    14         public delegate void ProcessingCompletedHandler(object sender, ProcessingCompletedEventArgs e);
    15 
    16         /// <summary>
    17         /// Event when processing is completed.
    18         /// WARNING: The calling thread is a background worker.
    19         /// </summary>
    20         public event ProcessingCompletedHandler ProcessingCompleted;
    21 
    22         private BackgroundWorker backgroundProcessor;
    23 
    24         /**************************************************************
    25          * 
    26          * Constructors/Destructors
    27          * 
    28          *************************************************************/
    29 
    30         /// <summary>
    31         /// Default constructor.
    32         /// </summary>
    33         public MsgProcessor()
    34         {
    35             // Setup the message processor background worker
    36             backgroundProcessor = new BackgroundWorker();
    37             backgroundProcessor.WorkerSupportsCancellation = false;
    38             backgroundProcessor.RunWorkerCompleted += BackgroundProcessor_RunWorkerCompleted;
    39             backgroundProcessor.DoWork += BackgroundProcessor_DoWork;
    40         }
    41 
    42         /// <summary>
    43         /// Implementing IDisposable
    44         /// </summary>
    45         public void Dispose()
    46         {
    47             backgroundProcessor.Dispose();
    48         }
    49 
    50         /**************************************************************
    51          * 
    52          * Event Handling
    53          * 
    54          *************************************************************/
    55 
    56         // not const because a TimeSpan cannot be const, but meant to be read only.
    57         private static TimeSpan SECOND = new TimeSpan(0, 0, 1);
    58 
    59         /// <summary>
    60         /// Event handler for when the processor background worker is doing work.
    61         /// </summary>
    62         private void BackgroundProcessor_DoWork(object sender, DoWorkEventArgs e)
    63         {
    64             PEPMessage processedMessage;
    65             PEPMessage mirror;
    66             pEpRating processedRating;
    67             pEpDecryptFlags decryptionFlags;
    68             MsgContainer msgCont;
    69             Globals.ReturnStatus sts;
    70             ProcessingCompletedEventArgs args = null;
    71 
    72             if (e.Argument is MsgContainer)
    73             {
    74                 msgCont = e.Argument as MsgContainer;
    75 
    76                 sts = this.ProcessMessage(msgCont.Message,
    77                                           msgCont.MessageCreationStatus,
    78                                           msgCont.IsInSecureStore,
    79                                           msgCont.IsInSentFolder,
    80                                           msgCont.IsDraft,
    81                                           out mirror,
    82                                           out processedMessage,
    83                                           out processedRating,
    84                                           out decryptionFlags);
    85 
    86                 args = new ProcessingCompletedEventArgs();
    87                 args.IsMirror = (mirror != null);
    88                 args.ProcessedDecryptionFlags = decryptionFlags;
    89                 args.ProcessedMessage = processedMessage;
    90                 args.ProcessedRating = processedRating;
    91                 args.ProcessedStatus = sts;
    92             }
    93 
    94             e.Result = args;
    95             return;
    96         }
    97 
    98         /// <summary>
    99         /// Event handler for when the processor background worker is completed.
   100         /// </summary>
   101         private void BackgroundProcessor_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
   102         {
   103             Log.Verbose("MsgProcessor.BackgroundProcessor: Completed");
   104             this.ProcessingCompleted?.Invoke(this, (e.Result as ProcessingCompletedEventArgs));
   105             return;
   106         }
   107 
   108         /**************************************************************
   109          * 
   110          * Methods
   111          * 
   112          *************************************************************/
   113 
   114         /// <summary>
   115         /// Starts the processing of the mail item which can result in decryption or encryption of the message.
   116         /// This will also return the latest message rating.
   117         /// </summary>
   118         /// <param name="msgCont">The message container to process with.</param>
   119         /// <returns>True if the processing started successfully, otherwise false if it is already busy.</returns>
   120         public bool StartProcessing(MsgContainer msgCont)
   121         {
   122             if (this.backgroundProcessor.IsBusy == false)
   123             {
   124                 Log.Verbose("MsgProcessor.BackgroundProcessor: Started");
   125                 this.backgroundProcessor.RunWorkerAsync(msgCont);
   126                 return (true);
   127             }
   128 
   129             return (false);
   130         }
   131 
   132         /// <summary>
   133         /// Processes the given message including any decryption where necessary.
   134         /// This will then return the latest pEp rating for the message.
   135         /// If rating is ever undefined: it can be assumed an error occured.
   136         /// </summary>
   137         /// 
   138         /// The main purpose of this method is to do the following (not in order)
   139         ///  • Decrypt messages when necessary
   140         ///  • Create mirrors when necessary
   141         ///  • Get the current color rating for the item (either stored or just calculated)
   142         ///  
   143         /// This uses the following logic:
   144         /// 
   145         /// -- Outoing message
   146         ///         |-- Draft
   147         ///         |     |-- Secure message            => Decrypt, use outgoing rating
   148         ///         |     |-- Unsecure message          => No decryption needed, use outgoing rating
   149         ///         |-- No Draft                        => Use Sent folder logic
   150         ///         
   151         /// -- Incoming or sent message
   152         ///         |-- Secure message
   153         ///         |     |-- Secure store    
   154         ///         |     |      |-- Mirror exists      => No decryption needed, use mirror 
   155         ///         |     |      |-- No mirror          => Decrypt and create mirror  
   156         ///         |     |-- Unsecure store            => Decrypt
   157         ///         |-- Unsecure message
   158         ///         |     |-- Secure store              => Unencrypted message
   159         ///         |     |-- Unsecure store
   160         ///         |     |      |-- Sent folder        => No decryption needed 
   161         ///         |     |      |-- Not in Sent folder => Decrypt for message flags
   162         /// 
   163         /// <param name="message">The original message to process.</param>
   164         /// <param name="messageCreationStatus">The return status after creating the message.</param>
   165         /// <param name="mirror">The existing mirror for the original message (if any).</param>
   166         /// <param name="isInSecureStore">Whether the message being processed is in a secure (untrusted) store.</param>
   167         /// <param name="isInSentFolder">Whether the message being processed in in a sent folder.</param>
   168         /// <param name="isDraft">Whether the message being processed is a draft.</param>
   169         /// <param name="outIsMirror">Whether the output data should be applied to a mirror (true) or the 
   170         /// original message (false).</param>
   171         /// <param name="outProcessedMessage">The processed message. This will contain any updated data for either 
   172         /// the original message or the mirror. Use the 'outIsMirror' pararameter to determine which.</param>
   173         /// <param name="outProcessedRating">The processed rating of the message.</param>
   174         /// <param name="outDecryptionFlags">The output flags after decrypting the message (if it was decrypted).</param>
   175         /// <returns>The status of the method.</returns>
   176         public Globals.ReturnStatus ProcessMessage(PEPMessage message,
   177                                                    Globals.ReturnStatus messageCreationStatus,
   178                                                    bool isInSecureStore,
   179                                                    bool isInSentFolder,
   180                                                    bool isDraft,
   181                                                    out PEPMessage outMirror,
   182                                                    out PEPMessage outProcessedMessage,
   183                                                    out pEpRating outProcessedRating,
   184                                                    out pEpDecryptFlags outDecryptionFlags)
   185         {
   186             bool success = true;
   187             bool sentItem = false;
   188             string[] decryptionKeyList;
   189             pEpDecryptFlags decryptionFlags = pEpDecryptFlags.pEpDecryptFlagsNone;
   190             PEPMessage processedMessage = null;
   191             PEPMessage mirror = null;
   192             pEpRating processedRating = pEpRating.pEpRatingUndefined;
   193             pEpRating decryptionRating;
   194             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
   195 
   196             if (message == null)
   197             {
   198                 status = Globals.ReturnStatus.Failure;
   199                 Log.Verbose("ProcessMessage: Can't process null message");
   200             }
   201             else
   202             {
   203                 if (messageCreationStatus == Globals.ReturnStatus.Success)
   204                 {
   205                     // Outgoing messages
   206                     if (message.Direction == pEpMsgDirection.pEpDirOutgoing)
   207                     {
   208                         Log.Verbose("ProcessMessage: Outgoing message");
   209 
   210                         // Draft
   211                         if (isDraft)
   212                         {
   213                             Log.Verbose("ProcessMessage: Is Draft");
   214 
   215                             // Apply outgoing rating to outgoing messages
   216                             processedRating = this.GetOutgoingRating(message);
   217 
   218                             if (message.IsSecure)
   219                             {
   220                                 Log.Verbose("ProcessMessage: Secure message");
   221 
   222                                 // Decrypt message
   223                                 success = this.Decrypt(message,
   224                                                        out processedMessage,
   225                                                        out decryptionKeyList,
   226                                                        out decryptionFlags,
   227                                                        out decryptionRating);
   228 
   229                                 if (success == false)
   230                                 {
   231                                     status = Globals.ReturnStatus.Failure;
   232                                 }
   233                             }
   234                         }
   235                         else
   236                         {
   237                             // Sent item that is no longer in the Sent folder. Apply full Sent folder logic.
   238                             sentItem = true;
   239                             Log.Verbose("ProcessMessage: Sent message");
   240                         }
   241                     }
   242 
   243                     // Incoming messages and sent messages
   244                     if ((message.Direction == pEpMsgDirection.pEpDirIncoming) ||
   245                         (isInSentFolder) ||
   246                         (sentItem))
   247                     {
   248                         Log.Verbose("ProcessMessage: Incoming or sent message");
   249 
   250                         // Secure incoming or sent message
   251                         if (message.IsSecure)
   252                         {
   253                             Log.Verbose("ProcessMessage: Secure message");
   254 
   255                             // Secure incoming or sent message in secure store or never unprotected
   256                             if (isInSecureStore || message.NeverUnsecure)
   257                             {
   258                                 Log.Verbose("ProcessMessage: In secure store or never unprotected");
   259 
   260                                 // Decrypt message
   261                                 success = this.Decrypt(message,
   262                                                        out processedMessage,
   263                                                        out decryptionKeyList,
   264                                                        out decryptionFlags,
   265                                                        out decryptionRating);
   266 
   267                                 if (success)
   268                                 {
   269                                     Log.Verbose("ProcessMessage: Message successfully decrypted");
   270                                     mirror = processedMessage.Copy();
   271                                     processedRating = decryptionRating;
   272                                 }
   273                                 else
   274                                 {
   275                                     Log.Verbose("ProcessMessage: Could not decrypt message");
   276                                     status = Globals.ReturnStatus.Failure;
   277                                     processedRating = pEpRating.pEpRatingCannotDecrypt;
   278                                 }
   279                             }
   280                             // Secure incoming or sent message in unsecure (trusted) store
   281                             else
   282                             {
   283                                 Log.Verbose("ProcessMessage: Unsecure (trusted) store");
   284 
   285                                 // Decrypt message
   286                                 success = this.Decrypt(message,
   287                                                        out processedMessage,
   288                                                        out decryptionKeyList,
   289                                                        out decryptionFlags,
   290                                                        out decryptionRating);
   291 
   292                                 if (success)
   293                                 {
   294                                     Log.Verbose("ProcessMessage: Message successfully decrypted");
   295                                     processedRating = decryptionRating;
   296                                 }
   297                                 else
   298                                 {
   299                                     Log.Verbose("ProcessMessage: Could not decrypt message");
   300                                     status = Globals.ReturnStatus.Failure;
   301                                     processedRating = pEpRating.pEpRatingCannotDecrypt;
   302                                 }
   303                             }
   304                         }
   305                         // Unsecure message
   306                         else
   307                         {
   308                             // If no rating is stored, the message was unencrypted
   309                             if (message.Rating == pEpRating.pEpRatingUndefined)
   310                             {
   311                                 processedRating = pEpRating.pEpRatingUnencrypted;
   312                             }
   313                             else
   314                             {
   315                                 processedRating = message.Rating;
   316                             }
   317 
   318                             // Unsecure incoming message (not in Sent folder or sent item)
   319                             if ((isInSentFolder == false) &&
   320                                 (sentItem == false))
   321                             {
   322                                 /* Decrypt message. This is done to check if it's a beacon
   323                                  * message and to remove public keys attached to the message.
   324                                  * The processed message itself can be ignored.
   325                                 */
   326                                 PEPMessage outMessage;
   327                                 this.Decrypt(message,
   328                                              out outMessage,
   329                                              out decryptionKeyList,
   330                                              out decryptionFlags);
   331                             }
   332 
   333                             /* Check all unsecure messages for attached keys and remove them.
   334                              * Although this is also done in the engine during decryption,
   335                              * it seems safer to only apply the round-trip Outlook mail item
   336                              * => PEPMessage => Outlook mail item if really needed.
   337                              */
   338                             bool keyFound = false;
   339 
   340                             for (int i = 0; i < message.Attachments?.Count; i++)
   341                             {
   342                                 if (message.Attachments[i].IsKey)
   343                                 {
   344                                     message.Attachments.Remove(message.Attachments[i]);
   345                                     keyFound = true;
   346                                     i--;
   347                                 }
   348                             }
   349 
   350                             // If a key was found and detached, return updated message
   351                             if (keyFound)
   352                             {
   353                                 processedMessage = message;
   354                             }
   355                         }
   356                     }
   357                 }
   358                 else if (messageCreationStatus == Globals.ReturnStatus.FailureNoConnection)
   359                 {
   360                     status = Globals.ReturnStatus.FailureNoConnection;
   361 
   362                     // Try to use the last stored rating, do no other processing
   363                     if ((isInSecureStore || message.NeverUnsecure) &&
   364                         (message.IsSecure) &&
   365                         (mirror != null))
   366                     {
   367                         processedRating = mirror.Rating; // Undefined if not already existing
   368                     }
   369                     else
   370                     {
   371                         processedRating = message.Rating; // Undefined if not already existing
   372                     }
   373                 }
   374                 else
   375                 {
   376                     status = Globals.ReturnStatus.Failure;
   377                     processedRating = pEpRating.pEpRatingCannotDecrypt;
   378                 }
   379             }
   380 
   381             // Set outputs
   382             outMirror = mirror;
   383             outProcessedMessage = processedMessage;
   384             outProcessedRating = processedRating;
   385             outDecryptionFlags = decryptionFlags;
   386 
   387             return status;
   388         }
   389 
   390         /// <summary>
   391         /// Processes the given PEP message mail item before sending.
   392         /// This will handle things like distribution lists, encryption, Bcc, etc.
   393         /// </summary>
   394         /// <param name="message">The original PEP message being sent.</param>
   395         /// <param name="isInSecureStore">Whether the PEP message being sent is in a secure (untrusted) store.</param>
   396         /// <param name="outMessages">The list of processed PEP messages.</param>
   397         /// <param name="outSavedSentMessage">The PEP message that should be saved in the sent folder.</param>
   398         /// <param name="outSavedSentMessageStoredRating">The pEp rating to be stored in the saved sent message.
   399         /// This is based on the original OutgoingRating. WARNING: Be careful about using this value externally on untrusted servers, 
   400         /// it should be saved to the mirror ONLY.</param>
   401         /// <returns>The status of the method.</returns>
   402         public Globals.ReturnStatus ProcessSentMessage(PEPMessage message,
   403                                                        bool isInSecureStore,
   404                                                        string[] extraKeys,
   405                                                        out PEPMessage[] outMessages,
   406                                                        out PEPMessage outSavedSentMessage,
   407                                                        out pEpRating outSavedSentMessageStoredRating)
   408         {
   409             PEPMessage[] encrypted;
   410             PEPMessage[] unencrypted;
   411             PEPMessage savedSentMessage;
   412             pEpRating savedSentMessageStoredRating;
   413             List<PEPMessage> result = new List<PEPMessage>();
   414             Globals.ReturnStatus status;
   415 
   416             status = this.ProcessSentMessage(message,
   417                                              isInSecureStore,
   418                                              extraKeys,
   419                                              out encrypted,
   420                                              out unencrypted,
   421                                              out savedSentMessage,
   422                                              out savedSentMessageStoredRating);
   423 
   424             for (int i = 0; i < encrypted.Length; i++)
   425             {
   426                 result.Add(encrypted[i]);
   427             }
   428 
   429             for (int i = 0; i < unencrypted.Length; i++)
   430             {
   431                 result.Add(unencrypted[i]);
   432             }
   433 
   434             outMessages = result.ToArray();
   435             outSavedSentMessage = savedSentMessage;
   436             outSavedSentMessageStoredRating = savedSentMessageStoredRating;
   437 
   438             return (status);
   439         }
   440 
   441         /// <summary>
   442         /// Processes the given PEP message mail item before sending.
   443         /// This will handle things like distribution lists, encryption, Bcc, etc.
   444         /// </summary>
   445         /// <param name="message">The original PEP message being sent.</param>
   446         /// <param name="isInEncryptedStore">Whether the PEP message being sent is in an
   447         /// encrypted (untrusted) store or not.</param>
   448         /// <param name="outEncryptedMessages">The list of encrypted PEP messages to be sent.</param>
   449         /// <param name="outUnencryptedMessages">The list of unencrypted PEP messages to be sent.</param>
   450         /// <param name="outSavedSentMessage">The PEP message that should be saved in the sent folder.</param>
   451         /// <param name="outSavedSentMessageStoredRating">The pEp rating to be stored in the saved sent message.
   452         /// This is based on the original OutgoingRating. WARNING: Be careful about using this value externally on untrusted servers, 
   453         /// it should be saved to the mirror ONLY.</param>
   454         /// <returns>The status of the method.</returns>
   455         public Globals.ReturnStatus ProcessSentMessage(PEPMessage message,
   456                                                        bool isInEncryptedStore,
   457                                                        string[] extraKeys,
   458                                                        out PEPMessage[] outEncryptedMessages,
   459                                                        out PEPMessage[] outUnencryptedMessages,
   460                                                        out PEPMessage outSavedSentMessage,
   461                                                        out pEpRating outSavedSentMessageStoredRating)
   462         {
   463             bool result;
   464             TextMessage src;
   465             pEpRating outgoingRating;
   466             PEPMessage newMessage;
   467             PEPMessage workingMessage = message.Copy();
   468             PEPMessage newEncryptedMessage = null;
   469             PEPMessage newUnencryptedMessage = null;
   470             PEPMessage savedSentMessage = null;
   471             pEpRating savedSentMessageStoredRating;
   472             List<PEPIdentity> bccUnencryptedGroup = new List<PEPIdentity>();
   473             List<PEPIdentity> bccEncryptedGroup = new List<PEPIdentity>();
   474             List<PEPMessage> unencryptedMessages = new List<PEPMessage>();
   475             List<PEPMessage> encryptedMessages = new List<PEPMessage>();
   476             List<PEPMessage> bccMessages = new List<PEPMessage>();
   477             Globals.ReturnStatus status = Globals.ReturnStatus.Success;
   478 
   479             Log.Verbose("ProcessSentMessage: Started");
   480 
   481             // Force the working message to outgoing direction.
   482             // This protects mostly for outgoing rating calculation which requires it.
   483             // However, if the message is ever incoming direction here, a failure occured elsewhere.
   484             if (workingMessage.Direction == pEpMsgDirection.pEpDirIncoming)
   485             {
   486                 workingMessage.Direction = pEpMsgDirection.pEpDirOutgoing;
   487                 Log.Warning("ProcessSentMessage: Message direction set to outgoing (was incorrectly incoming).");
   488             }
   489 
   490             // Set stored sent message rating to outgoing rating
   491             // WARNING: Be careful about using this value externally on untrusted servers, it should be saved to the mirror ONLY.
   492             savedSentMessageStoredRating = this.GetOutgoingRating(workingMessage);
   493 
   494             ///////////////////////////////////////////////////////////
   495             // Process HTML & Handle distribution lists
   496             //  This updates workingMessage
   497             ///////////////////////////////////////////////////////////
   498 
   499             Log.Verbose("ProcessSentMessage: Flattening recipients.");
   500 
   501             workingMessage.FlattenAllRecipientIdentities();
   502 
   503             ///////////////////////////////////////////////////////////
   504             // Handle send unencrypted
   505             //  This uses the encrypted/unencrypted messages
   506             ///////////////////////////////////////////////////////////
   507 
   508             Log.Verbose("ProcessSentMessage: Processing send unencrypted by identity.");
   509 
   510             // Bcc
   511             for (int i = 0; i < workingMessage.Bcc.Count; i++)
   512             {
   513                 if (workingMessage.Bcc[i].IsForceUnencryptedBool)
   514                 {
   515                     if (newUnencryptedMessage == null)
   516                     {
   517                         newUnencryptedMessage = workingMessage.Copy();
   518                         newUnencryptedMessage.Bcc.Clear();
   519                         newUnencryptedMessage.Cc.Clear();
   520                         newUnencryptedMessage.To.Clear();
   521                     }
   522 
   523                     newUnencryptedMessage.Bcc.Add(workingMessage.Bcc[i].Copy());
   524                 }
   525                 else
   526                 {
   527                     if (newEncryptedMessage == null)
   528                     {
   529                         newEncryptedMessage = workingMessage.Copy();
   530                         newEncryptedMessage.Bcc.Clear();
   531                         newEncryptedMessage.Cc.Clear();
   532                         newEncryptedMessage.To.Clear();
   533                     }
   534 
   535                     newEncryptedMessage.Bcc.Add(workingMessage.Bcc[i].Copy());
   536                 }
   537             }
   538 
   539             // Cc
   540             for (int i = 0; i < workingMessage.Cc.Count; i++)
   541             {
   542                 if (workingMessage.Cc[i].IsForceUnencryptedBool)
   543                 {
   544                     if (newUnencryptedMessage == null)
   545                     {
   546                         newUnencryptedMessage = workingMessage.Copy();
   547                         newUnencryptedMessage.Bcc.Clear();
   548                         newUnencryptedMessage.Cc.Clear();
   549                         newUnencryptedMessage.To.Clear();
   550                     }
   551 
   552                     newUnencryptedMessage.Cc.Add(workingMessage.Cc[i].Copy());
   553                 }
   554                 else
   555                 {
   556                     if (newEncryptedMessage == null)
   557                     {
   558                         newEncryptedMessage = workingMessage.Copy();
   559                         newEncryptedMessage.Bcc.Clear();
   560                         newEncryptedMessage.Cc.Clear();
   561                         newEncryptedMessage.To.Clear();
   562                     }
   563 
   564                     newEncryptedMessage.Cc.Add(workingMessage.Cc[i].Copy());
   565                 }
   566             }
   567 
   568             // To
   569             for (int i = 0; i < workingMessage.To.Count; i++)
   570             {
   571                 if (workingMessage.To[i].IsForceUnencryptedBool)
   572                 {
   573                     if (newUnencryptedMessage == null)
   574                     {
   575                         newUnencryptedMessage = workingMessage.Copy();
   576                         newUnencryptedMessage.Bcc.Clear();
   577                         newUnencryptedMessage.Cc.Clear();
   578                         newUnencryptedMessage.To.Clear();
   579                     }
   580 
   581                     newUnencryptedMessage.To.Add(workingMessage.To[i].Copy());
   582                 }
   583                 else
   584                 {
   585                     if (newEncryptedMessage == null)
   586                     {
   587                         newEncryptedMessage = workingMessage.Copy();
   588                         newEncryptedMessage.Bcc.Clear();
   589                         newEncryptedMessage.Cc.Clear();
   590                         newEncryptedMessage.To.Clear();
   591                     }
   592 
   593                     newEncryptedMessage.To.Add(workingMessage.To[i].Copy());
   594                 }
   595             }
   596 
   597             if (newUnencryptedMessage != null)
   598             {
   599                 unencryptedMessages.Add(newUnencryptedMessage);
   600             }
   601 
   602             if (newEncryptedMessage != null)
   603             {
   604                 encryptedMessages.Add(newEncryptedMessage);
   605             }
   606 
   607             ///////////////////////////////////////////////////////////
   608             // Handle Bcc
   609             //  This uses the encrypted messages
   610             ///////////////////////////////////////////////////////////
   611 
   612             Log.Verbose("ProcessSentMessage: Processing Bcc");
   613 
   614             // Remove any encrypted messages with Bcc for special processing
   615             for (int i = 0; i < encryptedMessages.Count; i++)
   616             {
   617                 if ((encryptedMessages[i].Bcc != null) &&
   618                     (encryptedMessages[i].Bcc.Count > 0))
   619                 {
   620                     bccMessages.Add(encryptedMessages[i]);
   621                     encryptedMessages.RemoveAt(i);
   622 
   623                     // Reset index
   624                     i = -1;
   625                 }
   626             }
   627 
   628             // Process the Bcc messages
   629             for (int i = 0; i < bccMessages.Count; i++)
   630             {
   631                 // First, add message without Bcc
   632                 if ((bccMessages[i].Cc.Count > 0) ||
   633                     (bccMessages[i].To.Count > 0))
   634                 {
   635                     newMessage = bccMessages[i].Copy();
   636                     newMessage.Bcc.Clear();
   637                     encryptedMessages.Add(newMessage);
   638                 }
   639 
   640                 // Second, check each Bcc to see if it will receive an encrypted copy
   641                 for (int j = 0; j < bccMessages[i].Bcc.Count; j++)
   642                 {
   643                     newMessage = new PEPMessage();
   644                     newMessage.Direction = pEpMsgDirection.pEpDirOutgoing;
   645                     newMessage.From = bccMessages[i].From.Copy();
   646                     newMessage.To.Add(bccMessages[i].Bcc[j].Copy());
   647 
   648                     // Calculate outgoing rating
   649                     outgoingRating = this.GetOutgoingRating(newMessage);
   650 
   651                     // Group identities
   652                     if (outgoingRating >= pEpRating.pEpRatingReliable)
   653                     {
   654                         bccEncryptedGroup.Add(bccMessages[i].Bcc[j].Copy());
   655                     }
   656                     else
   657                     {
   658                         bccUnencryptedGroup.Add(bccMessages[i].Bcc[j].Copy());
   659                     }
   660                 }
   661 
   662                 // Third, create individual mail items for encrypted Bcc
   663                 for (int j = 0; j < bccEncryptedGroup.Count; j++)
   664                 {
   665                     // New mail for each encrypted Bcc recipient
   666                     newMessage = bccMessages[i].Copy();
   667                     newMessage.Bcc.Clear();
   668                     newMessage.Cc.Clear();
   669                     newMessage.To.Clear();
   670 
   671                     newMessage.Bcc.Add(bccEncryptedGroup[j]);
   672 
   673                     encryptedMessages.Add(newMessage);
   674                 }
   675 
   676                 // Fourth, create single mail item for unencrypted Bcc
   677                 if (bccUnencryptedGroup.Count > 0)
   678                 {
   679                     // Look for existing unencrypted email to use (there should always be only one of these)
   680                     if (unencryptedMessages.Count > 0)
   681                     {
   682                         newMessage = unencryptedMessages[0];
   683                     }
   684                     else
   685                     {
   686                         newMessage = bccMessages[i].Copy();
   687                         newMessage.Bcc.Clear();
   688                         newMessage.Cc.Clear();
   689                         newMessage.To.Clear();
   690                         unencryptedMessages.Add(newMessage);
   691                     }
   692 
   693                     for (int j = 0; j < bccUnencryptedGroup.Count; j++)
   694                     {
   695                         newMessage.Bcc.Add(bccUnencryptedGroup[j]);
   696                     }
   697                 }
   698             }
   699 
   700             ///////////////////////////////////////////////////////////
   701             // Encrypt
   702             ///////////////////////////////////////////////////////////
   703 
   704             Log.Verbose("ProcessSentMessage: Encrypting messages, count=" + encryptedMessages.Count.ToString());
   705 
   706             for (int i = 0; i < encryptedMessages.Count; i++)
   707             {
   708                 try
   709                 {
   710                     // Get the new outgoing pEp rating since the message may have been modified during processing.
   711                     // This will be used to ensure the encrypt result is actually encrypted when it says it is.
   712                     // Since this is only to double check the engine rating, the full outgoing rating calculation is not needed.
   713                     // Just the engine rating is sufficient.
   714                     src = encryptedMessages[i].ToCOMType();
   715                     src.Dir = pEpMsgDirection.pEpDirOutgoing; // Should already be Outgoing, but just make sure
   716                     outgoingRating = ThisAddIn.PEPEngine.OutgoingMessageRating(ref src);
   717 
   718                     result = this.Encrypt(encryptedMessages[i], extraKeys, out newMessage);
   719                     encryptedMessages[i] = newMessage;
   720 
   721                     // Check to ensure the message was really encrypted
   722                     // This is just a double check against presenting the user with one rating, but actually delivering another.
   723                     // If this happens, an error occured elsewhere, likely in the engine
   724                     if ((result == false) ||
   725                         ((outgoingRating >= pEpRating.pEpRatingReliable) &&
   726                          (newMessage.IsSecure == false)))
   727                     {
   728                         status = Globals.ReturnStatus.Failure;
   729                         Log.Error("ProcessSentMessage: Message encryption failed or doesn't match outgoing rating.");
   730                     }
   731                 }
   732                 catch (Exception ex)
   733                 {
   734                     status = Globals.ReturnStatus.Failure;
   735                     Log.Error("ProcessSentMessage: Message encryption failed, " + ex.ToString());
   736                 }
   737             }
   738 
   739             ///////////////////////////////////////////////////////////
   740             // Create the saved sent message
   741             ///////////////////////////////////////////////////////////
   742 
   743             Log.Verbose("ProcessSentMessage: Creating saved sent message.");
   744 
   745             if (isInEncryptedStore)
   746             {
   747                 if ((encryptedMessages.Count == 1) &&
   748                     (unencryptedMessages.Count == 0))
   749                 {
   750                     // Use what is already encrypted
   751                     savedSentMessage = encryptedMessages[0].Copy();
   752                 }
   753                 else
   754                 {
   755                     /* Note: Use the original message before processing to ensure it appears to the user
   756                      * the same as it was first composed. This is especially important for recipients.
   757                      */
   758                     workingMessage = message.Copy();
   759 
   760                     result = this.Encrypt(workingMessage, extraKeys, out savedSentMessage);
   761 
   762                     if (result == false)
   763                     {
   764                         status = Globals.ReturnStatus.Failure;
   765                         Log.Error("ProcessSentMessage: Encryption of saved sent message failed.");
   766                     }
   767                 }
   768             }
   769             else
   770             {
   771                 savedSentMessage = message.Copy();
   772             }
   773 
   774             outEncryptedMessages = encryptedMessages.ToArray();
   775             outUnencryptedMessages = unencryptedMessages.ToArray();
   776             outSavedSentMessage = savedSentMessage;
   777             outSavedSentMessageStoredRating = savedSentMessageStoredRating;
   778 
   779             Log.Verbose("ProcessSentMessage: Encrypted Count=" + outEncryptedMessages.Length.ToString() +
   780                         " Unencrypted Count=" + outUnencryptedMessages.Length.ToString());
   781             Log.Verbose("ProcessSentMessage: Completed.");
   782 
   783             Log.Verbose("ProcessSentMessage: ReturnStatus=" + status.ToString());
   784             return (status);
   785         }
   786 
   787         /// <summary>
   788         /// Gets the outgoing pEp rating of the given message.
   789         /// This will take into account both contact-based rating and engine rating.
   790         /// Warning: Recipients must already be flattened.
   791         /// </summary>
   792         /// <param name="message">The message to get the outgoing rating for.</param>
   793         /// <returns>The calculated outgoing rating, otherwise undefined.</returns>
   794         public pEpRating GetOutgoingRating(PEPMessage message)
   795         {
   796             TextMessage _msg;
   797             pEpRating rating = pEpRating.pEpRatingUndefined;
   798             pEpRating contactRating = pEpRating.pEpRatingUndefined;
   799             pEpRating engineRating = pEpRating.pEpRatingUndefined;
   800 
   801             Log.Verbose("GetOutgoingRating: Started.");
   802 
   803             if (message != null)
   804             {
   805                 if (message.ForceUnencrypted)
   806                 {
   807                     rating = pEpRating.pEpRatingUnencrypted;
   808                 }
   809                 else
   810                 {
   811                     // Contact rating
   812                     contactRating = this.GetOutgoingRatingByRecipients(message);
   813 
   814                     // Engine rating
   815                     if (contactRating != pEpRating.pEpRatingUnencrypted)
   816                     {
   817                         try
   818                         {
   819                             _msg = message.ToCOMType();
   820                             _msg.Dir = pEpMsgDirection.pEpDirOutgoing;
   821                             engineRating = ThisAddIn.PEPEngine.OutgoingMessageRating(ref _msg);
   822                         }
   823                         catch (COMException ex)
   824                         {
   825                             engineRating = pEpRating.pEpRatingUndefined;
   826                             Log.Error("GetOutgoingRating: engine rating undefined due to error, " + ex.ToString());
   827                         }
   828                         catch (Exception ex)
   829                         {
   830                             engineRating = pEpRating.pEpRatingUndefined;
   831                             Log.Error("GetOutgoingRating: engine rating undefined due to error, " + ex.ToString());
   832                         }
   833                     }
   834 
   835                     // Merge ratings
   836                     if (contactRating == pEpRating.pEpRatingUnencrypted)
   837                     {
   838                         rating = pEpRating.pEpRatingUnencrypted;
   839                     }
   840                     else if (contactRating == pEpRating.pEpRatingUnencryptedForSome)
   841                     {
   842                         // Whichever is less: the engine rating or unencrypted_for_some
   843                         if ((int)engineRating < (int)pEpRating.pEpRatingUnencryptedForSome)
   844                         {
   845                             rating = engineRating;
   846                         }
   847                         else
   848                         {
   849                             rating = pEpRating.pEpRatingUnencryptedForSome;
   850                         }
   851                     }
   852                     else
   853                     {
   854                         rating = engineRating;
   855                     }
   856                 }
   857             }
   858 
   859             Log.Verbose("GetOutgoingRating: Completed.");
   860 
   861             return (rating);
   862         }
   863 
   864         /// <summary>
   865         /// Gets the outgoing pEp rating of the given message based on the recipients force unencrypted status.
   866         /// The IsForceUnencrypted property of each recipient must have already been set in the message.
   867         /// This property is copied from any associated Outlook contact's force unencrypted user property.
   868         /// </summary>
   869         /// <param name="message">The message to get the rating for.</param>
   870         /// <returns>The rating: undefined, reliable, unencrypted or unencrypted_for_some.
   871         /// Will return undefined by default or if no recipients exist.</returns>
   872         public pEpRating GetOutgoingRatingByRecipients(PEPMessage message)
   873         {
   874             // TODO: Check whether this method should be part of the engine?
   875             int unencryptedCount = 0;
   876             pEpRating rating = pEpRating.pEpRatingUndefined;
   877             List<PEPIdentity> forceUnencryptedList = new List<PEPIdentity>();
   878             PEPIdentity[] recipients;
   879             PEPMessage workingMessage;
   880 
   881             if (message != null)
   882             {
   883                 workingMessage = message.Copy(true);
   884                 workingMessage.FlattenAllRecipientIdentities();
   885 
   886                 recipients = workingMessage.Recipients;
   887 
   888                 if (recipients.Length > 0)
   889                 {
   890                     // Calculate for all recipients
   891                     for (int i = 0; i < recipients.Length; i++)
   892                     {
   893                         if (recipients[i].IsForceUnencryptedBool)
   894                         {
   895                             unencryptedCount++;
   896                         }
   897                     }
   898 
   899                     // Final rating determination
   900                     if (unencryptedCount == recipients.Length)
   901                     {
   902                         rating = pEpRating.pEpRatingUnencrypted;
   903                     }
   904                     else if (unencryptedCount == 0)
   905                     {
   906                         // Assume encrypted (will use engine rating in the end)
   907                         rating = pEpRating.pEpRatingReliable;
   908                     }
   909                     else if (unencryptedCount < recipients.Length)
   910                     {
   911                         rating = pEpRating.pEpRatingUnencryptedForSome;
   912                     }
   913                     else
   914                     {
   915                         // Should never get here
   916                         rating = pEpRating.pEpRatingUndefined;
   917                     }
   918                 }
   919             }
   920 
   921             return (rating);
   922         }
   923 
   924         /// <summary>
   925         /// Decrypts the given pEp message.
   926         /// This is a wrapper for special cases where we don't need to take care of
   927         /// rating, key list or flags.
   928         /// </summary>
   929         /// <param name="sourceMessage">The message to decrypt.</param>
   930         /// <param name="decryptedMessage">The output decrypted message.</param>
   931         /// <returns>True if decryption was considered successful, otherwise false.</returns>
   932         public bool Decrypt(PEPMessage sourceMessage,
   933                             out PEPMessage decryptedMessage)
   934         {
   935             string[] keyList;
   936             pEpDecryptFlags flags;
   937             pEpRating rating;
   938 
   939             return this.Decrypt(sourceMessage, out decryptedMessage, out keyList, out flags, out rating);
   940         }
   941 
   942         /// <summary>
   943         /// Decrypts the given pEp message.
   944         /// This is a wrapper of the standard Decrypt method that checks the rating automatically.
   945         /// </summary>
   946         /// <param name="sourceMessage">The message to decrypt.</param>
   947         /// <param name="destMessage">The output decrypted message.</param>
   948         /// <param name="keyList">The output keylist used to decrypt.</param>
   949         /// <param name="flags">Decryption flags by the engine.</param>
   950         /// <param name="rating">The output pEp rating after decryption.</param>
   951         /// <returns>True if decryption was considered successful, otherwise false.</returns>
   952         public bool Decrypt(PEPMessage sourceMessage,
   953                             out PEPMessage destMessage,
   954                             out string[] keyList,
   955                             out pEpDecryptFlags flags,
   956                             out pEpRating rating)
   957         {
   958             rating = this.Decrypt(sourceMessage, out destMessage, out keyList, out flags);
   959 
   960             if ((rating == pEpRating.pEpRatingB0rken) ||
   961                 (rating == pEpRating.pEpRatingUndefined) ||
   962                 (rating == pEpRating.pEpRatingCannotDecrypt) ||
   963                 (rating == pEpRating.pEpRatingHaveNoKey))
   964             {
   965                 return (false);
   966             }
   967             else
   968             {
   969                 return (true);
   970             }
   971         }
   972 
   973         /// <summary>
   974         /// Decrypts the given pEp message.
   975         /// </summary>
   976         /// <param name="sourceMessage">The message to decrypt.</param>
   977         /// <param name="destMessage">The output decrypted message.</param>
   978         /// <param name="keyList">The output keylist used to decrypt.</param>
   979         /// <param name="flags">Decryption flags by the engine.</param>
   980         /// <returns>The pEp rating after decryption.</returns>
   981         public pEpRating Decrypt(PEPMessage sourceMessage,
   982                                  out PEPMessage destMessage,
   983                                  out string[] keyList,
   984                                  out pEpDecryptFlags flags)
   985         {
   986             bool success = false;
   987             TextMessage src;
   988             TextMessage dst = new TextMessage();
   989             string[] dstKeyList = new string[0];
   990             pEpRating rating = pEpRating.pEpRatingCannotDecrypt;
   991             Globals.ReturnStatus sts;
   992             flags = pEpDecryptFlags.pEpDecryptFlagsNone;
   993 
   994             Log.Verbose("Decrypt started.");
   995             Log.SensitiveData("Decrypt: Decrypting " + sourceMessage?.Id);
   996 
   997             if (sourceMessage != null)
   998             {
   999                 // Decrypt
  1000                 try
  1001                 {
  1002                     src = sourceMessage.ToCOMType();
  1003                     rating = ThisAddIn.PEPEngine.DecryptMessage(src, out dst, out dstKeyList, out flags);
  1004                     success = true;
  1005                 }
  1006                 catch (COMException ex)
  1007                 {
  1008                     rating = pEpRating.pEpRatingCannotDecrypt;
  1009                     success = false;
  1010                     Log.Error("Decrypt: exception " + ex.ToString());
  1011                 }
  1012                 catch (Exception ex)
  1013                 {
  1014                     rating = pEpRating.pEpRatingCannotDecrypt;
  1015                     Log.Error("Decrypt: exception " + ex.ToString());
  1016                 }
  1017             }
  1018 
  1019             if (success)
  1020             {
  1021                 // Creating a PEPMessage from a TextMessage has basically no failure modes
  1022                 // Therefore, the result of the method is not used.
  1023                 sts = PEPMessage.Create(dst, out destMessage);
  1024 
  1025                 // Copy over lost properties
  1026                 destMessage.SetNonEnginePropertiesFrom(sourceMessage);
  1027                 keyList = dstKeyList;
  1028             }
  1029             else
  1030             {
  1031                 destMessage = null;
  1032                 keyList = new string[0];
  1033             }
  1034 
  1035             Log.Verbose("Decrypt: Complete. " + sourceMessage?.Id);
  1036 
  1037             return rating;
  1038         }
  1039 
  1040         /// <summary>
  1041         /// Encrypts the given PEPMessage.
  1042         /// </summary>
  1043         /// <param name="sourceMessage">The message to encrypt.</param>
  1044         /// <param name="extraKeys">Any extra keys to encrypt the message with.</param>
  1045         /// <param name="destMessage">The output encrypted message.</param>
  1046         /// <returns>True if successful, otherwise false.</returns>
  1047         public bool Encrypt(PEPMessage sourceMessage,
  1048                             string[] extraKeys,
  1049                             out PEPMessage destMessage)
  1050         {
  1051             return Encrypt(sourceMessage, extraKeys, 0, out destMessage);
  1052         }
  1053 
  1054         /// <summary>
  1055         /// Encrypts the given PEPMessage.
  1056         /// </summary>
  1057         /// <param name="sourceMessage">The message to encrypt.</param>
  1058         /// <param name="extraKeys">Any extra keys to encrypt the message with.</param>
  1059         /// <param name="destMessage">The output encrypted message.</param>
  1060         /// <returns>True if successful, otherwise false.</returns>
  1061         public bool Encrypt(PEPMessage sourceMessage,
  1062                             string[] extraKeys,
  1063                             pEpEncryptFlags flags,
  1064                             out PEPMessage destMessage)
  1065         {
  1066             bool success = false;
  1067             TextMessage src;
  1068             TextMessage dst = new TextMessage();
  1069             Globals.ReturnStatus sts;
  1070 
  1071             Log.Verbose("Encrypt: Started.");
  1072 
  1073             if (sourceMessage != null)
  1074             {
  1075                 // Encrypt
  1076                 try
  1077                 {
  1078                     src = sourceMessage.ToCOMType();
  1079 
  1080                     // If no Sent date has been set, add it before encryption
  1081                     if (src.Sent == 0)
  1082                     {
  1083                         src.Sent = DateTime.Now.ToFileTimeUtc();
  1084                     }
  1085 
  1086                     ThisAddIn.PEPEngine.EncryptMessage(src, out dst, extraKeys, flags);
  1087                     success = true;
  1088                 }
  1089                 catch (COMException ex)
  1090                 {
  1091                     Log.Error("Encrypt: exception " + ex.ToString());
  1092                     success = false;
  1093                 }
  1094                 catch (Exception ex)
  1095                 {
  1096                     Log.Error("Encrypt: exception " + ex.ToString());
  1097                     success = false;
  1098                 }
  1099             }
  1100 
  1101             if (success)
  1102             {
  1103                 // Creating a PEPMessage from a TextMessage has basically no failure modes
  1104                 // Therefore, the result of the method is not used.
  1105                 sts = PEPMessage.Create(dst, out destMessage);
  1106 
  1107                 // Copy over lost properties
  1108                 destMessage.SetNonEnginePropertiesFrom(sourceMessage);
  1109             }
  1110             else
  1111             {
  1112                 destMessage = null;
  1113             }
  1114 
  1115             Log.Verbose("Encrypt: Complete.");
  1116 
  1117             return (success);
  1118         }
  1119 
  1120         /**************************************************************
  1121          * 
  1122          * Sub-classes
  1123          * 
  1124          *************************************************************/
  1125 
  1126         /// <summary>
  1127         /// Class used to store the arguments in the ProcessingCompleted event.
  1128         /// </summary>
  1129         internal class ProcessingCompletedEventArgs : EventArgs
  1130         {
  1131             /// <summary>
  1132             /// Whether the ProcessedMessage corresponds to a mirror (true) or the original message (false).
  1133             /// </summary>
  1134             public bool IsMirror;
  1135 
  1136             /// <summary>
  1137             /// pEp Engine decryption flags after processing the message.
  1138             /// </summary>
  1139             public pEpDecryptFlags ProcessedDecryptionFlags;
  1140 
  1141             /// <summary>
  1142             /// The processed message. This will contain any updated data for either 
  1143             /// the original message or the mirror. Use the IsMirror property to determine which.
  1144             /// </summary>
  1145             public PEPMessage ProcessedMessage;
  1146 
  1147             /// <summary>
  1148             /// The rating after the processing is complete.
  1149             /// </summary>
  1150             public pEpRating ProcessedRating;
  1151 
  1152             /// <summary>
  1153             /// The returned status after processing was completed.
  1154             /// </summary>
  1155             public Globals.ReturnStatus ProcessedStatus;
  1156 
  1157             /// <summary>
  1158             /// Default constructor.
  1159             /// </summary>
  1160             public ProcessingCompletedEventArgs()
  1161             {
  1162                 this.IsMirror = false;
  1163                 this.ProcessedDecryptionFlags = pEpDecryptFlags.pEpDecryptFlagsNone;
  1164                 this.ProcessedMessage = null;
  1165                 this.ProcessedRating = pEpRating.pEpRatingUndefined;
  1166                 this.ProcessedStatus = Globals.ReturnStatus.Success;
  1167             }
  1168 
  1169             /// <summary>
  1170             /// Constructs a new ProcessingCompletedEventArgs with the given arguments.
  1171             /// </summary>
  1172             /// <param name="rating">The rating after the processing completes.</param>
  1173             public ProcessingCompletedEventArgs(pEpRating rating)
  1174             {
  1175                 this.IsMirror = false;
  1176                 this.ProcessedDecryptionFlags = pEpDecryptFlags.pEpDecryptFlagsNone;
  1177                 this.ProcessedMessage = null;
  1178                 this.ProcessedRating = rating;
  1179                 this.ProcessedStatus = Globals.ReturnStatus.Success;
  1180             }
  1181         }
  1182     }
  1183 }