diff --git a/src/oomhelp.cpp b/src/oomhelp.cpp index c6515d2..06fd72d 100644 --- a/src/oomhelp.cpp +++ b/src/oomhelp.cpp @@ -1,2375 +1,2409 @@ /* oomhelp.cpp - Helper functions for the Outlook Object Model * Copyright (C) 2009 g10 Code GmbH * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "common.h" #include "oomhelp.h" #include "gpgoladdin.h" HRESULT gpgol_queryInterface (LPUNKNOWN pObj, REFIID riid, LPVOID FAR *ppvObj) { HRESULT ret = pObj->QueryInterface (riid, ppvObj); if ((opt.enable_debug & DBG_OOM_EXTRA) && *ppvObj) { memdbg_addRef (*ppvObj); } return ret; } HRESULT gpgol_openProperty (LPMAPIPROP obj, ULONG ulPropTag, LPCIID lpiid, ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN FAR * lppUnk) { HRESULT ret = obj->OpenProperty (ulPropTag, lpiid, ulInterfaceOptions, ulFlags, lppUnk); if ((opt.enable_debug & DBG_OOM_EXTRA) && *lppUnk) { memdbg_addRef (*lppUnk); log_debug ("%s:%s: OpenProperty on %p prop %lx result %p", SRCNAME, __func__, obj, ulPropTag, *lppUnk); } return ret; } /* Return a malloced string with the utf-8 encoded name of the object or NULL if not available. */ char * get_object_name (LPUNKNOWN obj) { HRESULT hr; LPDISPATCH disp = NULL; LPTYPEINFO tinfo = NULL; BSTR bstrname; char *name = NULL; if (!obj) goto leave; /* We can't use gpgol_queryInterface here to avoid recursion */ obj->QueryInterface (IID_IDispatch, (void **)&disp); if (!disp) goto leave; disp->GetTypeInfo (0, 0, &tinfo); if (!tinfo) { log_debug ("%s:%s: no typeinfo found for object\n", SRCNAME, __func__); goto leave; } bstrname = NULL; hr = tinfo->GetDocumentation (MEMBERID_NIL, &bstrname, 0, 0, 0); if (hr || !bstrname) log_debug ("%s:%s: GetDocumentation failed: hr=%#lx\n", SRCNAME, __func__, hr); if (bstrname) { name = wchar_to_utf8 (bstrname); SysFreeString (bstrname); } leave: if (tinfo) tinfo->Release (); if (disp) disp->Release (); return name; } /* Lookup the dispid of object PDISP for member NAME. Returns DISPID_UNKNOWN on error. */ DISPID lookup_oom_dispid (LPDISPATCH pDisp, const char *name) { HRESULT hr; DISPID dispid; wchar_t *wname; if (!pDisp || !name) return DISPID_UNKNOWN; /* Error: Invalid arg. */ wname = utf8_to_wchar (name); if (!wname) return DISPID_UNKNOWN;/* Error: Out of memory. */ hr = pDisp->GetIDsOfNames (IID_NULL, &wname, 1, LOCALE_SYSTEM_DEFAULT, &dispid); xfree (wname); if (hr != S_OK || dispid == DISPID_UNKNOWN) log_debug ("%s:%s: error looking up dispid(%s)=%d: hr=0x%x\n", SRCNAME, __func__, name, (int)dispid, (unsigned int)hr); if (hr != S_OK) dispid = DISPID_UNKNOWN; return dispid; } static void init_excepinfo (EXCEPINFO *err) { if (!err) { return; } err->wCode = 0; err->wReserved = 0; err->bstrSource = nullptr; err->bstrDescription = nullptr; err->bstrHelpFile = nullptr; err->dwHelpContext = 0; err->pvReserved = nullptr; err->pfnDeferredFillIn = nullptr; err->scode = 0; } void dump_excepinfo (EXCEPINFO err) { log_debug ("%s:%s: Exception: \n" " wCode: 0x%x\n" " wReserved: 0x%x\n" " source: %S\n" " desc: %S\n" " help: %S\n" " helpCtx: 0x%x\n" " deferredFill: %p\n" " scode: 0x%x\n", SRCNAME, __func__, (unsigned int) err.wCode, (unsigned int) err.wReserved, err.bstrSource ? err.bstrSource : L"null", err.bstrDescription ? err.bstrDescription : L"null", err.bstrHelpFile ? err.bstrDescription : L"null", (unsigned int) err.dwHelpContext, err.pfnDeferredFillIn, (unsigned int) err.scode); } /* Return the OOM object's IDispatch interface described by FULLNAME. Returns NULL if not found. PSTART is the object where the search starts. FULLNAME is a dot delimited sequence of object names. If an object name has a "(foo)" suffix this passes it as a parameter to the invoke function (i.e. using (DISPATCH|PROPERTYGET)). Object names including the optional suffix are truncated at 127 byte. */ LPDISPATCH get_oom_object (LPDISPATCH pStart, const char *fullname) { HRESULT hr; LPDISPATCH pObj = pStart; LPDISPATCH pDisp = NULL; log_oom ("%s:%s: looking for %p->`%s'", SRCNAME, __func__, pStart, fullname); while (pObj) { DISPPARAMS dispparams; VARIANT aVariant[4]; VARIANT vtResult; wchar_t *wname; char name[128]; int n_parms = 0; BSTR parmstr = NULL; INT parmint = 0; DISPID dispid; char *p, *pend; int dispmethod; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); if (pDisp) { gpgol_release (pDisp); pDisp = NULL; } if (gpgol_queryInterface (pObj, IID_IDispatch, (LPVOID*)&pDisp) != S_OK) { log_error ("%s:%s Object does not support IDispatch", SRCNAME, __func__); gpgol_release (pObj); return NULL; } /* Confirmed through testing that the retval needs a release */ if (pObj != pStart) gpgol_release (pObj); pObj = NULL; if (!pDisp) return NULL; /* The object has no IDispatch interface. */ if (!*fullname) { if ((opt.enable_debug & DBG_OOM)) { pDisp->AddRef (); int ref = pDisp->Release (); log_oom ("%s:%s: got %p with %i refs", SRCNAME, __func__, pDisp, ref); } return pDisp; /* Ready. */ } /* Break out the next name part. */ { const char *dot; size_t n; dot = strchr (fullname, '.'); if (dot == fullname) { gpgol_release (pDisp); return NULL; /* Empty name part: error. */ } else if (dot) n = dot - fullname; else n = strlen (fullname); if (n >= sizeof name) n = sizeof name - 1; strncpy (name, fullname, n); name[n] = 0; if (dot) fullname = dot + 1; else fullname += strlen (fullname); } if (!strncmp (name, "get_", 4) && name[4]) { dispmethod = DISPATCH_PROPERTYGET; memmove (name, name+4, strlen (name+4)+1); } else if ((p = strchr (name, '('))) { *p++ = 0; pend = strchr (p, ')'); if (pend) *pend = 0; if (*p == ',' && p[1] != ',') { /* We assume this is "foo(,30007)". I.e. the frst arg is not given and the second one is an integer. */ parmint = (int)strtol (p+1, NULL, 10); n_parms = 4; } else { wname = utf8_to_wchar (p); if (wname) { parmstr = SysAllocString (wname); xfree (wname); } if (!parmstr) { gpgol_release (pDisp); return NULL; /* Error: Out of memory. */ } n_parms = 1; } dispmethod = DISPATCH_METHOD|DISPATCH_PROPERTYGET; } else dispmethod = DISPATCH_METHOD; /* Lookup the dispid. */ dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { if (parmstr) SysFreeString (parmstr); gpgol_release (pDisp); return NULL; /* Name not found. */ } /* Invoke the method. */ dispparams.rgvarg = aVariant; dispparams.cArgs = 0; if (n_parms) { if (n_parms == 4) { dispparams.rgvarg[0].vt = VT_ERROR; dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[1].vt = VT_ERROR; dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[2].vt = VT_INT; dispparams.rgvarg[2].intVal = parmint; dispparams.rgvarg[3].vt = VT_ERROR; dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; dispparams.cArgs = n_parms; } else if (n_parms == 1 && parmstr) { dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[0].bstrVal = parmstr; dispparams.cArgs++; } } dispparams.cNamedArgs = 0; VariantInit (&vtResult); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, dispmethod, &dispparams, &vtResult, &execpinfo, &argErr); if (parmstr) SysFreeString (parmstr); if (hr != S_OK || vtResult.vt != VT_DISPATCH) { log_debug ("%s:%s: failure: '%s' p=%p vt=%d hr=0x%x argErr=0x%x dispid=0x%x", SRCNAME, __func__, name, vtResult.pdispVal, vtResult.vt, (unsigned int)hr, (unsigned int)argErr, (unsigned int)dispid); dump_excepinfo (execpinfo); VariantClear (&vtResult); gpgol_release (pDisp); return NULL; /* Invoke failed. */ } pObj = vtResult.pdispVal; memdbg_addRef (pObj); } gpgol_release (pDisp); log_debug ("%s:%s: no object", SRCNAME, __func__); return NULL; } /* Helper for put_oom_icon. */ static int put_picture_or_mask (LPDISPATCH pDisp, int resource, int size, int is_mask) { HRESULT hr; PICTDESC pdesc; LPDISPATCH pPict; DISPID dispid_put = DISPID_PROPERTYPUT; UINT fuload; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[2]; /* When loading the mask we need to set the monochrome flag. We better create a DIB section to avoid possible rendering problems. */ fuload = LR_CREATEDIBSECTION | LR_SHARED; if (is_mask) fuload |= LR_MONOCHROME; memset (&pdesc, 0, sizeof pdesc); pdesc.cbSizeofstruct = sizeof pdesc; pdesc.picType = PICTYPE_BITMAP; pdesc.bmp.hbitmap = (HBITMAP) LoadImage (glob_hinst, MAKEINTRESOURCE (resource), IMAGE_BITMAP, size, size, fuload); if (!pdesc.bmp.hbitmap) { log_error_w32 (-1, "%s:%s: LoadImage(%d) failed\n", SRCNAME, __func__, resource); return -1; } /* Wrap the image into an OLE object. */ hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp, TRUE, (void **) &pPict); if (hr != S_OK || !pPict) { log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n", SRCNAME, __func__, hr); return -1; } /* Store to the Picture or Mask property of the CommandBarButton. */ dispid = lookup_oom_dispid (pDisp, is_mask? "Mask":"Picture"); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_DISPATCH; dispparams.rgvarg[0].pdispVal = pPict; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting icon failed: %#lx", SRCNAME, __func__, hr); return -1; } return 0; } /* Update the icon of PDISP using the bitmap with RESOURCE ID. The function adds the system pixel size to the resource id to compute the actual icon size. The resource id of the mask is the N+1. */ int put_oom_icon (LPDISPATCH pDisp, int resource_id, int size) { int rc; /* This code is only relevant for Outlook < 2010. Ideally it should grab the system pixel size and use an icon of the appropiate size (e.g. 32 or 64px) */ rc = put_picture_or_mask (pDisp, resource_id, size, 0); if (!rc) rc = put_picture_or_mask (pDisp, resource_id + 1, size, 1); return rc; } /* Set the boolean property NAME to VALUE. */ int put_oom_bool (LPDISPATCH pDisp, const char *name, int value) { HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) return -1; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; dispparams.rgvarg[0].boolVal = value? VARIANT_TRUE:VARIANT_FALSE; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); return -1; } return 0; } /* Set the property NAME to VALUE. */ int put_oom_int (LPDISPATCH pDisp, const char *name, int value) { HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) return -1; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = value; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); return -1; } return 0; } /* Set the property NAME to STRING. */ int put_oom_string (LPDISPATCH pDisp, const char *name, const char *string) { HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; BSTR bstring; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) return -1; { wchar_t *tmp = utf8_to_wchar (string); bstring = tmp? SysAllocString (tmp):NULL; xfree (tmp); if (!bstring) { log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__); return -1; } } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[0].bstrVal = bstring; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, &execpinfo, NULL); SysFreeString (bstring); if (hr != S_OK) { log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); dump_excepinfo (execpinfo); return -1; } return 0; } /* Set the property NAME to DISP. */ int put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH disp) { HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) return -1; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_DISPATCH; dispparams.rgvarg[0].pdispVal = disp; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, &dispparams, NULL, &execpinfo, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); dump_excepinfo (execpinfo); return -1; } return 0; } /* Get the boolean property NAME of the object PDISP. Returns False if not found or if it is not a boolean property. */ int get_oom_bool (LPDISPATCH pDisp, const char *name) { HRESULT hr; int result = 0; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_BOOL) log_debug ("%s:%s: Property `%s' is not a boolean (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else result = !!rVariant.boolVal; VariantClear (&rVariant); } return result; } /* Get the integer property NAME of the object PDISP. Returns 0 if not found or if it is not an integer property. */ int get_oom_int (LPDISPATCH pDisp, const char *name) { HRESULT hr; int result = 0; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_INT && rVariant.vt != VT_I4) log_debug ("%s:%s: Property `%s' is not an integer (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else result = rVariant.intVal; VariantClear (&rVariant); } return result; } /* Get the string property NAME of the object PDISP. Returns NULL if not found or if it is not a string property. */ char * get_oom_string (LPDISPATCH pDisp, const char *name) { HRESULT hr; char *result = NULL; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_BSTR) log_debug ("%s:%s: Property `%s' is not a string (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else if (rVariant.bstrVal) result = wchar_to_utf8 (rVariant.bstrVal); VariantClear (&rVariant); } return result; } /* Get the object property NAME of the object PDISP. Returns NULL if not found or if it is not an object perty. */ LPUNKNOWN get_oom_iunknown (LPDISPATCH pDisp, const char *name) { HRESULT hr; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_UNKNOWN) log_debug ("%s:%s: Property `%s' is not of class IUnknown (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else { memdbg_addRef (rVariant.punkVal); return rVariant.punkVal; } VariantClear (&rVariant); } return NULL; } /* Return the control object described by the tag property with value TAG. The object POBJ must support the FindControl method. Returns NULL if not found. */ LPDISPATCH get_oom_control_bytag (LPDISPATCH pDisp, const char *tag) { HRESULT hr; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[4]; VARIANT rVariant; BSTR bstring; LPDISPATCH result = NULL; dispid = lookup_oom_dispid (pDisp, "FindControl"); if (dispid == DISPID_UNKNOWN) { log_debug ("%s:%s: Object %p has no FindControl method", SRCNAME, __func__, pDisp); return NULL; } { wchar_t *tmp = utf8_to_wchar (tag); bstring = tmp? SysAllocString (tmp):NULL; xfree (tmp); if (!bstring) { log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__); return NULL; } } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_ERROR; /* Visible */ dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[1].vt = VT_BSTR; /* Tag */ dispparams.rgvarg[1].bstrVal = bstring; dispparams.rgvarg[2].vt = VT_ERROR; /* Id */ dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[3].vt = VT_ERROR;/* Type */ dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; dispparams.cArgs = 4; dispparams.cNamedArgs = 0; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, NULL, NULL); SysFreeString (bstring); if (hr == S_OK && rVariant.vt == VT_DISPATCH && rVariant.pdispVal) { gpgol_queryInterface (rVariant.pdispVal, IID_IDispatch, (LPVOID*)&result); gpgol_release (rVariant.pdispVal); if (!result) log_debug ("%s:%s: Object with tag `%s' has no dispatch intf.", SRCNAME, __func__, tag); } else { log_debug ("%s:%s: No object with tag `%s' found: vt=%d hr=%#lx", SRCNAME, __func__, tag, rVariant.vt, hr); VariantClear (&rVariant); } return result; } /* Add a new button to an object which supports the add method. Returns the new object or NULL on error. */ LPDISPATCH add_oom_button (LPDISPATCH pObj) { HRESULT hr; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[5]; VARIANT rVariant; dispid = lookup_oom_dispid (pObj, "Add"); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */ dispparams.rgvarg[0].boolVal = VARIANT_TRUE; dispparams.rgvarg[1].vt = VT_ERROR; /* Before */ dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[2].vt = VT_ERROR; /* Parameter */ dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[3].vt = VT_ERROR; /* Id */ dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[4].vt = VT_INT; /* Type */ dispparams.rgvarg[4].intVal = MSOCONTROLBUTTON; dispparams.cArgs = 5; dispparams.cNamedArgs = 0; VariantInit (&rVariant); hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK || rVariant.vt != VT_DISPATCH || !rVariant.pdispVal) { log_error ("%s:%s: Adding Control failed: %#lx - vt=%d", SRCNAME, __func__, hr, rVariant.vt); VariantClear (&rVariant); return NULL; } return rVariant.pdispVal; } /* Add a new button to an object which supports the add method. Returns the new object or NULL on error. */ void del_oom_button (LPDISPATCH pObj) { HRESULT hr; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[5]; dispid = lookup_oom_dispid (pObj, "Delete"); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */ dispparams.rgvarg[0].boolVal = VARIANT_FALSE; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, NULL, NULL); if (hr != S_OK) log_error ("%s:%s: Deleting Control failed: %#lx", SRCNAME, __func__, hr); } /* Gets the current contexts HWND. Returns NULL on error */ HWND get_oom_context_window (LPDISPATCH context) { LPOLEWINDOW actExplorer; HWND ret = NULL; actExplorer = (LPOLEWINDOW) get_oom_object(context, "Application.ActiveExplorer"); if (actExplorer) actExplorer->GetWindow (&ret); else { log_debug ("%s:%s: Could not find active window", SRCNAME, __func__); } gpgol_release (actExplorer); return ret; } int put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value) { LPDISPATCH propertyAccessor; VARIANT cVariant[2]; VARIANT rVariant; DISPID dispid; DISPPARAMS dispparams; HRESULT hr; EXCEPINFO execpinfo; BSTR b_property; wchar_t *w_property; unsigned int argErr = 0; init_excepinfo (&execpinfo); log_oom ("%s:%s: Looking up property: %s;", SRCNAME, __func__, dasl_id); propertyAccessor = get_oom_object (pDisp, "PropertyAccessor"); if (!propertyAccessor) { log_error ("%s:%s: Failed to look up property accessor.", SRCNAME, __func__); return -1; } dispid = lookup_oom_dispid (propertyAccessor, "SetProperty"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find SetProperty DISPID", SRCNAME, __func__); return -1; } /* Prepare the parameter */ w_property = utf8_to_wchar (dasl_id); b_property = SysAllocString (w_property); xfree (w_property); /* Variant 0 carries the data. */ VariantInit (&cVariant[0]); if (VariantCopy (&cVariant[0], value)) { log_error ("%s:%s: Falied to copy value.", SRCNAME, __func__); return -1; } /* Variant 1 is the DASL as found out by experiments. */ VariantInit (&cVariant[1]); cVariant[1].vt = VT_BSTR; cVariant[1].bstrVal = b_property; dispparams.rgvarg = cVariant; dispparams.cArgs = 2; dispparams.cNamedArgs = 0; VariantInit (&rVariant); hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, &execpinfo, &argErr); VariantClear (&cVariant[0]); VariantClear (&cVariant[1]); gpgol_release (propertyAccessor); if (hr != S_OK) { log_debug ("%s:%s: error: invoking SetProperty p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, rVariant.pdispVal, rVariant.vt, (unsigned int)hr, (unsigned int)argErr); VariantClear (&rVariant); dump_excepinfo (execpinfo); return -1; } VariantClear (&rVariant); return 0; } int put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value) { wchar_t *w_value = utf8_to_wchar (value); BSTR b_value = SysAllocString(w_value); xfree (w_value); VARIANT var; VariantInit (&var); var.vt = VT_BSTR; var.bstrVal = b_value; int ret = put_pa_variant (pDisp, dasl_id, &var); VariantClear (&var); return ret; } int put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value) { VARIANT var; VariantInit (&var); var.vt = VT_INT; var.intVal = value; int ret = put_pa_variant (pDisp, dasl_id, &var); VariantClear (&var); return ret; } /* Get a MAPI property through OOM using the PropertyAccessor * interface and the DASL Uid. Returns -1 on error. * Variant has to be cleared with VariantClear. * rVariant must be a pointer to a Variant. */ int get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant) { LPDISPATCH propertyAccessor; VARIANT cVariant[1]; DISPID dispid; DISPPARAMS dispparams; HRESULT hr; EXCEPINFO execpinfo; BSTR b_property; wchar_t *w_property; unsigned int argErr = 0; init_excepinfo (&execpinfo); log_oom ("%s:%s: Looking up property: %s;", SRCNAME, __func__, dasl_id); propertyAccessor = get_oom_object (pDisp, "PropertyAccessor"); if (!propertyAccessor) { log_error ("%s:%s: Failed to look up property accessor.", SRCNAME, __func__); return -1; } dispid = lookup_oom_dispid (propertyAccessor, "GetProperty"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find GetProperty DISPID", SRCNAME, __func__); return -1; } /* Prepare the parameter */ w_property = utf8_to_wchar (dasl_id); b_property = SysAllocString (w_property); xfree (w_property); cVariant[0].vt = VT_BSTR; cVariant[0].bstrVal = b_property; dispparams.rgvarg = cVariant; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; VariantInit (rVariant); hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, rVariant, &execpinfo, &argErr); SysFreeString (b_property); gpgol_release (propertyAccessor); if (hr != S_OK && strcmp (GPGOL_UID_DASL, dasl_id)) { /* It often happens that mails don't have a uid by us e.g. if they are not crypto mails or just dont have one. This is not an error. */ log_debug ("%s:%s: error: invoking GetProperty p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, rVariant->pdispVal, rVariant->vt, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); VariantClear (rVariant); return -1; } return 0; } /* Get a property string by using the PropertyAccessor of pDisp * returns NULL on error or a newly allocated result. */ char * get_pa_string (LPDISPATCH pDisp, const char *property) { VARIANT rVariant; char *result = NULL; if (get_pa_variant (pDisp, property, &rVariant)) { return NULL; } if (rVariant.vt == VT_BSTR && rVariant.bstrVal) { result = wchar_to_utf8 (rVariant.bstrVal); } else if (rVariant.vt & VT_ARRAY && !(rVariant.vt & VT_BYREF)) { LONG uBound, lBound; VARTYPE vt; char *data; SafeArrayGetVartype(rVariant.parray, &vt); if (SafeArrayGetUBound (rVariant.parray, 1, &uBound) != S_OK || SafeArrayGetLBound (rVariant.parray, 1, &lBound) != S_OK || vt != VT_UI1) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); VariantClear (&rVariant); return NULL; } result = (char *)xmalloc (uBound - lBound + 1); data = (char *) rVariant.parray->pvData; memcpy (result, data + lBound, uBound - lBound); result[uBound - lBound] = '\0'; } else { log_debug ("%s:%s: Property `%s' is not a string (vt=%d)", SRCNAME, __func__, property, rVariant.vt); } VariantClear (&rVariant); return result; } int get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt) { VARIANT rVariant; if (get_pa_variant (pDisp, property, &rVariant)) { return -1; } if (rVariant.vt != VT_I4) { log_debug ("%s:%s: Property `%s' is not a int (vt=%d)", SRCNAME, __func__, property, rVariant.vt); return -1; } *rInt = rVariant.lVal; VariantClear (&rVariant); return 0; } /* Helper for exchange address lookup. */ static char * get_recipient_addr_entry_fallbacks_ex (LPDISPATCH addr_entry) { /* Maybe check for type here? We are pretty sure that we are exchange */ /* According to MSDN Message Boards the PR_EMS_AB_PROXY_ADDRESSES_DASL is more avilable then the SMTP Address. */ char *ret = get_pa_string (addr_entry, PR_EMS_AB_PROXY_ADDRESSES_DASL); if (ret) { log_debug ("%s:%s: Found recipient through AB_PROXY: %s", SRCNAME, __func__, ret); char *smtpbegin = strstr(ret, "SMTP:"); if (smtpbegin == ret) { ret += 5; } return ret; } else { log_debug ("%s:%s: Failed AB_PROXY lookup.", SRCNAME, __func__); } LPDISPATCH ex_user = get_oom_object (addr_entry, "GetExchangeUser"); if (!ex_user) { log_debug ("%s:%s: Failed to find ExchangeUser", SRCNAME, __func__); return nullptr; } ret = get_oom_string (ex_user, "PrimarySmtpAddress"); gpgol_release (ex_user); if (ret) { log_debug ("%s:%s: Found recipient through exchange user primary smtp address: %s", SRCNAME, __func__, ret); return ret; } return nullptr; } /* Helper for additional fallbacks in recipient lookup */ static char * get_recipient_addr_fallbacks (LPDISPATCH recipient) { if (!recipient) { return nullptr; } LPDISPATCH addr_entry = get_oom_object (recipient, "AddressEntry"); if (!addr_entry) { log_debug ("%s:%s: Failed to find AddressEntry", SRCNAME, __func__); return nullptr; } char *ret = get_recipient_addr_entry_fallbacks_ex (addr_entry); gpgol_release (addr_entry); return ret; } /* Try to resolve a recipient group and add it to the recipients vector. returns true on success. */ static bool try_resolve_group (LPDISPATCH addrEntry, std::vector &ret) { /* Get the name for debugging */ std::string name; char *cname = get_oom_string (addrEntry, "Name"); if (cname) { name = cname; } xfree (cname); int type = get_oom_int (addrEntry, "AddressEntryUserType"); if (type != DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE) { log_mime_parser ("%s:%s: type of %s is %i", SRCNAME, __func__, name.c_str(), type); return false; } LPDISPATCH members = get_oom_object (addrEntry, "Members"); addrEntry = nullptr; if (!members) { TRACEPOINT; return false; } int count = get_oom_int (members, "Count"); if (!count) { TRACEPOINT; gpgol_release (members); return false; } bool foundOne = false; for (int i = 1; i <= count; i++) { auto item_str = std::string("Item(") + std::to_string (i) + ")"; LPDISPATCH entry = get_oom_object (members, item_str.c_str()); if (!entry) { TRACEPOINT; continue; } std::string entryName; char *entry_name = get_oom_string (entry, "Name"); if (entry_name) { entryName = entry_name; xfree (entry_name); } int subType = get_oom_int (entry, "AddressEntryUserType"); /* Resolve recursively, yeah fun. */ if (subType == DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE) { log_debug ("%s:%s: recursive address entry %s", SRCNAME, __func__, (opt.enable_debug & DBG_MIME_PARSER) ? entryName.c_str() : "omitted"); if (try_resolve_group (entry, ret)) { foundOne = true; gpgol_release (entry); continue; } } /* Resolve directly ? */ char *addrtype = get_pa_string (entry, PR_ADDRTYPE_DASL); if (addrtype && !strcmp (addrtype, "SMTP")) { xfree (addrtype); char *resolved = get_pa_string (entry, PR_EMAIL_ADDRESS_DASL); if (resolved) { ret.push_back(resolved); foundOne = true; gpgol_release (entry); continue; } } xfree (addrtype); /* Resolve through Exchange API */ char *ex_resolved = get_recipient_addr_entry_fallbacks_ex (entry); if (ex_resolved) { ret.push_back (ex_resolved); foundOne = true; gpgol_release (entry); continue; } gpgol_release (entry); log_debug ("%s:%s: failed to resolve name %s", SRCNAME, __func__, (opt.enable_debug & DBG_MIME_PARSER) ? entryName.c_str() : "omitted"); } gpgol_release (members); if (!foundOne) { log_debug ("%s:%s: failed to resolve group %s", SRCNAME, __func__, (opt.enable_debug & DBG_MIME_PARSER) ? name.c_str() : "omitted"); } return foundOne; } /* Gets the resolved smtp addresses of the recpients. */ std::vector get_oom_recipients (LPDISPATCH recipients, bool *r_err) { int recipientsCnt = get_oom_int (recipients, "Count"); std::vector ret; int i; if (!recipientsCnt) { return ret; } /* Get the recipients */ for (i = 1; i <= recipientsCnt; i++) { char buf[16]; LPDISPATCH recipient; snprintf (buf, sizeof (buf), "Item(%i)", i); recipient = get_oom_object (recipients, buf); if (!recipient) { /* Should be impossible */ log_error ("%s:%s: could not find Item %i;", SRCNAME, __func__, i); if (r_err) { *r_err = true; } break; } LPDISPATCH addrEntry = get_oom_object (recipient, "AddressEntry"); if (addrEntry && try_resolve_group (addrEntry, ret)) { log_debug ("%s:%s: Resolved recipient group", SRCNAME, __func__); gpgol_release (recipient); gpgol_release (addrEntry); continue; } gpgol_release (addrEntry); char *resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL); if (resolved) { ret.push_back (resolved); xfree (resolved); gpgol_release (recipient); continue; } /* No PR_SMTP_ADDRESS first fallback */ resolved = get_recipient_addr_fallbacks (recipient); if (resolved) { ret.push_back (resolved); xfree (resolved); gpgol_release (recipient); continue; } char *address = get_oom_string (recipient, "Address"); gpgol_release (recipient); log_debug ("%s:%s: Failed to look up Address probably EX addr is returned", SRCNAME, __func__); if (address) { ret.push_back (address); xfree (address); } else if (r_err) { *r_err = true; } } return ret; } /* Add an attachment to the outlook dispatcher disp that has an Attachment property. inFile is the path to the attachment. Name is the name that should be used in outlook. */ int add_oom_attachment (LPDISPATCH disp, const wchar_t* inFileW, const wchar_t* displayName) { LPDISPATCH attachments = get_oom_object (disp, "Attachments"); DISPID dispid; DISPPARAMS dispparams; VARIANT vtResult; VARIANT aVariant[4]; HRESULT hr; BSTR inFileB = nullptr, dispNameB = nullptr; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispid = lookup_oom_dispid (attachments, "Add"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find attachment dispatcher", SRCNAME, __func__); return -1; } if (inFileW) { inFileB = SysAllocString (inFileW); } if (displayName) { dispNameB = SysAllocString (displayName); } dispparams.rgvarg = aVariant; /* Contrary to the documentation the Source is the last parameter and not the first. Additionally DisplayName is documented but gets ignored by Outlook since Outlook 2003 */ dispparams.rgvarg[0].vt = VT_BSTR; /* DisplayName */ dispparams.rgvarg[0].bstrVal = dispNameB; dispparams.rgvarg[1].vt = VT_INT; /* Position */ dispparams.rgvarg[1].intVal = 1; dispparams.rgvarg[2].vt = VT_INT; /* Type */ dispparams.rgvarg[2].intVal = 1; dispparams.rgvarg[3].vt = VT_BSTR; /* Source */ dispparams.rgvarg[3].bstrVal = inFileB; dispparams.cArgs = 4; dispparams.cNamedArgs = 0; VariantInit (&vtResult); hr = attachments->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &vtResult, &execpinfo, &argErr); if (hr != S_OK) { log_debug ("%s:%s: error: invoking Add p=%p vt=%d hr=0x%x argErr=0x%x", SRCNAME, __func__, vtResult.pdispVal, vtResult.vt, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); } if (inFileB) SysFreeString (inFileB); if (dispNameB) SysFreeString (dispNameB); VariantClear (&vtResult); gpgol_release (attachments); return hr == S_OK ? 0 : -1; } LPDISPATCH get_object_by_id (LPDISPATCH pDisp, REFIID id) { LPDISPATCH disp = NULL; if (!pDisp) return NULL; if (gpgol_queryInterface(pDisp, id, (void **)&disp) != S_OK) return NULL; return disp; } LPDISPATCH get_strong_reference (LPDISPATCH mail) { VARIANT var; VariantInit (&var); DISPPARAMS args; VARIANT argvars[2]; VariantInit (&argvars[0]); VariantInit (&argvars[1]); argvars[1].vt = VT_DISPATCH; argvars[1].pdispVal = mail; argvars[0].vt = VT_INT; argvars[0].intVal = 1; args.cArgs = 2; args.cNamedArgs = 0; args.rgvarg = argvars; LPDISPATCH ret = NULL; if (!invoke_oom_method_with_parms ( GpgolAddin::get_instance()->get_application(), "GetObjectReference", &var, &args)) { ret = var.pdispVal; log_oom ("%s:%s: Got strong ref %p for %p", SRCNAME, __func__, ret, mail); memdbg_addRef (ret); } else { log_error ("%s:%s: Failed to get strong ref.", SRCNAME, __func__); } VariantClear (&var); return ret; } LPMESSAGE get_oom_message (LPDISPATCH mailitem) { LPUNKNOWN mapi_obj = get_oom_iunknown (mailitem, "MapiObject"); if (!mapi_obj) { log_error ("%s:%s: Failed to obtain MAPI Message.", SRCNAME, __func__); return NULL; } return (LPMESSAGE) mapi_obj; } static LPMESSAGE get_oom_base_message_from_mapi (LPDISPATCH mapi_message) { HRESULT hr; LPDISPATCH secureItem = NULL; LPMESSAGE message = NULL; LPMAPISECUREMESSAGE secureMessage = NULL; secureItem = get_object_by_id (mapi_message, IID_IMAPISecureMessage); if (!secureItem) { log_error ("%s:%s: Failed to obtain SecureItem.", SRCNAME, __func__); return NULL; } secureMessage = (LPMAPISECUREMESSAGE) secureItem; /* The call to GetBaseMessage is pretty much a jump in the dark. So it would not be surprising to get crashes here in the future. */ log_oom_extra("%s:%s: About to call GetBaseMessage.", SRCNAME, __func__); hr = secureMessage->GetBaseMessage (&message); memdbg_addRef (message); gpgol_release (secureMessage); if (hr != S_OK) { log_error_w32 (hr, "Failed to GetBaseMessage."); return NULL; } return message; } LPMESSAGE get_oom_base_message (LPDISPATCH mailitem) { LPMESSAGE mapi_message = get_oom_message (mailitem); LPMESSAGE ret = NULL; if (!mapi_message) { log_error ("%s:%s: Failed to obtain mapi_message.", SRCNAME, __func__); return NULL; } ret = get_oom_base_message_from_mapi ((LPDISPATCH)mapi_message); gpgol_release (mapi_message); return ret; } static int invoke_oom_method_with_parms_type (LPDISPATCH pDisp, const char *name, VARIANT *rVariant, DISPPARAMS *params, int type) { HRESULT hr; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { EXCEPINFO execpinfo; init_excepinfo (&execpinfo); DISPPARAMS dispparams = {NULL, NULL, 0, 0}; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, type, params ? params : &dispparams, rVariant, &execpinfo, NULL); if (hr != S_OK) { log_debug ("%s:%s: Method '%s' invokation failed: %#lx", SRCNAME, __func__, name, hr); dump_excepinfo (execpinfo); return -1; } } return 0; } int invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name, VARIANT *rVariant, DISPPARAMS *params) { return invoke_oom_method_with_parms_type (pDisp, name, rVariant, params, DISPATCH_METHOD); } int invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant) { return invoke_oom_method_with_parms (pDisp, name, rVariant, NULL); } LPMAPISESSION get_oom_mapi_session () { LPDISPATCH application = GpgolAddin::get_instance ()->get_application (); LPDISPATCH oom_session = NULL; LPMAPISESSION session = NULL; LPUNKNOWN mapiobj = NULL; HRESULT hr; if (!application) { log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__); return NULL; } oom_session = get_oom_object (application, "Session"); if (!oom_session) { log_error ("%s:%s: session object not found", SRCNAME, __func__); return NULL; } mapiobj = get_oom_iunknown (oom_session, "MAPIOBJECT"); gpgol_release (oom_session); if (!mapiobj) { log_error ("%s:%s: error getting Session.MAPIOBJECT", SRCNAME, __func__); return NULL; } session = NULL; hr = gpgol_queryInterface (mapiobj, IID_IMAPISession, (void**)&session); gpgol_release (mapiobj); if (hr != S_OK || !session) { log_error ("%s:%s: error getting IMAPISession: hr=%#lx", SRCNAME, __func__, hr); return NULL; } return session; } static int create_category (LPDISPATCH categories, const char *category, int color) { VARIANT cVariant[3]; VARIANT rVariant; DISPID dispid; DISPPARAMS dispparams; HRESULT hr; EXCEPINFO execpinfo; BSTR b_name; wchar_t *w_name; unsigned int argErr = 0; init_excepinfo (&execpinfo); if (!categories || !category) { TRACEPOINT; return 1; } dispid = lookup_oom_dispid (categories, "Add"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find Add DISPID", SRCNAME, __func__); return -1; } /* Do the string dance */ w_name = utf8_to_wchar (category); b_name = SysAllocString (w_name); xfree (w_name); /* Variants are in reverse order ShortcutKey -> 0 / Int Color -> 1 / Int Name -> 2 / Bstr */ VariantInit (&cVariant[2]); cVariant[2].vt = VT_BSTR; cVariant[2].bstrVal = b_name; VariantInit (&cVariant[1]); cVariant[1].vt = VT_INT; cVariant[1].intVal = color; VariantInit (&cVariant[0]); cVariant[0].vt = VT_INT; cVariant[0].intVal = 0; dispparams.cArgs = 3; dispparams.cNamedArgs = 0; dispparams.rgvarg = cVariant; hr = categories->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, &execpinfo, &argErr); SysFreeString (b_name); VariantClear (&cVariant[0]); VariantClear (&cVariant[1]); VariantClear (&cVariant[2]); if (hr != S_OK) { log_debug ("%s:%s: error: invoking Add p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, rVariant.pdispVal, rVariant.vt, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); VariantClear (&rVariant); return -1; } VariantClear (&rVariant); log_debug ("%s:%s: Created category '%s'", SRCNAME, __func__, category); return 0; } void ensure_category_exists (LPDISPATCH application, const char *category, int color) { if (!application || !category) { TRACEPOINT; return; } log_debug ("Ensure category exists called for %s, %i", category, color); LPDISPATCH stores = get_oom_object (application, "Session.Stores"); if (!stores) { log_error ("%s:%s: No stores found.", SRCNAME, __func__); return; } auto store_count = get_oom_int (stores, "Count"); for (int n = 1; n <= store_count; n++) { const auto store_str = std::string("Item(") + std::to_string(n) + ")"; LPDISPATCH store = get_oom_object (stores, store_str.c_str()); if (!store) { TRACEPOINT; continue; } LPDISPATCH categories = get_oom_object (store, "Categories"); gpgol_release (store); if (!categories) { categories = get_oom_object (application, "Session.Categories"); if (!categories) { TRACEPOINT; continue; } } auto count = get_oom_int (categories, "Count"); bool found = false; for (int i = 1; i <= count && !found; i++) { const auto item_str = std::string("Item(") + std::to_string(i) + ")"; LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str()); if (!category_obj) { TRACEPOINT; gpgol_release (categories); break; } char *name = get_oom_string (category_obj, "Name"); if (name && !strcmp (category, name)) { log_debug ("%s:%s: Found category '%s'", SRCNAME, __func__, name); found = true; } /* We don't check the color here as the user may change that. */ gpgol_release (category_obj); xfree (name); } if (!found) { if (create_category (categories, category, color)) { log_debug ("%s:%s: Found category '%s'", SRCNAME, __func__, category); } } /* Otherwise we have to create the category */ gpgol_release (categories); } gpgol_release (stores); } int add_category (LPDISPATCH mail, const char *category) { char *tmp = get_oom_string (mail, "Categories"); if (!tmp) { TRACEPOINT; return 1; } if (strstr (tmp, category)) { log_debug ("%s:%s: category '%s' already added.", SRCNAME, __func__, category); return 0; } std::string newstr (tmp); xfree (tmp); if (!newstr.empty ()) { newstr += ", "; } newstr += category; return put_oom_string (mail, "Categories", newstr.c_str ()); } int remove_category (LPDISPATCH mail, const char *category) { char *tmp = get_oom_string (mail, "Categories"); if (!tmp) { TRACEPOINT; return 1; } std::string newstr (tmp); xfree (tmp); std::string cat (category); size_t pos1 = newstr.find (cat); size_t pos2 = newstr.find (std::string(", ") + cat); if (pos1 == std::string::npos && pos2 == std::string::npos) { log_debug ("%s:%s: category '%s' not found.", SRCNAME, __func__, category); return 0; } size_t len = cat.size(); if (pos2) { len += 2; } newstr.erase (pos2 != std::string::npos ? pos2 : pos1, len); log_debug ("%s:%s: removing category '%s'", SRCNAME, __func__, category); return put_oom_string (mail, "Categories", newstr.c_str ()); } static char * generate_uid () { UUID uuid; UuidCreate (&uuid); unsigned char *str; UuidToStringA (&uuid, &str); char *ret = xstrdup ((char*)str); RpcStringFreeA (&str); return ret; } char * get_unique_id (LPDISPATCH mail, int create, const char *uuid) { if (!mail) { return NULL; } /* Get the User Properties. */ if (!create) { char *uid = get_pa_string (mail, GPGOL_UID_DASL); if (!uid) { log_debug ("%s:%s: No uuid found in oom for '%p'", SRCNAME, __func__, mail); return NULL; } else { log_debug ("%s:%s: Found uid '%s' for '%p'", SRCNAME, __func__, uid, mail); return uid; } } char *newuid; if (!uuid) { newuid = generate_uid (); } else { newuid = xstrdup (uuid); } int ret = put_pa_string (mail, GPGOL_UID_DASL, newuid); if (ret) { log_debug ("%s:%s: failed to set uid '%s' for '%p'", SRCNAME, __func__, newuid, mail); xfree (newuid); return NULL; } log_debug ("%s:%s: '%p' has now the uid: '%s' ", SRCNAME, __func__, mail, newuid); return newuid; } HWND get_active_hwnd () { LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; return nullptr; } LPDISPATCH activeWindow = get_oom_object (app, "ActiveWindow"); if (!activeWindow) { activeWindow = get_oom_object (app, "ActiveInspector"); if (!activeWindow) { activeWindow = get_oom_object (app, "ActiveExplorer"); if (!activeWindow) { TRACEPOINT; return nullptr; } } } /* Both explorer and inspector have this. */ char *caption = get_oom_string (activeWindow, "Caption"); gpgol_release (activeWindow); if (!caption) { TRACEPOINT; return nullptr; } /* Might not be completly true for multiple explorers on the same folder but good enugh. */ HWND hwnd = FindWindowExA(NULL, NULL, "rctrl_renwnd32", caption); xfree (caption); return hwnd; } LPDISPATCH create_mail () { LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; return nullptr; } VARIANT var; VariantInit (&var); VARIANT argvars[1]; DISPPARAMS args; VariantInit (&argvars[0]); argvars[0].vt = VT_I2; argvars[0].intVal = 0; args.cArgs = 1; args.cNamedArgs = 0; args.rgvarg = argvars; LPDISPATCH ret = nullptr; if (invoke_oom_method_with_parms (app, "CreateItem", &var, &args)) { log_error ("%s:%s: Failed to create mailitem.", SRCNAME, __func__); return ret; } ret = var.pdispVal; return ret; } LPDISPATCH get_account_for_mail (const char *mbox) { LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; return nullptr; } LPDISPATCH accounts = get_oom_object (app, "Session.Accounts"); if (!accounts) { TRACEPOINT; return nullptr; } int count = get_oom_int (accounts, "Count"); for (int i = 1; i <= count; i++) { std::string item = std::string ("Item(") + std::to_string (i) + ")"; LPDISPATCH account = get_oom_object (accounts, item.c_str ()); if (!account) { TRACEPOINT; continue; } char *smtpAddr = get_oom_string (account, "SmtpAddress"); if (!smtpAddr) { gpgol_release (account); TRACEPOINT; continue; } if (!stricmp (mbox, smtpAddr)) { gpgol_release (accounts); xfree (smtpAddr); return account; } gpgol_release (account); xfree (smtpAddr); } gpgol_release (accounts); log_error ("%s:%s: Failed to find account for '%s'.", SRCNAME, __func__, mbox); return nullptr; } char * get_sender_SendUsingAccount (LPDISPATCH mailitem, bool *r_is_GSuite) { LPDISPATCH sender = get_oom_object (mailitem, "SendUsingAccount"); if (!sender) { return nullptr; } char *buf = get_oom_string (sender, "SmtpAddress"); char *dispName = get_oom_string (sender, "DisplayName"); gpgol_release (sender); /* Check for G Suite account */ if (dispName && !strcmp ("G Suite", dispName) && r_is_GSuite) { *r_is_GSuite = true; } xfree (dispName); if (buf && strlen (buf)) { log_debug ("%s:%s: found sender", SRCNAME, __func__); return buf; } xfree (buf); return nullptr; } char * get_sender_Sender (LPDISPATCH mailitem) { LPDISPATCH sender = get_oom_object (mailitem, "Sender"); if (!sender) { return nullptr; } char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL); gpgol_release (sender); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 2", SRCNAME, __func__); return buf; } xfree (buf); /* We have a sender object but not yet an smtp address likely exchange. Try some more propertys of the message. */ buf = get_pa_string (mailitem, PR_TAG_SENDER_SMTP_ADDRESS); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 3", SRCNAME, __func__); return buf; } xfree (buf); buf = get_pa_string (mailitem, PR_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 4", SRCNAME, __func__); return buf; } xfree (buf); return nullptr; } char * get_sender_CurrentUser (LPDISPATCH mailitem) { LPDISPATCH sender = get_oom_object (mailitem, "Session.CurrentUser"); if (!sender) { return nullptr; } char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL); gpgol_release (sender); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 5", SRCNAME, __func__); return buf; } xfree (buf); return nullptr; } char * get_sender_SenderEMailAddress (LPDISPATCH mailitem) { char *type = get_oom_string (mailitem, "SenderEmailType"); if (type && !strcmp ("SMTP", type)) { char *senderMail = get_oom_string (mailitem, "SenderEmailAddress"); if (senderMail) { log_debug ("%s:%s: Sender found", SRCNAME, __func__); xfree (type); return senderMail; } } xfree (type); return nullptr; } char * get_inline_body () { LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; return nullptr; } LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer"); if (!explorer) { TRACEPOINT; return nullptr; } LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse"); gpgol_release (explorer); if (!inlineResponse) { return nullptr; } char *body = get_oom_string (inlineResponse, "Body"); gpgol_release (inlineResponse); return body; } int get_ex_major_version_for_addr (const char *mbox) { LPDISPATCH account = get_account_for_mail (mbox); if (!account) { TRACEPOINT; return -1; } char *version_str = get_oom_string (account, "ExchangeMailboxServerVersion"); gpgol_release (account); if (!version_str) { return -1; } long int version = strtol (version_str, nullptr, 10); xfree (version_str); return (int) version; } int get_ol_ui_language () { LPDISPATCH app = GpgolAddin::get_instance()->get_application(); if (!app) { TRACEPOINT; return 0; } LPDISPATCH langSettings = get_oom_object (app, "LanguageSettings"); if (!langSettings) { TRACEPOINT; return 0; } VARIANT var; VariantInit (&var); VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = 2; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; int ret = invoke_oom_method_with_parms_type (langSettings, "LanguageID", &var, &dispparams, DISPATCH_PROPERTYGET); gpgol_release (langSettings); if (ret) { TRACEPOINT; return 0; } if (var.vt != VT_INT && var.vt != VT_I4) { TRACEPOINT; return 0; } int result = var.intVal; VariantClear (&var); return result; } void log_addins () { LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; return; } LPDISPATCH addins = get_oom_object (app, "COMAddins"); if (!addins) { TRACEPOINT; return; } std::string activeAddins; int count = get_oom_int (addins, "Count"); for (int i = 1; i <= count; i++) { std::string item = std::string ("Item(") + std::to_string (i) + ")"; LPDISPATCH addin = get_oom_object (addins, item.c_str ()); if (!addin) { TRACEPOINT; continue; } bool connected = get_oom_bool (addin, "Connect"); if (!connected) { gpgol_release (addin); continue; } char *progId = get_oom_string (addin, "ProgId"); gpgol_release (addin); if (!progId) { TRACEPOINT; continue; } activeAddins += std::string (progId) + "\n"; xfree (progId); } gpgol_release (addins); log_debug ("%s:%s:Active Addins:\n%s", SRCNAME, __func__, activeAddins.c_str ()); return; } + +bool +is_preview_pane_visible (LPDISPATCH explorer) +{ + if (!explorer) + { + TRACEPOINT; + return false; + } + VARIANT var; + VariantInit (&var); + VARIANT argvars[1]; + DISPPARAMS args; + VariantInit (&argvars[0]); + argvars[0].vt = VT_INT; + argvars[0].intVal = 3; + args.cArgs = 1; + args.cNamedArgs = 0; + args.rgvarg = argvars; + + if (invoke_oom_method_with_parms (explorer, "IsPaneVisible", &var, &args)) + { + log_error ("%s:%s: Failed to check visibilty.", + SRCNAME, __func__); + return false; + } + + if (var.vt != VT_BOOL) + { + TRACEPOINT; + return false; + } + return !!var.boolVal; +} diff --git a/src/oomhelp.h b/src/oomhelp.h index 3a94579..ca7c1fd 100644 --- a/src/oomhelp.h +++ b/src/oomhelp.h @@ -1,379 +1,382 @@ /* oomhelp.h - Defs for helper functions for the Outlook Object Model * Copyright (C) 2009 g10 Code GmbH * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifndef OOMHELP_H #define OOMHELP_H #include #include "mymapi.h" #include #include #define MSOCONTROLBUTTON 1 #define MSOCONTROLEDIT 2 #define MSOCONTROLDROPDOWN 3 #define MSOCONTROLCOMBOBOX 4 #define MSOCONTROLPOPUP 10 enum { msoButtonAutomatic = 0, msoButtonIcon = 1, msoButtonCaption = 2, msoButtonIconAndCaption = 3, msoButtonIconAndWrapCaption = 7, msoButtonIconAndCaptionBelow = 11, msoButtonWrapCaption = 14, msoButtonIconAndWrapCaptionBelow = 15 }; enum { msoButtonDown = -1, msoButtonUp = 0, msoButtonMixed = 2 }; DEFINE_GUID(GUID_NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); DEFINE_GUID(IID_IConnectionPoint, 0xb196b286, 0xbab4, 0x101a, 0xb6, 0x9c, 0x00, 0xaa, 0x00, 0x34, 0x1d, 0x07); DEFINE_GUID(IID_IConnectionPointContainer, 0xb196b284, 0xbab4, 0x101a, 0xb6, 0x9c, 0x00, 0xaa, 0x00, 0x34, 0x1d, 0x07); DEFINE_GUID(IID_IPictureDisp, 0x7bf80981, 0xbf32, 0x101a, 0x8b, 0xbb, 0x00, 0xaa, 0x00, 0x30, 0x0c, 0xab); DEFINE_GUID(IID_FolderEvents, 0x000630F7, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_ApplicationEvents, 0x0006304E, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_ExplorerEvents, 0x0006300F, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_ExplorersEvents, 0x00063078, 0x0000, 0x0000, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_MailItemEvents, 0x0006302B, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_MailItem, 0x00063034, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); DEFINE_GUID(IID_IMAPISecureMessage, 0x253cc320, 0xeab6, 0x11d0, 0x82, 0x22, 0, 0x60, 0x97, 0x93, 0x87, 0xea); DEFINE_OLEGUID(IID_IUnknown, 0x00000000, 0, 0); DEFINE_OLEGUID(IID_IDispatch, 0x00020400, 0, 0); DEFINE_OLEGUID(IID_IOleWindow, 0x00000114, 0, 0); #ifndef PR_SMTP_ADDRESS_DASL #define PR_SMTP_ADDRESS_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x39FE001E" #endif #ifndef PR_EMS_AB_PROXY_ADDRESSES_DASL #define PR_EMS_AB_PROXY_ADDRESSES_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x800F101E" #endif #ifndef PR_ATTACHMENT_HIDDEN_DASL #define PR_ATTACHMENT_HIDDEN_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x7FFE000B" #endif #ifndef PR_ADDRTYPE_DASL #define PR_ADDRTYPE_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x3002001E" #endif #ifndef PR_EMAIL_ADDRESS_DASL #define PR_EMAIL_ADDRESS_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x3003001E" #endif #define PR_MESSAGE_CLASS_W_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x001A001F" #define GPGOL_ATTACHTYPE_DASL \ "http://schemas.microsoft.com/mapi/string/" \ "{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL Attach Type/0x00000003" #define GPGOL_UID_DASL \ "http://schemas.microsoft.com/mapi/string/" \ "{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL UID/0x0000001F" #define PR_ATTACH_DATA_BIN_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x37010102" #define PR_BODY_W_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x1000001F" #define PR_ATTACHMENT_HIDDEN_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x7FFE000B" #define PR_ATTACH_MIME_TAG_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x370E001F" #define PR_ATTACH_CONTENT_ID_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x3712001F" #define PR_TAG_SENDER_SMTP_ADDRESS \ "http://schemas.microsoft.com/mapi/proptag/0x5D01001F" #define PR_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS \ "http://schemas.microsoft.com/mapi/proptag/0x5D08001F" #define PR_PIDNameContentType_DASL \ "http://schemas.microsoft.com/mapi/string/{00020386-0000-0000-C000-000000000046}/content-type/0x0000001F" #define PR_BLOCK_STATUS_DASL \ "http://schemas.microsoft.com/mapi/proptag/0x10960003" #define DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE 11 /* Return the malloced name of an COM+ object. */ char *get_object_name (LPUNKNOWN obj); /* Helper to lookup a dispid. */ DISPID lookup_oom_dispid (LPDISPATCH pDisp, const char *name); /* Return the OOM object's IDispatch interface described by FULLNAME. */ LPDISPATCH get_oom_object (LPDISPATCH pStart, const char *fullname); /* Set the Icon of a CommandBarControl. */ int put_oom_icon (LPDISPATCH pDisp, int rsource_id, int size); /* Set the boolean property NAME to VALUE. */ int put_oom_bool (LPDISPATCH pDisp, const char *name, int value); /* Set the property NAME to VALUE. */ int put_oom_int (LPDISPATCH pDisp, const char *name, int value); /* Set the property NAME to STRING. */ int put_oom_string (LPDISPATCH pDisp, const char *name, const char *string); /* Set the property NAME to DISP. */ int put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH value); /* Get the boolean property NAME of the object PDISP. */ int get_oom_bool (LPDISPATCH pDisp, const char *name); /* Get the integer property NAME of the object PDISP. */ int get_oom_int (LPDISPATCH pDisp, const char *name); /* Get the string property NAME of the object PDISP. */ char *get_oom_string (LPDISPATCH pDisp, const char *name); /* Get an IUnknown object from property NAME of PDISP. */ LPUNKNOWN get_oom_iunknown (LPDISPATCH pDisp, const char *name); /* Return the control object with tag property value TAG. */ LPDISPATCH get_oom_control_bytag (LPDISPATCH pObj, const char *tag); /* Add a new button to an object which supports the add method. Returns the new object or NULL on error. */ LPDISPATCH add_oom_button (LPDISPATCH pObj); /* Delete a button. */ void del_oom_button (LPDISPATCH button); /* Get the HWND of the active window in the current context */ HWND get_oom_context_window (LPDISPATCH context); /* Get the address of the recipients as string list. If r_err is not null it is set to true in case of an error. */ std::vector get_oom_recipients (LPDISPATCH recipients, bool *r_err = nullptr); /* Add an attachment to a dispatcher */ int add_oom_attachment (LPDISPATCH disp, const wchar_t* inFile, const wchar_t *displayName); /* Look up a string with the propertyAccessor interface */ char * get_pa_string (LPDISPATCH pDisp, const char *property); /* Look up a long with the propertyAccessor interface. returns -1 on error.*/ int get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt); /* Set a variant with the propertyAccessor interface. This is tested to work at least vor BSTR variants. Trying to set PR_ATTACH_DATA_BIN_DASL with this failed with hresults 0x80020005 type mismatch or 0x80020008 vad variable type for: VT_ARRAY | VT_UI1 | VT_BYREF VT_SAFEARRAY | VT_UI1 | VT_BYREF VT_BSTR | VT_BYREF VT_BSTR VT_ARRAY | VT_UI1 VT_SAFEARRAY | VT_UI1 No idea whats wrong there. Needs more experiments. The Type is only documented as "Binary". Outlookspy also fails with the same error when trying to modify the property. */ int put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value); int put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value); int put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value); /* Look up a variant with the propertyAccessor interface */ int get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant); /* Look up a LONG with the propertyAccessor interface */ LONG get_pa_long (LPDISPATCH pDisp, const char *dasl_id); /* Queries the interface of the dispatcher for the id id. Returns NULL on error. The returned Object must be released. Mainly useful to check if an object is what it appears to be. */ LPDISPATCH get_object_by_id (LPDISPATCH pDisp, REFIID id); /* Obtain the MAPI Message corresponding to the Mailitem. Returns NULL on error. The returned Message needs to be released by the caller */ LPMESSAGE get_oom_message (LPDISPATCH mailitem); /* Obtain the Base MAPI Message of a MailItem. The parameter should be a pointer to a MailItem. returns NULL on error. The returned Message needs to be released by the caller. */ LPMESSAGE get_oom_base_message (LPDISPATCH mailitem); /* Get a strong reference for a mail object by calling Application.GetObjectReference with type strong. The documentation is unclear what this acutally does. This function is left over from experiments about strong references. Maybe there is a use for them. The reference we use in the Mail object is documented as a Weak reference. But changing that does not appear to make a difference. */ LPDISPATCH get_strong_reference (LPDISPATCH mail); /* Invoke a method of an outlook object. returns true on success false otherwise. rVariant should either point to a propery initialized variant (initinalized wiht VariantInit) to hold the return value or a pointer to NULL. */ int invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant); /* Invoke a method of an outlook object. returns true on success false otherwise. rVariant should either point to a propery initialized variant (initinalized wiht VariantInit) to hold the return value or a pointer to NULL. parms can optionally be used to provide a DISPPARAMS structure with parameters for the function. */ int invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name, VARIANT *rVariant, DISPPARAMS *params); /* Try to obtain the mapisession through the Application. returns NULL on error.*/ LPMAPISESSION get_oom_mapi_session (void); /* Ensure a category of the name name exists in the session for the Mail mail. Creates the category with the specified color if required. returns 0 on success. */ void ensure_category_exists (LPDISPATCH mail, const char *category, int color); /* Add a category to a mail if it is not already added. */ int add_category (LPDISPATCH mail, const char *category); /* Remove a category from a mail if it was added. */ int remove_category (LPDISPATCH mail, const char *category); /* Get a unique identifier for a mail object. The uuid is a custom property. If create is set a new uuid will be added if none exists and the value of that uuid returned. The optinal uuid value can be set to be used as uuid instead of a generated one. Return value has to be freed by the caller. */ char * get_unique_id (LPDISPATCH mail, int create, const char* uuid); /* Uses the Application->ActiveWindow to determine the hwnd through FindWindow and the caption. Does not use IOleWindow because that was unreliable somhow. */ HWND get_active_hwnd (void); /* Create a new mailitem and return it */ LPDISPATCH create_mail (void); LPDISPATCH get_account_for_mail (const char *mbox); /* Print all active addins to log */ void log_addins (void); /* Sender fallbacks. All return either null or a malloced address. */ char *get_sender_CurrentUser (LPDISPATCH mailitem); char *get_sender_Sender (LPDISPATCH mailitem); char *get_sender_SenderEMailAddress (LPDISPATCH mailitem); /* Get the body of the active inline response */ char *get_inline_body (void); /* Get the major version of the exchange server of the account for the mail address "mbox". Returns -1 if no version could be detected or exchange is not used.*/ int get_ex_major_version_for_addr (const char *mbox); /* Get the language code used for Outlooks UI */ int get_ol_ui_language (void); char *get_sender_SendUsingAccount (LPDISPATCH mailitem, bool *r_is_GSuite); /* memtracing query interface */ HRESULT gpgol_queryInterface (LPUNKNOWN pObj, REFIID riid, LPVOID FAR *ppvObj); HRESULT gpgol_openProperty (LPMAPIPROP obj, ULONG ulPropTag, LPCIID lpiid, ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN FAR * lppUnk); + +/* Check if the preview pane in the explorer is visible */ +bool is_preview_pane_visible (LPDISPATCH explorer); #endif /*OOMHELP_H*/ diff --git a/src/ribbon-callbacks.cpp b/src/ribbon-callbacks.cpp index 3eda852..43ab432 100644 --- a/src/ribbon-callbacks.cpp +++ b/src/ribbon-callbacks.cpp @@ -1,766 +1,777 @@ /* ribbon-callbacks.h - Callbacks for the ribbon extension interface * Copyright (C) 2013 Intevation GmbH * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "ribbon-callbacks.h" #include "gpgoladdin.h" #include "common.h" #include "mymapi.h" #include "mymapitags.h" #include "common.h" #include "mapihelp.h" #include "mimemaker.h" #include "filetype.h" #include "mail.h" #include #include #undef _ #define _(a) utf8_gettext (a) using namespace GpgME; /* This is so super stupid. I bet even Microsft developers laugh about the definition of VARIANT_BOOL. And then for COM we have to pass pointers to this stuff. */ static VARIANT_BOOL var_true = VARIANT_TRUE; static VARIANT_BOOL var_false = VARIANT_FALSE; /* Gets the context of a ribbon control. And prints some useful debug output */ HRESULT getContext (LPDISPATCH ctrl, LPDISPATCH *context) { *context = get_oom_object (ctrl, "get_Context"); if (*context) { char *name = get_object_name (*context); log_debug ("%s:%s: contextObj: %s", SRCNAME, __func__, name); xfree (name); } return context ? S_OK : E_FAIL; } /* getIcon Loads a PNG image from the resurce converts it into a Bitmap and Wraps it in an PictureDispatcher that is returned as result. Based on documentation from: http://www.codeproject.com/Articles/3537/Loading-JPG-PNG-resources-using-GDI */ HRESULT getIcon (int id, VARIANT* result) { PICTDESC pdesc; LPDISPATCH pPict; HRESULT hr; Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::Bitmap* pbitmap; ULONG_PTR gdiplusToken; HRSRC hResource; DWORD imageSize; const void* pResourceData; HGLOBAL hBuffer; memset (&pdesc, 0, sizeof pdesc); pdesc.cbSizeofstruct = sizeof pdesc; pdesc.picType = PICTYPE_BITMAP; if (!result) { log_error ("getIcon called without result variant."); return E_POINTER; } /* Initialize GDI */ gdiplusStartupInput.DebugEventCallback = NULL; gdiplusStartupInput.SuppressBackgroundThread = FALSE; gdiplusStartupInput.SuppressExternalCodecs = FALSE; gdiplusStartupInput.GdiplusVersion = 1; GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL); /* Get the image from the resource file */ hResource = FindResource (glob_hinst, MAKEINTRESOURCE(id), RT_RCDATA); if (!hResource) { log_error ("%s:%s: failed to find image: %i", SRCNAME, __func__, id); return E_FAIL; } imageSize = SizeofResource (glob_hinst, hResource); if (!imageSize) return E_FAIL; pResourceData = LockResource (LoadResource(glob_hinst, hResource)); if (!pResourceData) { log_error ("%s:%s: failed to load image: %i", SRCNAME, __func__, id); return E_FAIL; } hBuffer = GlobalAlloc (GMEM_MOVEABLE, imageSize); if (hBuffer) { void* pBuffer = GlobalLock (hBuffer); if (pBuffer) { IStream* pStream = NULL; CopyMemory (pBuffer, pResourceData, imageSize); if (CreateStreamOnHGlobal (hBuffer, TRUE, &pStream) == S_OK) { memdbg_addRef (pStream); pbitmap = Gdiplus::Bitmap::FromStream (pStream); gpgol_release (pStream); if (!pbitmap || pbitmap->GetHBITMAP (0, &pdesc.bmp.hbitmap)) { log_error ("%s:%s: failed to get PNG.", SRCNAME, __func__); } } } GlobalUnlock (pBuffer); } GlobalFree (hBuffer); Gdiplus::GdiplusShutdown (gdiplusToken); /* Wrap the image into an OLE object. */ hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp, TRUE, (void **) &pPict); if (hr != S_OK || !pPict) { log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n", SRCNAME, __func__, hr); return -1; } result->pdispVal = pPict; result->vt = VT_DISPATCH; return S_OK; } HRESULT mark_mime_action (LPDISPATCH ctrl, int flags, bool is_explorer) { LPMESSAGE message = NULL; int oldflags, newflags; log_debug ("%s:%s: enter", SRCNAME, __func__); LPDISPATCH context = NULL; if (FAILED(getContext (ctrl, &context))) { TRACEPOINT; return E_FAIL; } LPDISPATCH mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" : "CurrentItem"); gpgol_release (context); if (!mailitem) { log_error ("%s:%s: Failed to get mailitem.", SRCNAME, __func__); return E_FAIL; } /* Get the uid of this item. */ char *uid = get_unique_id (mailitem, 0, nullptr); if (!uid) { LPMESSAGE msg = get_oom_base_message (mailitem); uid = mapi_get_uid (msg); gpgol_release (msg); if (!uid) { log_debug ("%s:%s: Failed to get uid for %p", SRCNAME, __func__, mailitem); } } Mail *mail = nullptr; if (uid) { mail = Mail::getMailForUUID (uid); xfree (uid); } if (mail) { mail->setCryptoSelectedManually (true); } else { log_debug ("%s:%s: Failed to get mail object.", SRCNAME, __func__); } message = get_oom_base_message (mailitem); gpgol_release (mailitem); if (!message) { log_error ("%s:%s: Failed to get message.", SRCNAME, __func__); return S_OK; } oldflags = get_gpgol_draft_info_flags (message); if (flags == 3 && oldflags != 3) { // If only one sub button is active activate // both now. newflags = 3; } else { newflags = oldflags xor flags; } if (set_gpgol_draft_info_flags (message, newflags)) { log_error ("%s:%s: Failed to set draft flags.", SRCNAME, __func__); } gpgol_release (message); /* We need to invalidate the UI to update the toggle states of the subbuttons and the top button. Yeah, we invalidate a lot *sigh* */ gpgoladdin_invalidate_ui (); if (newflags & 1) { Mail::locateAllCryptoRecipients_o (); } return S_OK; } /* Get the state of encrypt / sign toggle buttons. flag values: 1 get the state of the encrypt button. 2 get the state of the sign button. If is_explorer is set to true we look at the inline response. */ HRESULT get_crypt_pressed (LPDISPATCH ctrl, int flags, VARIANT *result, bool is_explorer) { HRESULT hr; bool value; LPDISPATCH context = NULL, mailitem = NULL; LPMESSAGE message = NULL; result->vt = VT_BOOL | VT_BYREF; result->pboolVal = &var_false; /* First the usual defensive check about our parameters */ if (!ctrl || !result) { log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); return E_FAIL; } hr = getContext (ctrl, &context); if (hr) { log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__, hr); return E_FAIL; } mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" : "CurrentItem"); if (!mailitem) { log_error ("%s:%s: Failed to get mailitem.", SRCNAME, __func__); goto done; } message = get_oom_base_message (mailitem); if (!message) { log_error ("%s:%s: No message found.", SRCNAME, __func__); goto done; } value = (get_gpgol_draft_info_flags (message) & flags) == flags; result->pboolVal = value ? &var_true: &var_false; done: gpgol_release (context); gpgol_release (mailitem); gpgol_release (message); return S_OK; } static Mail * get_mail_from_control (LPDISPATCH ctrl, bool *none_selected) { HRESULT hr; LPDISPATCH context = NULL, mailitem = NULL; *none_selected = false; if (!ctrl) { log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); return NULL; } hr = getContext (ctrl, &context); if (hr) { log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__, hr); return NULL; } char *name = get_object_name (context); std::string ctx_name; if (name) { ctx_name = name; xfree (name); } if (ctx_name.empty()) { log_error ("%s:%s: Failed to get context name", SRCNAME, __func__); gpgol_release (context); return NULL; } if (!strcmp (ctx_name.c_str(), "_Inspector")) { mailitem = get_oom_object (context, "CurrentItem"); } else if (!strcmp (ctx_name.c_str(), "_Explorer")) { /* Avoid showing wrong crypto state if we don't have a reading pane. In that case the parser will finish for a mail which is gone and the crypto state will not get updated. */ - if (0 /*g_ol_version_major >= 16 */) + + if (!is_preview_pane_visible (context)) + { + *none_selected = true; + gpgol_release (mailitem); + mailitem = nullptr; + log_debug ("%s:%s: Preview pane invisible", SRCNAME, __func__); + } + +#if 0 + if (g_ol_version_major >= 16) { /* Some Versions of Outlook 2016 crashed when accessing the current view of the Explorer. This was even reproducible with GpgOL disabled and only with Outlook Spy active. If you selected the explorer of an Outlook.com resource and then access the CurrentView and close the CurrentView again in Outlook Spy outlook crashes. See: T3484 The crash no longer occured at least since build 10228. As I'm not sure which Version fixed the crash we don't do a version check and just use the same codepath as for Outlook 2010 and 2013 again. Accessing PreviewPane.WordEditor is not a good solution here as it requires "Microsoft VBA for Office" (See T4056 ). A possible solution for that might be to check if "Mail.GetInspector().WordEditor()" returns NULL. In that case we know that we also won't get a WordEditor in the preview pane. */ LPDISPATCH prevEdit = get_oom_object (context, "PreviewPane.WordEditor"); gpgol_release (prevEdit); if (!prevEdit) { *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; } } else { // Preview Pane is not available in older outlooks LPDISPATCH tableView = get_oom_object (context, "CurrentView"); if (!tableView) { // Woops, should not happen. TRACEPOINT; *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; } else { int hasReadingPane = get_oom_bool (tableView, "ShowReadingPane"); gpgol_release (tableView); if (!hasReadingPane) { *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; } } } +#endif if (!*none_selected) { /* Accessing the selection item can trigger a load event so we only do this here if we think that there might be something visible / selected. To avoid triggering a load if there is no content shown. */ LPDISPATCH selection = get_oom_object (context, "Selection"); if (!selection) { log_error ("%s:%s: Failed to get selection.", SRCNAME, __func__); gpgol_release (context); return NULL; } int count = get_oom_int (selection, "Count"); if (count == 1) { // If we call this on a selection with more items // Outlook sends an ItemLoad event for each mail // in that selection. mailitem = get_oom_object (selection, "Item(1)"); } gpgol_release (selection); if (!mailitem) { *none_selected = true; } } } else if (!strcmp (ctx_name.c_str(), "Selection")) { int count = get_oom_int (context, "Count"); if (count == 1) { // If we call this on a selection with more items // Outlook sends an ItemLoad event for each mail // in that selection. mailitem = get_oom_object (context, "Item(1)"); } if (!mailitem) { *none_selected = true; } } gpgol_release (context); if (!mailitem) { log_debug ("%s:%s: No mailitem. From %s", SRCNAME, __func__, ctx_name.c_str()); return NULL; } char *uid; /* Get the uid of this item. */ uid = get_unique_id (mailitem, 0, nullptr); if (!uid) { LPMESSAGE msg = get_oom_base_message (mailitem); uid = mapi_get_uid (msg); gpgol_release (msg); if (!uid) { log_debug ("%s:%s: Failed to get uid for %p", SRCNAME, __func__, mailitem); gpgol_release (mailitem); return NULL; } } auto ret = Mail::getMailForUUID (uid); xfree (uid); if (!ret) { log_error ("%s:%s: Failed to find mail %p in map.", SRCNAME, __func__, mailitem); } gpgol_release (mailitem); return ret; } /* Helper to reduce code duplication.*/ #define MY_MAIL_GETTER \ if (!ctrl) \ { \ log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); \ return E_FAIL; \ } \ bool none_selected; \ const auto mail = get_mail_from_control (ctrl, &none_selected); \ (void)none_selected; \ if (!mail) \ { \ log_oom ("%s:%s:%i Failed to get mail", \ SRCNAME, __func__, __LINE__); \ } HRESULT get_is_details_enabled (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER if (!result) { TRACEPOINT; return S_OK; } result->vt = VT_BOOL | VT_BYREF; result->pboolVal = none_selected ? &var_false : &var_true; TRACEPOINT; return S_OK; } HRESULT get_sig_label (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BSTR; wchar_t *w_result; if (!mail) { log_debug ("%s:%s: No mail.", SRCNAME, __func__); w_result = utf8_to_wchar (_("Insecure")); result->bstrVal = SysAllocString (w_result); xfree (w_result); return S_OK; } w_result = utf8_to_wchar (mail->getCryptoSummary ().c_str ()); result->bstrVal = SysAllocString (w_result); xfree (w_result); TRACEPOINT; return S_OK; } HRESULT get_sig_ttip (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BSTR; wchar_t *w_result; if (mail) { w_result = utf8_to_wchar (mail->getCryptoOneLine ().c_str()); } else if (!none_selected) { w_result = utf8_to_wchar (_("Insecure message")); } else { w_result = utf8_to_wchar (_("No message selected")); } result->bstrVal = SysAllocString (w_result); xfree (w_result); TRACEPOINT; return S_OK; } HRESULT get_sig_stip (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BSTR; if (none_selected) { result->bstrVal = SysAllocString (L""); TRACEPOINT; return S_OK; } if (!mail || !mail->isCryptoMail ()) { wchar_t *w_result; w_result = utf8_to_wchar (utf8_gettext ("You cannot be sure who sent, " "modified and read the message in transit.\n\n" "Click here to learn more.")); result->bstrVal = SysAllocString (w_result); xfree (w_result); TRACEPOINT; return S_OK; } const auto message = mail->getCryptoDetails_o (); wchar_t *w_message = utf8_to_wchar (message.c_str()); result->bstrVal = SysAllocString (w_message); xfree (w_message); TRACEPOINT; return S_OK; } HRESULT launch_cert_details (LPDISPATCH ctrl) { MY_MAIL_GETTER if (!mail || (!mail->isSigned () && !mail->isEncrypted ())) { ShellExecuteA(NULL, NULL, "https://emailselfdefense.fsf.org/infographic", 0, 0, SW_SHOWNORMAL); return S_OK; } if (!mail->isSigned () && mail->isEncrypted ()) { /* Encrypt only, no information but show something. because we want the button to be active. Aheinecke: I don't think we should show to which keys the message is encrypted here. This would confuse users if they see keyids of unknown keys and the information can't be "true" because the sender could have sent the same information to other people or used throw keyids etc. */ char * buf; gpgrt_asprintf (&buf, _("The message was not cryptographically signed.\n" "There is no additional information available if it " "was actually sent by '%s' or if someone faked the sender address."), mail->getSender_o ().c_str()); MessageBox (NULL, buf, _("GpgOL"), MB_ICONINFORMATION|MB_OK); xfree (buf); return S_OK; } if (!mail->getSigFpr ()) { std::string buf = _("There was an error verifying the signature.\n" "Full details:\n"); buf += mail->getVerificationResultDump (); gpgol_message_box (get_active_hwnd(), buf.c_str(), _("GpgOL"), MB_OK); } char *uiserver = get_uiserver_name (); bool showError = false; if (uiserver) { std::string path (uiserver); xfree (uiserver); if (path.find("kleopatra.exe") != std::string::npos) { size_t dpos; if ((dpos = path.find(" --daemon")) != std::string::npos) { path.erase(dpos, strlen(" --daemon")); } auto ctx = Context::createForEngine(SpawnEngine); if (!ctx) { log_error ("%s:%s: No spawn engine.", SRCNAME, __func__); } std::string parentWid = std::to_string ((int) (intptr_t) get_active_hwnd ()); const char *argv[] = {path.c_str(), "--query", mail->getSigFpr (), "--parent-windowid", parentWid.c_str(), NULL }; log_debug ("%s:%s: Starting %s %s %s", SRCNAME, __func__, path.c_str(), argv[1], argv[2]); Data d(Data::null); ctx->spawnAsync(path.c_str(), argv, d, d, d, (GpgME::Context::SpawnFlags) ( GpgME::Context::SpawnAllowSetFg | GpgME::Context::SpawnShowWindow)); } else { showError = true; } } else { showError = true; } if (showError) { MessageBox (NULL, _("Could not find Kleopatra.\n" "Please reinstall Gpg4win with the Kleopatra component enabled."), _("GpgOL"), MB_ICONINFORMATION|MB_OK); } return S_OK; } HRESULT get_crypto_icon (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER if (mail) { TRACEPOINT; return getIcon (mail->getCryptoIconID (), result); } TRACEPOINT; return getIcon (IDI_LEVEL_0, result); } HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BOOL | VT_BYREF; result->pboolVal = mail && (mail->isSigned () || mail->isEncrypted ()) ? &var_true : &var_false; TRACEPOINT; return S_OK; } HRESULT print_decrypted (LPDISPATCH ctrl) { MY_MAIL_GETTER if (!mail) { log_error ("%s:%s: Failed to get mail.", SRCNAME, __func__); return S_OK; } invoke_oom_method (mail->item(), "PrintOut", NULL); return S_OK; }