diff --git a/src/mailitem-events.cpp b/src/mailitem-events.cpp
index edbd4df..dca0d56 100644
--- a/src/mailitem-events.cpp
+++ b/src/mailitem-events.cpp
@@ -1,1057 +1,1058 @@
/* mailitem-events.h - Event handling for mails.
* Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
* Copyright (C) 2019, 2020 g10code 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 .
*/
#include "config.h"
#include "common.h"
#include "eventsink.h"
#include "eventsinks.h"
#include "mymapi.h"
#include "mymapitags.h"
#include "oomhelp.h"
#include "ocidl.h"
#include "windowmessages.h"
#include "mail.h"
#include "mapihelp.h"
#include "gpgoladdin.h"
#include "wks-helper.h"
#undef _
#define _(a) utf8_gettext (a)
const wchar_t *prop_blacklist[] = {
L"Body",
L"HTMLBody",
L"To", /* Somehow this is done when a mail is opened */
L"CC", /* Ditto */
L"BCC", /* Ditto */
L"Categories",
L"UnRead",
L"OutlookVersion",
L"OutlookInternalVersion",
L"ReceivedTime",
L"InternetCodepage",
L"ConversationIndex",
L"Subject",
L"SentOnBehalfOfName",
L"MessageClass",
L"BodyFormat",
NULL };
typedef enum
{
AfterWrite = 0xFC8D,
AttachmentAdd = 0xF00B,
AttachmentRead = 0xF00C,
AttachmentRemove = 0xFBAE,
BeforeAttachmentAdd = 0xFBB0,
BeforeAttachmentPreview = 0xFBAF,
BeforeAttachmentRead = 0xFBAB,
BeforeAttachmentSave = 0xF00D,
BeforeAttachmentWriteToTempFile = 0xFBB2,
BeforeAutoSave = 0xFC02,
BeforeCheckNames = 0xF00A,
BeforeDelete = 0xFA75,
BeforeRead = 0xFC8C,
Close = 0xF004,
CustomAction = 0xF006,
CustomPropertyChange = 0xF008,
Forward = 0xF468,
Open = 0xF003,
PropertyChange = 0xF009,
Read = 0xF001,
ReadComplete = 0xFC8F,
Reply = 0xF466,
ReplyAll = 0xF467,
Send = 0xF005,
Unload = 0xFBAD,
Write = 0xF002
} MailEvent;
/* Mail Item Events */
BEGIN_EVENT_SINK(MailItemEvents, IDispatch)
/* We are still in the class declaration */
private:
Mail * m_mail; /* The mail object related to this mailitem */
};
MailItemEvents::MailItemEvents() :
m_object(NULL),
m_pCP(NULL),
m_cookie(0),
m_ref(1),
m_mail(NULL)
{
}
MailItemEvents::~MailItemEvents()
{
if (m_pCP)
m_pCP->Unadvise(m_cookie);
if (m_object)
gpgol_release (m_object);
}
static bool propchangeWarnShown = false;
static bool attachRemoveWarnShown = false;
static bool addinsLogged = false;
static DWORD WINAPI
do_delayed_locate (LPVOID arg)
{
Sleep(100);
do_in_ui_thread (RECIPIENT_ADDED, arg);
return 0;
}
/* The main Invoke function. The return value of this
function does not appear to have any effect on outlook
although I have read in an example somewhere that you
should return S_OK so that outlook continues to handle
the event I have not yet seen any effect by returning
error values here and no MSDN documentation about the
return values.
*/
EVENT_SINK_INVOKE(MailItemEvents)
{
USE_INVOKE_ARGS
TSTART;
if (!m_mail)
{
m_mail = Mail::getMailForItem (m_object);
if (!m_mail)
{
log_error ("%s:%s: mail event without mail object known. Bug.",
SRCNAME, __func__);
TRETURN S_OK;
}
}
bool is_reply = false;
switch(dispid)
{
case BeforeAutoSave:
{
log_oom ("%s:%s: BeforeAutoSave : %p",
SRCNAME, __func__, m_mail);
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
/* This happens in the weird case */
log_debug ("%s:%s: Uncancellable BeforeAutoSave.",
SRCNAME, __func__);
TBREAK;
}
if (m_mail->isCryptoMail() && !m_mail->decryptedSuccessfully ())
{
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
log_debug ("%s:%s: Autosave for not successfuly decrypted mail."
"Cancel it.",
SRCNAME, __func__);
TBREAK;
}
if (opt.draft_key && (m_mail->needs_crypto_m () & 1) &&
!m_mail->isDraftEncrypt())
{
log_debug ("%s:%s: Draft encryption for autosave starting now.",
SRCNAME, __func__);
m_mail->setIsDraftEncrypt (true);
m_mail->prepareCrypto_o ();
m_mail->encryptSignStart_o ();
}
TRETURN S_OK;
}
case Open:
{
log_oom ("%s:%s: Open : %p",
SRCNAME, __func__, m_mail);
int draft_flags = 0;
if (!opt.encrypt_default && !opt.sign_default)
{
TRETURN S_OK;
}
LPMESSAGE message = get_oom_base_message (m_object);
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
TBREAK;
}
int old_flags = get_gpgol_draft_info_flags (message);
if (old_flags)
{
log_dbg ("Open of already flagged mail. "
"Not overwriting with settings.");
gpgol_release (message);
TBREAK;
}
if (opt.encrypt_default)
{
draft_flags = 1;
}
if (opt.sign_default)
{
draft_flags += 2;
}
set_gpgol_draft_info_flags (message, draft_flags);
gpgol_release (message);
TBREAK;
}
case BeforeRead:
{
log_oom ("%s:%s: BeforeRead : %p",
SRCNAME, __func__, m_mail);
if (GpgolAddin::get_instance ()->isShutdown())
{
log_debug ("%s:%s: Ignoring read after shutdown.",
SRCNAME, __func__);
TBREAK;
}
if (m_mail->preProcessMessage_m ())
{
log_error ("%s:%s: Pre process message failed.",
SRCNAME, __func__);
}
if (m_mail->isDecryptAgain ())
{
log_debug ("%s:%s: Decrypting after 500ms",
SRCNAME, __func__);
do_in_ui_thread_async (DECRYPT, m_mail, 500);
}
TBREAK;
}
case Read:
{
log_oom ("%s:%s: Read : %p",
SRCNAME, __func__, m_mail);
if (!addinsLogged)
{
// We do it here as this nearly always comes and we want to remove
// as much as possible from the startup time.
log_addins ();
addinsLogged = true;
}
if (!m_mail->isCryptoMail ())
{
log_debug ("%s:%s: Non crypto mail %p opened. Updating sigstatus.",
SRCNAME, __func__, m_mail);
/* Ensure that no wrong sigstatus is shown */
CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0,
NULL));
TBREAK;
}
if (m_mail->setUUID_o ())
{
log_debug ("%s:%s: Failed to set uuid.",
SRCNAME, __func__);
delete m_mail; /* deletes this, too */
TRETURN S_OK;
}
if (m_mail->decryptVerify_o ())
{
log_error ("%s:%s: Decrypt message failed.",
SRCNAME, __func__);
}
if (!opt.enable_smime && m_mail->isSMIME_m ())
{
/* We want to save the mail when it's an smime mail and smime
is disabled to revert it. */
log_debug ("%s:%s: S/MIME mail but S/MIME is disabled."
" Need save.",
SRCNAME, __func__);
m_mail->setNeedsSave (true);
}
TBREAK;
}
case PropertyChange:
{
if (!parms || parms->cArgs != 1 ||
parms->rgvarg[0].vt != VT_BSTR ||
!parms->rgvarg[0].bstrVal)
{
log_error ("%s:%s: Unexpected params.",
SRCNAME, __func__);
TBREAK;
}
const wchar_t *prop_name = parms->rgvarg[0].bstrVal;
if (!m_mail->isCryptoMail ())
{
if (m_mail->hasOverrideMimeData())
{
/* This is a mail created by us. Ignore propchanges. */
TBREAK;
}
if (!wcscmp (prop_name, L"To") /* ||
!wcscmp (prop_name, L"BCC") ||
!wcscmp (prop_name, L"CC")
Testing shows that Outlook always sends these three in a row
*/)
{
if (opt.autosecure || (m_mail->needs_crypto_m () & 1))
{
/* XXX Racy race. This is a fix for crashes
that happend if a resolved recipient is copied an pasted.
If we then access the recipients object in the Property
Change event we crash. Thus we do the delay dance. */
HANDLE thread = CreateThread (NULL, 0, do_delayed_locate,
(LPVOID) m_mail, 0,
NULL);
CloseHandle(thread);
}
}
TBREAK;
}
for (const wchar_t **cur = prop_blacklist; *cur; cur++)
{
if (!wcscmp (prop_name, *cur))
{
log_oom ("%s:%s: Mail %p propchange: %ls no warning.",
SRCNAME, __func__, m_mail, prop_name);
TRETURN S_OK;
}
}
log_oom ("%s:%s: Mail %p propchange: %ls.",
SRCNAME, __func__, m_mail, prop_name);
if (!wcscmp (prop_name, L"SendUsingAccount"))
{
bool sent = get_oom_bool (m_object, "Sent");
if (sent)
{
log_debug ("%s:%s: Ignoring SendUsingAccount change for sent %p ",
SRCNAME, __func__, m_object);
TRETURN S_OK;
}
log_debug ("%s:%s: Message %p looks like send again.",
SRCNAME, __func__, m_object);
m_mail->setIsSendAgain (true);
TRETURN S_OK;
}
if (is_draft_mail (m_object))
{
log_oom ("%s:%s: Change allowed for draft",
SRCNAME, __func__);
TRETURN S_OK;
}
/* We have tried several scenarios to handle propery changes.
Only save the property in MAPI and call MAPI SaveChanges
worked and did not leak plaintext but this caused outlook
still to break the attachments of PGP/MIME Mails into two
attachments and add them as winmail.dat so other clients
are broken.
Alternatively reverting the mail, saving the property and
then decrypt again also worked a bit but there were some
weird side effects and breakages. But this has the usual
problem of a revert that the mail is created by outlook and
e.g. multipart/signed signatures from most MUA's are broken.
Some things to try out might be the close approach and then
another open or a selection change. But for now we just warn.
As a workardound a user should make property changes when
the mail was not read by us. */
if (propchangeWarnShown)
{
TRETURN S_OK;
}
wchar_t *title = utf8_to_wchar (_("Sorry, that's not possible, yet"));
char *fmt;
gpgrt_asprintf (&fmt, _("GpgOL has prevented the change to the \"%s\" property.\n"
"Property changes are not yet handled for crypto messages.\n\n"
"To workaround this limitation please change the property when the "
"message is not open in any window and not selected in the "
"messagelist.\n\nFor example by right clicking but not selecting the message.\n"),
wchar_to_utf8(prop_name));
memdbg_alloc (fmt);
wchar_t *msg = utf8_to_wchar (fmt);
xfree (fmt);
MessageBoxW (get_active_hwnd(), msg, title,
MB_ICONINFORMATION | MB_OK);
xfree (msg);
xfree (title);
propchangeWarnShown = true;
TRETURN S_OK;
}
case CustomPropertyChange:
{
log_oom ("%s:%s: CustomPropertyChange : %p",
SRCNAME, __func__, m_mail);
/* TODO */
TBREAK;
}
case Send:
{
/* This is the only event where we can cancel the send of a
mailitem. But it is too early for us to encrypt as the MAPI
structures are not yet filled. Crypto based on the
Outlook Object Model data did not work as the messages
were only sent out empty. See 2b376a48 for a try of
this.
This is why we store send_seen and invoke a save which
may result in an error but only after triggering all the
behavior we need -> filling mapi structures and invoking the
AfterWrite handler where we encrypt.
If this encryption is successful and we pass the send
as then the encrypted data is sent.
*/
log_oom ("%s:%s: Send : %p",
SRCNAME, __func__, m_mail);
if (!m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NotStarted)
{
if (m_mail->isCryptoMail ())
{
log_debug ("%s:%s: Want to send crypto mail without crypto. "
"Decrypting.", SRCNAME, __func__);
m_mail->decryptPermanently_o ();
TBREAK;
}
log_debug ("%s:%s: No crypto neccessary. Passing send for %p obj %p",
SRCNAME, __func__, m_mail, m_object);
TBREAK;
}
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
log_debug ("%s:%s: Uncancellable send event.",
SRCNAME, __func__);
TBREAK;
}
if (m_mail->cryptState () == Mail::NotStarted &&
m_mail->needs_crypto_m ())
{
log_dbg ("Send event for mail %p to be encrypted starting.",
m_mail);
if (m_mail->prepareCrypto_o ())
{
log_dbg ("Prepare crypto requested send abort.");
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
/* Reset the crypter state */
m_mail->setCryptState (Mail::NotStarted);
TBREAK;
}
m_mail->setIsDraftEncrypt (false);
/* Do the crypto operation */
log_dbg ("Starting crypto.");
if (m_mail->encryptSignStart_o ())
{
log_debug ("%s:%s: Encrypt sign start failed.",
SRCNAME, __func__);
m_mail->setCryptState (Mail::NotStarted);
m_mail->releaseCurrentItem();
}
if (!m_mail->isAsyncCryptDisabled ())
{
/* Cancel sending. CRYPTO_DONE should trigger sending */
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
TBREAK;
}
/* Only reached for synchronous operation. Mail should now be
* encrypted and in a want's send state or the user aborted /
* error */
if (m_mail->cryptState () == Mail::NotStarted)
{
// Crypto failed or was canceled
log_debug ("%s:%s: Message %p mail %p cancelling send - "
"Crypto failed or canceled.",
SRCNAME, __func__, m_object, m_mail);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
TBREAK;
}
if (m_mail->cryptState () == Mail::BackendDone)
{
m_mail->updateCryptOOM_o ();
}
if (m_mail->cryptState () == Mail::OOMUpdated)
{
m_mail->setCryptState (Mail::CryptFinished);
}
}
/* Check if updating the body worked for send inline */
if (m_mail->cryptState () == Mail::CryptFinished &&
m_mail->getDoPGPInline ())
{
if (!m_mail->hasCryptedOrEmptyBody_o ())
{
log_debug ("%s:%s: Message %p mail %p cancelling send - "
"not encrypted or not empty body detected.",
SRCNAME, __func__, m_object, m_mail);
gpgol_bug (m_mail->getWindow (),
ERR_WANTS_SEND_INLINE_BODY);
m_mail->setCryptState (Mail::NotStarted);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
TBREAK;
}
log_debug ("%s:%s: Passing send event for no-mime message %p.",
SRCNAME, __func__, m_object);
WKSHelper::instance()->allow_notify (1000);
TBREAK;
}
else if (m_mail->cryptState () == Mail::CryptFinished)
{
if (!m_mail->hasCryptedOrEmptyBody_o ())
{
/* The safety checks here trigger too often. Somehow for some
users the body is not empty after the encryption but when
it is sent it is still sent with the crypto content because
the encrypted MIME Structure is used because it is
correct in MAPI land.
For safety reasons enabling the checks might be better but
until we figure out why for some users the body replacement
does not work we have to disable them. Otherwise GpgOL
is unusuable for such users. GnuPG-Bug-Id: T3875
*/
#define DISABLE_SAFTEY_CHECKS
#ifndef DISABLE_SAFTEY_CHECKS
gpgol_bug (m_mail->getWindow (),
ERR_WANTS_SEND_MIME_BODY);
log_debug ("%s:%s: Message %p mail %p cancelling send mime - "
"not encrypted or not empty body detected.",
SRCNAME, __func__, m_object, m_mail);
m_mail->setCryptState (Mail::NotStarted);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
TBREAK;
#else
log_debug ("%s:%s: Message %p mail %p - "
"not encrypted or not empty body detected - MIME.",
SRCNAME, __func__, m_object, m_mail);
#endif
}
/* Now we adress T3656 if Outlooks internal S/MIME is somehow
* mixed in (even if it is enabled and then disabled) it might
* cause strange behavior in that it sends the plain message
* and not the encrypted message. Tests have shown that we can
* bypass that by calling submit message on our base
* message.
*
* We do this conditionally as our other way of using OOM
* to send is proven to work and we don't want to mess
* with it.
*/
// Get the Message class.
HRESULT hr;
LPSPropValue propval = NULL;
// It's important we use the _not_ base message here.
LPMESSAGE message = get_oom_message (m_object);
hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
gpgol_release (message);
if (FAILED (hr))
{
log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
SRCNAME, __func__, hr);
TBREAK;
}
if (propval->Value.lpszA && !strstr (propval->Value.lpszA, "GpgOL") &&
strcmp (propval->Value.lpszA, "IPM.Note.SMIME.MultipartSigned") &&
strcmp (propval->Value.lpszA, "IPM.Note.SMIME"))
{
// Does not have a message class by us.
log_debug ("%s:%s: Message %p - No GpgOL Message class "
"after crypto. cls is: '%s'",
SRCNAME, __func__, m_object, propval->Value.lpszA);
log_debug ("%s:%s: Message %p - Activating T3656 Workaround",
SRCNAME, __func__, m_object);
message = get_oom_base_message (m_object);
if (message)
{
// It's important we use the _base_ message here.
mapi_save_changes (message, FORCE_SAVE);
message->SubmitMessage(0);
gpgol_release (message);
// Close the composer and trigger unloads
CloseHandle(CreateThread (NULL, 0, close_mail, (LPVOID) m_mail, 0,
NULL));
}
else
{
gpgol_bug (nullptr,
ERR_GET_BASE_MSG_FAILED);
}
// Cancel send
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
}
MAPIFreeBuffer (propval);
if (*(parms->rgvarg[0].pboolVal) == VARIANT_TRUE)
{
TBREAK;
}
log_debug ("%s:%s: Passing send event for mime-encrypted message %p.",
SRCNAME, __func__, m_object);
WKSHelper::instance()->allow_notify (1000);
TBREAK;
}
else
{
log_debug ("%s:%s: Message %p cancelling send - "
"crypto or second save failed state: %i.",
SRCNAME, __func__, m_object,
m_mail->cryptState ());
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
}
TRETURN S_OK;
}
case ReadComplete:
{
log_oom ("%s:%s: ReadComplete: %p",
SRCNAME, __func__, m_mail);
TRETURN S_OK;
}
case Write:
{
log_oom ("%s:%s: Write : %p", SRCNAME, __func__, m_mail);
/* This is a bit strange. We sometimes get multiple write events
without a read in between. When we access the message in
the second event it fails and if we cancel the event outlook
crashes. So we have keep the m_needs_wipe state variable
to keep track of that. */
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
/* This happens in the weird case */
log_debug ("%s:%s: Uncancellable write event.",
SRCNAME, __func__);
TBREAK;
}
if (m_mail->passWrite())
{
log_debug ("%s:%s: Passing write because passNextWrite was set for %p",
SRCNAME, __func__, m_mail);
m_mail->setPassWrite (false);
TBREAK;
}
#if 0
TODO: Handle split copy in another way
if (m_mail->cryptState() == Mail::NeedsFirstAfterWrite)
{
log_debug ("%s:%s: Crypto mail needs first after write. "
"Auto-Passing but removing our categories.",
SRCNAME, __func__);
gpgol_message_box (nullptr, "NeedsFirstAfterWrite detected.",
"Write event wants first after write.",
MB_OK);
m_mail->removeCategories_o ();
if (g_mail_copy_triggerer)
{
log_dbg ("Needs first after write for copied mail.");
g_mail_copy_triggerer->splitCopyMailCallback (m_mail);
}
TBREAK;
}
#endif
if (m_mail->isCryptoMail () && !m_mail->needsSave ())
{
if (opt.draft_key && (m_mail->needs_crypto_m () & 1) &&
is_draft_mail (m_object) && m_mail->decryptedSuccessfully ())
{
if (m_mail->cryptState () == Mail::OOMUpdated)
{
log_debug ("%s:%s: re-encryption in progress. Passing.",
SRCNAME, __func__);
TBREAK;
}
/* This is the case for a modified draft */
log_debug ("%s:%s: Draft re-encryption starting now.",
SRCNAME, __func__);
m_mail->setIsDraftEncrypt (true);
m_mail->prepareCrypto_o ();
m_mail->encryptSignStart_o ();
/* Draft encryption happens synchronously so we pass it. */
TBREAK;
}
if (m_mail->cryptState() == Mail::CryptFinished)
{
log_debug ("%s:%s: Mail wants send mime. Passing.",
SRCNAME, __func__);
TBREAK;
}
Mail *last_mail = Mail::getLastMail ();
if (Mail::isValidPtr (last_mail))
{
/* We want to identify here if there was a mail created that
should receive the contents of this mail. For this we check
for a write in the same loop as a mail creation.
Now when switching from one mail to another this is also what
happens. The new mail is loaded and the old mail is written.
To distinguish the two we check that the new mail does not have
an entryID, a Subject and No Size. Maybe just size or entryID
would be enough but better save then sorry.
Security consideration: Worst case we pass the write here but
an unload follows before we get the scheduled revert. This
would leak plaintext. But does not happen in our tests.
Similarly if we crash or Outlook is closed before we see this
revert. But as we immediately revert after the write this should
also not happen. */
const std::string lastSubject = last_mail->getSubject_o ();
char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
int lastSize = get_oom_int (last_mail->item (), "Size");
std::string lastEntryStr;
if (lastEntryID)
{
lastEntryStr = lastEntryID;
xfree (lastEntryID);
}
if (!lastSize && !lastEntryStr.size () && !lastSubject.size ())
{
log_debug ("%s:%s: Write in the same loop as empty load."
" Pass but schedule revert.",
SRCNAME, __func__);
/* This might be a forward. So don't invalidate yet. */
// Mail::clearLastMail ();
do_in_ui_thread_async (REVERT_MAIL, m_mail);
TRETURN S_OK;
}
}
/* We cancel the write event to stop outlook from excessively
syncing our changes.
if smime support is disabled and we still have an smime
mail we also don't want to cancel the write event
to enable reverting this mails.
*/
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
log_debug ("%s:%s: Canceling write event.",
SRCNAME, __func__);
TRETURN S_OK;
}
if (m_mail->isCryptoMail () && m_mail->needsSave () &&
m_mail->revert_o ())
{
/* An error cleaning the mail should not happen normally.
But just in case there is an error we cancel the
write here. */
log_debug ("%s:%s: Failed to remove plaintext. Canceling.",
SRCNAME, __func__);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
TRETURN S_OK;
}
if (!m_mail->isCryptoMail () && m_mail->is_forwarded_crypto_mail () &&
!m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NotStarted)
{
/* We are sure now that while this is a forward of an encrypted
* mail that the forward should not be signed or encrypted. So
* it's not constructed by us. We need to remove our attachments
* though so that they are not included in the forward. */
log_debug ("%s:%s: Writing unencrypted forward of crypt mail. "
"Removing attachments. mail: %p item: %p",
SRCNAME, __func__, m_mail, m_object);
if (m_mail->removeOurAttachments_o ())
{
// Worst case we forward some encrypted data here not
// a security problem, so let it pass.
log_error ("%s:%s: Failed to remove our attachments.",
SRCNAME, __func__);
}
/* Remove marker because we did this now. */
m_mail->setIsForwardedCryptoMail (false);
}
if (m_mail->isDraftEncrypt () &&
m_mail->cryptState () != Mail::OOMUpdated)
{
log_debug ("%s:%s: Canceling write because draft encrypt is in"
" progress.",
SRCNAME, __func__);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
TRETURN S_OK;
}
if (opt.draft_key && (m_mail->needs_crypto_m () & 1) &&
!m_mail->isDraftEncrypt () &&
m_mail->cryptState () == Mail::NotStarted)
{
log_debug ("%s:%s: Draft encryption starting now.",
SRCNAME, __func__);
m_mail->setIsDraftEncrypt (true);
m_mail->prepareCrypto_o ();
m_mail->encryptSignStart_o ();
/* Draft encryption happens synchronously so we pass it. */
TRETURN S_OK;
}
log_debug ("%s:%s: Passing write event. %i %i",
SRCNAME, __func__, m_mail->isDraftEncrypt(), m_mail->cryptState());
m_mail->setNeedsSave (false);
TBREAK;
}
case AfterWrite:
{
log_oom ("%s:%s: AfterWrite : %p",
SRCNAME, __func__, m_mail);
if (m_mail->cryptState () == Mail::OOMUpdated)
{
m_mail->setCryptState (Mail::OOMSynced);
m_mail->updateCryptMAPI_m ();
if (!m_mail->isAsyncCryptDisabled())
{
m_mail->releaseCurrentItem();
}
log_debug ("%s:%s: Second after write done.",
SRCNAME, __func__);
TRETURN S_OK;
}
TBREAK;
}
case Close:
{
log_oom ("%s:%s: Close : %p",
SRCNAME, __func__, m_mail);
if (m_mail->isCryptoMail ())
{
if (is_draft_mail (m_object))
{
/* In that case we want to ask the question to avoid data loss
*/
log_oom ("%s:%s: Passing close because of draft status: %p",
SRCNAME, __func__, m_mail);
- m_mail->setDecryptAgain (true);
+ /* Clear selections to avoid conflicts with inline editors */
+ oom_clear_selections ();
TBREAK;
}
/* Close. This happens when an Opened mail is closed.
To prevent the question of wether or not to save the changes
(Which would save the decrypted data without an event to
prevent it) we cancel the close and then either close it
with discard changes or revert / save it.
Contrary to documentation we can invoke close from
close.
*/
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
/* This happens in the weird case */
log_debug ("%s:%s: Uncancellable close event.",
SRCNAME, __func__);
TBREAK;
}
if (m_mail->getCloseTriggered ())
{
/* Our close with discard changes, pass through */
m_mail->setCloseTriggered (false);
TRETURN S_OK;
}
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
log_oom ("%s:%s: Canceling close event.",
SRCNAME, __func__);
if (m_mail->close ())
{
log_debug ("%s:%s: Close request failed.",
SRCNAME, __func__);
}
}
TRETURN S_OK;
}
case Unload:
{
log_oom ("%s:%s: Unload : %p",
SRCNAME, __func__, m_mail);
log_debug ("%s:%s: Removing Mail for message: %p.",
SRCNAME, __func__, m_object);
delete m_mail;
log_oom ("%s:%s: deletion done",
SRCNAME, __func__);
memdbg_dump ();
TRETURN S_OK;
}
case ReplyAll:
case Reply:
is_reply = true;
/* fall through */
case Forward:
{
log_oom ("%s:%s: %s : %p",
SRCNAME, __func__, is_reply ? "reply" : "forward", m_mail);
int draft_flags = 0;
if (opt.encrypt_default)
{
draft_flags = 1;
}
if (opt.sign_default)
{
draft_flags += 2;
}
bool is_crypto_mail = m_mail->isCryptoMail ();
if (opt.reply_crypt && is_crypto_mail)
{
int crypto_flags = m_mail->getCryptoFlags ();
if (crypto_flags)
{
if (opt.sign_default)
{
/* When default signing is on we also sign unsigned encrypted
* mails. */
crypto_flags |= 2;
/* reply / forward flags override the setting flags. */
}
draft_flags = crypto_flags;
}
}
/* If it is a crypto mail and the settings should not be taken
* from the crypto mail and always encrypt / sign is on. Or
* If it is not a crypto mail and we have automaticalls sign_encrypt. */
if (draft_flags)
{
/* Check if we can use the dispval */
if (parms->cArgs == 2 && parms->rgvarg[1].vt == (VT_DISPATCH) &&
parms->rgvarg[0].vt == (VT_BOOL | VT_BYREF))
{
LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal);
if (msg)
{
set_gpgol_draft_info_flags (msg, draft_flags);
gpgol_release (msg);
}
else
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
}
}
else
{
log_error ("%s:%s: Unexpected parameters.",
SRCNAME, __func__);
}
}
if (!is_crypto_mail)
{
/* Replys to non crypto mails do not interest us anymore. */
TBREAK;
}
Mail *last_mail = Mail::getLastMail ();
if (Mail::isValidPtr (last_mail))
{
/* We want to identify here if there was a mail created that
should receive the contents of this mail. For this we check
for a forward in the same loop as a mail creation.
We need to do it this complicated and can't just use
get_mail_for_item because the mailitem pointer we get here
is a different one then the one with which the mail was loaded.
*/
char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
int lastSize = get_oom_int (last_mail->item (), "Size");
std::string lastEntryStr;
if (lastEntryID)
{
lastEntryStr = lastEntryID;
xfree (lastEntryID);
}
if (!lastSize && !lastEntryStr.size ())
{
if (!is_reply)
{
log_debug ("%s:%s: Forward in the same loop as empty "
"load Marking %p (item %p) as forwarded.",
SRCNAME, __func__, last_mail,
last_mail->item ());
last_mail->setIsForwardedCryptoMail (true);
}
else
{
log_debug ("%s:%s: Reply in the same loop as empty "
"load treating %p (item %p) as reply.",
SRCNAME, __func__, last_mail,
last_mail->item ());
}
if (m_mail->isBlockHTML () && !opt.smime_insecure_reply_fw_allowed)
{
std::string buf;
/** TRANSLATORS: Part of a warning dialog that disallows
reply and forward with contents */
buf = is_reply ? _("You are replying to an unsigned S/MIME "
"email.") :
_("You are forwarding an unsigned S/MIME "
"email.");
buf +="\n\n";
buf += _("In this version of S/MIME an attacker could "
"use the missing signature to have you "
"decrypt contents from a different, otherwise "
"completely unrelated email and place it in the "
"quote so they can get hold of it.\n"
"This is why we only allow quoting to be done manually.");
buf += "\n\n";
buf += _("Please copy the relevant contents and insert "
"them into the new email.");
gpgol_message_box (get_active_hwnd (), buf.c_str(),
_("GpgOL"), MB_OK);
do_in_ui_thread_async (CLEAR_REPLY_FORWARD, last_mail, 1000);
}
}
// We can now invalidate the last mail
Mail::clearLastMail ();
}
TBREAK;
}
case AttachmentRemove:
{
log_oom ("%s:%s: AttachmentRemove: %p",
SRCNAME, __func__, m_mail);
if (!m_mail->isCryptoMail () || attachRemoveWarnShown ||
m_mail->attachmentRemoveWarningDisabled ())
{
TRETURN S_OK;
}
gpgol_message_box (get_active_hwnd (),
_("Attachments are part of the crypto message.\nThey "
"can't be permanently removed and will be shown again the next "
"time this message is opened."),
_("Sorry, that's not possible, yet"), MB_OK);
attachRemoveWarnShown = true;
TRETURN S_OK;
}
default:
log_oom ("%s:%s: Message:%p Unhandled Event: %lx \n",
SRCNAME, __func__, m_object, dispid);
}
TRETURN S_OK;
}
END_EVENT_SINK(MailItemEvents, IID_MailItemEvents)
diff --git a/src/oomhelp.cpp b/src/oomhelp.cpp
index f07e8ec..3ae5433 100644
--- a/src/oomhelp.cpp
+++ b/src/oomhelp.cpp
@@ -1,3779 +1,3810 @@
/* 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
* Copyright (C) 2018 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 "common.h"
#include "oomhelp.h"
#include "cpphelp.h"
#include "gpgoladdin.h"
#include "categorymanager.h"
#include "recipient.h"
HRESULT
gpgol_queryInterface (LPUNKNOWN pObj, REFIID riid, LPVOID FAR *ppvObj)
{
HRESULT ret = pObj->QueryInterface (riid, ppvObj);
if (ret)
{
log_debug ("%s:%s: QueryInterface failed hr=%#lx",
SRCNAME, __func__, ret);
}
else if ((opt.enable_debug & DBG_MEMORY) && *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 (ret)
{
log_debug ("%s:%s: OpenProperty failed hr=%#lx %s",
SRCNAME, __func__, ret, mapi_err_to_string (ret));
}
else if ((opt.enable_debug & DBG_MEMORY) && *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)
{
TSTART;
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 */
hr = obj->QueryInterface (IID_IDispatch, (void **)&disp);
if (!disp || hr != S_OK)
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 ();
TRETURN name;
}
std::string
get_object_name_s (LPUNKNOWN obj)
{
char *name = get_object_name (obj);
std::string ret = "(null)";
if (name)
{
ret = name;
}
xfree (name);
return ret;
}
std::string
get_object_name_s (shared_disp_t obj)
{
return get_object_name_s (obj.get ());
}
/* 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)
{
TRETURN DISPID_UNKNOWN; /* Error: Invalid arg. */
}
wname = utf8_to_wchar (name);
if (!wname)
{
TRETURN 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)
{
TRETURN;
}
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_oom ("%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)
{
TSTART;
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__);
if (pObj != pStart)
gpgol_release (pObj);
TRETURN NULL;
}
/* Confirmed through testing that the retval needs a release */
if (pObj != pStart)
gpgol_release (pObj);
pObj = NULL;
if (!pDisp)
{
TRETURN NULL; /* The object has no IDispatch interface. */
}
if (!*fullname)
{
if ((opt.enable_debug & DBG_MEMORY))
{
pDisp->AddRef ();
int ref = pDisp->Release ();
log_oom ("%s:%s: got %p with %i refs",
SRCNAME, __func__, pDisp, ref);
}
TRETURN pDisp; /* Ready. */
}
/* Break out the next name part. */
{
const char *dot;
size_t n;
dot = strchr (fullname, '.');
if (dot == fullname)
{
gpgol_release (pDisp);
TRETURN 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);
TRETURN 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);
TRETURN 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);
TRETURN NULL; /* Invoke failed. */
}
pObj = vtResult.pdispVal;
memdbg_addRef (pObj);
}
gpgol_release (pDisp);
log_debug ("%s:%s: no object", SRCNAME, __func__);
TRETURN NULL;
}
shared_disp_t
get_oom_object_s (shared_disp_t pStart, const char *fullname)
{
return MAKE_SHARED (get_oom_object (pStart.get (), fullname));
}
shared_disp_t
get_oom_object_s (LPDISPATCH pStart, const char *fullname)
{
return MAKE_SHARED (get_oom_object (pStart, fullname));
}
/* Helper for put_oom_icon. */
static int
put_picture_or_mask (LPDISPATCH pDisp, int resource, int size, int is_mask)
{
TSTART;
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);
TRETURN -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);
TRETURN -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);
TRETURN -1;
}
TRETURN 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)
{
TSTART;
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);
TRETURN rc;
}
/* Set the boolean property NAME to VALUE. */
int
put_oom_bool (LPDISPATCH pDisp, const char *name, int value)
{
TSTART;
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
{
TRETURN -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);
TRETURN -1;
}
TRETURN 0;
}
/* Set the property NAME to VALUE. */
int
put_oom_int (LPDISPATCH pDisp, const char *name, int value)
{
TSTART;
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
{
TRETURN -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);
TRETURN -1;
}
TRETURN 0;
}
/* Set the property NAME to VALUE. */
int
put_oom_array (LPDISPATCH pDisp, const char *name, unsigned char *value,
size_t size)
{
TSTART;
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
unsigned int argErr = 0;
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
VariantInit (aVariant);
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
{
TRETURN -1;
}
/* Prepare the savearray */
SAFEARRAYBOUND saBound;
saBound.lLbound = 0;
saBound.cElements = size;
SAFEARRAY* psa = SafeArrayCreate(VT_UI1, 1, &saBound);
if (!psa)
{
log_err ("Failed to create SafeArray");
TRETURN -1;
}
hr = SafeArrayLock(psa);
if (!SUCCEEDED (hr))
{
log_err ("Failed to lock array.");
}
memcpy (psa->pvData, value, size);
SafeArrayUnlock(psa);
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_ARRAY;
dispparams.rgvarg[0].parray = psa;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT, &dispparams,
NULL, &execpinfo, &argErr);
SafeArrayDestroy(psa);
if (hr != S_OK)
{
log_debug ("%s:%s: error: putting %s"
" hr=0x%x argErr=0x%x",
SRCNAME, __func__, name,
(unsigned int)hr,
(unsigned int)argErr);
dump_excepinfo (execpinfo);
TRETURN -1;
}
TRETURN 0;
}
/* Set the property NAME to STRING. */
int
put_oom_string (LPDISPATCH pDisp, const char *name, const char *string)
{
TSTART;
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)
{
TRETURN -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__);
TRETURN -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);
TRETURN -1;
}
TRETURN 0;
}
/* Set the property NAME to DISP. */
int
put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH disp)
{
TSTART;
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)
{
TRETURN -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);
TRETURN -1;
}
TRETURN 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)
{
TSTART;
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);
}
TRETURN 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)
{
TSTART;
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);
}
TRETURN result;
}
int
get_oom_int (shared_disp_t pDisp, const char *name)
{
return get_oom_int (pDisp.get (), name);
}
int
get_oom_dirty (LPDISPATCH pDisp)
{
TSTART;
HRESULT hr;
DISPID dispid = DISPID_DIRTY_RAT;
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
VARIANT rVariant;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET | DISPATCH_METHOD,
&dispparams, &rVariant, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Property dirty not found: %#lx",
SRCNAME, __func__, hr);
TRETURN -1;
}
return !!rVariant.bVal;
}
#if 0
int
put_oom_dirty (LPDISPATCH pDisp, bool value)
{
TSTART;
/* NOTE: I have found no scenario where this does
not return the exception that the property is
write protected. But we can never know when
we need such an arcane function so I left it in. */
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid = DISPID_DIRTY_RAT;
DISPPARAMS dispparams;
VARIANT aVariant[1];
unsigned int argErr = 0;
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
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 | DISPATCH_METHOD, &dispparams,
NULL, &execpinfo, &argErr);
if (hr != S_OK)
{
log_debug ("%s:%s: error: invoking dirty p=%p vt=%d"
" hr=0x%x argErr=0x%x",
SRCNAME, __func__,
nullptr, 0, (unsigned int)hr,
(unsigned int)argErr);
dump_excepinfo (execpinfo);
TRETURN -1;
}
TRETURN 0;
}
#endif
/* 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)
{
TSTART;
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);
}
TRETURN result;
}
std::string
get_oom_string_s (LPDISPATCH pDisp, const char *name)
{
char *ret_c = get_oom_string (pDisp, name);
std::string ret;
if (ret_c)
{
ret = ret_c;
xfree (ret_c);
}
return ret;
}
std::string
get_oom_string_s (shared_disp_t pDisp, const char *name)
{
return get_oom_string_s (pDisp.get (), name);
}
/* 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)
{
TSTART;
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);
TRETURN rVariant.punkVal;
}
VariantClear (&rVariant);
}
TRETURN 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)
{
TSTART;
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);
TRETURN 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__);
TRETURN 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);
}
TRETURN 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)
{
TSTART;
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);
TRETURN NULL;
}
TRETURN 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)
{
TSTART;
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);
TRETURN;
}
/* Gets the current contexts HWND. Returns NULL on error */
HWND
get_oom_context_window (LPDISPATCH context)
{
TSTART;
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);
TRETURN ret;
}
int
put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value)
{
TSTART;
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__);
TRETURN -1;
}
dispid = lookup_oom_dispid (propertyAccessor, "SetProperty");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find SetProperty DISPID",
SRCNAME, __func__);
TRETURN -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__);
TRETURN -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: failure: 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);
TRETURN -1;
}
VariantClear (&rVariant);
TRETURN 0;
}
int
put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value)
{
TSTART;
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);
TRETURN ret;
}
int
put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value)
{
TSTART;
VARIANT var;
VariantInit (&var);
var.vt = VT_I4;
var.lVal = value;
int ret = put_pa_variant (pDisp, dasl_id, &var);
VariantClear (&var);
TRETURN 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)
{
TSTART;
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__);
TRETURN -1;
}
dispid = lookup_oom_dispid (propertyAccessor, "GetProperty");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find GetProperty DISPID",
SRCNAME, __func__);
TRETURN -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);
TRETURN -1;
}
TRETURN 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)
{
TSTART;
VARIANT rVariant;
char *result = NULL;
if (get_pa_variant (pDisp, property, &rVariant))
{
TRETURN 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);
TRETURN 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);
TRETURN result;
}
int
get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt)
{
TSTART;
VARIANT rVariant;
if (get_pa_variant (pDisp, property, &rVariant))
{
TRETURN -1;
}
if (rVariant.vt != VT_I4)
{
log_debug ("%s:%s: Property `%s' is not a int (vt=%d)",
SRCNAME, __func__, property, rVariant.vt);
TRETURN -1;
}
*rInt = rVariant.lVal;
VariantClear (&rVariant);
TRETURN 0;
}
/* Helper for exchange address lookup. */
static char *
get_recipient_addr_entry_fallbacks_ex (LPDISPATCH addr_entry)
{
TSTART;
/* 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__, anonstr (ret));
char *smtpbegin = strstr(ret, "SMTP:");
if (smtpbegin == ret)
{
ret += 5;
}
TRETURN 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__);
TRETURN 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__, anonstr (ret));
TRETURN ret;
}
TRETURN nullptr;
}
/* Helper for additional fallbacks in recipient lookup */
static char *
get_recipient_addr_fallbacks (LPDISPATCH recipient)
{
TSTART;
if (!recipient)
{
TRETURN nullptr;
}
LPDISPATCH addr_entry = get_oom_object (recipient, "AddressEntry");
if (!addr_entry)
{
log_debug ("%s:%s: Failed to find AddressEntry",
SRCNAME, __func__);
TRETURN nullptr;
}
char *ret = get_recipient_addr_entry_fallbacks_ex (addr_entry);
gpgol_release (addr_entry);
TRETURN 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,
int recipient_type)
{
TSTART;
/* Get the name for debugging */
std::string name;
char *cname = get_oom_string (addrEntry, "Name");
if (cname)
{
name = cname;
}
xfree (cname);
int user_type = get_oom_int (addrEntry, "AddressEntryUserType");
if (user_type != DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE)
{
log_data ("%s:%s: type of %s is %i",
SRCNAME, __func__, anonstr (name.c_str()), user_type);
TRETURN false;
}
LPDISPATCH members = get_oom_object (addrEntry, "Members");
addrEntry = nullptr;
if (!members)
{
TRACEPOINT;
TRETURN false;
}
int count = get_oom_int (members, "Count");
if (!count)
{
TRACEPOINT;
gpgol_release (members);
TRETURN false;
}
bool foundOne = false;
for (int i = 1; i <= count; i++)
{
auto item_str = std::string("Item(") + std::to_string (i) + ")";
auto entry = MAKE_SHARED (get_oom_object (members, item_str.c_str()));
if (!entry)
{
TRACEPOINT;
continue;
}
std::string entryName;
char *entry_name = get_oom_string (entry.get(), "Name");
if (entry_name)
{
entryName = entry_name;
xfree (entry_name);
}
int subType = get_oom_int (entry.get(), "AddressEntryUserType");
/* Resolve recursively, yeah fun. */
if (subType == DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE)
{
log_debug ("%s:%s: recursive address entry %s",
SRCNAME, __func__,
anonstr (entryName.c_str()));
if (try_resolve_group (entry.get(), ret, recipient_type))
{
foundOne = true;
continue;
}
}
std::pair element;
element.second = entry;
/* Resolve directly ? */
char *addrtype = get_pa_string (entry.get(), PR_ADDRTYPE_DASL);
if (addrtype && !strcmp (addrtype, "SMTP"))
{
xfree (addrtype);
char *resolved = get_pa_string (entry.get(), PR_EMAIL_ADDRESS_DASL);
if (resolved)
{
element.first = Recipient (resolved, entryName.c_str (),
recipient_type);
ret.push_back (element);
foundOne = true;
continue;
}
}
xfree (addrtype);
/* Resolve through Exchange API */
char *ex_resolved = get_recipient_addr_entry_fallbacks_ex (entry.get());
if (ex_resolved)
{
element.first = Recipient (ex_resolved, entryName.c_str (),
recipient_type);
ret.push_back (element);
foundOne = true;
continue;
}
log_debug ("%s:%s: failed to resolve name %s",
SRCNAME, __func__,
anonstr (entryName.c_str()));
}
gpgol_release (members);
if (!foundOne)
{
log_debug ("%s:%s: failed to resolve group %s",
SRCNAME, __func__,
anonstr (name.c_str()));
}
TRETURN foundOne;
}
/* Get the recipient mbox addresses with the addrEntry
object corresponding to the resolved address. */
std::vector >
get_oom_recipients_with_addrEntry (LPDISPATCH recipients, bool *r_err)
{
TSTART;
int recipientsCnt = get_oom_int (recipients, "Count");
std::vector > ret;
int i;
if (!recipientsCnt)
{
TRETURN 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;
}
int recipient_type = get_oom_int (recipient, "Type");
std::string entryName;
char *entry_name = get_oom_string (recipient, "Name");
if (entry_name)
{
entryName = entry_name;
xfree (entry_name);
}
auto addrEntry = MAKE_SHARED (get_oom_object (recipient, "AddressEntry"));
if (addrEntry && try_resolve_group (addrEntry.get (), ret,
recipient_type))
{
log_debug ("%s:%s: Resolved recipient group",
SRCNAME, __func__);
gpgol_release (recipient);
continue;
}
std::pair entry;
entry.second = addrEntry;
char *resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL);
if (resolved)
{
entry.first = Recipient (resolved, entryName.c_str (),
recipient_type);
entry.first.setIndex (i);
xfree (resolved);
gpgol_release (recipient);
ret.push_back (entry);
continue;
}
/* No PR_SMTP_ADDRESS first fallback */
resolved = get_recipient_addr_fallbacks (recipient);
if (resolved)
{
entry.first = Recipient (resolved, entryName.c_str (),
recipient_type);
entry.first.setIndex (i);
xfree (resolved);
gpgol_release (recipient);
ret.push_back (entry);
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)
{
entry.first = Recipient (resolved, recipient_type);
entry.first.setIndex (i);
ret.push_back (entry);
xfree (address);
}
else if (r_err)
{
*r_err = true;
}
}
TRETURN ret;
}
/* Gets the resolved smtp addresses of the recpients. */
std::vector
get_oom_recipients (LPDISPATCH recipients, bool *r_err)
{
TSTART;
std::vector ret;
for (const auto pair: get_oom_recipients_with_addrEntry (recipients, r_err))
{
ret.push_back (pair.first);
}
TRETURN 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, std::string &r_error_str,
int *r_err_code)
{
TSTART;
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__);
TRETURN -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 (r_err_code)
{
*r_err_code = (int) execpinfo.scode;
}
if (execpinfo.bstrDescription)
{
char *utf8Err = wchar_to_utf8 (execpinfo.bstrDescription);
if (utf8Err)
{
r_error_str = utf8Err;
}
xfree (utf8Err);
}
}
if (inFileB)
SysFreeString (inFileB);
if (dispNameB)
SysFreeString (dispNameB);
VariantClear (&vtResult);
gpgol_release (attachments);
TRETURN hr == S_OK ? 0 : -1;
}
LPDISPATCH
get_object_by_id (LPDISPATCH pDisp, REFIID id)
{
TSTART;
LPDISPATCH disp = NULL;
if (!pDisp)
{
TRETURN NULL;
}
if (gpgol_queryInterface(pDisp, id, (void **)&disp) != S_OK)
{
TRETURN NULL;
}
TRETURN disp;
}
LPDISPATCH
get_strong_reference (LPDISPATCH mail)
{
TSTART;
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);
TRETURN ret;
}
LPMESSAGE
get_oom_message (LPDISPATCH mailitem)
{
TSTART;
LPUNKNOWN mapi_obj = get_oom_iunknown (mailitem, "MapiObject");
if (!mapi_obj)
{
log_error ("%s:%s: Failed to obtain MAPI Message.",
SRCNAME, __func__);
TRETURN NULL;
}
TRETURN (LPMESSAGE) mapi_obj;
}
static LPMESSAGE
get_oom_base_message_from_mapi (LPDISPATCH mapi_message)
{
TSTART;
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__);
TRETURN 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("%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.");
TRETURN NULL;
}
TRETURN message;
}
LPMESSAGE
get_oom_base_message (LPDISPATCH mailitem)
{
TSTART;
LPMESSAGE mapi_message = get_oom_message (mailitem);
LPMESSAGE ret = NULL;
if (!mapi_message)
{
log_error ("%s:%s: Failed to obtain mapi_message.",
SRCNAME, __func__);
TRETURN NULL;
}
ret = get_oom_base_message_from_mapi ((LPDISPATCH)mapi_message);
gpgol_release (mapi_message);
TRETURN ret;
}
static int
invoke_oom_method_with_parms_type (LPDISPATCH pDisp, const char *name,
VARIANT *rVariant, DISPPARAMS *params,
int type)
{
TSTART;
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);
TRETURN -1;
}
}
TRETURN 0;
}
int
invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name,
VARIANT *rVariant, DISPPARAMS *params)
{
TSTART;
TRETURN invoke_oom_method_with_parms_type (pDisp, name, rVariant, params,
DISPATCH_METHOD);
}
int
invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant)
{
TSTART;
TRETURN invoke_oom_method_with_parms (pDisp, name, rVariant, NULL);
}
LPMAPISESSION
get_oom_mapi_session ()
{
TSTART;
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__);
TRETURN NULL;
}
oom_session = get_oom_object (application, "Session");
if (!oom_session)
{
log_error ("%s:%s: session object not found", SRCNAME, __func__);
TRETURN NULL;
}
mapiobj = get_oom_iunknown (oom_session, "MAPIOBJECT");
gpgol_release (oom_session);
if (!mapiobj)
{
log_error ("%s:%s: error getting Session.MAPIOBJECT", SRCNAME, __func__);
TRETURN 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);
TRETURN NULL;
}
TRETURN session;
}
int
create_category (LPDISPATCH categories, const char *category, int color)
{
TSTART;
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;
TRETURN 1;
}
dispid = lookup_oom_dispid (categories, "Add");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find Add DISPID",
SRCNAME, __func__);
TRETURN -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);
TRETURN -1;
}
VariantClear (&rVariant);
log_oom ("%s:%s: Created category '%s'",
SRCNAME, __func__, anonstr (category));
TRETURN 0;
}
LPDISPATCH
get_store_for_id (const char *storeID)
{
TSTART;
LPDISPATCH application = GpgolAddin::get_instance ()->get_application ();
if (!application || !storeID)
{
TRACEPOINT;
TRETURN nullptr;
}
LPDISPATCH stores = get_oom_object (application, "Session.Stores");
if (!stores)
{
log_error ("%s:%s: No stores found.",
SRCNAME, __func__);
TRETURN nullptr;
}
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;
}
char *id = get_oom_string (store, "StoreID");
if (id && !strcmp (id, storeID))
{
gpgol_release (stores);
xfree (id);
return store;
}
xfree (id);
gpgol_release (store);
}
gpgol_release (stores);
TRETURN nullptr;
}
void
ensure_category_exists (const char *category, int color)
{
TSTART;
LPDISPATCH application = GpgolAddin::get_instance ()->get_application ();
if (!application || !category)
{
TRACEPOINT;
TRETURN;
}
log_oom ("%s:%s: Ensure category exists called for %s, %i",
SRCNAME, __func__,
category, color);
LPDISPATCH stores = get_oom_object (application, "Session.Stores");
if (!stores)
{
log_error ("%s:%s: No stores found.",
SRCNAME, __func__);
TRETURN;
}
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_oom ("%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_oom ("%s:%s: Found category '%s'",
SRCNAME, __func__, category);
}
}
/* Otherwise we have to create the category */
gpgol_release (categories);
}
gpgol_release (stores);
TRETURN;
}
int
add_category (LPDISPATCH mail, const char *category)
{
TSTART;
char *tmp = get_oom_string (mail, "Categories");
if (!tmp)
{
TRACEPOINT;
TRETURN 1;
}
if (strstr (tmp, category))
{
log_oom ("%s:%s: category '%s' already added.",
SRCNAME, __func__, category);
TRETURN 0;
}
std::string newstr (tmp);
xfree (tmp);
if (!newstr.empty ())
{
newstr += CategoryManager::getSeperator () + std::string (" ");
}
newstr += category;
TRETURN put_oom_string (mail, "Categories", newstr.c_str ());
}
int
remove_category (LPDISPATCH mail, const char *category, bool exactMatch)
{
TSTART;
char *tmp = get_oom_string (mail, "Categories");
if (!tmp)
{
TRACEPOINT;
TRETURN 1;
}
std::vector categories;
std::istringstream f(tmp);
std::string s;
const std::string sep = CategoryManager::getSeperator();
while (std::getline(f, s, *(sep.c_str())))
{
ltrim(s);
categories.push_back(s);
}
xfree (tmp);
const std::string categoryStr = category;
categories.erase (std::remove_if (categories.begin(),
categories.end(),
[categoryStr, exactMatch] (const std::string &cat)
{
if (exactMatch)
{
return cat == categoryStr;
}
return cat.compare (0, categoryStr.size(), categoryStr) == 0;
}), categories.end ());
std::string newCategories;
std::string newsep = sep + " ";
join (categories, newsep.c_str (), newCategories);
TRETURN put_oom_string (mail, "Categories", newCategories.c_str ());
}
static int
_delete_category (LPDISPATCH categories, int idx)
{
TSTART;
VARIANT aVariant[1];
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = idx;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
TRETURN invoke_oom_method_with_parms (categories, "Remove", NULL,
&dispparams);
}
int
delete_category (LPDISPATCH store, const char *category)
{
TSTART;
if (!store || !category)
{
TRETURN -1;
}
LPDISPATCH categories = get_oom_object (store, "Categories");
if (!categories)
{
categories = get_oom_object (
GpgolAddin::get_instance ()->get_application (),
"Session.Categories");
if (!categories)
{
TRACEPOINT;
TRETURN -1;
}
}
auto count = get_oom_int (categories, "Count");
int ret = 0;
for (int i = 1; i <= count; 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");
gpgol_release (category_obj);
if (name && !strcmp (category, name))
{
if ((ret = _delete_category (categories, i)))
{
log_error ("%s:%s: Failed to delete category '%s'",
SRCNAME, __func__, anonstr (category));
}
else
{
log_debug ("%s:%s: Deleted category '%s'",
SRCNAME, __func__, anonstr (category));
}
xfree (name);
break;
}
xfree (name);
}
gpgol_release (categories);
TRETURN ret;
}
void
delete_all_categories_starting_with (const char *string)
{
LPDISPATCH application = GpgolAddin::get_instance ()->get_application ();
if (!application || !string)
{
TRACEPOINT;
TRETURN;
}
log_oom ("%s:%s: Delete categories starting with: \"%s\"",
SRCNAME, __func__, string);
LPDISPATCH stores = get_oom_object (application, "Session.Stores");
if (!stores)
{
log_error ("%s:%s: No stores found.",
SRCNAME, __func__);
TRETURN;
}
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");
if (!categories)
{
categories = get_oom_object (application, "Session.Categories");
if (!categories)
{
TRACEPOINT;
gpgol_release (store);
continue;
}
}
auto count = get_oom_int (categories, "Count");
std::vector to_delete;
for (int i = 1; i <= count; 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 && !strncmp (string, name, strlen (string)))
{
log_oom ("%s:%s: Found category for deletion '%s'",
SRCNAME, __func__, anonstr(name));
to_delete.push_back (name);
}
/* We don't check the color here as the user may change that. */
gpgol_release (category_obj);
xfree (name);
}
/* Do this one after another to avoid messing with indexes. */
for (const auto &str: to_delete)
{
delete_category (store, str.c_str ());
}
gpgol_release (store);
/* Otherwise we have to create the category */
gpgol_release (categories);
}
gpgol_release (stores);
TRETURN;
}
static char *
generate_uid ()
{
TSTART;
UUID uuid;
UuidCreate (&uuid);
unsigned char *str;
UuidToStringA (&uuid, &str);
char *ret = xstrdup ((char*)str);
RpcStringFreeA (&str);
TRETURN ret;
}
char *
get_unique_id (LPDISPATCH mail, int create, const char *uuid)
{
TSTART;
if (!mail)
{
TRETURN 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);
TRETURN NULL;
}
else
{
log_debug ("%s:%s: Found uid '%s' for '%p'",
SRCNAME, __func__, uid, mail);
TRETURN 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);
TRETURN NULL;
}
log_debug ("%s:%s: '%p' has now the uid: '%s' ",
SRCNAME, __func__, mail, newuid);
TRETURN newuid;
}
char *
reset_unique_id (LPDISPATCH mail)
{
TSTART;
char *newuid = generate_uid ();
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);
TRETURN NULL;
}
TRETURN newuid;
}
std::string
get_unique_id_s (LPDISPATCH mail, int create, const char *uuid)
{
char *val = get_unique_id (mail, create, uuid);
if (val)
{
return val;
}
return std::string (val);
}
HWND
get_active_hwnd ()
{
TSTART;
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
TRETURN 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;
TRETURN nullptr;
}
}
}
/* Both explorer and inspector have this. */
char *caption = get_oom_string (activeWindow, "Caption");
gpgol_release (activeWindow);
if (!caption)
{
TRACEPOINT;
TRETURN 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);
TRETURN hwnd;
}
LPDISPATCH
create_mail ()
{
TSTART;
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
TRETURN 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__);
TRETURN ret;
}
ret = var.pdispVal;
TRETURN ret;
}
LPDISPATCH
get_account_for_mail (const char *mbox)
{
TSTART;
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
TRETURN nullptr;
}
LPDISPATCH accounts = get_oom_object (app, "Session.Accounts");
if (!accounts)
{
TRACEPOINT;
TRETURN 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);
TRETURN account;
}
gpgol_release (account);
xfree (smtpAddr);
}
gpgol_release (accounts);
log_error ("%s:%s: Failed to find account for '%s'.",
SRCNAME, __func__, anonstr (mbox));
TRETURN nullptr;
}
char *
get_sender_SendUsingAccount (LPDISPATCH mailitem, bool *r_is_GSuite)
{
TSTART;
LPDISPATCH sender = get_oom_object (mailitem, "SendUsingAccount");
if (!sender)
{
TRETURN 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__);
TRETURN buf;
}
xfree (buf);
TRETURN nullptr;
}
char *
get_sender_Sender (LPDISPATCH mailitem)
{
TSTART;
LPDISPATCH sender = get_oom_object (mailitem, "Sender");
if (!sender)
{
TRETURN 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__);
TRETURN 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__);
TRETURN 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__);
TRETURN buf;
}
xfree (buf);
TRETURN nullptr;
}
char *
get_sender_CurrentUser (LPDISPATCH mailitem)
{
TSTART;
LPDISPATCH sender = get_oom_object (mailitem,
"Session.CurrentUser");
if (!sender)
{
TRETURN 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__);
TRETURN buf;
}
xfree (buf);
TRETURN nullptr;
}
char *
get_sender_SenderEMailAddress (LPDISPATCH mailitem)
{
TSTART;
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);
TRETURN senderMail;
}
}
xfree (type);
TRETURN nullptr;
}
char *
get_sender_SentRepresentingAddress (LPDISPATCH mailitem)
{
TSTART;
char *buf = get_pa_string (mailitem,
PR_SENT_REPRESENTING_EMAIL_ADDRESS_W_DASL);
if (buf && strlen (buf))
{
log_debug ("%s:%s Found sent representing address \"%s\"",
SRCNAME, __func__, anonstr (buf));
TRETURN buf;
}
xfree (buf);
TRETURN nullptr;
}
char *
get_inline_body ()
{
TSTART;
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
TRETURN nullptr;
}
LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer");
if (!explorer)
{
TRACEPOINT;
TRETURN nullptr;
}
LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse");
gpgol_release (explorer);
if (!inlineResponse)
{
TRETURN nullptr;
}
char *body = get_oom_string (inlineResponse, "Body");
gpgol_release (inlineResponse);
TRETURN body;
}
int
get_ex_major_version_for_addr (const char *mbox)
{
TSTART;
LPDISPATCH account = get_account_for_mail (mbox);
if (!account)
{
TRACEPOINT;
TRETURN -1;
}
char *version_str = get_oom_string (account, "ExchangeMailboxServerVersion");
gpgol_release (account);
if (!version_str)
{
TRETURN -1;
}
log_debug ("%s:%s: Detected exchange major version: %s",
SRCNAME, __func__, version_str);
long int version = strtol (version_str, nullptr, 10);
xfree (version_str);
TRETURN (int) version;
}
int
get_ol_ui_language ()
{
TSTART;
LPDISPATCH app = GpgolAddin::get_instance()->get_application();
if (!app)
{
TRACEPOINT;
TRETURN 0;
}
LPDISPATCH langSettings = get_oom_object (app, "LanguageSettings");
if (!langSettings)
{
TRACEPOINT;
TRETURN 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;
TRETURN 0;
}
if (var.vt != VT_INT && var.vt != VT_I4)
{
TRACEPOINT;
TRETURN 0;
}
int result = var.intVal;
VariantClear (&var);
TRETURN result;
}
void
log_addins ()
{
TSTART;
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
TRETURN;
}
LPDISPATCH addins = get_oom_object (app, "COMAddins");
if (!addins)
{
TRACEPOINT;
TRETURN;
}
std::string activeAddins;
int count = get_oom_int (addins, "Count");
for (int i = 1; i <= count; i++)
{
VARIANT aVariant[1];
VARIANT rVariant;
VariantInit (&rVariant);
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = i;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
/* We need this instead of get_oom_object item(1) as usual becase
the item method accepts a string or an int. String would
be the ProgID and int is just the index. So Fun. */
if (invoke_oom_method_with_parms_type (addins, "Item", &rVariant,
&dispparams,
DISPATCH_METHOD |
DISPATCH_PROPERTYGET))
{
log_error ("%s:%s: Failed to invoke item func.",
SRCNAME, __func__);
continue;
}
if (rVariant.vt != (VT_DISPATCH))
{
log_error ("%s:%s: Invalid ret val",
SRCNAME, __func__);
continue;
}
LPDISPATCH addin = rVariant.pdispVal;
if (!addin)
{
TRACEPOINT;
continue;
}
memdbg_addRef (addin);
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 ());
TRETURN;
}
bool
is_preview_pane_visible (LPDISPATCH explorer)
{
TSTART;
if (!explorer)
{
TRACEPOINT;
TRETURN 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__);
TRETURN false;
}
if (var.vt != VT_BOOL)
{
TRACEPOINT;
TRETURN false;
}
TRETURN !!var.boolVal;
}
static LPDISPATCH
add_user_prop (LPDISPATCH user_props, const char *name)
{
TSTART;
if (!user_props || !name)
{
TRACEPOINT;
TRETURN nullptr;
}
wchar_t *w_name = utf8_to_wchar (name);
BSTR b_name = SysAllocString (w_name);
xfree (w_name);
/* Args:
0: DisplayFormat int OlUserPropertyType
1: AddToFolderFields Bool Should the filed be added to the folder.
2: Type int OlUserPropertyType Type of the field.
3: Name Bstr Name of the field.
Returns the added Property.
*/
VARIANT var;
VariantInit (&var);
DISPPARAMS args;
VARIANT argvars[4];
VariantInit (&argvars[0]);
VariantInit (&argvars[1]);
VariantInit (&argvars[2]);
VariantInit (&argvars[3]);
argvars[0].vt = VT_INT;
argvars[0].intVal = 1; // 1 means text.
argvars[1].vt = VT_BOOL;
argvars[1].boolVal = VARIANT_FALSE;
argvars[2].vt = VT_INT;
argvars[2].intVal = 1;
argvars[3].vt = VT_BSTR;
argvars[3].bstrVal = b_name;
args.cArgs = 4;
args.cNamedArgs = 0;
args.rgvarg = argvars;
int res = invoke_oom_method_with_parms (user_props, "Add", &var, &args);
VariantClear (&argvars[0]);
VariantClear (&argvars[1]);
VariantClear (&argvars[2]);
VariantClear (&argvars[3]);
if (res)
{
log_oom ("%s:%s: Failed to add property %s.",
SRCNAME, __func__, name);
TRETURN nullptr;
}
if (var.vt != VT_DISPATCH)
{
TRACEPOINT;
TRETURN nullptr;
}
LPDISPATCH ret = var.pdispVal;
memdbg_addRef (ret);
TRETURN ret;
}
LPDISPATCH
find_user_prop (LPDISPATCH user_props, const char *name)
{
TSTART;
if (!user_props || !name)
{
TRACEPOINT;
TRETURN nullptr;
}
VARIANT var;
VariantInit (&var);
wchar_t *w_name = utf8_to_wchar (name);
BSTR b_name = SysAllocString (w_name);
xfree (w_name);
/* Name -> 1 / Bstr
Custom 0 -> Bool True for search in custom properties. False
for builtin properties. */
DISPPARAMS args;
VARIANT argvars[2];
VariantInit (&argvars[0]);
VariantInit (&argvars[1]);
argvars[1].vt = VT_BSTR;
argvars[1].bstrVal = b_name;
argvars[0].vt = VT_BOOL;
argvars[0].boolVal = VARIANT_TRUE;
args.cArgs = 2;
args.cNamedArgs = 0;
args.rgvarg = argvars;
int res = invoke_oom_method_with_parms (user_props, "Find", &var, &args);
VariantClear (&argvars[0]);
VariantClear (&argvars[1]);
if (res)
{
log_oom ("%s:%s: Failed to find property %s.",
SRCNAME, __func__, name);
TRETURN nullptr;
}
if (var.vt != VT_DISPATCH)
{
TRACEPOINT;
TRETURN nullptr;
}
LPDISPATCH ret = var.pdispVal;
memdbg_addRef (ret);
TRETURN ret;
}
LPDISPATCH
find_or_add_text_prop (LPDISPATCH user_props, const char *name)
{
TSTART;
LPDISPATCH ret = find_user_prop (user_props, name);
if (ret)
{
TRETURN ret;
}
ret = add_user_prop (user_props, name);
TRETURN ret;
}
void
release_disp (LPDISPATCH obj)
{
TSTART;
gpgol_release (obj);
TRETURN;
}
enum FolderID
{
olFolderCalendar = 9,
olFolderConflicts = 19,
olFolderContacts = 10,
olFolderDeletedItems = 3,
olFolderDrafts = 16,
olFolderInbox = 6,
olFolderJournal = 11,
olFolderJunk = 23,
olFolderLocalFailures = 21,
olFolderManagedEmail = 29,
olFolderNotes = 12,
olFolderOutbox = 4,
olFolderSentMail = 5,
olFolderServerFailures = 22,
olFolderSuggestedContacts = 30,
olFolderSyncIssues = 20,
olFolderTasks = 13,
olFolderToDo = 28,
olPublicFoldersAllPublicFolders = 18,
olFolderRssFeeds = 25,
};
static bool
is_mail_in_folder (LPDISPATCH mailitem, int folder)
{
TSTART;
if (!mailitem)
{
STRANGEPOINT;
TRETURN false;
}
auto store = MAKE_SHARED (get_oom_object (mailitem, "Parent.Store"));
if (!store)
{
log_debug ("%s:%s: Mail has no parent folder. Probably unsafed",
SRCNAME, __func__);
TRETURN false;
}
std::string tmp = std::string("GetDefaultFolder(") + std::to_string (folder) +
std::string(")");
auto target_folder = MAKE_SHARED (get_oom_object (store.get(),
tmp.c_str()));
if (!target_folder)
{
STRANGEPOINT;
TRETURN false;
}
auto mail_folder = MAKE_SHARED (get_oom_object (mailitem, "Parent"));
if (!mail_folder)
{
STRANGEPOINT;
TRETURN false;
}
char *target_id = get_oom_string (target_folder.get(), "entryID");
if (!target_id)
{
STRANGEPOINT;
TRETURN false;
}
char *folder_id = get_oom_string (mail_folder.get(), "entryID");
if (!folder_id)
{
STRANGEPOINT;
free (target_id);
TRETURN false;
}
bool ret = !strcmp (target_id, folder_id);
free (target_id);
free (folder_id);
TRETURN ret;
}
bool
is_junk_mail (LPDISPATCH mailitem)
{
TSTART;
TRETURN is_mail_in_folder (mailitem, FolderID::olFolderJunk);
}
bool
is_draft_mail (LPDISPATCH mailitem)
{
TSTART;
TRETURN is_mail_in_folder (mailitem, FolderID::olFolderDrafts);
}
void
format_variant (std::stringstream &stream, VARIANT* var)
{
if (!var)
{
stream << " (null) ";
}
stream << "VT: " << std::hex << var->vt << " Value: ";
VARTYPE vt = var->vt;
if (vt == VT_BOOL)
{
stream << (var->boolVal == VARIANT_FALSE ? "false" : "true");
}
else if (vt == (VT_BOOL | VT_BYREF))
{
stream << (*(var->pboolVal) == VARIANT_FALSE ? "false" : "true");
}
else if (vt == VT_BSTR)
{
char *buf = wchar_to_utf8 (var->bstrVal);
stream << "BStr: " << buf;
xfree (buf);
}
else if (vt == VT_INT || vt == VT_I4)
{
stream << var->intVal;
}
else if (vt == VT_DISPATCH)
{
char *buf = get_object_name ((LPUNKNOWN) var->pdispVal);
stream << "IDispatch: " << buf;
xfree (buf);
}
else if (vt == (VT_VARIANT | VT_BYREF))
{
format_variant (stream, var->pvarVal);
}
else
{
stream << "?";
}
stream << std::endl;
}
std::string
format_dispparams (DISPPARAMS *p)
{
if (!p)
{
return "(null)";
}
std::stringstream stream;
stream << "Count: " << p->cArgs << " CNamed: " << p->cNamedArgs << std::endl;
for (int i = 0; i < p->cArgs; i++)
{
format_variant (stream, p->rgvarg + i);
}
return stream.str ();
}
int
count_visible_attachments (LPDISPATCH attachments)
{
int ret = 0;
if (!attachments)
{
return 0;
}
int att_count = get_oom_int (attachments, "Count");
for (int i = 1; i <= att_count; i++)
{
std::string item_str;
item_str = std::string("Item(") + std::to_string (i) + ")";
LPDISPATCH oom_attach = get_oom_object (attachments, item_str.c_str ());
if (!oom_attach)
{
log_error ("%s:%s: Failed to get attachment.",
SRCNAME, __func__);
continue;
}
VARIANT var;
VariantInit (&var);
if (get_pa_variant (oom_attach, PR_ATTACHMENT_HIDDEN_DASL, &var))
{
/* SECURITY: Testing has shown that for all mail types GpgOL
handles that might contain an unsigned attachment we always
have the MAPIOBJECT / get the hidden state. Only the transient
MIME attachments. The ones used for the MAPI to MIME conversion
and which are hidden by GpgOL and handled by GpgOL will have
no MAPIOBJECT when a mail is opened from file. So this will
remove the warning that "smime.p7m" or "gpgol_mime_structure.txt"
are unsigned and unencrypted attachments. */
log_dbg ("Failed to get hidden state.");
LPUNKNOWN mapiobj = get_oom_iunknown (oom_attach, "MAPIOBJECT");
if (!mapiobj)
{
const auto dispName = get_oom_string_s (oom_attach,
"DisplayName");
log_dbg ("Attachment: %s has no mapiobject. Ignoring it.",
anonstr (dispName.c_str ()));
}
else
{
gpgol_release (mapiobj);
const auto dispName = get_oom_string_s (oom_attach,
"DisplayName");
log_dbg ("Attachment %s without hidden state but mapiobj. "
"Count as visible.", anonstr (dispName.c_str ()));
ret++;
}
}
else if (var.vt == VT_BOOL && var.boolVal == VARIANT_FALSE)
{
ret++;
}
gpgol_release (oom_attach);
VariantClear (&var);
}
return ret;
}
int invoke_oom_method_with_int (LPDISPATCH pDisp, const char *name,
int arg,
VARIANT *rVariant)
{
TSTART;
DISPPARAMS parms;
VARIANT argvars[1];
VariantInit (&argvars[0]);
argvars[0].vt = VT_INT;
argvars[0].intVal = arg;
parms.cArgs = 1;
parms.cNamedArgs = 0;
parms.rgvarg = argvars;
TRETURN invoke_oom_method_with_parms (pDisp, name,
rVariant, &parms);
}
int invoke_oom_method_with_string (LPDISPATCH pDisp, const char *name,
const char *arg,
VARIANT *rVariant)
{
TSTART;
if (!arg)
{
TRETURN 0;
}
wchar_t *warg = utf8_to_wchar (arg);
if (!warg)
{
TRETURN 1;
}
VARIANT aVariant[1];
VariantInit (&aVariant[0]);
aVariant[0].vt = VT_BSTR;
aVariant[0].bstrVal = SysAllocString (warg);
xfree (warg);
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
int ret = invoke_oom_method_with_parms (pDisp, name, rVariant, &dispparams);
VariantClear(&aVariant[0]);
TRETURN ret;
}
int
set_oom_recipients (LPDISPATCH item, const std::vector &recps)
{
if (!item)
{
STRANGEPOINT;
TRETURN -1;
}
auto oom_recps = MAKE_SHARED (get_oom_object (item, "Recipients"));
if (!oom_recps)
{
STRANGEPOINT;
TRETURN -1;
}
int count = get_oom_int (oom_recps.get (), "Count");
for (int i = 1; i <= count; i++)
{
/* First clear out the current recipients. */
int ret = invoke_oom_method_with_int (oom_recps.get (),
"Remove", 1,
nullptr);
if (ret)
{
STRANGEPOINT;
TRETURN ret;
}
}
for (const auto &recp: recps)
{
if (recp.type() == Recipient::olOriginator)
{
/* Skip the originator, we only add it internally but
it does not need to be in OOM. */
continue;
}
VARIANT result;
VariantInit (&result);
int ret = invoke_oom_method_with_string (oom_recps.get (), "Add",
recp.mbox ().c_str (),
&result);
if (ret)
{
log_err ("Failed to add recipient.");
TRETURN ret;
}
if (result.vt != VT_DISPATCH || !result.pdispVal)
{
log_err ("No recipient result.");
continue;
}
if (put_oom_int (result.pdispVal, "Type", recp.type()))
{
log_err ("Failed to set recipient type.");
}
/* This releases the recipient. */
VariantClear (&result);
}
TRETURN 0;
}
int
remove_oom_recipient (LPDISPATCH item, const std::string &mbox)
{
TSTART;
if (!item)
{
STRANGEPOINT;
TRETURN -1;
}
auto oom_recps = MAKE_SHARED (get_oom_object (item, "Recipients"));
if (!oom_recps)
{
STRANGEPOINT;
TRETURN -1;
}
bool r_err = false;
const auto recps = get_oom_recipients (oom_recps.get (), &r_err);
if (r_err)
{
log_debug ("Failure to lookup recipients via OOM");
TRETURN -1;
}
for (const auto &recp: recps)
{
if (recp.mbox () == mbox && recp.index () != -1)
{
TRETURN invoke_oom_method_with_int (oom_recps.get (),
"Remove", recp.index (),
nullptr);
}
}
TRETURN -1;
}
void
oom_dump_idispatch (LPDISPATCH obj)
{
log_dbg ("Start infos about %p", obj);
if (!obj)
{
log_dbg ("It's NULL");
return;
}
log_dbg ("Name: '%s'", get_object_name_s (obj).c_str ());
LPTYPEINFO typeinfo = nullptr;
HRESULT hr = obj->GetTypeInfo (0, 0, &typeinfo);
if (!typeinfo || FAILED (hr))
{
log_dbg ("No typeinfo.");
return;
}
TYPEATTR* pta = NULL;
hr = typeinfo->GetTypeAttr(&pta);
if (!pta || FAILED (hr))
{
log_dbg ("No type attr");
return;
}
/* First the IID to have it clear */
LPOLESTR lpsz = NULL;
hr = StringFromIID(pta->guid, &lpsz);
if(FAILED (hr))
{
hr = StringFromCLSID (pta->guid, &lpsz);
}
if(SUCCEEDED (hr))
{
log_dbg ("Interface: %S", lpsz);
CoTaskMemFree(lpsz);
}
FUNCDESC *pfd = nullptr;
/* Lets see what functions we have. */
for(int i = 0; i < pta->cFuncs; i++)
{
typeinfo->GetFuncDesc(i, &pfd);
BSTR names[1];
unsigned int dumb;
typeinfo->GetNames(pfd->memid, names, 1, &dumb);
if (!names[0])
{
typeinfo->ReleaseFuncDesc(pfd);
continue;
}
log_dbg ("%i: %S id=0x%li With %d param(s)\n", i,
(names[0]), pfd->memid, pfd->cParams);
typeinfo->ReleaseFuncDesc(pfd);
SysFreeString(names[0]);
}
typeinfo->ReleaseTypeAttr(pta);
/* Now for some interesting object relations
that many oom objecs have. */
const char * relations[] = {
"Parent",
"GetInspector",
"Session",
"Sender",
nullptr
};
for (int i = 0; relations [i]; i++)
{
LPDISPATCH rel = get_oom_object (obj, relations[i]);
log_dbg ("%s: %s", relations [i],
get_object_name_s (rel).c_str ());
gpgol_release (rel);
}
/* Now for some interesting string values. */
const char * stringVals[] = {
"EntryID",
"Subject",
"MessageClass",
"Body",
nullptr
};
for (int i = 0; stringVals[i]; i++)
{
const auto str = get_oom_string_s (obj, stringVals[i]);
log_dbg ("%s: %s", stringVals[i], str.c_str ());
}
log_dbg ("Object dump done");
return;
}
int
get_oom_crypto_flags (LPDISPATCH mailitem)
{
TSTART;
int r_val = 0;
int err = get_pa_int (mailitem, PR_SECURITY_FLAGS_DASL, &r_val);
if (err)
{
log_dbg ("Failed to get security flags.");
TRETURN 0;
}
TRETURN r_val;
}
shared_disp_t
show_folder_select ()
{
TSTART;
VARIANT var;
VariantInit (&var);
LPDISPATCH rVal;
auto namespace_obj = get_oom_object_s (oApp (), "Session");
if (!namespace_obj)
{
STRANGEPOINT;
TRETURN nullptr;
}
if (!invoke_oom_method (namespace_obj.get (), "PickFolder", &var))
{
if (!(var.vt & VT_DISPATCH))
{
log_dbg ("Failed to get disp obj. No folder selected?");
TRETURN nullptr;
}
rVal = var.pdispVal;
log_oom ("%s:%s: Got folder ref %p",
SRCNAME, __func__, rVal);
memdbg_addRef (rVal);
TRETURN MAKE_SHARED (rVal);
}
log_dbg ("No folder returned.");
TRETURN nullptr;
}
LPDISPATCH
oApp ()
{
return GpgolAddin::get_instance()->get_application();
}
BSTR utf8_to_bstr (const char *string)
{
wchar_t *tmp = utf8_to_wchar (string);
BSTR bstring = tmp ? SysAllocString (tmp) : NULL;
xfree (tmp);
if (!bstring)
{
log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__);
TRETURN nullptr;
}
TRETURN bstring;
}
int
oom_save_as (LPDISPATCH obj, const char *path, oomSaveAsType type)
{
if (!obj || !path)
{
/* invalid arguments */
STRANGEPOINT;
TRETURN -1;
}
/* Params are first path and then type as optional. With
COM Marshalling this means that param 1 is the path
and 0 is the type. */
VARIANT aVariant[2];
VariantInit(aVariant);
VariantInit(aVariant + 1);
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = (int) type;
dispparams.rgvarg[1].vt = VT_BSTR;
dispparams.rgvarg[1].bstrVal = utf8_to_bstr (path);
dispparams.cArgs = 2;
dispparams.cNamedArgs = 0;
int rc = invoke_oom_method_with_parms (obj, "SaveAs", nullptr, &dispparams);
if (rc)
{
log_err ("Failed to call SaveAs");
}
VariantClear(aVariant);
VariantClear(aVariant + 1);
return rc;
}
int
oom_save_as_file (LPDISPATCH obj, const char *path)
{
if (!obj || !path)
{
/* invalid arguments */
STRANGEPOINT;
TRETURN -1;
}
VARIANT aVariant[1];
VariantInit(aVariant);
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = utf8_to_bstr (path);
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
int rc = invoke_oom_method_with_parms (obj, "SaveAsFile",
nullptr, &dispparams);
if (rc)
{
log_err ("Failed to call SaveAsFile");
}
VariantClear(aVariant);
return rc;
}
+
+void
+oom_clear_selections ()
+{
+ TSTART;
+ auto explorers_obj = get_oom_object_s (oApp (), "Explorers");
+
+ if (!explorers_obj)
+ {
+ STRANGEPOINT;
+ TRETURN;
+ }
+
+ int count = get_oom_int (explorers_obj.get (), "Count");
+
+ for (int i = 1; i <= count; i++)
+ {
+ auto item_str = std::string("Item(") + std::to_string (i) + ")";
+ auto explorer = get_oom_object_s (explorers_obj, item_str.c_str ());
+ if (!explorer)
+ {
+ STRANGEPOINT;
+ TRETURN;
+ }
+ if (invoke_oom_method (explorer.get (), "ClearSelection", NULL))
+ {
+ log_err ("Clearing Explorers %i", i);
+ }
+ }
+ TRETURN;
+}
diff --git a/src/oomhelp.h b/src/oomhelp.h
index 45a84fa..105fbd7 100644
--- a/src/oomhelp.h
+++ b/src/oomhelp.h
@@ -1,538 +1,541 @@
/* 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 "common.h"
#include
#include
#include
#define MSOCONTROLBUTTON 1
#define MSOCONTROLEDIT 2
#define MSOCONTROLDROPDOWN 3
#define MSOCONTROLCOMBOBOX 4
#define MSOCONTROLPOPUP 10
class Recipient;
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_ApplicationEvents_11, 0x0006302C, 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_ATTACH_FLAGS_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x37140003"
#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 PR_SENT_REPRESENTING_EMAIL_ADDRESS_W_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x0065001F"
#define PR_SENDER_NAME_W_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x0C1A001F"
#define PR_SENT_REPRESENTING_NAME_W_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x0042001F"
#define PR_SECURITY_FLAGS_DASL \
"http://schemas.microsoft.com/mapi/proptag/0x6E010003"
#define DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE 11
#define DISPID_DIRTY_RAT 0xF024
typedef std::shared_ptr shared_disp_t;
/* Function to contain the gpgol_release macro */
void release_disp (LPDISPATCH obj);
#define MAKE_SHARED(X) shared_disp_t ((LPDISPATCH)X, &release_disp)
/* Return the malloced name of an COM+ object. */
char *get_object_name (LPUNKNOWN obj);
std::string get_object_name_s (LPUNKNOWN obj);
std::string get_object_name_s (shared_disp_t 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);
/* Do the same but with a shared disp return value */
shared_disp_t get_oom_object_s (LPDISPATCH pStart, const char *fullname);
shared_disp_t get_oom_object_s (shared_disp_t 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);
/* Set the byte array property NAME to VALUE. */
int put_oom_array (LPDISPATCH pDisp, const char *name,
unsigned char *value, size_t size);
/* 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);
int get_oom_int (shared_disp_t pDisp, const char *name);
/* Get the string property NAME of the object PDISP. */
char *get_oom_string (LPDISPATCH pDisp, const char *name);
std::string get_oom_string_s (LPDISPATCH pDisp, const char *name);
std::string get_oom_string_s (shared_disp_t 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.
The second part of the pair returned is the recipient type
which corresponds in value to Mail::recipientType.
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);
/* Same as above but include the AddrEntry object in the result.
Caller needs to release the AddrEntry. */
std::vector >
get_oom_recipients_with_addrEntry (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, std::string &r_err_str,
int *r_err_code);
/* 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);
/* Same as invoke oom method but do the string marshalling for arg */
int invoke_oom_method_with_string (LPDISPATCH pDisp, const char *name,
const char *arg,
VARIANT *rVariant = nullptr);
/* Same as invoke oom method but with a single int argument */
int invoke_oom_method_with_int (LPDISPATCH pDisp, const char *name,
int arg,
VARIANT *rVariant = nullptr);
/* 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.
Creates the category with the specified color if required.
returns 0 on success. */
void
ensure_category_exists (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, bool exactMatch);
/* Create the category */
int
create_category (LPDISPATCH categories, const char *category, int color);
/* Delete a category from the store. */
int delete_category (LPDISPATCH store, const char *category);
/* Delete categories starting with "string" from all stores. */
void delete_all_categories_starting_with (const char *string);
/* Iterate over application stores and return the one with the ID
@storeID */
LPDISPATCH get_store_for_id (const char *storeID);
/* 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);
std::string
get_unique_id_s (LPDISPATCH mail, int create, const char* uuid);
/* Sets a new GpgOL UUID for this mail and returns the
value. */
char *
reset_unique_id (LPDISPATCH mail);
/* 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);
/* Get the SentRepresentingAddress */
char *get_sender_SentRepresentingAddress (LPDISPATCH mailitem);
/* 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);
/* Find or add a text user property with that name. */
LPDISPATCH find_or_add_text_prop (LPDISPATCH props, const char *name);
/* Find a user property and return it if found. */
LPDISPATCH find_user_prop (LPDISPATCH props, const char *name);
/* Return true if this message is in the junk folder for this account */
bool is_junk_mail (LPDISPATCH mailitem);
/* Return true if this message is in the draft folder for this account */
bool is_draft_mail (LPDISPATCH mailitem);
/* Returns info about a dispparms variable for debugging. */
void format_variant (std::istringstream &stream, VARIANT *var);
std::string format_dispparams (DISPPARAMS *p);
/* Returns the count of attachments that are not hidden. */
int count_visible_attachments (LPDISPATCH attachments);
/* Remove a recipient from the OOM. */
int remove_oom_recipient (LPDISPATCH item, const std::string &mbox);
/* Remove all recipients and replace them with the list of our objects. */
int set_oom_recipients (LPDISPATCH item, const std::vector &recps);
/* Print some introspective infos about the object. */
void oom_dump_idispatch (LPDISPATCH obj);
/* Get the hidden dirty property of the object. */
int get_oom_dirty (LPDISPATCH pDisp);
/* Get the Outlook crypto flags indicating in Outlook if the mail should
be encrypted with S/MIME. */
int get_oom_crypto_flags (LPDISPATCH mailitem);
/* Show a folder picker dialog and return a folder disp or NULL */
shared_disp_t show_folder_select ();
/* Return the main Application object. Like qApp ;-) */
LPDISPATCH oApp ();
/* Setter for dirty - Returns an error that the property
is write protected on mails. But maybe we can use it
somewhere else. Code is ifdeffed out.
int put_oom_dirty (LPDISPATCH pDisp, bool val);
*/
/* Invoke ->SaveAs to a path, path is expected to be
in utf-8 returns 0 on success. */
enum oomSaveAsType
{
olDoc = 4, /* Microsoft Office Word format (.doc) */
olHTML = 5, /* HTML format (.html) */
olICal = 8, /* iCal format (.ics) */
olMHTML = 10, /* MIME HTML format (.mht) */
olMSG = 3, /* Outlook message format (.msg) */
olMSGUnicode = 9, /* Outlook Unicode message format (.msg) */
olRTF = 1, /* Rich Text format (.rtf) */
olTemplate = 2, /* Microsoft Outlook template (.oft) */
olTXT = 0, /* Text format (.txt) */
olVCal = 7, /* VCal format (.vcs) */
olVCard = 6, /* VCard format (.vcf) */
};
int oom_save_as (LPDISPATCH obj, const char *path,
oomSaveAsType type = olMSG);
int oom_save_as_file (LPDISPATCH obj, const char *path);
/* Convert a utf8 value to a bstr allocated with SysAllocString.
Call SysFreeString on the allocated value. VariantClear on
a variant with VT_BSTR does this for you. */
BSTR utf8_to_bstr (const char *val);
+
+/* Clear the selection in all explorers */
+void oom_clear_selections ();
#endif /*OOMHELP_H*/