DecryptionStack.cs
author nikolaj
Thu, 12 Jul 2018 18:14:31 +0200
branchOUT-471
changeset 2289 5892f184a8ac
parent 2249 0891fb2e6fbe
child 2290 99cb384413da
permissions -rw-r--r--
OUT-471: Added SQL query to hide messages with "pep-auto-consume" in TRANSPORT_MESSAGE_HEADERS
Thomas@1770
     1
´╗┐using System;
Thomas@1770
     2
using System.Collections.Generic;
Thomas@1770
     3
using Outlook = Microsoft.Office.Interop.Outlook;
Thomas@1770
     4
Thomas@1770
     5
namespace pEp
Thomas@1770
     6
{
Thomas@1785
     7
    internal class DecryptionStack : Stack<string>, IDisposable
Thomas@1770
     8
    {
Thomas@1778
     9
        private const int MAX_DECRYPTION_COUNT                       = 5;       // Maximum amount of concurrently decrypted items
Thomas@1770
    10
        private const int DECRYPTION_INTERVAL                        = 500;     // in ms
Thomas@1770
    11
Thomas@1770
    12
        private System.Windows.Forms.Timer decryptionTimer           = null;
Thomas@1770
    13
        private int decryptionCounter                                = 0;
Thomas@1770
    14
        private object mutexDecryptionCounter                        = new object();
Thomas@1770
    15
        private object mutexDecryptionStack                          = new object();
Thomas@1770
    16
Thomas@1770
    17
        /// <summary>
Thomas@2230
    18
        /// List to store already copied items to prevent unnecessary processing
nikolaj@2116
    19
        /// </summary>
Thomas@2230
    20
        private List<string> copiedItemsList = new List<string>();
Thomas@2230
    21
nikolaj@2116
    22
        /// <summary>
Thomas@1770
    23
        /// Default constructor.
Thomas@1770
    24
        /// </summary>
Thomas@1770
    25
        public DecryptionStack()
Thomas@1770
    26
        {
Thomas@1770
    27
            this.decryptionTimer = new System.Windows.Forms.Timer();
Thomas@1770
    28
            this.decryptionTimer.Interval = DECRYPTION_INTERVAL;
Thomas@1770
    29
            this.decryptionTimer.Tick += this.DecryptionTimer_Tick;
Thomas@1770
    30
        }
Thomas@1770
    31
Thomas@1770
    32
        /// <summary>
Thomas@1785
    33
        /// Implementing IDisposable
Thomas@1785
    34
        /// </summary>
Thomas@1785
    35
        public void Dispose()
Thomas@1785
    36
        {
Thomas@1785
    37
            this.decryptionTimer.Dispose();
Thomas@1785
    38
        }
Thomas@1785
    39
Thomas@1785
    40
        /// <summary>
Thomas@1770
    41
        /// Increments the decryption counter if maximum has not yet been reached.
Thomas@1770
    42
        /// </summary>
Thomas@1770
    43
        /// <returns>True if counter was encrypted, false if maximum has already been reached.</returns>
Thomas@1770
    44
        private bool IncrementDecryptionCounter()
Thomas@1770
    45
        {
Thomas@1770
    46
            bool incremented = false;
Thomas@1770
    47
Thomas@1770
    48
            lock (mutexDecryptionCounter)
Thomas@1770
    49
            {
Thomas@1770
    50
                if (this.decryptionCounter < MAX_DECRYPTION_COUNT)
Thomas@1770
    51
                {
Thomas@1770
    52
                    this.decryptionCounter++;
Thomas@1770
    53
                    incremented = true;
Thomas@1803
    54
                    Log.Verbose("DecryptionStack.IncrementDecryptionCounter: counter incremented to " + decryptionCounter);
Thomas@1770
    55
                }
Thomas@1770
    56
            }
Thomas@1770
    57
Thomas@1770
    58
            return incremented;
Thomas@1770
    59
        }
Thomas@1770
    60
Thomas@1770
    61
        /// <summary>
Thomas@1770
    62
        /// Decrements the decryption counter if it is bigger than 0.
Thomas@1770
    63
        /// </summary>
Thomas@1770
    64
        public void DecrementDecryptionCounter()
Thomas@1770
    65
        {
Thomas@1770
    66
            lock (mutexDecryptionCounter)
Thomas@1770
    67
            {
Thomas@1770
    68
                if (this.decryptionCounter > 0)
Thomas@1770
    69
                {
Thomas@1770
    70
                    this.decryptionCounter--;
Thomas@1803
    71
                    Log.Verbose("DecryptionStack.DecrementDecryptionCounter: counter decremented to " + decryptionCounter);
Thomas@1770
    72
                }
Thomas@1770
    73
            }
Thomas@1770
    74
        }
Thomas@1770
    75
Thomas@1770
    76
        /// <summary>
Thomas@1770
    77
        /// Resets the decryption counter to 0.
Thomas@1770
    78
        /// </summary>
Thomas@1770
    79
        private void ResetDecryptionCounter()
Thomas@1770
    80
        {
Thomas@1770
    81
            lock (mutexDecryptionCounter)
Thomas@1770
    82
            {
Thomas@1770
    83
                this.decryptionCounter = 0;
Thomas@1803
    84
                Log.Verbose("DecryptionStack.ResetDecryptionCounter: counter reset");
Thomas@1770
    85
            }
Thomas@1770
    86
        }
Thomas@1770
    87
Thomas@1770
    88
        /// <summary>
Thomas@1770
    89
        /// Locks the decryption stack and tries to get its first element.
Thomas@1770
    90
        /// Also checks if the following element(s) are duplicates of the one
Thomas@1770
    91
        /// returned and removes them in this case.
Thomas@1770
    92
        /// </summary>
Thomas@1770
    93
        /// <param name="entryId">The retrieved entryId or an empty string if none was retrieved.</param>
Thomas@1770
    94
        /// <returns>Tue if the operation retrieved successfully an entryId. Otherwise false.</returns>
Thomas@1778
    95
        private bool TryPop(out string entryId)
Thomas@1770
    96
        {
Thomas@1778
    97
            entryId = null;
Thomas@1770
    98
Thomas@1770
    99
            lock (mutexDecryptionStack)
Thomas@1770
   100
            {
Thomas@1770
   101
                try
Thomas@1770
   102
                {
Thomas@1770
   103
                    if (this.Count > 0)
Thomas@1770
   104
                    {
Thomas@1770
   105
                        entryId = this.Pop();
Thomas@1807
   106
                        Log.Verbose("DecryptionStack.TryPop: retrieved entryId " + entryId);
Thomas@1770
   107
                    }
Thomas@1770
   108
                }
Thomas@1770
   109
                catch (Exception ex)
Thomas@1770
   110
                {
Thomas@1770
   111
                    entryId = string.Empty;
Thomas@1770
   112
                    Log.Error("TryPopDecryptionStack: Error getting first element from stack. " + ex.ToString());
Thomas@1770
   113
                }
Thomas@1770
   114
Thomas@1770
   115
                // If an entryId could be retrieved, check following item(s)
Thomas@1770
   116
                if (string.IsNullOrEmpty(entryId) == false)
Thomas@1770
   117
                {
Thomas@1770
   118
                    do
Thomas@1770
   119
                    {
Thomas@1770
   120
                        if (this.Count > 0)
Thomas@1770
   121
                        {
Thomas@1778
   122
                            string nextEntryId = null;
Thomas@1770
   123
Thomas@1770
   124
                            try
Thomas@1770
   125
                            {
Thomas@1770
   126
                                nextEntryId = this.Peek();
Thomas@1770
   127
                            }
Thomas@1770
   128
                            catch (Exception ex)
Thomas@1770
   129
                            {
Thomas@1770
   130
                                Log.Error("TryPopDecryptionStack: Error removing duplicates from stack. " + ex.ToString());
Thomas@1770
   131
                            }
Thomas@1770
   132
Thomas@1770
   133
                            // If next item equals the retrieved entryId, remove it
Thomas@1770
   134
                            if (string.Equals(entryId, nextEntryId))
Thomas@1770
   135
                            {
Thomas@1770
   136
                                try
Thomas@1770
   137
                                {
Thomas@1770
   138
                                    this.Pop();
Thomas@1770
   139
                                }
Thomas@1770
   140
                                catch (Exception ex)
Thomas@1770
   141
                                {
Thomas@1770
   142
                                    Log.Error("TryPopDecryptionStack: Error removing duplicates from stack. " + ex.ToString());
Thomas@1770
   143
                                    break;
Thomas@1770
   144
                                }
Thomas@1770
   145
                            }
Thomas@1770
   146
                            else
Thomas@1770
   147
                            {
Thomas@1770
   148
                                break;
Thomas@1770
   149
                            }
Thomas@1770
   150
                        }
Thomas@1770
   151
                        else
Thomas@1770
   152
                        {
Thomas@1770
   153
                            break;
Thomas@1770
   154
                        }
Thomas@1770
   155
Thomas@1770
   156
                    } while (true);
Thomas@1770
   157
                }
Thomas@1770
   158
            }
Thomas@1770
   159
Thomas@1770
   160
            return (string.IsNullOrEmpty(entryId) == false);
Thomas@1770
   161
        }
Thomas@1770
   162
Thomas@1770
   163
        /// <summary>
Thomas@1770
   164
        /// Locks the decryption stack and tries to push the given entryId.
Thomas@1770
   165
        /// The item is not pushed if the last entryId is the same.
Thomas@1770
   166
        /// </summary>
Thomas@1770
   167
        /// <param name="entryId">The entryId to push to the stack.</param>
Thomas@1770
   168
        /// <returns>True if item was pushed, otherwise false.</returns>
Thomas@1778
   169
        public bool TryPush(string entryId)
Thomas@1770
   170
        {
Thomas@1770
   171
            bool pushed = false;
Thomas@1778
   172
            string currentId = null;
Thomas@1770
   173
Thomas@1770
   174
            if (string.IsNullOrEmpty(entryId))
Thomas@1770
   175
            {
Thomas@1770
   176
                return false;
Thomas@1770
   177
            }
Thomas@1770
   178
Thomas@1770
   179
            // Push item to stack if not already on top of it
Thomas@1770
   180
            lock (mutexDecryptionStack)
Thomas@1770
   181
            {
Thomas@1770
   182
                if (this.Count > 0)
Thomas@1770
   183
                {
Thomas@1770
   184
                    try
Thomas@1770
   185
                    {
Thomas@1770
   186
                        currentId = this.Peek();
Thomas@1770
   187
                    }
Thomas@1770
   188
                    catch (Exception ex)
Thomas@1770
   189
                    {
Thomas@1778
   190
                        Log.Error("DecryptionStack.TryPush: Error peeking item. " + ex.ToString());
Thomas@1770
   191
                    }
Thomas@1770
   192
                }
Thomas@1770
   193
Thomas@1770
   194
                if (entryId?.Equals(currentId) == false)
Thomas@1770
   195
                {
Thomas@1770
   196
                    this.Push(entryId);
Thomas@1770
   197
                    pushed = true;
Thomas@1803
   198
                    Log.Verbose("DecryptionStack.TryPush: pushed entryId " + entryId);
Thomas@1770
   199
                }
Thomas@1770
   200
            }
Thomas@1778
   201
Thomas@1778
   202
            // If item was pushed, enable timer and start decryption
Thomas@1778
   203
            if (pushed &&
Thomas@1778
   204
                this.decryptionTimer.Enabled == false)
Thomas@1778
   205
            {
Thomas@1778
   206
                this.decryptionTimer.Start();
Thomas@1778
   207
                this.DecryptionTimer_Tick(null, null);
Thomas@1803
   208
                Log.Verbose("DecryptionStack.TryPush: decryptionTimer started");
Thomas@1778
   209
            }
Thomas@1778
   210
Thomas@1770
   211
            return pushed;
Thomas@1770
   212
        }
Thomas@1770
   213
Thomas@1770
   214
        /// <summary>
Thomas@1778
   215
        /// Event handler for when the decryption timer has elapsed.
Thomas@1770
   216
        /// </summary>
Thomas@1770
   217
        private void DecryptionTimer_Tick(object sender, EventArgs e)
Thomas@1770
   218
        {
Thomas@1770
   219
            Outlook.MailItem omi = null;
Thomas@2230
   220
            Outlook.MailItem omiCopy = null;
Thomas@1778
   221
            string entryId = null;
Thomas@1770
   222
            CryptableMailItem cmi = null;
Thomas@1770
   223
Thomas@1770
   224
            // Try to increment the decryption counter
Thomas@1770
   225
            if (this.IncrementDecryptionCounter() == false)
Thomas@1770
   226
            {
Thomas@2234
   227
                Log.Verbose("DecryptionStack.DecryptionTimer_Tick: decryption queue full. Items in queue: " + this.Count);
Thomas@1770
   228
                return;
Thomas@1770
   229
            }
Thomas@1770
   230
Thomas@1778
   231
            // Pop item from the decryption stack
Thomas@1778
   232
            if (this.TryPop(out entryId))
Thomas@1770
   233
            {
Thomas@1778
   234
                // Get mail item and set process flag to true
Thomas@1778
   235
                try
Thomas@1770
   236
                {
Thomas@1778
   237
                    omi = Globals.ThisAddIn.Application.Session.GetItemFromID(entryId);
Thomas@1778
   238
                }
Thomas@1778
   239
                catch (Exception ex)
Thomas@1778
   240
                {
Thomas@1778
   241
                    omi = null;
Thomas@1778
   242
                    Log.Error("DecryptionTimer_Tick: Error getting item from ID. " + ex.ToString());
Thomas@1778
   243
                }
Thomas@1770
   244
Thomas@1784
   245
                if ((omi != null) &&
Thomas@2249
   246
                    (omi.GetIsPEPEnabled() || omi.GetIsDecryptAlwaysEnabled()) &&
Thomas@2249
   247
                    (omi.GetIsSMIMEEnabled() == false))
Thomas@1778
   248
                {
Thomas@2230
   249
                    /* In Outlook 2010, mails may arrive with download state OlHeaderOnly, which means we have no attachments at this point.
Thomas@2230
   250
                     * Setting the MarkForDownload can lead to Outlook crashing. Therefore, the workaround here is to create a copy of
Thomas@2230
   251
                     * the mail item, which makes Outlook download the full item.
Thomas@2230
   252
                     */
Thomas@2230
   253
                    if ((Globals.OutlookVersion == Globals.Version.Outlook2010) &&
Thomas@2230
   254
                        (omi.GetIsAutoConsume()) &&
Thomas@2231
   255
                        (omi.DownloadState == Outlook.OlDownloadState.olHeaderOnly) &&
Thomas@2230
   256
                        (omi.GetIsInIMAPStore()) &&
Thomas@2230
   257
                        (copiedItemsList.Contains(omi.EntryID) == false))
Thomas@2230
   258
                    {
Thomas@2230
   259
                        try
Thomas@2230
   260
                        {
Thomas@2230
   261
                            omiCopy = omi.Copy();
Thomas@2230
   262
                            omiCopy.UnRead = false;
Thomas@2230
   263
                            omiCopy.SetPEPProperty(MailItemExtensions.PEPProperty.AutoConsume, "yes");
Thomas@2230
   264
                            copiedItemsList.Add(omi.EntryID);
Thomas@2230
   265
                            copiedItemsList.Add(omiCopy.EntryID);
Thomas@2230
   266
                            Log.Verbose("DecryptionStack.DecryptionTimer_Tick: Copy OMI: " + omi.EntryID + " to: " + omiCopy.EntryID);
Thomas@2230
   267
                        }
Thomas@2230
   268
                        catch (Exception ex)
Thomas@2230
   269
                        {
Thomas@2230
   270
                            omiCopy = null;
Thomas@2230
   271
                            Log.Error("DecryptionStack.DecryptionTimer_Tick: Error during copying mailitem: " + ex.ToString());
Thomas@2230
   272
                        }
Thomas@2230
   273
                    }
Thomas@2230
   274
Thomas@1833
   275
                    /* Forcefully protected messages need a special treatment as it might be needed to open a
Thomas@1833
   276
                     * handshake dialog, which has to happen from the main thread. The constructors of those 
Thomas@1833
   277
                     * dialogs cannot be called from the background worker. 
Thomas@2230
   278
                     */
Thomas@2028
   279
                    if (omi.GetIsForcefullyProtected())
Thomas@1833
   280
                    {
Thomas@1833
   281
                        Log.Verbose("DecryptionStack.DecryptionTimer_Tick: Force Protection Protocol Message found. Processing...");
Thomas@2230
   282
nikolaj@2116
   283
Thomas@2125
   284
                        FPPMessage fppMessage = new FPPMessage(omiCopy ?? omi);
nikolaj@2116
   285
nikolaj@2116
   286
                        try
nikolaj@2116
   287
                        {
nikolaj@2116
   288
                            omiCopy?.Delete();
nikolaj@2116
   289
                        }
nikolaj@2116
   290
                        catch (Exception ex)
nikolaj@2116
   291
                        {
nikolaj@2116
   292
                            Log.Error("DecryptionStack.DecryptionTimer_Tick: Error during deleting copied FPP mailitem: " + ex.ToString());
nikolaj@2116
   293
                        }
nikolaj@2116
   294
Thomas@1833
   295
                        fppMessage.ProcessIncoming();
Thomas@1894
   296
                        if (omi.GetIsAutoConsume())
Thomas@1888
   297
                        {
Thomas@1888
   298
                            omi.UnRead = false;
Thomas@1888
   299
                        }
Thomas@1833
   300
                    }
Thomas@1833
   301
                    else
Thomas@1833
   302
                    {
Thomas@1833
   303
                        // Process item
Thomas@2230
   304
                        cmi = new CryptableMailItem(omiCopy ?? omi, null, true);
Thomas@1778
   305
Thomas@1833
   306
                        /* Note: Make sure to dispose the MailItem after processing (pass true).
Thomas@1833
   307
                         * This is needed as decryption is asynchronous but the MailItem reference still needs to be released.
Thomas@1833
   308
                         * Do NOT use omi or cmi references after this point!
Thomas@1833
   309
                         */
Thomas@1833
   310
                        cmi.StartProcessing(true);
Thomas@1869
   311
                        Log.Verbose("DecryptionStack.DecryptionTimer_Tick: started decryption of item with entryId " + omi?.EntryID);
Thomas@1833
   312
                    }
Thomas@1885
   313
Thomas@1885
   314
                    omi = null;
Thomas@2230
   315
                    omiCopy = null;
Thomas@1770
   316
                }
Thomas@1770
   317
                else
Thomas@1770
   318
                {
Thomas@1778
   319
                    // If we don't process, decrement the decryption counter again
Thomas@1778
   320
                    this.DecrementDecryptionCounter();
Thomas@1803
   321
                    Log.Verbose("DecryptionStack.DecryptionTimer_Tick: item will not be processed");
Thomas@1770
   322
                }
Thomas@1770
   323
            }
Thomas@1802
   324
            else
Thomas@1770
   325
            {
Thomas@1802
   326
                // If no more item is on the stack, stop the timer and set decryption counter to 0
Thomas@1802
   327
                if (this.Count <= 0)
Thomas@1802
   328
                {
Thomas@1802
   329
                    this.ResetDecryptionCounter();
Thomas@1802
   330
                    this.decryptionTimer.Stop();
Thomas@1803
   331
                    Log.Verbose("DecryptionStack.DecryptionTimer_Tick: counter reset and timer stopped");
Thomas@1802
   332
                }
Thomas@1770
   333
            }
Thomas@1770
   334
        }
Thomas@1770
   335
    }
Thomas@1770
   336
}