New code to install forms so that appropriate icons are shown in the Viewer.
[gpgol.git] / src / ext-commands.cpp
1 /* ext-commands.cpp - Subclass impl of IExchExtCommands
2  *      Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH
3  * 
4  * This file is part of GpgOL.
5  * 
6  * GpgOL is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  * 
11  * GpgOL is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  * 
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #define _WIN32_IE 0x400 /* Need TBIF_COMMAND et al.  */
25 #include <windows.h>
26
27 #include "mymapi.h"
28 #include "mymapitags.h"
29 #include "myexchext.h"
30 #include "common.h"
31 #include "display.h"
32 #include "msgcache.h"
33 #include "mapihelp.h"
34
35 #include "dialogs.h"       /* For IDB_foo. */
36 #include "olflange-def.h"
37 #include "olflange.h"
38 #include "ol-ext-callback.h"
39 #include "message.h"
40 #include "engine.h"
41 #include "ext-commands.h"
42 #include "revert.h"
43
44 #define TRACEPOINT() do { log_debug ("%s:%s:%d: tracepoint\n", \
45                                      SRCNAME, __func__, __LINE__); \
46                         } while (0)
47
48 /* An object to store information about active (installed) toolbar
49    buttons.  */
50 struct toolbar_info_s
51 {
52   toolbar_info_t next;
53
54   UINT button_id;/* The ID of the button as assigned by Outlook.  */
55   UINT bitmap;   /* The bitmap of the button.  */
56   UINT cmd_id;   /* The ID of the command to send on a click.  */
57   const char *desc;/* The description text.  */
58   ULONG context; /* Context under which this entry will be used.  */ 
59   int did_qbi;   /* Has been processed by QueryButtonInfo.  */
60 };
61
62
63 /* Keep copies of some bitmaps.  */
64 static int bitmaps_initialized;
65 static HBITMAP my_check_bitmap, my_uncheck_bitmap;
66
67
68
69 static void add_menu (LPEXCHEXTCALLBACK eecb, 
70                       UINT FAR *pnCommandIDBase, ...)
71 #if __GNUC__ >= 4 
72                                __attribute__ ((sentinel))
73 #endif
74   ;
75
76
77
78
79 /* Wrapper around UlRelease with error checking. */
80 static void 
81 ul_release (LPVOID punk, const char *func, int lnr)
82 {
83   ULONG res;
84   
85   if (!punk)
86     return;
87   res = UlRelease (punk);
88   if (opt.enable_debug & DBG_MEMORY)
89     log_debug ("%s:%s:%d: UlRelease(%p) had %lu references\n", 
90                SRCNAME, func, lnr, punk, res);
91 }
92
93
94
95 /* Constructor */
96 GpgolExtCommands::GpgolExtCommands (GpgolExt* pParentInterface)
97
98   m_pExchExt = pParentInterface; 
99   m_lRef = 0; 
100   m_lContext = 0; 
101   m_nCmdProtoAuto = 0;
102   m_nCmdProtoPgpmime = 0;
103   m_nCmdProtoSmime = 0;
104   m_nCmdEncrypt = 0;  
105   m_nCmdSign = 0; 
106   m_nCmdKeyManager = 0;
107   m_nCmdRevertFolder = 0;
108   m_nCmdCryptoState = 0;
109   m_nCmdDebug0 = 0;
110   m_nCmdDebug1 = 0;
111   m_nCmdDebug2 = 0;
112   m_nCmdDebug3 = 0;
113   m_toolbar_info = NULL; 
114   m_hWnd = NULL; 
115
116   if (!bitmaps_initialized)
117     {
118       my_uncheck_bitmap = get_system_check_bitmap (0);
119       my_check_bitmap = get_system_check_bitmap (1);
120       bitmaps_initialized = 1;
121     }
122 }
123
124 /* Destructor */
125 GpgolExtCommands::~GpgolExtCommands (void)
126 {
127   while (m_toolbar_info)
128     {
129       toolbar_info_t tmp = m_toolbar_info->next;
130       xfree (m_toolbar_info);
131       m_toolbar_info = tmp;
132     }
133 }
134
135
136
137 STDMETHODIMP 
138 GpgolExtCommands::QueryInterface (REFIID riid, LPVOID FAR * ppvObj)
139 {
140     *ppvObj = NULL;
141     if ((riid == IID_IExchExtCommands) || (riid == IID_IUnknown)) {
142         *ppvObj = (LPVOID)this;
143         AddRef ();
144         return S_OK;
145     }
146     return E_NOINTERFACE;
147 }
148
149
150 /* Add a new menu.  The variable entries are made up of pairs of
151    strings and UINT *.  A NULL is used to terminate this list.  An
152    empty string is translated to a separator menu item.  One level of
153    submenus are supported. */
154 static void
155 add_menu (LPEXCHEXTCALLBACK eecb, UINT FAR *pnCommandIDBase, ...)
156 {
157   va_list arg_ptr;
158   HMENU mainmenu, submenu, menu;
159   const char *string;
160   UINT *cmdptr;
161   
162   va_start (arg_ptr, pnCommandIDBase);
163   /* We put all new entries into the tools menu.  To make this work we
164      need to pass the id of an existing item from that menu.  */
165   eecb->GetMenuPos (EECMDID_ToolsCustomizeToolbar, &mainmenu, NULL, NULL, 0);
166   menu = mainmenu;
167   submenu = NULL;
168   while ( (string = va_arg (arg_ptr, const char *)) )
169     {
170       cmdptr = va_arg (arg_ptr, UINT*);
171
172       if (!*string)
173         ; /* Ignore this entry.  */
174       else if (*string == '@' && !string[1])
175         AppendMenu (menu, MF_SEPARATOR, 0, NULL);
176       else if (*string == '>')
177         {
178           submenu = CreatePopupMenu ();
179           AppendMenu (menu, MF_STRING|MF_POPUP, (UINT_PTR)submenu, string+1);
180           menu = submenu;
181         }
182       else if (*string == '<')
183         {
184           menu = mainmenu;
185           submenu = NULL;
186         }
187       else
188         {
189           AppendMenu (menu, MF_STRING, *pnCommandIDBase, string);
190           if (menu == submenu)
191             SetMenuItemBitmaps (menu, *pnCommandIDBase, MF_BYCOMMAND,
192                                 my_uncheck_bitmap, my_check_bitmap);
193           if (cmdptr)
194             *cmdptr = *pnCommandIDBase;
195           (*pnCommandIDBase)++;
196         }
197     }
198   va_end (arg_ptr);
199 }
200
201
202 static void
203 check_menu (LPEXCHEXTCALLBACK eecb, UINT menu_id, int checked)
204 {
205   HMENU menu;
206
207   eecb->GetMenuPos (EECMDID_ToolsCustomizeToolbar, &menu, NULL, NULL, 0);
208   if (debug_commands)
209     log_debug ("check_menu: eecb=%p menu_id=%u checked=%d -> menu=%p\n", 
210                eecb, menu_id, checked, menu);
211   CheckMenuItem (menu, menu_id, 
212                  MF_BYCOMMAND | (checked?MF_CHECKED:MF_UNCHECKED));
213 }
214
215
216 static void
217 check_toolbar (LPEXCHEXTCALLBACK eecb, struct toolbar_info_s *toolbar_info,
218                UINT cmd_id, int checked)
219 {
220   HWND hwnd;
221   toolbar_info_t tb_info;
222   TBBUTTONINFOA tbb;
223
224   eecb->GetToolbar (EETBID_STANDARD, &hwnd);
225   if (debug_commands)
226     log_debug ("check_toolbar: eecb=%p cmd_id=%u checked=%d -> hwnd=%p\n", 
227                eecb, cmd_id, checked, hwnd);
228
229   for (tb_info = toolbar_info; tb_info; tb_info = tb_info->next )
230     if (tb_info->cmd_id == cmd_id)
231       break;
232   if (!tb_info)
233     {
234       log_error ("check_toolbar: no such toolbar button");
235       return;
236     }
237   if (!tb_info->did_qbi)
238     {
239       if(debug_commands)
240         log_debug ("check_toolbar: button(cmd_id=%d) not yet initialized",
241                    cmd_id);
242       return;
243     }
244
245   tbb.cbSize = sizeof (tbb);
246   tbb.dwMask = TBIF_COMMAND | TBIF_STATE | TBIF_STYLE;
247   if (!SendMessage (hwnd, TB_GETBUTTONINFO, cmd_id, (LPARAM)&tbb))
248     log_error_w32 (-1, "TB_GETBUTTONINFO failed");
249   else
250     {
251       tbb.cbSize = sizeof (tbb);
252       tbb.dwMask = TBIF_STATE;
253       if (checked)
254         tbb.fsState |= TBSTATE_CHECKED;
255       else
256         tbb.fsState &= ~TBSTATE_CHECKED;
257       if (!SendMessage (hwnd, TB_SETBUTTONINFO, cmd_id, (LPARAM)&tbb))
258         log_error_w32 (-1, "TB_SETBUTTONINFO failed");
259     }
260 }
261
262
263 static void
264 check_menu_toolbar (LPEXCHEXTCALLBACK eecb,
265                     struct toolbar_info_s *toolbar_info,
266                     UINT cmd_id, int checked)
267 {
268   check_menu (eecb, cmd_id, checked);
269   check_toolbar (eecb, toolbar_info, cmd_id, checked);
270 }
271
272
273 void
274 GpgolExtCommands::update_protocol_menu (LPEXCHEXTCALLBACK eecb)
275 {
276   if (debug_commands)
277     log_debug ("update_protocol_menu called\n");
278   switch (m_pExchExt->m_protoSelection)
279     {
280     case PROTOCOL_OPENPGP:
281       check_menu_toolbar (eecb, m_toolbar_info, m_nCmdProtoAuto, FALSE);
282       check_menu_toolbar (eecb, m_toolbar_info, m_nCmdProtoPgpmime, TRUE);
283       check_menu_toolbar (eecb, m_toolbar_info, m_nCmdProtoSmime, FALSE);
284       break;
285     case PROTOCOL_SMIME:
286       check_menu_toolbar (eecb, m_toolbar_info, m_nCmdProtoAuto, FALSE);
287       check_menu_toolbar (eecb, m_toolbar_info, m_nCmdProtoPgpmime, FALSE);
288       check_menu_toolbar (eecb, m_toolbar_info, m_nCmdProtoSmime, TRUE);
289       break;
290     default:
291       check_menu_toolbar (eecb, m_toolbar_info, m_nCmdProtoAuto, TRUE);
292       check_menu_toolbar (eecb, m_toolbar_info, m_nCmdProtoPgpmime, FALSE);
293       check_menu_toolbar (eecb, m_toolbar_info, m_nCmdProtoSmime, FALSE);
294       break;
295     }
296 }
297
298
299 void
300 GpgolExtCommands::add_toolbar (LPTBENTRY tbearr, UINT n_tbearr, ...)
301 {
302   va_list arg_ptr;
303   const char *desc;
304   UINT bmapid;
305   UINT cmdid;
306   int tbeidx;
307   toolbar_info_t tb_info;
308   int rc;
309
310   for (tbeidx = n_tbearr-1; tbeidx > -1; tbeidx--)
311     if (tbearr[tbeidx].tbid == EETBID_STANDARD)
312       break;
313   if (!(tbeidx > -1))
314     {
315       log_error ("standard toolbar not found");
316       return;
317     }
318   
319   SendMessage (tbearr[tbeidx].hwnd, TB_BUTTONSTRUCTSIZE,
320                (WPARAM)(int)sizeof (TBBUTTON), 0);
321
322   
323   va_start (arg_ptr, n_tbearr);
324
325   while ( (desc = va_arg (arg_ptr, const char *)) )
326     {
327       bmapid = va_arg (arg_ptr, UINT);
328       cmdid = va_arg (arg_ptr, UINT);
329
330       if (!*desc)
331         ; /* Empty description - ignore this item.  */
332       else if (*desc == '|' && !desc[1])
333         {
334           /* Separator. Ignore BMAPID and CMDID.  */
335           /* Not yet implemented.  */
336         }
337       else
338         {
339           TBADDBITMAP tbab;
340   
341           tb_info = (toolbar_info_t)xcalloc (1, sizeof *tb_info);
342           tb_info->button_id = tbearr[tbeidx].itbbBase++;
343
344           tbab.hInst = glob_hinst;
345           tbab.nID = bmapid;
346           rc = SendMessage (tbearr[tbeidx].hwnd, TB_ADDBITMAP,1,(LPARAM)&tbab);
347           if (rc == -1)
348             log_error_w32 (-1, "TB_ADDBITMAP failed for `%s'", desc);
349           tb_info->bitmap = rc;
350           tb_info->cmd_id = cmdid;
351           tb_info->desc = desc;
352           tb_info->context = m_lContext;
353
354           tb_info->next = m_toolbar_info;
355           m_toolbar_info = tb_info;
356           if (debug_commands)
357             log_debug ("%s:%s: ctx=%lx button_id=%d cmd_id=%d '%s'\n", 
358                        SRCNAME, __func__, m_lContext,
359                        tb_info->button_id, tb_info->cmd_id, tb_info->desc);
360         }
361     }
362   va_end (arg_ptr);
363
364 }
365
366
367
368
369 /* Called by Exchange to install commands and toolbar buttons.  Returns
370    S_FALSE to signal Exchange to continue calling extensions. */
371 STDMETHODIMP 
372 GpgolExtCommands::InstallCommands (
373         LPEXCHEXTCALLBACK eecb, // The Exchange Callback Interface.
374         HWND hWnd,               // The window handle to the main window
375                                  // of context.
376         HMENU hMenu,             // The menu handle to main menu of context.
377         UINT FAR *pnCommandIDBase,  // The base command id.
378         LPTBENTRY pTBEArray,     // The array of toolbar button entries.
379         UINT nTBECnt,            // The count of button entries in array.
380         ULONG lFlags)            // reserved
381 {
382   HRESULT hr;
383   m_hWnd = hWnd;
384   LPDISPATCH pDisp;
385   DISPID dispid;
386   DISPID dispid_put = DISPID_PROPERTYPUT;
387   DISPPARAMS dispparams;
388   VARIANT aVariant;
389   int force_encrypt = 0;
390
391   
392   if (debug_commands)
393     log_debug ("%s:%s: context=%s flags=0x%lx\n", SRCNAME, __func__, 
394                ext_context_name (m_lContext), lFlags);
395
396
397   /* Outlook 2003 sometimes displays the plaintext and sometimes the
398      original undecrypted text when doing a reply.  This seems to
399      depend on the size of the message; my guess it that only short
400      messages are locally saved in the process and larger ones are
401      fetched again from the backend - or the other way around.
402      Anyway, we can't rely on that and thus me make sure to update the
403      Body object right here with our own copy of the plaintext.  To
404      match the text we use the ConversationIndex property.
405
406      Unfortunately there seems to be no way of resetting the saved
407      property after updating the body, thus even without entering a
408      single byte the user will be asked when cancelling a reply
409      whether he really wants to do that.
410
411      Note, that we can't optimize the code here by first reading the
412      body because this would pop up the security window, telling the
413      user that someone is trying to read this data.
414   */
415   if (m_lContext == EECONTEXT_SENDNOTEMESSAGE)
416     {
417       LPMDB mdb = NULL;
418       LPMESSAGE message = NULL;
419       
420       /*  Note that for read and send the object returned by the
421           outlook extension callback is of class 43 (MailItem) so we
422           only need to ask for Body then. */
423       hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
424       if (FAILED(hr))
425         log_debug ("%s:%s: getObject failed: hr=%#lx\n", SRCNAME,__func__,hr);
426       else if (!opt.compat.no_msgcache)
427         {
428           const char *body;
429           char *key = NULL;
430           size_t keylen = 0;
431           void *refhandle = NULL;
432      
433           pDisp = find_outlook_property (eecb, "ConversationIndex", &dispid);
434           if (pDisp)
435             {
436               DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
437
438               aVariant.bstrVal = NULL;
439               hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
440                                   DISPATCH_PROPERTYGET, &dispparamsNoArgs,
441                                   &aVariant, NULL, NULL);
442               if (hr != S_OK)
443                 log_debug ("%s:%s: retrieving ConversationIndex failed: %#lx",
444                            SRCNAME, __func__, hr);
445               else if (aVariant.vt != VT_BSTR)
446                 log_debug ("%s:%s: ConversationIndex is not a string (%d)",
447                            SRCNAME, __func__, aVariant.vt);
448               else if (aVariant.bstrVal)
449                 {
450                   char *p;
451
452                   key = wchar_to_utf8 (aVariant.bstrVal);
453                   log_debug ("%s:%s: ConversationIndex is `%s'",
454                            SRCNAME, __func__, key);
455                   /* The key is a hex string.  Convert it to binary. */
456                   for (keylen=0,p=key; hexdigitp(p) && hexdigitp(p+1); p += 2)
457                     ((unsigned char*)key)[keylen++] = xtoi_2 (p);
458                   
459                   SysFreeString (aVariant.bstrVal);
460                 }
461
462               pDisp->Release();
463               pDisp = NULL;
464             }
465           
466           if (key && keylen
467               && (body = msgcache_get (key, keylen, &refhandle)) 
468               && (pDisp = find_outlook_property (eecb, "Body", &dispid)))
469             {
470               dispparams.cNamedArgs = 1;
471               dispparams.rgdispidNamedArgs = &dispid_put;
472               dispparams.cArgs = 1;
473               dispparams.rgvarg = &aVariant;
474               dispparams.rgvarg[0].vt = VT_LPWSTR;
475               dispparams.rgvarg[0].bstrVal = utf8_to_wchar (body);
476               hr = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
477                                  DISPATCH_PROPERTYPUT, &dispparams,
478                                  NULL, NULL, NULL);
479               xfree (dispparams.rgvarg[0].bstrVal);
480               log_debug ("%s:%s: PROPERTYPUT(body) result -> %#lx\n",
481                          SRCNAME, __func__, hr);
482
483               pDisp->Release();
484               pDisp = NULL;
485               
486               /* Because we found the plaintext in the cache we can assume
487                  that the orginal message has been encrypted and thus we
488                  now set a flag to make sure that by default the reply
489                  gets encrypted too. */
490               force_encrypt = 1;
491             }
492           msgcache_unref (refhandle);
493           xfree (key);
494         }
495       
496       ul_release (message, __func__, __LINE__);
497       ul_release (mdb, __func__, __LINE__);
498     }
499
500   /* Now install menu and toolbar items.  */
501   if (m_lContext == EECONTEXT_READNOTEMESSAGE)
502     {
503       int is_encrypted = 0;
504       LPMDB mdb = NULL;
505       LPMESSAGE message = NULL;
506
507       hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
508       if (FAILED(hr))
509         log_debug ("%s:%s: getObject failed: hr=%#lx\n", SRCNAME, __func__, hr);
510       else
511         {
512           switch (mapi_get_message_type (message))
513             {
514             case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
515             case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
516             case MSGTYPE_GPGOL_PGP_MESSAGE:
517               is_encrypted = 1;
518               break;
519             default:
520               break;
521             }
522         }
523       ul_release (message, __func__, __LINE__);
524       ul_release (mdb, __func__, __LINE__);
525       
526       /* We always enable the verify button as it might be useful on
527          an already decrypted message. */
528       add_menu (eecb, pnCommandIDBase,
529         "@", NULL,
530         opt.disable_gpgol? "":_("GpgOL Decrypt/Verify"), &m_nCmdCryptoState,
531         opt.enable_debug? "GpgOL Debug-0 (display crypto info)":"", 
532                 &m_nCmdDebug0,
533         (opt.enable_debug && !opt.disable_gpgol)?
534                 "GpgOL Debug-1 (open_inspector)":"", &m_nCmdDebug1,
535         (opt.enable_debug && !opt.disable_gpgol)? 
536                 "GpgOL Debug-2 (change msg class)":"", &m_nCmdDebug2,
537         opt.enable_debug? "GpgOL Debug-3 (revert message class)":"",
538                 &m_nCmdDebug3,
539         NULL);
540
541       if ( !opt.disable_gpgol)
542         add_toolbar (pTBEArray, nTBECnt, 
543                      is_encrypted
544                      ? _("This is an encrypted message.\n"
545                          "Click for more information. ")
546                      : _("This is a signed message.\n"
547                        "Click for more information. "),
548                      IDB_CRYPTO_STATE, m_nCmdCryptoState,
549                      NULL, 0, 0);
550
551     }
552   else if (m_lContext == EECONTEXT_SENDNOTEMESSAGE && !opt.disable_gpgol) 
553     {
554       add_menu (eecb, pnCommandIDBase,
555         "@", NULL,
556         _(">GnuPG protocol"), NULL,
557         _("auto"),   &m_nCmdProtoAuto,        
558         _("PGP/MIME"),&m_nCmdProtoPgpmime,        
559         _("S/MIME"), &m_nCmdProtoSmime,        
560           "<", NULL,
561         _("&encrypt message with GnuPG"), &m_nCmdEncrypt,
562         _("&sign message with GnuPG"), &m_nCmdSign,
563         NULL );
564       
565       add_toolbar (pTBEArray, nTBECnt,
566                    "Encrypt", IDB_ENCRYPT, m_nCmdEncrypt,
567                    "Sign",    IDB_SIGN,    m_nCmdSign,
568                    "Autoselect", IDB_PROTO_AUTO, m_nCmdProtoAuto,
569                    "Use PGP/MIME", IDB_PROTO_PGPMIME, m_nCmdProtoPgpmime,
570                    "Use/MIME", IDB_PROTO_SMIME, m_nCmdProtoSmime,
571                    NULL, 0, 0);
572
573       m_pExchExt->m_protoSelection = opt.default_protocol;
574       update_protocol_menu (eecb);
575
576       m_pExchExt->m_gpgEncrypt = opt.encrypt_default;
577
578       m_pExchExt->m_gpgSign    = opt.sign_default;
579       if (force_encrypt)
580         m_pExchExt->m_gpgEncrypt = true;
581       check_menu (eecb, m_nCmdEncrypt, m_pExchExt->m_gpgEncrypt);
582       check_menu (eecb, m_nCmdSign, m_pExchExt->m_gpgSign);
583     }
584   else if (m_lContext == EECONTEXT_VIEWER) 
585     {
586       add_menu (eecb, pnCommandIDBase, 
587         "@", NULL,
588         _("GnuPG Certificate &Manager"), &m_nCmdKeyManager,
589         _("Remove GpgOL flags from this folder"), &m_nCmdRevertFolder,
590         NULL);
591
592       add_toolbar (pTBEArray, nTBECnt, 
593         _("Open the certificate manager"), IDB_KEY_MANAGER, m_nCmdKeyManager,
594         NULL, 0, 0);
595     }
596   return S_FALSE;
597 }
598
599
600 /* Called by Exchange when a user selects a command.  Return value:
601    S_OK if command is handled, otherwise S_FALSE. */
602 STDMETHODIMP 
603 GpgolExtCommands::DoCommand (LPEXCHEXTCALLBACK eecb, UINT nCommandID)
604 {
605   HRESULT hr;
606   HWND hwnd = NULL;
607   LPMESSAGE message = NULL;
608   LPMDB mdb = NULL;
609       
610   if (FAILED (eecb->GetWindow (&hwnd)))
611     hwnd = NULL;
612
613   if (debug_commands)
614     log_debug ("%s:%s: commandID=%u (%#x) context=%s hwnd=%p\n",
615                SRCNAME, __func__, nCommandID, nCommandID, 
616                ext_context_name (m_lContext), hwnd);
617
618   if (nCommandID == SC_CLOSE && m_lContext == EECONTEXT_READNOTEMESSAGE)
619     {
620       /* This is the system close command. Replace it with our own to
621          avoid the "save changes" query, apparently induced by OL
622          internal syncronisation of our SetWindowText message with its
623          own OOM (in this case Body). */
624       LPDISPATCH pDisp;
625       DISPID dispid;
626       DISPPARAMS dispparams;
627       VARIANT aVariant;
628       
629       if (debug_commands)
630         log_debug ("%s:%s: command Close called\n", SRCNAME, __func__);
631       pDisp = find_outlook_property (eecb, "Close", &dispid);
632       if (pDisp)
633         {
634           /* Note that there is a report on the Net from 2005 by Amit
635              Joshi where he claims that in Outlook XP olDiscard does
636              not work but is treated like olSave.  */ 
637           dispparams.rgvarg = &aVariant;
638           dispparams.rgvarg[0].vt = VT_INT;
639           dispparams.rgvarg[0].intVal = 1; /* olDiscard */
640           dispparams.cArgs = 1;
641           dispparams.cNamedArgs = 0;
642           hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
643                               DISPATCH_METHOD, &dispparams,
644                               NULL, NULL, NULL);
645           pDisp->Release();
646           pDisp = NULL;
647           if (hr == S_OK)
648             {
649               log_debug ("%s:%s: invoking Close succeeded", SRCNAME,__func__);
650               message_wipe_body_cruft (eecb);
651               return S_OK; /* We handled the close command. */
652             }
653
654           log_debug ("%s:%s: invoking Close failed: %#lx",
655                      SRCNAME, __func__, hr);
656         }
657       else
658         log_debug ("%s:%s: invoking Close failed: no Close method)",
659                    SRCNAME, __func__);
660
661       message_wipe_body_cruft (eecb);
662
663       /* Closing on our own failed - pass it on. */
664       return S_FALSE; 
665     }
666   else if (nCommandID == EECMDID_ComposeReplyToSender)
667     {
668       if (debug_commands)
669         log_debug ("%s:%s: command Reply called\n", SRCNAME, __func__);
670       /* What we might want to do is to call Reply, then GetInspector
671          and then Activate - this allows us to get full control over
672          the quoted message and avoids the ugly msgcache. */
673       return S_FALSE; /* Pass it on.  */
674     }
675   else if (nCommandID == EECMDID_ComposeReplyToAll)
676     {
677       if (debug_commands)
678         log_debug ("%s:%s: command ReplyAll called\n", SRCNAME, __func__);
679       return S_FALSE; /* Pass it on.  */
680     }
681   else if (nCommandID == EECMDID_ComposeForward)
682     {
683       if (debug_commands)
684         log_debug ("%s:%s: command Forward called\n", SRCNAME, __func__);
685       return S_FALSE; /* Pass it on.  */
686     }
687   else if (nCommandID == m_nCmdCryptoState
688            && m_lContext == EECONTEXT_READNOTEMESSAGE)
689     {
690       log_debug ("%s:%s: command CryptoState called\n", SRCNAME, __func__);
691       hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
692       if (SUCCEEDED (hr))
693         {
694           if (message_incoming_handler (message, hwnd, true))
695             message_display_handler (eecb, hwnd);
696         }
697       else
698         log_debug_w32 (hr, "%s:%s: command CryptoState failed", 
699                        SRCNAME, __func__);
700       ul_release (message, __func__, __LINE__);
701       ul_release (mdb, __func__, __LINE__);
702     }
703   else if (nCommandID == m_nCmdProtoAuto
704            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
705     {
706       log_debug ("%s:%s: command ProtoAuto called\n", SRCNAME, __func__);
707       m_pExchExt->m_protoSelection = PROTOCOL_UNKNOWN;
708       update_protocol_menu (eecb);
709     }
710   else if (nCommandID == m_nCmdProtoPgpmime
711            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
712     {
713       log_debug ("%s:%s: command ProtoPgpmime called\n", SRCNAME, __func__);
714       m_pExchExt->m_protoSelection = PROTOCOL_OPENPGP;
715       update_protocol_menu (eecb);
716     }
717   else if (nCommandID == m_nCmdProtoSmime
718            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
719     {
720       log_debug ("%s:%s: command ProtoSmime called\n", SRCNAME, __func__);
721       if (opt.enable_smime)
722         {
723           m_pExchExt->m_protoSelection = PROTOCOL_SMIME;
724           update_protocol_menu (eecb);
725         }
726       else
727         {
728           MessageBox (hwnd,
729                       _("Support for S/MIME has not been enabled.\n"
730                         "\n"
731                         "To enable S/MIME support, open the option dialog"
732                         " and check \"Enable the S/MIME support\".  The"
733                         " option dialog can be found in the main menu at:"
734                        " Extras->Options->GpgOL.\n"),
735                       "GpgOL", MB_ICONHAND|MB_OK);
736         }
737     }
738   else if (nCommandID == m_nCmdEncrypt
739            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
740     {
741       log_debug ("%s:%s: command Encrypt called\n", SRCNAME, __func__);
742       m_pExchExt->m_gpgEncrypt = !m_pExchExt->m_gpgEncrypt;
743       check_menu (eecb, m_nCmdEncrypt, m_pExchExt->m_gpgEncrypt);
744     }
745   else if (nCommandID == m_nCmdSign
746            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
747     {
748       log_debug ("%s:%s: command Sign called\n", SRCNAME, __func__);
749       m_pExchExt->m_gpgSign = !m_pExchExt->m_gpgSign;
750       check_menu (eecb, m_nCmdSign, m_pExchExt->m_gpgSign);
751     }
752   else if (nCommandID == m_nCmdKeyManager
753            && m_lContext == EECONTEXT_VIEWER)
754     {
755       log_debug ("%s:%s: command KeyManager called\n", SRCNAME, __func__);
756       if (engine_start_keymanager (hwnd))
757         if (start_key_manager ())
758           MessageBox (NULL, _("Could not start certificate manager"),
759                       _("GpgOL"), MB_ICONERROR|MB_OK);
760     }
761   else if (nCommandID == m_nCmdRevertFolder
762            && m_lContext == EECONTEXT_VIEWER)
763     {
764       log_debug ("%s:%s: command ReverFoldert called\n", SRCNAME, __func__);
765       /* Notify the user that the general GpgOl fucntionaly will be
766          disabled when calling this function the first time.  */
767       if ( opt.disable_gpgol
768            || (MessageBox 
769                (hwnd,
770                 _("You are about to start the process of reversing messages "
771                   "created by GpgOL to prepare deinstalling of GpgOL. "
772                   "Running this command will put GpgOL into a disabled state "
773                   "so that messages are not anymore processed by GpgOL.\n"
774                   "\n"
775                   "You should convert all folders one after the other with "
776                   "this command, close Outlook and then deinstall GpgOL.\n"
777                   "\n"
778                   "Note that if you start Outlook again with GpgOL still "
779                   "being installed, GpgOL will again process messages."),
780                 _("GpgOL"), MB_ICONWARNING|MB_OKCANCEL) == IDOK))
781         {
782           if ( MessageBox 
783                (hwnd,
784                 _("Do you want to revert this folder?"),
785                 _("GpgOL"), MB_ICONQUESTION|MB_YESNO) == IDYES )
786             {
787               if (!opt.disable_gpgol)
788                 opt.disable_gpgol = 1;
789           
790               gpgol_folder_revert (eecb);
791             }
792         }
793     }
794   else if (opt.enable_debug && nCommandID == m_nCmdDebug0
795            && m_lContext == EECONTEXT_READNOTEMESSAGE)
796     {
797       log_debug ("%s:%s: command Debug0 (showInfo) called\n",
798                  SRCNAME, __func__);
799       hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
800       if (SUCCEEDED (hr))
801         {
802           message_show_info (message, hwnd);
803         }
804       ul_release (message, __func__, __LINE__);
805       ul_release (mdb, __func__, __LINE__);
806     }
807   else if (opt.enable_debug && nCommandID == m_nCmdDebug1
808            && m_lContext == EECONTEXT_READNOTEMESSAGE)
809     {
810       log_debug ("%s:%s: command Debug1 (open inspector) called\n",
811                  SRCNAME, __func__);
812       hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
813       if (SUCCEEDED (hr))
814         {
815           open_inspector (eecb, message);
816         }
817       ul_release (message, __func__, __LINE__);
818       ul_release (mdb, __func__, __LINE__);
819     }
820   else if (opt.enable_debug && nCommandID == m_nCmdDebug2
821            && m_lContext == EECONTEXT_READNOTEMESSAGE)
822     {
823       log_debug ("%s:%s: command Debug2 (change message class) called\n", 
824                  SRCNAME, __func__);
825       hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
826       if (SUCCEEDED (hr))
827         {
828           /* We sync here. */
829           mapi_change_message_class (message, 1);
830         }
831       ul_release (message, __func__, __LINE__);
832       ul_release (mdb, __func__, __LINE__);
833     }
834   else if (opt.enable_debug && nCommandID == m_nCmdDebug3
835            && m_lContext == EECONTEXT_READNOTEMESSAGE)
836     {
837       log_debug ("%s:%s: command Debug3 (revert_message_class) called\n", 
838                  SRCNAME, __func__);
839       hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
840       if (SUCCEEDED (hr))
841         {
842           int rc = gpgol_message_revert (message, 1, 
843                                          KEEP_OPEN_READWRITE|FORCE_SAVE);
844           log_debug ("%s:%s: gpgol_message_revert returns %d\n", 
845                      SRCNAME, __func__, rc);
846         }
847       ul_release (message, __func__, __LINE__);
848       ul_release (mdb, __func__, __LINE__);
849     }
850   else
851     {
852       if (debug_commands)
853         log_debug ("%s:%s: command passed on\n", SRCNAME, __func__);
854       return S_FALSE; /* Pass on unknown command. */
855     }
856   
857
858   return S_OK; 
859 }
860
861
862 /* Called by Exchange when it receives a WM_INITMENU message, allowing
863    the extension object to enable, disable, or update its menu
864    commands before the user sees them. This method is called
865    frequently and should be written in a very efficient manner. */
866 STDMETHODIMP_(VOID) 
867 GpgolExtCommands::InitMenu(LPEXCHEXTCALLBACK eecb) 
868 {
869 }
870
871
872 /* Called by Exchange when the user requests help for a menu item.
873    EECB is the pointer to Exchange Callback Interface.  NCOMMANDID is
874    the command id.  Return value: S_OK when it is a menu item of this
875    plugin and the help was shown; otherwise S_FALSE.  */
876 STDMETHODIMP 
877 GpgolExtCommands::Help (LPEXCHEXTCALLBACK eecb, UINT nCommandID)
878 {
879   if (nCommandID == m_nCmdProtoAuto
880            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
881     {
882       MessageBox (m_hWnd,
883                  _("Select this option to automatically select the protocol."),
884                   "GpgOL", MB_OK);      
885     } 
886   else if (nCommandID == m_nCmdProtoPgpmime
887            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
888     {
889       MessageBox (m_hWnd,
890                   _("Select this option to select the PGP/MIME protocol."),
891                   "GpgOL", MB_OK);      
892     } 
893   else if (nCommandID == m_nCmdProtoSmime
894            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
895     {
896       MessageBox (m_hWnd,
897                   _("Select this option to select the S/MIME protocol."),
898                   "GpgOL", MB_OK);      
899     } 
900   else if (nCommandID == m_nCmdEncrypt 
901            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
902     {
903       MessageBox (m_hWnd,
904                   _("Select this option to encrypt the message."),
905                   "GpgOL", MB_OK);      
906     } 
907   else if (nCommandID == m_nCmdSign
908            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
909     {
910       MessageBox (m_hWnd,
911                   _("Select this option to sign the message."),
912                   "GpgOL", MB_OK);      
913     }
914   else if (nCommandID == m_nCmdKeyManager
915            && m_lContext == EECONTEXT_VIEWER) 
916     {
917       MessageBox (m_hWnd,
918                   _("Select this option to open the certificate manager"),
919                   "GpgOL", MB_OK);
920     }
921   else
922     return S_FALSE;
923
924   return S_OK;
925 }
926
927
928 /* Called by Exchange to get the status bar text or the tooltip of a
929    menu item.  NCOMMANDID is the command id corresponding to the menu
930    item activated.  LFLAGS identifies either EECQHT_STATUS or
931    EECQHT_TOOLTIP.  PSZTEXT is a pointer to buffer to received the
932    text to be displayed.  NCHARCNT is the available space in PSZTEXT.
933
934    Returns S_OK when it is a menu item of this plugin and the text was
935    set; otherwise S_FALSE.  */
936 STDMETHODIMP 
937 GpgolExtCommands::QueryHelpText(UINT nCommandID, ULONG lFlags,
938                                 LPTSTR pszText,  UINT nCharCnt)    
939 {
940         
941   if (nCommandID == m_nCmdProtoAuto
942            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
943     {
944       if (lFlags == EECQHT_STATUS)
945         lstrcpyn (pszText, ".", nCharCnt);
946       if (lFlags == EECQHT_TOOLTIP)
947         lstrcpyn (pszText,
948                   _("Automatically select the protocol for sign/encrypt"),
949                   nCharCnt);
950     }
951   else if (nCommandID == m_nCmdProtoPgpmime
952            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
953     {
954       if (lFlags == EECQHT_STATUS)
955         lstrcpyn (pszText, ".", nCharCnt);
956       if (lFlags == EECQHT_TOOLTIP)
957         lstrcpyn (pszText,
958                   _("Use PGP/MIME for sign/encrypt"),
959                   nCharCnt);
960     }
961   else if (nCommandID == m_nCmdProtoSmime
962            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
963     {
964       if (lFlags == EECQHT_STATUS)
965         lstrcpyn (pszText, ".", nCharCnt);
966       if (lFlags == EECQHT_TOOLTIP)
967         lstrcpyn (pszText,
968                   _("Use S/MIME for sign/encrypt"),
969                   nCharCnt);
970     }
971   else if (nCommandID == m_nCmdEncrypt
972            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
973     {
974       if (lFlags == EECQHT_STATUS)
975         lstrcpyn (pszText, ".", nCharCnt);
976       if (lFlags == EECQHT_TOOLTIP)
977         lstrcpyn (pszText,
978                   _("Encrypt message with GnuPG"),
979                   nCharCnt);
980     }
981   else if (nCommandID == m_nCmdSign
982            && m_lContext == EECONTEXT_SENDNOTEMESSAGE) 
983     {
984       if (lFlags == EECQHT_STATUS)
985         lstrcpyn (pszText, ".", nCharCnt);
986       if (lFlags == EECQHT_TOOLTIP)
987         lstrcpyn (pszText,
988                   _("Sign message with GnuPG"),
989                   nCharCnt);
990     }
991   else if (nCommandID == m_nCmdKeyManager
992            && m_lContext == EECONTEXT_VIEWER) 
993     {
994       if (lFlags == EECQHT_STATUS)
995         lstrcpyn (pszText, ".", nCharCnt);
996       if (lFlags == EECQHT_TOOLTIP)
997         lstrcpyn (pszText,
998                   _("Open the GpgOL certificate manager"),
999                   nCharCnt);
1000     }
1001   else 
1002     return S_FALSE;
1003
1004   return S_OK;
1005 }
1006
1007
1008 /* Called by Exchange to get toolbar button infos.  TOOLBARID is the
1009    toolbar identifier.  BUTTONID is the toolbar button index.  PTBB is
1010    a pointer to the toolbar button structure.  DESCRIPTION is a pointer to
1011    buffer receiving the text for the button.  DESCRIPTION_SIZE is the
1012    maximum size of DESCRIPTION.  FLAGS are flags which might have the
1013    EXCHEXT_UNICODE bit set.
1014
1015    Returns S_OK when it is a button of this plugin and the requested
1016    info was delivered; otherwise S_FALSE.  */
1017 STDMETHODIMP 
1018 GpgolExtCommands::QueryButtonInfo (ULONG toolbarid, UINT buttonid, 
1019                                    LPTBBUTTON pTBB, 
1020                                    LPTSTR description, UINT description_size,
1021                                    ULONG flags)          
1022 {
1023   toolbar_info_t tb_info;
1024
1025   for (tb_info = m_toolbar_info; tb_info; tb_info = tb_info->next )
1026     if (tb_info->button_id == buttonid
1027         && tb_info->context == m_lContext)
1028       break;
1029   if (!tb_info)
1030     return S_FALSE; /* Not one of our toolbar buttons.  */
1031
1032   if (debug_commands)
1033     log_debug ("%s:%s: ctx=%lx tbid=%ld button_id(req)=%d got=%d"
1034                " cmd_id=%d '%s'\n", 
1035                SRCNAME, __func__, m_lContext, toolbarid, buttonid,
1036                tb_info->button_id, tb_info->cmd_id, tb_info->desc);
1037
1038   /* Mark that this button has passed this function.  */
1039   tb_info->did_qbi = 1;
1040   
1041   pTBB->iBitmap = tb_info->bitmap;
1042   pTBB->idCommand = tb_info->cmd_id;
1043   pTBB->fsState = TBSTATE_ENABLED;
1044   pTBB->fsStyle = TBSTYLE_BUTTON;
1045   pTBB->dwData = 0;
1046   pTBB->iString = -1;
1047   
1048   lstrcpyn (description, tb_info->desc, strlen (tb_info->desc));
1049
1050   if (tb_info->cmd_id == m_nCmdEncrypt)
1051     {
1052       pTBB->fsStyle |= TBSTYLE_CHECK;
1053       if (m_pExchExt->m_gpgEncrypt)
1054         pTBB->fsState |= TBSTATE_CHECKED;
1055     }
1056   else if (tb_info->cmd_id == m_nCmdSign)
1057     {
1058       pTBB->fsStyle |= TBSTYLE_CHECK;
1059       if (m_pExchExt->m_gpgSign)
1060         pTBB->fsState |= TBSTATE_CHECKED;
1061     }
1062   else if (tb_info->cmd_id == m_nCmdProtoAuto)
1063     {
1064       pTBB->fsStyle |= TBSTYLE_CHECK;
1065       if (m_pExchExt->m_protoSelection != PROTOCOL_OPENPGP
1066           && m_pExchExt->m_protoSelection != PROTOCOL_SMIME)
1067         pTBB->fsState |= TBSTATE_CHECKED;
1068     }
1069   else if (tb_info->cmd_id == m_nCmdProtoPgpmime)
1070     {
1071       pTBB->fsStyle |= TBSTYLE_CHECK;
1072       if (m_pExchExt->m_protoSelection == PROTOCOL_OPENPGP)
1073         pTBB->fsState |= TBSTATE_CHECKED;
1074     }
1075   else if (tb_info->cmd_id == m_nCmdProtoSmime)
1076     {
1077       pTBB->fsStyle |= TBSTYLE_CHECK;
1078       if (m_pExchExt->m_protoSelection == PROTOCOL_SMIME)
1079         pTBB->fsState |= TBSTATE_CHECKED;
1080     }
1081
1082   return S_OK;
1083 }
1084
1085
1086
1087 STDMETHODIMP 
1088 GpgolExtCommands::ResetToolbar (ULONG lToolbarID, ULONG lFlags)
1089 {       
1090   return S_OK;
1091 }
1092