Add minimalistic protected-headers support
[gpgol.git] / src / ribbon-callbacks.cpp
1 /* ribbon-callbacks.h - Callbacks for the ribbon extension interface
2  * Copyright (C) 2013 Intevation GmbH
3  * Software engineering by Intevation GmbH
4  *
5  * This file is part of GpgOL.
6  *
7  * GpgOL is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * GpgOL is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <windows.h>
26 #include <olectl.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <gdiplus.h>
30
31 #include <objidl.h>
32
33 #include "ribbon-callbacks.h"
34 #include "gpgoladdin.h"
35 #include "common.h"
36
37 #include "mymapi.h"
38 #include "mymapitags.h"
39
40 #include "common.h"
41 #include "mapihelp.h"
42 #include "mimemaker.h"
43 #include "filetype.h"
44 #include "mail.h"
45 #include "dispcache.h"
46 #include "addressbook.h"
47
48 #include <gpgme++/context.h>
49 #include <gpgme++/data.h>
50
51 #undef _
52 #define _(a) utf8_gettext (a)
53
54 using namespace GpgME;
55
56 /* This is so super stupid. I bet even Microsft developers laugh
57    about the definition of VARIANT_BOOL. And then for COM we
58    have to pass pointers to this stuff. */
59 static VARIANT_BOOL var_true = VARIANT_TRUE;
60 static VARIANT_BOOL var_false = VARIANT_FALSE;
61
62 /* Gets the context of a ribbon control. And prints some
63    useful debug output */
64 HRESULT getContext (LPDISPATCH ctrl, LPDISPATCH *context)
65 {
66   *context = get_oom_object (ctrl, "get_Context");
67   if (*context)
68     {
69       char *name = get_object_name (*context);
70       log_debug ("%s:%s: contextObj: %s",
71                  SRCNAME, __func__, name);
72       xfree (name);
73     }
74
75   return context ? S_OK : E_FAIL;
76 }
77
78 /* getIconDisp
79    Loads a PNG image from the resurce converts it into a Bitmap
80    and Wraps it in an PictureDispatcher that is returned as result.
81
82    Based on documentation from:
83    http://www.codeproject.com/Articles/3537/Loading-JPG-PNG-resources-using-GDI
84 */
85 static LPDISPATCH
86 getIconDisp (int id)
87 {
88   PICTDESC pdesc;
89   LPDISPATCH pPict;
90   HRESULT hr;
91   Gdiplus::GdiplusStartupInput gdiplusStartupInput;
92   Gdiplus::Bitmap* pbitmap;
93   ULONG_PTR gdiplusToken;
94   HRSRC hResource;
95   DWORD imageSize;
96   const void* pResourceData;
97   HGLOBAL hBuffer;
98
99   memset (&pdesc, 0, sizeof pdesc);
100   pdesc.cbSizeofstruct = sizeof pdesc;
101   pdesc.picType = PICTYPE_BITMAP;
102
103   /* Initialize GDI */
104   gdiplusStartupInput.DebugEventCallback = NULL;
105   gdiplusStartupInput.SuppressBackgroundThread = FALSE;
106   gdiplusStartupInput.SuppressExternalCodecs = FALSE;
107   gdiplusStartupInput.GdiplusVersion = 1;
108   GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL);
109
110   /* Get the image from the resource file */
111   hResource = FindResource (glob_hinst, MAKEINTRESOURCE(id), RT_RCDATA);
112   if (!hResource)
113     {
114       log_error ("%s:%s: failed to find image: %i",
115                  SRCNAME, __func__, id);
116       return nullptr;
117     }
118
119   imageSize = SizeofResource (glob_hinst, hResource);
120   if (!imageSize)
121     return nullptr;
122
123   pResourceData = LockResource (LoadResource(glob_hinst, hResource));
124
125   if (!pResourceData)
126     {
127       log_error ("%s:%s: failed to load image: %i",
128                  SRCNAME, __func__, id);
129       return nullptr;
130     }
131
132   hBuffer = GlobalAlloc (GMEM_MOVEABLE, imageSize);
133
134   if (hBuffer)
135     {
136       void* pBuffer = GlobalLock (hBuffer);
137       if (pBuffer)
138         {
139           IStream* pStream = NULL;
140           CopyMemory (pBuffer, pResourceData, imageSize);
141
142           if (CreateStreamOnHGlobal (hBuffer, TRUE, &pStream) == S_OK)
143             {
144               memdbg_addRef (pStream);
145               pbitmap = Gdiplus::Bitmap::FromStream (pStream);
146               gpgol_release (pStream);
147               if (!pbitmap || pbitmap->GetHBITMAP (0, &pdesc.bmp.hbitmap))
148                 {
149                   log_error ("%s:%s: failed to get PNG.",
150                              SRCNAME, __func__);
151                 }
152             }
153         }
154       GlobalUnlock (pBuffer);
155     }
156   GlobalFree (hBuffer);
157
158   Gdiplus::GdiplusShutdown (gdiplusToken);
159
160   /* Wrap the image into an OLE object.  */
161   hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp,
162                                  TRUE, (void **) &pPict);
163   if (hr != S_OK || !pPict)
164     {
165       log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n",
166                  SRCNAME, __func__, hr);
167       return nullptr;
168     }
169
170   return pPict;
171 }
172
173 HRESULT
174 getIcon (int id, VARIANT* result)
175 {
176   if (!result)
177     {
178       TRACEPOINT;
179       return S_OK;
180     }
181
182   auto cache = DispCache::instance ();
183   result->pdispVal = cache->getDisp (id);
184
185   if (!result->pdispVal)
186     {
187       result->pdispVal = getIconDisp (id);
188       cache->addDisp (id, result->pdispVal);
189       memdbg_addRef (result->pdispVal);
190     }
191
192   if (result->pdispVal)
193     {
194       result->vt = VT_DISPATCH;
195       result->pdispVal->AddRef();
196     }
197
198   return S_OK;
199 }
200
201 HRESULT
202 mark_mime_action (LPDISPATCH ctrl, int flags, bool is_explorer)
203 {
204   LPMESSAGE message = NULL;
205   int oldflags,
206       newflags;
207
208   log_debug ("%s:%s: enter", SRCNAME, __func__);
209   LPDISPATCH context = NULL;
210   if (FAILED(getContext (ctrl, &context)))
211     {
212       TRACEPOINT;
213       return E_FAIL;
214     }
215
216   LPDISPATCH mailitem = get_oom_object (context,
217                                         is_explorer ? "ActiveInlineResponse" :
218                                                       "CurrentItem");
219   gpgol_release (context);
220
221   if (!mailitem)
222     {
223       log_error ("%s:%s: Failed to get mailitem.",
224                  SRCNAME, __func__);
225       return E_FAIL;
226     }
227
228   /* Get the uid of this item. */
229   char *uid = get_unique_id (mailitem, 0, nullptr);
230   if (!uid)
231     {
232       LPMESSAGE msg = get_oom_base_message (mailitem);
233       uid = mapi_get_uid (msg);
234       gpgol_release (msg);
235       if (!uid)
236         {
237           log_debug ("%s:%s: Failed to get uid for %p",
238                    SRCNAME, __func__, mailitem);
239         }
240     }
241   Mail *mail = nullptr;
242   if (uid)
243     {
244       mail = Mail::getMailForUUID (uid);
245       xfree (uid);
246     }
247
248   if (mail)
249     {
250       mail->setCryptoSelectedManually (true);
251     }
252   else
253     {
254       log_debug ("%s:%s: Failed to get mail object.",
255                  SRCNAME, __func__);
256     }
257
258   message = get_oom_base_message (mailitem);
259   gpgol_release (mailitem);
260
261   if (!message)
262     {
263       log_error ("%s:%s: Failed to get message.",
264                  SRCNAME, __func__);
265       return S_OK;
266     }
267
268   oldflags = get_gpgol_draft_info_flags (message);
269   if (flags == 3 && oldflags != 3)
270     {
271       // If only one sub button is active activate
272       // both now.
273       newflags = 3;
274     }
275   else
276     {
277       newflags = oldflags xor flags;
278     }
279
280   if (set_gpgol_draft_info_flags (message, newflags))
281     {
282       log_error ("%s:%s: Failed to set draft flags.",
283                  SRCNAME, __func__);
284     }
285   gpgol_release (message);
286
287   /*  We need to invalidate the UI to update the toggle
288       states of the subbuttons and the top button. Yeah,
289       we invalidate a lot *sigh* */
290   gpgoladdin_invalidate_ui ();
291
292   if (newflags & 1)
293     {
294       Mail::locateAllCryptoRecipients_o ();
295     }
296
297   return S_OK;
298 }
299
300 /* Get the state of encrypt / sign toggle buttons.
301   flag values: 1 get the state of the encrypt button.
302                2 get the state of the sign button.
303   If is_explorer is set to true we look at the inline response.
304 */
305 HRESULT get_crypt_pressed (LPDISPATCH ctrl, int flags, VARIANT *result,
306                            bool is_explorer)
307 {
308   HRESULT hr;
309   bool value;
310   LPDISPATCH context = NULL,
311              mailitem = NULL;
312   LPMESSAGE message = NULL;
313
314   result->vt = VT_BOOL | VT_BYREF;
315   result->pboolVal = &var_false;
316
317   /* First the usual defensive check about our parameters */
318   if (!ctrl || !result)
319     {
320       log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
321       return E_FAIL;
322     }
323
324   hr = getContext (ctrl, &context);
325
326   if (hr)
327     {
328       log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
329                  hr);
330       return E_FAIL;
331     }
332
333   mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" :
334                                                     "CurrentItem");
335
336   if (!mailitem)
337     {
338       log_error ("%s:%s: Failed to get mailitem.",
339                  SRCNAME, __func__);
340       goto done;
341     }
342
343   message = get_oom_base_message (mailitem);
344
345   if (!message)
346     {
347       log_error ("%s:%s: No message found.",
348                  SRCNAME, __func__);
349       goto done;
350     }
351
352   value = (get_gpgol_draft_info_flags (message) & flags) == flags;
353
354   result->pboolVal = value ? &var_true: &var_false;
355
356 done:
357   gpgol_release (context);
358   gpgol_release (mailitem);
359   gpgol_release (message);
360
361   return S_OK;
362 }
363
364 static Mail *
365 get_mail_from_control (LPDISPATCH ctrl, bool *none_selected)
366 {
367   HRESULT hr;
368   LPDISPATCH context = NULL,
369              mailitem = NULL;
370   *none_selected = false;
371   if (!ctrl)
372     {
373       log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
374       return NULL;
375     }
376   hr = getContext (ctrl, &context);
377
378   if (hr)
379     {
380       log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
381                  hr);
382       return NULL;
383     }
384   char *name = get_object_name (context);
385
386   std::string ctx_name;
387
388   if (name)
389     {
390       ctx_name = name;
391       xfree (name);
392     }
393
394   if (ctx_name.empty())
395     {
396       log_error ("%s:%s: Failed to get context name",
397                  SRCNAME, __func__);
398       gpgol_release (context);
399       return NULL;
400     }
401
402   if (!strcmp (ctx_name.c_str(), "_Inspector"))
403     {
404       mailitem = get_oom_object (context, "CurrentItem");
405     }
406   else if (!strcmp (ctx_name.c_str(), "_Explorer"))
407     {
408       /* Avoid showing wrong crypto state if we don't have a reading
409          pane. In that case the parser will finish for a mail which is gone
410          and the crypto state will not get updated. */
411
412       if (!is_preview_pane_visible (context))
413         {
414           *none_selected = true;
415           gpgol_release (mailitem);
416           mailitem = nullptr;
417           log_debug ("%s:%s: Preview pane invisible", SRCNAME, __func__);
418         }
419
420 #if 0
421       if (g_ol_version_major >= 16)
422         {
423           /* Some Versions of Outlook 2016 crashed when accessing the current view
424              of the Explorer. This was even reproducible with
425              GpgOL disabled and only with Outlook Spy active. If you selected
426              the explorer of an Outlook.com resource and then access
427              the CurrentView and close the CurrentView again in Outlook Spy
428              outlook crashes. See: T3484
429
430              The crash no longer occured at least since build 10228. As
431              I'm not sure which Version fixed the crash we don't do a version
432              check and just use the same codepath as for Outlook 2010 and
433              2013 again.
434
435              Accessing PreviewPane.WordEditor is not a good solution here as
436              it requires "Microsoft VBA for Office" (See T4056 ).
437              A possible solution for that might be to check if
438              "Mail.GetInspector().WordEditor()" returns NULL. In that case we
439              know that we also won't get a WordEditor in the preview pane. */
440           LPDISPATCH prevEdit = get_oom_object (context, "PreviewPane.WordEditor");
441           gpgol_release (prevEdit);
442           if (!prevEdit)
443             {
444               *none_selected = true;
445               gpgol_release (mailitem);
446               mailitem = nullptr;
447             }
448         }
449       else
450         {
451           // Preview Pane is not available in older outlooks
452           LPDISPATCH tableView = get_oom_object (context, "CurrentView");
453           if (!tableView)
454             {
455               // Woops, should not happen.
456               TRACEPOINT;
457               *none_selected = true;
458               gpgol_release (mailitem);
459               mailitem = nullptr;
460             }
461           else
462             {
463               int hasReadingPane = get_oom_bool (tableView, "ShowReadingPane");
464               gpgol_release (tableView);
465               if (!hasReadingPane)
466                 {
467                   *none_selected = true;
468                   gpgol_release (mailitem);
469                   mailitem = nullptr;
470                 }
471             }
472         }
473 #endif
474
475       if (!*none_selected)
476         {
477           /* Accessing the selection item can trigger a load event
478              so we only do this here if we think that there might be
479              something visible / selected. To avoid triggering a load
480              if there is no content shown. */
481           LPDISPATCH selection = get_oom_object (context, "Selection");
482           if (!selection)
483             {
484               log_error ("%s:%s: Failed to get selection.",
485                          SRCNAME, __func__);
486               gpgol_release (context);
487               return NULL;
488             }
489           int count = get_oom_int (selection, "Count");
490           if (count == 1)
491             {
492               // If we call this on a selection with more items
493               // Outlook sends an ItemLoad event for each mail
494               // in that selection.
495               mailitem = get_oom_object (selection, "Item(1)");
496             }
497           gpgol_release (selection);
498
499           if (!mailitem)
500             {
501               *none_selected = true;
502             }
503         }
504     }
505   else if (!strcmp (ctx_name.c_str(), "Selection"))
506     {
507       int count = get_oom_int (context, "Count");
508       if (count == 1)
509         {
510           // If we call this on a selection with more items
511           // Outlook sends an ItemLoad event for each mail
512           // in that selection.
513           mailitem = get_oom_object (context, "Item(1)");
514         }
515       if (!mailitem)
516         {
517           *none_selected = true;
518         }
519     }
520
521   gpgol_release (context);
522   if (!mailitem)
523     {
524       log_debug ("%s:%s: No mailitem. From %s",
525                  SRCNAME, __func__, ctx_name.c_str());
526       return NULL;
527     }
528
529   char *uid;
530   /* Get the uid of this item. */
531   uid = get_unique_id (mailitem, 0, nullptr);
532   if (!uid)
533     {
534       LPMESSAGE msg = get_oom_base_message (mailitem);
535       if (!msg)
536         {
537           log_debug ("%s:%s: Failed to get message for %p",
538                    SRCNAME, __func__, mailitem);
539           gpgol_release (mailitem);
540           return NULL;
541         }
542       uid = mapi_get_uid (msg);
543       gpgol_release (msg);
544       if (!uid)
545         {
546           log_debug ("%s:%s: Failed to get uid for %p",
547                    SRCNAME, __func__, mailitem);
548           gpgol_release (mailitem);
549           return NULL;
550         }
551     }
552
553   auto ret = Mail::getMailForUUID (uid);
554   xfree (uid);
555   if (!ret)
556     {
557       log_error ("%s:%s: Failed to find mail %p in map.",
558                  SRCNAME, __func__, mailitem);
559     }
560   gpgol_release (mailitem);
561   return ret;
562 }
563
564 /* Helper to reduce code duplication.*/
565 #define MY_MAIL_GETTER \
566   if (!ctrl) \
567     { \
568       log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); \
569       return E_FAIL; \
570     } \
571   bool none_selected; \
572   const auto mail = get_mail_from_control (ctrl, &none_selected); \
573   (void)none_selected; \
574   if (!mail) \
575     { \
576       log_oom ("%s:%s:%i Failed to get mail", \
577                SRCNAME, __func__, __LINE__); \
578     }
579
580 HRESULT get_is_details_enabled (LPDISPATCH ctrl, VARIANT *result)
581 {
582   MY_MAIL_GETTER
583
584   if (!result)
585     {
586       TRACEPOINT;
587       return S_OK;
588     }
589
590   result->vt = VT_BOOL | VT_BYREF;
591   result->pboolVal = none_selected ? &var_false : &var_true;
592
593   TRACEPOINT;
594   return S_OK;
595 }
596
597 HRESULT get_sig_label (LPDISPATCH ctrl, VARIANT *result)
598 {
599   MY_MAIL_GETTER
600
601   result->vt = VT_BSTR;
602   wchar_t *w_result;
603   if (!mail)
604     {
605       log_debug ("%s:%s: No mail.",
606                  SRCNAME, __func__);
607       w_result = utf8_to_wchar (_("Insecure"));
608       result->bstrVal = SysAllocString (w_result);
609       xfree (w_result);
610       return S_OK;
611     }
612   w_result = utf8_to_wchar (mail->getCryptoSummary ().c_str ());
613   result->bstrVal = SysAllocString (w_result);
614   xfree (w_result);
615   TRACEPOINT;
616   return S_OK;
617 }
618
619 HRESULT get_sig_ttip (LPDISPATCH ctrl, VARIANT *result)
620 {
621   MY_MAIL_GETTER
622
623   result->vt = VT_BSTR;
624   wchar_t *w_result;
625   if (mail)
626     {
627       w_result = utf8_to_wchar (mail->getCryptoOneLine ().c_str());
628     }
629   else if (!none_selected)
630     {
631       w_result = utf8_to_wchar (_("Insecure message"));
632     }
633   else
634     {
635       w_result = utf8_to_wchar (_("No message selected"));
636     }
637   result->bstrVal = SysAllocString (w_result);
638   xfree (w_result);
639   TRACEPOINT;
640   return S_OK;
641 }
642
643 HRESULT get_sig_stip (LPDISPATCH ctrl, VARIANT *result)
644 {
645   MY_MAIL_GETTER
646
647   result->vt = VT_BSTR;
648   if (none_selected)
649     {
650       result->bstrVal = SysAllocString (L"");
651       TRACEPOINT;
652       return S_OK;
653     }
654   if (!mail || !mail->isCryptoMail ())
655     {
656       wchar_t *w_result;
657       w_result = utf8_to_wchar (utf8_gettext ("You cannot be sure who sent, "
658                                   "modified and read the message in transit.\n\n"
659                                   "Click here to learn more."));
660       result->bstrVal = SysAllocString (w_result);
661       xfree (w_result);
662       TRACEPOINT;
663       return S_OK;
664     }
665   const auto message = mail->getCryptoDetails_o ();
666   wchar_t *w_message = utf8_to_wchar (message.c_str());
667   result->bstrVal = SysAllocString (w_message);
668   xfree (w_message);
669   TRACEPOINT;
670   return S_OK;
671 }
672
673 HRESULT launch_cert_details (LPDISPATCH ctrl)
674 {
675   MY_MAIL_GETTER
676
677   if (!mail || (!mail->isSigned () && !mail->isEncrypted ()))
678     {
679       ShellExecuteA(NULL, NULL, "https://emailselfdefense.fsf.org/en/infographic.html",
680                     0, 0, SW_SHOWNORMAL);
681       return S_OK;
682     }
683
684   if (!mail->isSigned () && mail->isEncrypted ())
685     {
686       /* Encrypt only, no information but show something. because
687          we want the button to be active.
688
689          Aheinecke: I don't think we should show to which keys the message
690          is encrypted here. This would confuse users if they see keyids
691          of unknown keys and the information can't be "true" because the
692          sender could have sent the same information to other people or
693          used throw keyids etc.
694          */
695       char * buf;
696       gpgrt_asprintf (&buf, _("The message was not cryptographically signed.\n"
697                       "There is no additional information available if it "
698                       "was actually sent by '%s' or if someone faked the sender address."), mail->getSender_o ().c_str());
699       wchar_t *w_msg = utf8_to_wchar (buf);
700       wchar_t *w_title = utf8_to_wchar (_("GpgOL"));
701       MessageBoxW (NULL, w_msg, w_title,
702                    MB_ICONINFORMATION|MB_OK);
703       xfree (buf);
704       xfree (w_msg);
705       xfree (w_title);
706       return S_OK;
707     }
708
709   if (!mail->getSigFpr ())
710     {
711       std::string buf = _("There was an error verifying the signature.\n"
712                            "Full details:\n");
713       buf += mail->getVerificationResultDump ();
714       gpgol_message_box (get_active_hwnd(), buf.c_str(), _("GpgOL"), MB_OK);
715     }
716
717   char *uiserver = get_uiserver_name ();
718   bool showError = false;
719   if (uiserver)
720     {
721       std::string path (uiserver);
722       xfree (uiserver);
723       if (path.find("kleopatra.exe") != std::string::npos)
724         {
725         size_t dpos;
726         if ((dpos = path.find(" --daemon")) != std::string::npos)
727             {
728               path.erase(dpos, strlen(" --daemon"));
729             }
730           auto ctx = Context::createForEngine(SpawnEngine);
731           if (!ctx)
732             {
733               log_error ("%s:%s: No spawn engine.",
734                          SRCNAME, __func__);
735             }
736             std::string parentWid = std::to_string ((int) (intptr_t) get_active_hwnd ());
737             const char *argv[] = {path.c_str(),
738                                   "--query",
739                                   mail->getSigFpr (),
740                                   "--parent-windowid",
741                                   parentWid.c_str(),
742                                   NULL };
743             log_debug ("%s:%s: Starting %s %s %s",
744                        SRCNAME, __func__, path.c_str(), argv[1], argv[2]);
745             Data d(Data::null);
746             ctx->spawnAsync(path.c_str(), argv, d, d,
747                             d, (GpgME::Context::SpawnFlags) (
748                                 GpgME::Context::SpawnAllowSetFg |
749                                 GpgME::Context::SpawnShowWindow));
750         }
751       else
752         {
753           showError = true;
754         }
755     }
756   else
757     {
758       showError = true;
759     }
760
761   if (showError)
762     {
763       wchar_t *w_title = utf8_to_wchar (_("GpgOL"));
764       wchar_t *w_msg = utf8_to_wchar (_("Could not find Kleopatra.\n"
765                   "Please reinstall Gpg4win with the Kleopatra component enabled."));
766       MessageBoxW (NULL, w_msg, w_title,
767                    MB_ICONINFORMATION|MB_OK);
768       xfree (w_title);
769       xfree (w_msg);
770     }
771   return S_OK;
772 }
773
774 HRESULT get_crypto_icon (LPDISPATCH ctrl, VARIANT *result)
775 {
776   MY_MAIL_GETTER
777
778   if (mail)
779     {
780       TRACEPOINT;
781       return getIcon (mail->getCryptoIconID (), result);
782     }
783   TRACEPOINT;
784   return getIcon (IDI_LEVEL_0, result);
785 }
786
787 HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result)
788 {
789   MY_MAIL_GETTER
790
791   result->vt = VT_BOOL | VT_BYREF;
792   result->pboolVal = mail && (mail->isSigned () || mail->isEncrypted ()) ?
793                           &var_true : &var_false;
794
795   TRACEPOINT;
796   return S_OK;
797 }
798
799 HRESULT print_decrypted (LPDISPATCH ctrl)
800 {
801   MY_MAIL_GETTER
802
803   if (!mail)
804     {
805       log_error ("%s:%s: Failed to get mail.",
806                  SRCNAME, __func__);
807       return S_OK;
808     }
809   invoke_oom_method (mail->item(), "PrintOut", NULL);
810   return S_OK;
811 }
812
813 HRESULT open_contact_key (LPDISPATCH ctrl)
814 {
815   if (!ctrl)
816     {
817       log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
818       return E_FAIL;
819     }
820   LPDISPATCH inspector = NULL;
821   HRESULT hr = getContext (ctrl, &inspector);
822
823   if (hr)
824     {
825       log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
826                  hr);
827       return S_OK;
828     }
829
830   /* Context is assumed to be the Insepector */
831   LPDISPATCH contact = get_oom_object (inspector, "CurrentItem");
832   gpgol_release (inspector);
833
834   if (!contact)
835     {
836       TRACEPOINT;
837       return S_OK;
838     }
839
840   Addressbook::edit_key_o (contact);
841
842   gpgol_release (contact);
843   return S_OK;
844 }
845
846 HRESULT override_file_close ()
847 {
848   TSTART;
849   auto inst = GpgolAddin::get_instance ();
850   /* We need to get it first as shutdown releases the reference */
851   auto app = inst->get_application ();
852   app->AddRef ();
853   inst->shutdown();
854   log_debug ("%s:%s: Shutdown complete. Quitting.",
855              SRCNAME, __func__);
856   invoke_oom_method (app, "Quit", nullptr);
857
858   TRETURN S_OK;
859 }