failedNames;
for (auto att: attachments)
{
int err = 0;
const auto dispName = att->get_display_name ();
if (dispName.empty())
{
log_error ("%s:%s: Ignoring attachment without display name.",
SRCNAME, __func__);
continue;
}
wchar_t* wchar_name = utf8_to_wchar (dispName.c_str());
if (!wchar_name)
{
log_error ("%s:%s: Failed to convert '%s' to wchar.",
SRCNAME, __func__, anonstr (dispName.c_str()));
continue;
}
HANDLE hFile;
wchar_t* wchar_file = get_tmp_outfile (wchar_name,
&hFile);
if (!wchar_file)
{
log_error ("%s:%s: Failed to obtain a tmp filename for: %s",
SRCNAME, __func__, anonstr (dispName.c_str()));
err = 1;
}
if (!err && copy_attachment_to_file (att, hFile))
{
log_error ("%s:%s: Failed to copy attachment %s to temp file",
SRCNAME, __func__, anonstr (dispName.c_str()));
err = 1;
}
if (!err && add_oom_attachment (m_mailitem, wchar_file, wchar_name,
addErrStr, &addErrCode))
{
log_error ("%s:%s: Failed to add attachment: %s",
SRCNAME, __func__, anonstr (dispName.c_str()));
failedNames.push_back (dispName);
err = 1;
}
if (hFile && hFile != INVALID_HANDLE_VALUE)
{
CloseHandle (hFile);
}
if (wchar_file && !DeleteFileW (wchar_file))
{
log_error ("%s:%s: Failed to delete tmp attachment for: %s",
SRCNAME, __func__, anonstr (dispName.c_str()));
err = 1;
}
xfree (wchar_file);
xfree (wchar_name);
if (!err)
{
log_debug ("%s:%s: Added attachment '%s'",
SRCNAME, __func__, anonstr (dispName.c_str()));
err = fixup_last_attachment_o (m_mailitem, att);
}
if (err)
{
anyError = true;
}
}
if (anyError)
{
std::string msg = _("Not all attachments can be shown.\n\n"
"The hidden attachments are:");
msg += "\n";
std::string filenames;
join (failedNames, "\n", filenames);
msg += filenames;
msg += "\n\n";
if (addErrCode == 0x80004005)
{
msg += _("The mail exceeds the maximum size GpgOL "
"can handle on this server.");
}
else
{
msg += _("Reason:");
msg += " " + addErrStr;
}
gpgol_message_box (getWindow (),
msg.c_str (), _("GpgOL"), MB_OK);
}
m_disable_att_remove_warning = false;
TRETURN anyError;
}
GPGRT_LOCK_DEFINE(parser_lock);
static DWORD WINAPI
do_parsing (LPVOID arg)
{
TSTART;
gpgol_lock (&dtor_lock);
/* We lock with mail dtors so we can be sure the mail->parser
call is valid. */
Mail *mail = (Mail *)arg;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: canceling parsing for: %p already deleted",
SRCNAME, __func__, arg);
gpgol_unlock (&dtor_lock);
TRETURN 0;
}
blockInv ();
/* This takes a shared ptr of parser. So the parser is
still valid when the mail is deleted. */
auto parser = mail->parser ();
gpgol_unlock (&dtor_lock);
gpgol_lock (&parser_lock);
/* We lock the parser here to avoid too many
decryption attempts if there are
multiple mailobjects which might have already
been deleted (e.g. by quick switches of the mailview.)
Let's rather be a bit slower.
*/
log_debug ("%s:%s: preparing the parser for: %p",
SRCNAME, __func__, arg);
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: cancel for: %p already deleted",
SRCNAME, __func__, arg);
gpgol_unlock (&parser_lock);
unblockInv();
TRETURN 0;
}
if (!parser)
{
log_error ("%s:%s: no parser found for mail: %p",
SRCNAME, __func__, arg);
gpgol_unlock (&parser_lock);
unblockInv();
TRETURN -1;
}
parser->parse();
if (!opt.sync_dec)
{
do_in_ui_thread (PARSING_DONE, arg);
}
gpgol_unlock (&parser_lock);
unblockInv();
TRETURN 0;
}
/* How encryption is done:
There are two modes of encryption. Synchronous and Async.
If async is used depends on the value of mail->async_crypt_disabled.
Synchronous crypto:
> Send Event < | State NoCryptMail
Needs Crypto ? (get_gpgol_draft_info_flags != 0)
-> No:
Pass send -> unencrypted mail.
-> Yes:
mail->update_oom_data
State = Mail::NeedsFirstAfterWrite
checkSyncCrypto_o
invoke_oom_method (m_object, "Save", NULL);
> Write Event <
Pass because is_crypto_mail is false (not a decrypted mail)
> AfterWrite Event < | State NeedsFirstAfterWrite
State = NeedsActualCrypo
encrypt_sign_start
collect_input_data
-> Check if Inline PGP should be used
do_crypt
-> Resolve keys / do crypto
State = NeedsUpdateInMAPI
update_crypt_mapi
crypter->update_mail_mapi
if (inline) (Meaning PGP/Inline)
<-- do nothing.
else
build MSOXSMIME attachment and clear body / attachments.
State = NeedsUpdateInOOM
<- Back to Send Event
update_crypt_oom
-> Cleans body or sets PGP/Inline body. (inline_body_to_body)
State = WantsSendMIME or WantsSendInline
-> Saftey check "has_crypted_or_empty_body"
-> If MIME Mail do the T3656 check.
Send.
State order for "inline_response" (sync) Mails.
NoCryptMail
NeedsFirstAfterWrite
NeedsActualCrypto
NeedsUpdateInMAPI
NeedsUpdateInOOM
WantsSendMIME (or inline for PGP Inline)
-> Send.
State order for async Mails
NoCryptMail
NeedsFirstAfterWrite
NeedsActualCrypto
-> Cancel Send.
Windowmessages -> Crypto Done
NeedsUpdateInOOM
NeedsSecondAfterWrite
trigger Save.
NeedsUpdateInMAPI
WantsSendMIME
trigger Send.
*/
static DWORD WINAPI
do_crypt (LPVOID arg)
{
TSTART;
gpgol_lock (&dtor_lock);
/* We lock with mail dtors so we can be sure the mail->parser
call is valid. */
Mail *mail = (Mail *)arg;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: canceling crypt for: %p already deleted",
SRCNAME, __func__, arg);
gpgol_unlock (&dtor_lock);
TRETURN 0;
}
if (mail->cryptState () != Mail::NeedsActualCrypt)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, mail->cryptState ());
mail->enableWindow ();
gpgol_unlock (&dtor_lock);
TRETURN -1;
}
/* This takes a shared ptr of crypter. So the crypter is
still valid when the mail is deleted. */
auto crypter = mail->cryper ();
gpgol_unlock (&dtor_lock);
if (!crypter)
{
log_error ("%s:%s: no crypter found for mail: %p",
SRCNAME, __func__, arg);
gpgol_unlock (&parser_lock);
mail->enableWindow ();
TRETURN -1;
}
GpgME::Error err;
std::string diag;
int rc = crypter->do_crypto(err, diag);
gpgol_lock (&dtor_lock);
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: aborting crypt for: %p already deleted",
SRCNAME, __func__, arg);
gpgol_unlock (&dtor_lock);
TRETURN 0;
}
mail->enableWindow ();
if (rc == -1 || err)
{
mail->resetCrypter ();
crypter = nullptr;
if (err)
{
char *buf = nullptr;
gpgrt_asprintf (&buf, _("Crypto operation failed:\n%s"),
err.asString());
std::string msg = buf;
memdbg_alloc (buf);
xfree (buf);
if (!diag.empty())
{
msg += "\n\n";
msg += _("Diagnostics");
msg += ":\n";
msg += diag;
}
gpgol_message_box (mail->getWindow (), msg.c_str (),
_("GpgOL"), MB_OK);
}
else
{
gpgol_bug (mail->getWindow (),
ERR_CRYPT_RESOLVER_FAILED);
}
}
if (rc || err.isCanceled())
{
log_debug ("%s:%s: crypto failed for: %p with: %i err: %i",
SRCNAME, __func__, arg, rc, err.code());
mail->setCryptState (Mail::NoCryptMail);
mail->setIsDraftEncrypt (false);
mail->resetCrypter ();
crypter = nullptr;
gpgol_unlock (&dtor_lock);
TRETURN rc;
}
if (!mail->isAsyncCryptDisabled ())
{
mail->setCryptState (Mail::NeedsUpdateInOOM);
gpgol_unlock (&dtor_lock);
// This deletes the Mail in Outlook 2010
do_in_ui_thread (CRYPTO_DONE, arg);
log_debug ("%s:%s: UI thread finished for %p",
SRCNAME, __func__, arg);
}
else if (mail->isDraftEncrypt ())
{
mail->setCryptState (Mail::NeedsUpdateInMAPI);
mail->updateCryptMAPI_m ();
mail->setIsDraftEncrypt (false);
mail->setCryptState (Mail::NoCryptMail);
log_debug ("%s:%s: Synchronous draft encrypt finished for %p",
SRCNAME, __func__, arg);
gpgol_unlock (&dtor_lock);
}
else
{
mail->setCryptState (Mail::NeedsUpdateInMAPI);
mail->updateCryptMAPI_m ();
if (mail->cryptState () == Mail::WantsSendMIME)
{
// For sync crypto we need to switch this.
mail->setCryptState (Mail::NeedsUpdateInOOM);
}
else
{
// A bug!
log_debug ("%s:%s: Resetting crypter because of state mismatch. %p",
SRCNAME, __func__, arg);
crypter = nullptr;
mail->resetCrypter ();
}
gpgol_unlock (&dtor_lock);
}
/* This works around a bug in pinentry that it might
bring the wrong window to front. So after encryption /
signing we bring outlook back to front.
See GnuPG-Bug-Id: T3732
*/
do_in_ui_thread_async (BRING_TO_FRONT, nullptr, 250);
log_debug ("%s:%s: crypto thread for %p finished",
SRCNAME, __func__, arg);
TRETURN 0;
}
bool
Mail::isCryptoMail () const
{
TSTART;
if (m_type == MSGTYPE_UNKNOWN || m_type == MSGTYPE_GPGOL ||
m_type == MSGTYPE_SMIME)
{
/* Not a message for us. */
TRETURN false;
}
TRETURN true;
}
int
Mail::decryptVerify_o ()
{
TSTART;
if (!isCryptoMail ())
{
log_debug ("%s:%s: Decrypt Verify for non crypto mail: %p.",
SRCNAME, __func__, m_mailitem);
TRETURN 0;
}
m_decrypt_again = false;
if (isSMIME_m ())
{
LPMESSAGE oom_message = get_oom_message (m_mailitem);
if (oom_message)
{
char *old_class = mapi_get_old_message_class (oom_message);
char *current_class = mapi_get_message_class (oom_message);
if (current_class)
{
/* Store our own class for an eventual close */
m_gpgol_class = current_class;
xfree (current_class);
current_class = nullptr;
}
if (old_class)
{
const char *new_class = old_class;
/* Workaround that our own class might be the original */
if (!strcmp (old_class, "IPM.Note.GpgOL.OpaqueEncrypted"))
{
new_class = "IPM.Note.SMIME";
}
else if (!strcmp (old_class, "IPM.Note.GpgOL.MultipartSigned"))
{
new_class = "IPM.Note.SMIME.MultipartSigned";
}
log_debug ("%s:%s:Restoring message class to %s in decverify.",
SRCNAME, __func__, new_class);
put_oom_string (m_mailitem, "MessageClass", new_class);
xfree (old_class);
setPassWrite (true);
/* Sync to MAPI */
invoke_oom_method (m_mailitem, "Save", nullptr);
setPassWrite (false);
}
gpgol_release (oom_message);
}
}
check_html_preferred ();
auto cipherstream = get_attachment_stream_o (m_mailitem, m_moss_position);
if (!cipherstream)
{
m_is_junk = is_junk_mail (m_mailitem);
if (m_is_junk)
{
log_debug ("%s:%s: Detected: %p as junk",
SRCNAME, __func__, m_mailitem);
auto mngr = CategoryManager::instance ();
m_store_id = mngr->addCategoryToMail (this,
CategoryManager::getJunkMailCategory (),
3 /* peach */);
installFolderEventHandler_o ();
TRETURN 0;
}
log_debug ("%s:%s: Failed to get cipherstream. Aborting handling.",
SRCNAME, __func__);
m_type = MSGTYPE_UNKNOWN;
TRETURN 1;
}
setUUID_o ();
m_processed = true;
m_pass_write = false;
/* Insert placeholder */
char *placeholder_buf = nullptr;
if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION)
{
gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html :
decrypt_template,
"OpenPGP",
_("Pubkey directory confirmation"),
_("This is a confirmation request to publish your Pubkey in the "
"directory for your domain.\n\n"
"If you did not request to publish your Pubkey in your providers "
"directory, simply ignore this message.
\n"));
}
else if (gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html :
decrypt_template,
isSMIME_m () ? "S/MIME" : "OpenPGP",
_("message"),
_("Please wait while the message is being decrypted / verified...")) == -1)
{
log_error ("%s:%s: Failed to format placeholder.",
SRCNAME, __func__);
TRETURN 1;
}
if (opt.prefer_html)
{
char *tmp = get_oom_string (m_mailitem, "HTMLBody");
if (!tmp)
{
TRACEPOINT;
TRETURN 1;
}
m_orig_body = tmp;
xfree (tmp);
if (put_oom_string (m_mailitem, "HTMLBody", placeholder_buf))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
put_oom_int (m_mailitem, "BodyFormat", 2);
}
else
{
char *tmp = get_oom_string (m_mailitem, "Body");
if (!tmp)
{
TRACEPOINT;
TRETURN 1;
}
m_orig_body = tmp;
xfree (tmp);
if (put_oom_string (m_mailitem, "Body", placeholder_buf))
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
put_oom_int (m_mailitem, "BodyFormat", 1);
}
memdbg_alloc (placeholder_buf);
xfree (placeholder_buf);
if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION)
{
WKSHelper::instance ()->handle_confirmation_read (this, cipherstream);
TRETURN 0;
}
m_parser = std::shared_ptr (new ParseController (cipherstream, m_type));
m_parser->setSender(GpgME::UserID::addrSpecFromString(getSender_o ().c_str()));
if (opt.autoimport)
{
/* Handle autocrypt header. As we want to have the import
of the header in the same thread as the parser we leave it
to the parser. */
auto message = MAKE_SHARED (get_oom_message (m_mailitem));
if (!message)
{
/* Hmmm */
STRANGEPOINT;
}
else
{
autocrypt_s ainfo;
if (!mapi_get_header_info ((LPMESSAGE)message.get(), ainfo))
{
STRANGEPOINT;
}
else if (ainfo.exists)
{
m_parser->setAutocryptInfo (ainfo);
}
}
}
log_data ("%s:%s: Parser for \"%s\" is %p",
SRCNAME, __func__, getSubject_o ().c_str(), m_parser.get());
gpgol_release (cipherstream);
/* Printing happens in two steps. First a Mail is loaded after the
BeforePrint event, then it is loaded a second time when the actual print
happens. We have to catch both. */
if (!m_printing)
{
m_printing = checkIfMailIsChildOfPrintMail_o ();
}
if (!opt.sync_dec && !m_printing)
{
HANDLE parser_thread = CreateThread (NULL, 0, do_parsing, (LPVOID) this, 0,
NULL);
if (!parser_thread)
{
log_error ("%s:%s: Failed to create decrypt / verify thread.",
SRCNAME, __func__);
}
CloseHandle (parser_thread);
TRETURN 0;
}
else
{
/* Parse synchronously */
do_parsing ((LPVOID) this);
parsing_done ();
TRETURN 0;
}
}
void find_and_replace(std::string& source, const std::string &find,
const std::string &replace)
{
TSTART;
for(std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos;)
{
source.replace(i, find.length(), replace);
i += replace.length();
}
TRETURN;
}
static void
set_body (LPDISPATCH item, const std::string &plain, const std::string &html)
{
if (opt.prefer_html && !html.empty())
{
if (put_oom_string (item, "HTMLBody", html.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
if (!plain.empty ())
{
if (put_oom_string (item, "Body", plain.c_str ()))
{
log_error ("%s:%s: Failed to put plaintext into body of item.",
SRCNAME, __func__);
}
put_oom_int (item, "BodyFormat", 1);
}
else
{
if (put_oom_string (item, "HTMLBody", plain.c_str ()))
{
log_error ("%s:%s: Failed to put plaintext into html of item.",
SRCNAME, __func__);
}
put_oom_int (item, "BodyFormat", 2);
}
}
else
{
put_oom_int (item, "BodyFormat", 2);
}
}
else if (!plain.empty ())
{
if (put_oom_string (item, "Body", plain.c_str ()))
{
log_error ("%s:%s: Failed to put plaintext into body of item.",
SRCNAME, __func__);
}
put_oom_int (item, "BodyFormat", 1);
}
}
void
Mail::updateBody_o ()
{
TSTART;
if (!m_parser)
{
TRACEPOINT;
TRETURN;
}
const auto error = m_parser->get_formatted_error ();
if (!error.empty())
{
set_body (m_mailitem, error, error);
TRETURN;
}
if (m_verify_result.error())
{
log_error ("%s:%s: Verification failed. Restoring Body.",
SRCNAME, __func__);
set_body (m_mailitem, m_orig_body, m_orig_body);
TRETURN;
}
// No need to carry body anymore
m_orig_body = std::string();
auto html = m_parser->get_html_body ();
auto body = m_parser->get_body ();
/** Outlook does not show newlines if \r\r\n is a newline. We replace
these as apparently some other buggy MUA sends this. */
find_and_replace (html, "\r\r\n", "\r\n");
if (opt.prefer_html && !html.empty())
{
if (!m_block_html)
{
const auto charset = m_parser->get_html_charset();
int codepage = 0;
if (charset.empty())
{
codepage = get_oom_int (m_mailitem, "InternetCodepage");
log_debug ("%s:%s: Did not find html charset."
" Using internet Codepage %i.",
SRCNAME, __func__, codepage);
}
char *converted = ansi_charset_to_utf8 (charset.c_str(), html.c_str(),
html.size(), codepage);
TRACEPOINT;
int ret = put_oom_string (m_mailitem, "HTMLBody", converted ?
converted : "");
xfree (converted);
put_oom_int (m_mailitem, "BodyFormat", 2);
TRACEPOINT;
if (ret)
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
TRETURN;
}
else if (!body.empty())
{
/* We had a multipart/alternative mail but html should be
blocked. So we prefer the text/plain part and warn
once about this so that we hopefully don't get too
many bugreports about this. */
if (!opt.smime_html_warn_shown)
{
std::string caption = _("GpgOL") + std::string (": ") +
std::string (_("HTML display disabled."));
std::string buf = _("HTML content in unsigned S/MIME mails "
"is insecure.");
buf += "\n";
buf += _("GpgOL will only show such mails as text.");
buf += "\n\n";
buf += _("This message is shown only once.");
gpgol_message_box (getWindow (), buf.c_str(), caption.c_str(),
MB_OK);
opt.smime_html_warn_shown = true;
write_options ();
}
}
}
if (body.empty () && m_block_html && !html.empty())
{
#if 0
Sadly the following code still offers to load external references
it might also be too dangerous if Outlook somehow autoloads the
references as soon as the Body is put into HTML
// Fallback to show HTML as plaintext if HTML display
// is blocked.
log_error ("%s:%s: No text body. Putting HTML into plaintext.",
SRCNAME, __func__);
char *converted = ansi_charset_to_utf8 (m_parser->get_html_charset().c_str(),
html.c_str(), html.size());
int ret = put_oom_string (m_mailitem, "HTMLBody", converted ? converted : "");
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
body = html;
}
else
{
char *plainBody = get_oom_string (m_mailitem, "Body");
if (!plainBody)
{
log_error ("%s:%s: Failed to obtain converted plain body.",
SRCNAME, __func__);
body = html;
}
else
{
ret = put_oom_string (m_mailitem, "HTMLBody", plainBody);
xfree (plainBody);
if (ret)
{
log_error ("%s:%s: Failed to put plain into html body of item.",
SRCNAME, __func__);
body = html;
}
else
{
TRETURN;
}
}
}
#endif
body = html;
std::string caption = _("GpgOL") + std::string (": ") +
std::string (_("HTML display disabled."));
std::string buf = _("HTML content in unsigned S/MIME mails "
"is insecure.");
buf += "\n";
buf += _("GpgOL will only show such mails as text.");
buf += "\n\n";
buf += _("Please ask the sender to sign the message or\n"
"to send it with a plain text alternative.");
gpgol_message_box (getWindow (), buf.c_str(), caption.c_str(),
MB_OK);
}
find_and_replace (body, "\r\r\n", "\r\n");
const auto plain_charset = m_parser->get_body_charset();
int codepage = 0;
if (plain_charset.empty())
{
codepage = get_oom_int (m_mailitem, "InternetCodepage");
log_debug ("%s:%s: Did not find body charset. "
"Using internet Codepage %i.",
SRCNAME, __func__, codepage);
}
char *converted = ansi_charset_to_utf8 (plain_charset.c_str(),
body.c_str(), body.size(),
codepage);
TRACEPOINT;
int ret = put_oom_string (m_mailitem, "Body", converted ? converted : "");
put_oom_int (m_mailitem, "BodyFormat", 1);
TRACEPOINT;
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
TRETURN;
}
void
Mail::updateHeaders_o ()
{
TSTART;
if (!m_parser)
{
STRANGEPOINT;
TRETURN;
}
const auto subject = m_parser->get_protected_header ("Subject");
if (!subject.empty ())
{
put_oom_string (m_mailitem, "Subject", subject.c_str ());
}
const auto to = m_parser->get_protected_header ("To");
if (!to.empty())
{
put_oom_string (m_mailitem, "To", to.c_str ());
}
const auto cc = m_parser->get_protected_header ("Cc");
if (!cc.empty())
{
put_oom_string (m_mailitem, "CC", cc.c_str ());
}
/* TODO: What about Date ? */
const auto reply_to = m_parser->get_protected_header ("Reply-To");
const auto followup_to = m_parser->get_protected_header ("Followup-To");
if (!reply_to.empty () || !followup_to.empty())
{
auto recipients = MAKE_SHARED (get_oom_object (m_mailitem, "ReplyRecipents"));
if (recipients)
{
if (!reply_to.empty ())
{
invoke_oom_method_with_string (recipients.get (), "Add",
reply_to.c_str ());
}
if (!followup_to.empty ())
{
invoke_oom_method_with_string (recipients.get (), "Add",
followup_to.c_str ());
}
}
}
const auto from = m_parser->get_protected_header ("From");
if (!from.empty ())
{
LPDISPATCH sender = get_oom_object (m_mailitem, "Sender");
if (!sender)
{
log_debug ("%s:%s: Sender not found. From not set.", SRCNAME, __func__);
TRETURN;
}
/* Declare that the address is SMTP */
put_oom_int (sender, "AddressEntryUserType", 30);
const auto mail_start = from.find (" <");
if (mail_start == std::string::npos)
{
put_oom_string (sender, "Address", from.c_str ());
put_oom_string (sender, "Name", "");
}
else
{
put_oom_string (sender, "Address",
GpgME::UserID::addrSpecFromString (from.c_str ()).c_str ());
put_oom_string (sender, "Name", from.substr (0,
mail_start).c_str ());
}
gpgol_release (sender);
}
}
static int parsed_count;
void
Mail::parsing_done()
{
TSTART;
TRACEPOINT;
log_oom ("Mail %p Parsing done for parser num %i: %p",
this, parsed_count++, m_parser.get());
if (!m_parser)
{
/* This should not happen but it happens when outlook
sends multiple ItemLoad events for the same Mail
Object. In that case it could happen that one
parser was already done while a second is now
returning for the wrong mail (as it's looked up
by uuid.)
We have a check in get_uuid that the uuid was
not in the map before (and the parser is replaced).
So this really really should not happen. We
handle it anyway as we crash otherwise.
It should not happen because the parser is only
created in decrypt_verify which is called in the
read event. And even in there we check if the parser
was set.
*/
log_error ("%s:%s: No parser obj. For mail: %p",
SRCNAME, __func__, this);
TRETURN;
}
/* Store the results. */
m_decrypt_result = m_parser->decrypt_result ();
m_verify_result = m_parser->verify_result ();
/* Handle protected headers */
updateHeaders_o ();
m_crypto_flags = 0;
if (!m_decrypt_result.isNull())
{
m_crypto_flags |= 1;
}
if (m_verify_result.numSignatures())
{
m_crypto_flags |= 2;
}
TRACEPOINT;
updateSigstate ();
m_needs_wipe = !m_is_send_again;
TRACEPOINT;
/* Set categories according to the result. */
updateCategories_o ();
TRACEPOINT;
m_block_html = m_parser->shouldBlockHtml ();
if (m_block_html)
{
// Just to be careful.
setBlockStatus_m ();
}
TRACEPOINT;
/* Update the body */
updateBody_o ();
TRACEPOINT;
/* When printing we have already shown the warning. So we
should not show it again but silently remove any attachments
that are not hidden before our add_attachments. This
also fixes an issue that when printing sometimes the
child mails which are created for preview and print already
have the decrypted attachments. */
checkAttachments_o (isPrint ());
/* Update attachments */
if (add_attachments_o (m_parser->get_attachments()))
{
log_error ("%s:%s: Failed to update attachments.",
SRCNAME, __func__);
}
if (m_is_send_again)
{
log_debug ("%s:%s: I think that this is the send again of a crypto mail.",
SRCNAME, __func__);
/* We no longer want to be treated like a crypto mail. */
m_type = MSGTYPE_UNKNOWN;
LPMESSAGE msg = get_oom_base_message (m_mailitem);
if (!msg)
{
TRACEPOINT;
}
else
{
set_gpgol_draft_info_flags (msg, m_crypto_flags);
gpgol_release (msg);
}
removeOurAttachments_o ();
}
installFolderEventHandler_o ();
log_debug ("%s:%s: Delayed invalidate to update sigstate.",
SRCNAME, __func__);
CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0,
NULL));
TRACEPOINT;
TRETURN;
}
int
Mail::encryptSignStart_o ()
{
TSTART;
if (m_crypt_state != NeedsActualCrypt)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
TRETURN -1;
}
int flags = 0;
if (!needs_crypto_m ())
{
TRETURN 0;
}
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
TRETURN -1;
}
flags = get_gpgol_draft_info_flags (message);
gpgol_release (message);
const auto window = get_active_hwnd ();
if (m_is_gsuite)
{
auto att_table = mapi_create_attach_table (message, 0);
int n_att_usable = count_usable_attachments (att_table);
mapi_release_attach_table (att_table);
/* Check for attachments if we have some abort. */
if (n_att_usable)
{
wchar_t *w_title = utf8_to_wchar (_(
"GpgOL: Oops, G Suite Sync account detected"));
wchar_t *msg = utf8_to_wchar (
_("G Suite Sync breaks outgoing crypto mails "
"with attachments.\nUsing crypto and attachments "
"with G Suite Sync is not supported.\n\n"
"See: https://dev.gnupg.org/T3545 for details."));
MessageBoxW (window,
msg,
w_title,
MB_ICONINFORMATION|MB_OK);
xfree (msg);
xfree (w_title);
TRETURN -1;
}
}
m_do_inline = m_is_draft_encrypt ? false :
m_is_gsuite ? true : opt.inline_pgp;
GpgME::Protocol proto = opt.enable_smime ? GpgME::UnknownProtocol: GpgME::OpenPGP;
m_crypter = std::shared_ptr (new CryptController (this, flags & 1,
flags & 2,
proto));
// Careful from here on we have to check every
// error condition with window enabling again.
disableWindow_o ();
if (m_crypter->collect_data ())
{
log_error ("%s:%s: Crypter for mail %p failed to collect data.",
SRCNAME, __func__, this);
enableWindow ();
TRETURN -1;
}
if (!m_async_crypt_disabled)
{
CloseHandle(CreateThread (NULL, 0, do_crypt,
(LPVOID) this, 0,
NULL));
}
else
{
log_debug ("%s:%s: Starting sync crypt",
SRCNAME, __func__);
do_crypt (this);
}
TRETURN 0;
}
int
Mail::needs_crypto_m () const
{
TSTART;
LPMESSAGE message = get_oom_message (m_mailitem);
int ret;
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
TRETURN false;
}
ret = get_gpgol_draft_info_flags (message);
gpgol_release(message);
TRETURN ret;
}
int
Mail::wipe_o (bool force)
{
TSTART;
if (!m_needs_wipe && !force)
{
TRETURN 0;
}
log_debug ("%s:%s: Removing plaintext from mailitem: %p.",
SRCNAME, __func__, m_mailitem);
if (put_oom_string (m_mailitem, "HTMLBody",
""))
{
if (put_oom_string (m_mailitem, "Body",
""))
{
log_debug ("%s:%s: Failed to wipe mailitem: %p.",
SRCNAME, __func__, m_mailitem);
TRETURN -1;
}
TRETURN -1;
}
else
{
put_oom_string (m_mailitem, "Body", "");
}
m_needs_wipe = false;
TRETURN 0;
}
int
Mail::updateOOMData_o (bool for_encryption)
{
TSTART;
char *buf = nullptr;
log_debug ("%s:%s", SRCNAME, __func__);
for_encryption |= !isCryptoMail();
if (for_encryption)
{
/* Update the body format. */
m_is_html_alternative = get_oom_int (m_mailitem, "BodyFormat") > 1;
/* Store the body. It was not obvious for me (aheinecke) how
to access this through MAPI. */
if (m_is_html_alternative)
{
log_debug ("%s:%s: Is html alternative mail.", SRCNAME, __func__);
xfree (m_cached_html_body);
m_cached_html_body = get_oom_string (m_mailitem, "HTMLBody");
}
xfree (m_cached_plain_body);
m_cached_plain_body = get_oom_string (m_mailitem, "Body");
m_cached_recipients = getRecipients_o ();
}
else
{
/* This is the case where we are reading a mail and not composing.
When composing we need to use the SendUsingAccount because if
you send from the folder of userA but change the from to userB
outlook will keep the SenderEmailAddress of UserA. This is all
so horrible. */
buf = get_sender_SenderEMailAddress (m_mailitem);
if (!buf)
{
/* Try the sender Object */
buf = get_sender_Sender (m_mailitem);
}
/* We also want to cache sent representing email address so that
we can use it for verification information. */
char *buf2 = get_sender_SentRepresentingAddress (m_mailitem);
if (buf2)
{
m_sent_on_behalf = buf2;
xfree (buf2);
}
}
if (!buf)
{
buf = get_sender_SendUsingAccount (m_mailitem, &m_is_gsuite);
}
if (!buf && !isCryptoMail ())
{
/* Try the sender Object */
buf = get_sender_Sender (m_mailitem);
}
if (!buf)
{
/* We don't have s sender object or SendUsingAccount,
well, in that case fall back to the current user. */
buf = get_sender_CurrentUser (m_mailitem);
}
if (!buf)
{
log_debug ("%s:%s: All fallbacks failed.",
SRCNAME, __func__);
TRETURN -1;
}
m_sender = buf;
xfree (buf);
TRETURN 0;
}
std::string
Mail::getSender_o ()
{
TSTART;
if (m_sender.empty())
updateOOMData_o ();
TRETURN m_sender;
}
std::string
Mail::getSender () const
{
TSTART;
TRETURN m_sender;
}
int
Mail::closeAllMails_o ()
{
TSTART;
int err = 0;
/* Detach Folder sinks */
for (auto fit = s_folder_events_map.begin(); fit != s_folder_events_map.end(); ++fit)
{
detach_FolderEvents_sink (fit->second);
gpgol_release (fit->second);
}
s_folder_events_map.clear();
std::map::iterator it;
TRACEPOINT;
gpgol_lock (&mail_map_lock);
std::map mail_map_copy = s_mail_map;
gpgol_unlock (&mail_map_lock);
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
/* XXX For non racy code the is_valid_ptr check should not
be necessary but we crashed sometimes closing a destroyed
mail. */
if (!isValidPtr (it->second))
{
log_debug ("%s:%s: Already deleted mail for %p",
SRCNAME, __func__, it->first);
continue;
}
if (!it->second->isCryptoMail ())
{
continue;
}
bool close_failed = false;
if (closeInspector_o (it->second))
{
log_error ("%s:%s: Failed to close mail inspector: %p ",
SRCNAME, __func__, it->first);
close_failed = true;
}
if (isValidPtr (it->second))
{
log_debug ("%s:%s: Inspector closed for %p closing object.",
SRCNAME, __func__, it->first);
if (it->second->close ())
{
log_error ("%s:%s: Failed to close mail itself: %p ",
SRCNAME, __func__, it->first);
close_failed = true;
}
}
else
{
log_debug ("%s:%s: Mail gone after inspector close.",
SRCNAME, __func__);
close_failed = false;
}
/* Beware: The close code removes our Plaintext from the
Outlook Object Model and temporary MAPI. If there
is an error we might put Plaintext into permanent
storage and leak it to the server. So we have
an extra safeguard below. The revert is likely
to fail if close and closeInspector fails but
to guard against a bug in our close code we
try it anyway as revert will also try to remove
the plaintext from memory and restore the original
message. */
if (close_failed)
{
if (isValidPtr (it->second) && it->second->revert_o ())
{
err++;
}
}
}
TRETURN err;
}
int
Mail::revertAllMails_o ()
{
TSTART;
int err = 0;
std::map::iterator it;
gpgol_lock (&mail_map_lock);
auto mail_map_copy = s_mail_map;
gpgol_unlock (&mail_map_lock);
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
if (it->second->revert_o ())
{
log_error ("Failed to revert mail: %p ", it->first);
err++;
continue;
}
it->second->setNeedsSave (true);
if (!invoke_oom_method (it->first, "Save", NULL))
{
log_error ("Failed to save reverted mail: %p ", it->second);
err++;
continue;
}
}
TRETURN err;
}
int
Mail::wipeAllMails_o ()
{
TSTART;
int err = 0;
std::map::iterator it;
gpgol_lock (&mail_map_lock);
auto mail_map_copy = s_mail_map;
gpgol_unlock (&mail_map_lock);
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
if (it->second->wipe_o ())
{
log_error ("Failed to wipe mail: %p ", it->first);
err++;
}
}
TRETURN err;
}
int
Mail::revert_o ()
{
TSTART;
int err = 0;
if (!m_processed)
{
TRETURN 0;
}
m_disable_att_remove_warning = true;
err = gpgol_mailitem_revert (m_mailitem);
if (err == -1)
{
log_error ("%s:%s: Message revert failed falling back to wipe.",
SRCNAME, __func__);
TRETURN wipe_o ();
}
/* We need to reprocess the mail next time around. */
m_processed = false;
m_needs_wipe = false;
m_disable_att_remove_warning = false;
TRETURN 0;
}
bool
Mail::isSMIME_m ()
{
TSTART;
msgtype_t msgtype;
LPMESSAGE message;
if (m_is_smime_checked)
{
TRETURN m_is_smime;
}
message = get_oom_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: No message?",
SRCNAME, __func__);
TRETURN false;
}
msgtype = mapi_get_message_type (message);
m_is_smime = msgtype == MSGTYPE_GPGOL_OPAQUE_ENCRYPTED ||
msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED ||
msgtype == MSGTYPE_SMIME;
/* Check if it is an smime mail. Multipart signed can
also be true. */
if (!m_is_smime && msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED)
{
char *proto;
char *ct = mapi_get_message_content_type (message, &proto, NULL);
if (ct && proto)
{
m_is_smime = (!strcmp (proto, "application/pkcs7-signature") ||
!strcmp (proto, "application/x-pkcs7-signature"));
}
else
{
log_error ("%s:%s: No protocol in multipart / signed mail.",
SRCNAME, __func__);
}
xfree (proto);
xfree (ct);
}
gpgol_release (message);
m_is_smime_checked = true;
log_debug ("%s:%s: Detected %s mail",
SRCNAME, __func__,
m_is_smime ? "S/MIME" : "not S/MIME");
TRETURN m_is_smime;
}
static std::string
get_string_o (LPDISPATCH item, const char *str)
{
TSTART;
char *buf = get_oom_string (item, str);
if (!buf)
{
TRETURN std::string();
}
std::string ret = buf;
xfree (buf);
TRETURN ret;
}
std::string
Mail::getSubject_o () const
{
TSTART;
TRETURN get_string_o (m_mailitem, "Subject");
}
std::string
Mail::getBody_o () const
{
TSTART;
TRETURN get_string_o (m_mailitem, "Body");
}
-std::vector
+std::vector
Mail::getRecipients_o () const
{
TSTART;
LPDISPATCH recipients = get_oom_object (m_mailitem, "Recipients");
if (!recipients)
{
TRACEPOINT;
std::vector();
}
bool err = false;
auto ret = get_oom_recipients (recipients, &err);
gpgol_release (recipients);
if (err)
{
log_debug ("%s:%s: Failed to resolve recipients at this time.",
SRCNAME, __func__);
}
TRETURN ret;
}
int
Mail::closeInspector_o (Mail *mail)
{
TSTART;
LPDISPATCH inspector = get_oom_object (mail->item(), "GetInspector");
HRESULT hr;
DISPID dispid;
if (!inspector)
{
log_debug ("%s:%s: No inspector.",
SRCNAME, __func__);
TRETURN -1;
}
dispid = lookup_oom_dispid (inspector, "Close");
if (dispid != DISPID_UNKNOWN)
{
VARIANT aVariant[1];
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = 1;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
hr = inspector->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Failed to close inspector: %#lx",
SRCNAME, __func__, hr);
gpgol_release (inspector);
TRETURN -1;
}
}
gpgol_release (inspector);
TRETURN 0;
}
int
Mail::close (bool restoreSMIMEClass)
{
TSTART;
wm_after_move_data_t *move_data = nullptr;
VARIANT aVariant[1];
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = 1;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
if (isSMIME_m ())
{
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
LPMESSAGE mapi_msg = get_oom_message (m_mailitem);
if (!mapi_msg)
{
log_error ("%s:%s:Failed to obtain mapi message for %p on close.",
SRCNAME, __func__, m_mailitem);
}
if (m_gpgol_class.empty())
{
log_debug ("%s:%s: GpgOL Class empty for S/MIME Mail.",
SRCNAME, __func__);
}
/* This is strangely important. Because Outlook apparently treats
* S/MIME Mails differently we need to change our message class
* here again so that discard changes works properly. */
if (mapi_msg && !m_gpgol_class.empty ())
{
char *current = mapi_get_message_class (mapi_msg);
if (current && strcmp (current, m_gpgol_class.c_str ()))
{
log_debug ("%s:%s:Setting message class to %s on close.",
SRCNAME, __func__, m_gpgol_class.c_str ());
mapi_set_mesage_class (mapi_msg, m_gpgol_class.c_str ());
if (restoreSMIMEClass)
{
/* After the close we need to change the message class back
again so as to not break compatibility with other clients. */
move_data = (wm_after_move_data_t *)
xmalloc (sizeof (wm_after_move_data_t));
size_t entryIDLen = 0;
char *entryID = nullptr;
entryID = mapi_get_binary_prop (mapi_msg, PR_ENTRYID,
&entryIDLen);
LPDISPATCH folder = get_oom_object (m_mailitem, "Parent");
if (folder)
{
char *name = get_object_name ((LPUNKNOWN) folder);
if (!name || strcmp (name, "MAPIFolder"))
{
log_error ("%s:%s: Failed to obtain folder on close object is %s",
SRCNAME, __func__, name ? name : "(null)");
}
else
{
xfree (name);
move_data->target_folder = (LPMAPIFOLDER) get_oom_iunknown (
folder, "MAPIOBJECT");
if (!move_data->target_folder)
{
log_error ("%s:%s: Failed to obtain target folder.",
SRCNAME, __func__);
xfree (entryID);
xfree (current);
xfree (move_data);
move_data = nullptr;
}
else
{
memdbg_addRef (move_data->target_folder);
}
}
}
move_data->entry_id = entryID;
move_data->entry_id_len = entryIDLen;
move_data->old_class = current;
}
}
}
if (attachments)
{
int count = get_oom_int (attachments, "Count");
gpgol_release (attachments);
if (count)
{
/* On exchange Outlook sometimes detects that an S/MIME mail
* is an S/MIME Mail. When this mail is then modified by
* us and the mail should be moved or closed Outlook will try
* to save it. This fails and the user gets an error.
*
* So we save here, which should not be dangerous as we do not
* put plaintext in mapi.
*
* Still better only do it if it is really
* necessary as the changed message class can hurt.
*
* Tests show no plaintext leaks. The save saves the
* message class and in that way outlook no longer
* thinks the mails are S/MIME mails and we can
* use our own handling. See T4525
*/
removeCategories_o ();
HRESULT hr = 0;
if (mapi_msg)
{
log_debug ("%s:%s: MAPI Save for: %p",
SRCNAME, __func__, m_mailitem);
mapi_msg->SaveChanges (KEEP_OPEN_READWRITE);
}
if (!mapi_msg || hr)
{
log_error ("%s:%s: Failed to save mapi for %p hr=%#lx",
SRCNAME, __func__, this, hr);
}
/* In case the mail is still visible in a different window */
updateCategories_o ();
}
}
gpgol_release (mapi_msg);
}
log_oom ("%s:%s: Invoking close for: %p",
SRCNAME, __func__, m_mailitem);
setCloseTriggered (true);
int rc = invoke_oom_method_with_parms (m_mailitem, "Close",
nullptr, &dispparams);
if (move_data)
{
log_debug ("%s:%s:Restoring message class to %s after close.",
SRCNAME, __func__, move_data->old_class);
do_in_ui_thread_async (AFTER_MOVE, move_data, 0);
}
setCloseTriggered (false);
if (!rc)
{
/* Saveguard against oom writes when our data is in OOM. This
* can happen when the mail was opened in a new window. But
* also shown in the preview.
* We get a close event and discard changes but the data is
* still in oom because it is still visible in the opened
* window.
*
* In that case we may not write! Otherwise the plaintext
* might be leaked back to the server if the folder is synced.
* */
char *body = get_oom_string (m_mailitem, "Body");
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (body && strlen (body))
{
log_debug ("%s:%s: Close successful. But body found. "
"Mail still open.",
SRCNAME, __func__);
}
else if (count_visible_attachments (attachments))
{
log_debug ("%s:%s: Close successful. But attachments found. "
"Mail still open.",
SRCNAME, __func__);
}
else
{
setPassWrite (true);
log_debug ("%s:%s: Close successful. Next write may pass.",
SRCNAME, __func__);
}
gpgol_release (attachments);
xfree (body);
}
log_oom ("%s:%s: returned from close",
SRCNAME, __func__);
TRETURN rc;
}
void
Mail::setCloseTriggered (bool value)
{
TSTART;
m_close_triggered = value;
TRETURN;
}
bool
Mail::getCloseTriggered () const
{
TSTART;
TRETURN m_close_triggered;
}
static const UserID
get_uid_for_sender (const Key &k, const char *sender)
{
TSTART;
UserID ret;
if (!sender)
{
TRETURN ret;
}
if (!k.numUserIDs())
{
log_debug ("%s:%s: Key without uids",
SRCNAME, __func__);
TRETURN ret;
}
for (const auto uid: k.userIDs())
{
if (!uid.email() || !*(uid.email()))
{
/* This happens for S/MIME a lot */
log_debug ("%s:%s: skipping uid without email.",
SRCNAME, __func__);
continue;
}
auto normalized_uid = uid.addrSpec();
auto normalized_sender = UserID::addrSpecFromString(sender);
if (normalized_sender.empty() || normalized_uid.empty())
{
log_error ("%s:%s: normalizing '%s' or '%s' failed.",
SRCNAME, __func__, anonstr (uid.email()),
anonstr (sender));
continue;
}
if (normalized_sender == normalized_uid)
{
ret = uid;
}
}
TRETURN ret;
}
void
Mail::updateSigstate ()
{
TSTART;
std::string sender = getSender ();
if (sender.empty())
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
TRETURN;
}
if (m_verify_result.isNull())
{
log_debug ("%s:%s: No verify result.",
SRCNAME, __func__);
TRETURN;
}
if (m_verify_result.error())
{
log_debug ("%s:%s: verify error.",
SRCNAME, __func__);
TRETURN;
}
for (const auto sig: m_verify_result.signatures())
{
m_is_signed = true;
const auto key = KeyCache::instance ()->getByFpr (sig.fingerprint(),
true);
m_uid = get_uid_for_sender (key, sender.c_str());
if (m_uid.isNull() && !m_sent_on_behalf.empty ())
{
m_uid = get_uid_for_sender (key, m_sent_on_behalf.c_str ());
if (!m_uid.isNull())
{
log_debug ("%s:%s: Using sent on behalf '%s' instead of '%s'",
SRCNAME, __func__, anonstr (m_sent_on_behalf.c_str()),
anonstr (sender.c_str ()));
}
}
/* Sigsum valid or green is somehow not set in this case.
* Which is strange as AFAIK this worked in the past. */
if ((sig.summary() & Signature::Summary::Valid) &&
m_uid.origin() == GpgME::Key::OriginWKD &&
(sig.validity() == Signature::Validity::Unknown ||
sig.validity() == Signature::Validity::Marginal))
{
// WKD is a shortcut to Level 2 trust.
log_debug ("%s:%s: Unknown or marginal from WKD -> Level 2",
SRCNAME, __func__);
}
else if (m_uid.isNull() || (sig.validity() != Signature::Validity::Marginal &&
sig.validity() != Signature::Validity::Full &&
sig.validity() != Signature::Validity::Ultimate))
{
/* For our category we only care about trusted sigs. And
the UID needs to match.*/
continue;
}
else if (sig.validity() == Signature::Validity::Marginal)
{
const auto tofu = m_uid.tofuInfo();
if (!tofu.isNull() &&
(tofu.validity() != TofuInfo::Validity::BasicHistory &&
tofu.validity() != TofuInfo::Validity::LargeHistory))
{
/* Marginal is only good enough without tofu.
Otherwise we wait for basic trust. */
log_debug ("%s:%s: Discarding marginal signature."
"With too little history.",
SRCNAME, __func__);
continue;
}
}
log_debug ("%s:%s: Classified sender as verified uid validity: %i origin: %i",
SRCNAME, __func__, m_uid.validity(), m_uid.origin());
m_sig = sig;
m_is_valid = true;
TRETURN;
}
log_debug ("%s:%s: No signature with enough trust. Using first",
SRCNAME, __func__);
m_sig = m_verify_result.signature(0);
TRETURN;
}
bool
Mail::isValidSig () const
{
TSTART;
TRETURN m_is_valid;
}
void
Mail::removeCategories_o ()
{
TSTART;
if (!m_store_id.empty () && !m_verify_category.empty ())
{
log_oom ("%s:%s: Unreffing verify category",
SRCNAME, __func__);
CategoryManager::instance ()->removeCategory (this,
m_verify_category);
}
if (!m_store_id.empty () && !m_decrypt_result.isNull())
{
log_oom ("%s:%s: Unreffing dec category",
SRCNAME, __func__);
CategoryManager::instance ()->removeCategory (this,
CategoryManager::getEncMailCategory ());
}
if (m_is_junk)
{
log_oom ("%s:%s: Unreffing junk category",
SRCNAME, __func__);
CategoryManager::instance ()->removeCategory (this,
CategoryManager::getJunkMailCategory ());
}
TRETURN;
}
/* Now for some tasty hack: Outlook sometimes does
not show the new categories properly but instead
does some weird scrollbar thing. This can be
avoided by resizing the message a bit. But somehow
this only needs to be done once.
Weird isn't it? But as this workaround worked let's
do it programatically. Fun. Wan't some tomato sauce
with this hack? */
static void
resize_active_window ()
{
TSTART;
HWND wnd = get_active_hwnd ();
static std::vector resized_windows;
if(std::find(resized_windows.begin(), resized_windows.end(), wnd) != resized_windows.end()) {
/* We only need to do this once per window. XXX But sometimes we also
need to do this once per view of the explorer. So for now this might
break but we reduce the flicker. A better solution would be to find
the current view and track that. */
TRETURN;
}
if (!wnd)
{
TRACEPOINT;
TRETURN;
}
RECT oldpos;
if (!GetWindowRect (wnd, &oldpos))
{
TRACEPOINT;
TRETURN;
}
if (!SetWindowPos (wnd, nullptr,
(int)oldpos.left,
(int)oldpos.top,
/* Anything smaller then 19 was ignored when the window was
* maximized on Windows 10 at least with a 1980*1024
* resolution. So I assume it's at least 1 percent.
* This is all hackish and ugly but should work for 90%...
* hopefully.
*/
(int)oldpos.right - oldpos.left - 20,
(int)oldpos.bottom - oldpos.top, 0))
{
TRACEPOINT;
TRETURN;
}
if (!SetWindowPos (wnd, nullptr,
(int)oldpos.left,
(int)oldpos.top,
(int)oldpos.right - oldpos.left,
(int)oldpos.bottom - oldpos.top, 0))
{
TRACEPOINT;
TRETURN;
}
resized_windows.push_back(wnd);
TRETURN;
}
#if 0
static std::string
pretty_id (const char *keyId)
{
/* Three spaces, four quads and a NULL */
char buf[20];
buf[19] = '\0';
if (!keyId)
{
return std::string ("null");
}
size_t len = strlen (keyId);
if (!len)
{
return std::string ("empty");
}
if (len < 16)
{
return std::string (_("Invalid Key"));
}
const char *p = keyId + (len - 16);
int j = 0;
for (size_t i = 0; i < 16; i++)
{
if (i && i % 4 == 0)
{
buf[j++] = ' ';
}
buf[j++] = *(p + i);
}
return std::string (buf);
}
#endif
void
Mail::updateCategories_o ()
{
TSTART;
if (m_printing)
{
log_debug ("%s:%s: Not updating categories as we are printing.",
SRCNAME, __func__);
return;
}
auto mngr = CategoryManager::instance ();
if (isValidSig ())
{
char *buf;
/* Resolve to the primary fingerprint */
#if 0
const auto sigKey = KeyCache::instance ()->getByFpr (m_sig.fingerprint (),
true);
const char *sigFpr;
if (sigKey.isNull())
{
sigFpr = m_sig.fingerprint ();
}
else
{
sigFpr = sigKey.primaryFingerprint ();
}
#endif
/* If m_uid addrSpec would not return a result we would never
* have gotten the UID. */
int lvl = get_signature_level ();
/* TRANSLATORS: The first placeholder is for tranlsation of "Level".
The second one is for the level number. The third is for the
translation of "trust in" and the last one is for the mail
address used for verification. The result is used as the
text on the green bar for signed mails. e.g.:
"GpgOL: Level 3 trust in 'john.doe@example.org'" */
gpgrt_asprintf (&buf, "GpgOL: %s %i %s '%s'", _("Level"), lvl,
_("trust in"),
m_uid.addrSpec ().c_str ());
memdbg_alloc (buf);
int color = 0;
if (lvl == 2)
{
color = 7; /* Olive */
}
if (lvl == 3)
{
color = 5; /* Green */
}
if (lvl == 4)
{
color = 20; /* Dark Green */
}
m_store_id = mngr->addCategoryToMail (this, buf, color);
m_verify_category = buf;
xfree (buf);
}
else
{
remove_category (m_mailitem, "GpgOL: ", false);
}
if (!m_decrypt_result.isNull())
{
const auto id = mngr->addCategoryToMail (this,
CategoryManager::getEncMailCategory (),
8 /* Blue */);
if (m_store_id.empty())
{
m_store_id = id;
}
if (m_store_id != id)
{
log_error ("%s:%s unexpected store mismatch "
"between '%s' and dec cat '%s'",
SRCNAME, __func__, m_store_id.c_str(), id.c_str());
}
}
else
{
/* As a small safeguard against fakes we remove our
categories */
remove_category (m_mailitem,
CategoryManager::getEncMailCategory ().c_str (),
true);
}
resize_active_window();
TRETURN;
}
bool
Mail::isSigned () const
{
TSTART;
TRETURN m_verify_result.numSignatures() > 0;
}
bool
Mail::isEncrypted () const
{
TSTART;
TRETURN !m_decrypt_result.isNull();
}
int
Mail::setUUID_o ()
{
TSTART;
char *uuid;
if (!m_uuid.empty())
{
/* This codepath is reached by decrypt again after a
close with discard changes. The close discarded
the uuid on the OOM object so we have to set
it again. */
log_debug ("%s:%s: Resetting uuid for %p to %s",
SRCNAME, __func__, this,
m_uuid.c_str());
uuid = get_unique_id (m_mailitem, 1, m_uuid.c_str());
}
else
{
uuid = get_unique_id (m_mailitem, 1, nullptr);
log_debug ("%s:%s: uuid for %p set to %s",
SRCNAME, __func__, this, uuid);
}
if (!uuid)
{
log_debug ("%s:%s: Failed to get/set uuid for %p",
SRCNAME, __func__, m_mailitem);
TRETURN -1;
}
if (m_uuid.empty())
{
m_uuid = uuid;
Mail *other = getMailForUUID (uuid);
if (other)
{
/* According to documentation this should not
happen as this means that multiple ItemLoad
events occured for the same mailobject without
unload / destruction of the mail.
But it happens. If you invalidate the UI
in the selection change event Outlook loads a
new mailobject for the mail. Might happen in
other surprising cases. We replace in that
case as experiments have shown that the last
mailobject is the one that is visible.
Still troubling state so we log this as an error.
*/
log_error ("%s:%s: There is another mail for %p "
"with uuid: %s replacing it.",
SRCNAME, __func__, m_mailitem, uuid);
delete other;
}
gpgol_lock (&uid_map_lock);
s_uid_map.insert (std::pair (m_uuid, this));
gpgol_unlock (&uid_map_lock);
log_debug ("%s:%s: uuid for %p is now %s",
SRCNAME, __func__, this,
m_uuid.c_str());
}
xfree (uuid);
TRETURN 0;
}
/* TRETURNs 2 if the userid is ultimately trusted.
TRETURNs 1 if the userid is fully trusted but has
a signature by a key for which we have a secret
and which is ultimately trusted. (Direct trust)
0 otherwise */
static int
level_4_check (const UserID &uid)
{
TSTART;
if (uid.isNull())
{
TRETURN 0;
}
if (uid.validity () == UserID::Validity::Ultimate)
{
TRETURN 2;
}
if (uid.validity () == UserID::Validity::Full)
{
const auto ultimate_keys = KeyCache::instance()->getUltimateKeys ();
for (const auto sig: uid.signatures ())
{
const char *sigID = sig.signerKeyID ();
if (sig.isNull() || !sigID)
{
/* should not happen */
TRACEPOINT;
continue;
}
/* Direct trust information is not available
through gnupg so we cached the keys with ultimate
trust during parsing and now see if we find a direct
trust path.*/
for (const auto secKey: ultimate_keys)
{
/* Check that the Key id of the key matches */
const char *secKeyID = secKey.keyID ();
if (!secKeyID || strcmp (secKeyID, sigID))
{
continue;
}
/* Check that the userID of the signature is the ultimately
trusted one. */
const char *sig_uid_str = sig.signerUserID();
if (!sig_uid_str)
{
/* should not happen */
TRACEPOINT;
continue;
}
for (const auto signer_uid: secKey.userIDs ())
{
if (signer_uid.validity() != UserID::Validity::Ultimate)
{
TRACEPOINT;
continue;
}
const char *signer_uid_str = signer_uid.id ();
if (!sig_uid_str)
{
/* should not happen */
TRACEPOINT;
continue;
}
if (!strcmp(sig_uid_str, signer_uid_str))
{
/* We have a match */
log_debug ("%s:%s: classified %s as ultimate because "
"it was signed by uid %s of key %s",
SRCNAME, __func__, anonstr (signer_uid_str),
anonstr (sig_uid_str),
anonstr (secKeyID));
TRETURN 1;
}
}
}
}
}
TRETURN 0;
}
std::string
Mail::getCryptoSummary () const
{
TSTART;
const int level = get_signature_level ();
bool enc = isEncrypted ();
if (level == 4 && enc)
{
TRETURN _("Security Level 4");
}
if (level == 4)
{
TRETURN _("Trust Level 4");
}
if (level == 3 && enc)
{
TRETURN _("Security Level 3");
}
if (level == 3)
{
TRETURN _("Trust Level 3");
}
if (level == 2 && enc)
{
TRETURN _("Security Level 2");
}
if (level == 2)
{
TRETURN _("Trust Level 2");
}
if (enc)
{
TRETURN _("Encrypted");
}
if (isSigned ())
{
/* Even if it is signed, if it is not validly
signed it's still completly insecure as anyone
could have signed this. So we avoid the label
"signed" here as this word already implies some
security. */
TRETURN _("Insecure");
}
TRETURN _("Insecure");
}
std::string
Mail::getCryptoOneLine () const
{
TSTART;
bool sig = isSigned ();
bool enc = isEncrypted ();
if (sig || enc)
{
if (sig && enc)
{
TRETURN _("Signed and encrypted message");
}
else if (sig)
{
TRETURN _("Signed message");
}
else if (enc)
{
TRETURN _("Encrypted message");
}
}
TRETURN _("Insecure message");
}
std::string
Mail::getCryptoDetails_o ()
{
TSTART;
std::string message;
/* No signature with keys but error */
if (!isEncrypted () && !isSigned () && m_verify_result.error())
{
message = _("You cannot be sure who sent, "
"modified and read the message in transit.");
message += "\n\n";
message += _("The message was signed but the verification failed with:");
message += "\n";
message += m_verify_result.error().asString();
TRETURN message;
}
/* No crypo, what are we doing here? */
if (!isEncrypted () && !isSigned ())
{
TRETURN _("You cannot be sure who sent, "
"modified and read the message in transit.");
}
/* Handle encrypt only */
if (isEncrypted () && !isSigned ())
{
if (in_de_vs_mode ())
{
if (m_sig.isDeVs())
{
message += _("The encryption was VS-NfD-compliant.");
}
else
{
message += _("The encryption was not VS-NfD-compliant.");
}
}
message += "\n\n";
message += _("You cannot be sure who sent the message because "
"it is not signed.");
TRETURN message;
}
bool keyFound = true;
const auto sigKey = KeyCache::instance ()->getByFpr (m_sig.fingerprint (),
true);
bool isOpenPGP = sigKey.isNull() ? !isSMIME_m () :
sigKey.protocol() == Protocol::OpenPGP;
char *buf;
bool hasConflict = false;
int level = get_signature_level ();
log_debug ("%s:%s: Formatting sig. Validity: %x Summary: %x Level: %i",
SRCNAME, __func__, m_sig.validity(), m_sig.summary(),
level);
if (level == 4)
{
/* level 4 check for direct trust */
int four_check = level_4_check (m_uid);
if (four_check == 2 && sigKey.hasSecret ())
{
message = _("You signed this message.");
}
else if (four_check == 1)
{
message = _("The senders identity was certified by yourself.");
}
else if (four_check == 2)
{
message = _("The sender is allowed to certify identities for you.");
}
else
{
log_error ("%s:%s:%i BUG: Invalid sigstate.",
SRCNAME, __func__, __LINE__);
TRETURN message;
}
}
else if (level == 3 && isOpenPGP)
{
/* Level three is only reachable through web of trust and no
direct signature. */
message = _("The senders identity was certified by several trusted people.");
}
else if (level == 3 && !isOpenPGP)
{
/* Level three is the only level for trusted S/MIME keys. */
gpgrt_asprintf (&buf, _("The senders identity is certified by the trusted issuer:\n'%s'\n"),
sigKey.issuerName());
memdbg_alloc (buf);
message = buf;
xfree (buf);
}
else if (level == 2 && m_uid.origin () == GpgME::Key::OriginWKD)
{
message = _("The mail provider of the recipient served this key.");
}
else if (level == 2 && m_uid.tofuInfo ().isNull ())
{
/* Marginal trust through pgp only */
message = _("Some trusted people "
"have certified the senders identity.");
}
else if (level == 2)
{
unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(),
m_uid.tofuInfo().encrFirst());
char *time = format_date_from_gpgme (first_contact);
/* i18n note signcount is always pulral because with signcount 1 we
* would not be in this branch. */
gpgrt_asprintf (&buf, _("The senders address is trusted, because "
"you have established a communication history "
"with this address starting on %s.\n"
"You encrypted %i and verified %i messages since."),
time, m_uid.tofuInfo().encrCount(),
m_uid.tofuInfo().signCount ());
memdbg_alloc (buf);
xfree (time);
message = buf;
xfree (buf);
}
else if (level == 1)
{
/* This could be marginal trust through pgp, or tofu with little
history. */
if (m_uid.tofuInfo ().signCount() == 1)
{
message += _("The senders signature was verified for the first time.");
}
else if (m_uid.tofuInfo ().validity() == TofuInfo::Validity::LittleHistory)
{
unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(),
m_uid.tofuInfo().encrFirst());
char *time = format_date_from_gpgme (first_contact);
gpgrt_asprintf (&buf, _("The senders address is not trustworthy yet because "
"you only verified %i messages and encrypted %i messages to "
"it since %s."),
m_uid.tofuInfo().signCount (),
m_uid.tofuInfo().encrCount (), time);
memdbg_alloc (buf);
xfree (time);
message = buf;
xfree (buf);
}
}
else
{
/* Now we are in level 0, this could be a technical problem, no key
or just unkown. */
message = isEncrypted () ? _("But the sender address is not trustworthy because:") :
_("The sender address is not trustworthy because:");
message += "\n";
keyFound = !(m_sig.summary() & Signature::Summary::KeyMissing);
bool general_problem = true;
/* First the general stuff. */
if (m_sig.summary() & Signature::Summary::Red)
{
message += _("The signature is invalid: \n");
if (m_sig.status().code() == GPG_ERR_BAD_SIGNATURE)
{
message += std::string("\n") + _("The signature does not match.");
return message;
}
}
else if (m_sig.summary() & Signature::Summary::SysError ||
m_verify_result.numSignatures() < 1)
{
message += _("There was an error verifying the signature.\n");
const auto err = m_sig.status ();
if (err)
{
message += err.asString () + std::string ("\n");
}
}
else if (m_sig.summary() & Signature::Summary::SigExpired)
{
message += _("The signature is expired.\n");
}
else
{
message += isOpenPGP ? _("The used key") : _("The used certificate");
message += " ";
general_problem = false;
}
/* Now the key problems */
if ((m_sig.summary() & Signature::Summary::KeyMissing))
{
message += _("is not available.");
}
else if ((m_sig.summary() & Signature::Summary::KeyRevoked))
{
message += _("is revoked.");
}
else if ((m_sig.summary() & Signature::Summary::KeyExpired))
{
message += _("is expired.");
}
else if ((m_sig.summary() & Signature::Summary::BadPolicy))
{
message += _("is not meant for signing.");
}
else if ((m_sig.summary() & Signature::Summary::CrlMissing))
{
message += _("could not be checked for revocation.");
}
else if ((m_sig.summary() & Signature::Summary::CrlTooOld))
{
message += _("could not be checked for revocation.");
}
else if ((m_sig.summary() & Signature::Summary::TofuConflict) ||
m_uid.tofuInfo().validity() == TofuInfo::Conflict)
{
message += _("is not the same as the key that was used "
"for this address in the past.");
hasConflict = true;
}
else if (m_uid.isNull())
{
gpgrt_asprintf (&buf, _("does not claim the address: \"%s\"."),
getSender_o ().c_str());
memdbg_alloc (buf);
message += buf;
xfree (buf);
}
else if (((m_sig.validity() & Signature::Validity::Undefined) ||
(m_sig.validity() & Signature::Validity::Unknown) ||
(m_sig.summary() == Signature::Summary::None) ||
(m_sig.validity() == 0))&& !general_problem)
{
/* Bit of a catch all for weird results. */
if (isOpenPGP)
{
message += _("is not certified by any trustworthy key.");
}
else
{
message += _("is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown.");
}
}
else if (m_uid.isRevoked())
{
message += _("The sender marked this address as revoked.");
}
else if ((m_sig.validity() & Signature::Validity::Never))
{
message += _("is marked as not trustworthy.");
}
}
message += "\n\n";
if (in_de_vs_mode ())
{
if (isSigned ())
{
if (m_sig.isDeVs ())
{
message += _("The signature is VS-NfD-compliant.");
}
else
{
message += _("The signature is not VS-NfD-compliant.");
}
message += "\n";
}
if (isEncrypted ())
{
if (m_decrypt_result.isDeVs ())
{
message += _("The encryption is VS-NfD-compliant.");
}
else
{
message += _("The encryption is not VS-NfD-compliant.");
}
message += "\n\n";
}
else
{
message += "\n";
}
}
if (hasConflict)
{
message += _("Click here to change the key used for this address.");
}
else if (keyFound)
{
message += isOpenPGP ? _("Click here for details about the key.") :
_("Click here for details about the certificate.");
}
else
{
message += isOpenPGP ? _("Click here to search the key on the configured keyserver.") :
_("Click here to search the certificate on the configured X509 keyserver.");
}
TRETURN message;
}
int
Mail::get_signature_level () const
{
TSTART;
if (!m_is_signed)
{
TRETURN 0;
}
if (m_uid.isNull ())
{
/* No m_uid matches our sender. */
TRETURN 0;
}
if (m_is_valid && (m_uid.validity () == UserID::Validity::Ultimate ||
(m_uid.validity () == UserID::Validity::Full &&
level_4_check (m_uid))) && (!in_de_vs_mode () || m_sig.isDeVs()))
{
TRETURN 4;
}
if (m_is_valid && m_uid.validity () == UserID::Validity::Full &&
(!in_de_vs_mode () || m_sig.isDeVs()))
{
TRETURN 3;
}
if (m_is_valid)
{
TRETURN 2;
}
if (m_sig.validity() == Signature::Validity::Marginal)
{
TRETURN 1;
}
if (m_sig.summary() & Signature::Summary::TofuConflict ||
m_uid.tofuInfo().validity() == TofuInfo::Conflict)
{
TRETURN 0;
}
TRETURN 0;
}
int
Mail::getCryptoIconID () const
{
TSTART;
int level = get_signature_level ();
int offset = isEncrypted () ? ENCRYPT_ICON_OFFSET : 0;
TRETURN IDI_LEVEL_0 + level + offset;
}
const char*
Mail::getSigFpr () const
{
TSTART;
if (!m_is_signed || m_sig.isNull())
{
TRETURN nullptr;
}
TRETURN m_sig.fingerprint();
}
/** Try to locate the keys for all recipients */
void
Mail::locateKeys_o ()
{
TSTART;
if (m_locate_in_progress)
{
/** XXX
The strangest thing seems to happen here:
In get_recipients the lookup for "AddressEntry" on
an unresolved address might cause network traffic.
So Outlook somehow "detaches" this call and keeps
processing window messages while the call is running.
So our do_delayed_locate might trigger a second locate.
If we access the OOM in this call while we access the
same object in the blocked "detached" call we crash.
(T3931)
After the window message is handled outlook retunrs
in the original lookup.
A better fix here might be a non recursive lock
of the OOM. But I expect that if we lock the handling
of the Windowmessage we might deadlock.
*/
log_debug ("%s:%s: Locate for %p already in progress.",
SRCNAME, __func__, this);
TRETURN;
}
m_locate_in_progress = true;
Addressbook::check_o (this);
if (opt.autoresolve)
{
// First update oom data to have recipients and sender updated.
updateOOMData_o ();
KeyCache::instance()->startLocateSecret (getSender_o ().c_str (), this);
KeyCache::instance()->startLocate (getSender_o ().c_str (), this);
- KeyCache::instance()->startLocate (getCachedRecipients (), this);
+ KeyCache::instance()->startLocate (getCachedRecipientAddresses (), this);
}
autosecureCheck ();
m_locate_in_progress = false;
TRETURN;
}
bool
Mail::isHTMLAlternative () const
{
TSTART;
TRETURN m_is_html_alternative;
}
char *
Mail::takeCachedHTMLBody ()
{
TSTART;
char *ret = m_cached_html_body;
m_cached_html_body = nullptr;
TRETURN ret;
}
char *
Mail::takeCachedPlainBody ()
{
TSTART;
char *ret = m_cached_plain_body;
m_cached_plain_body = nullptr;
TRETURN ret;
}
int
Mail::getCryptoFlags () const
{
TSTART;
TRETURN m_crypto_flags;
}
void
Mail::setNeedsEncrypt (bool value)
{
TSTART;
m_needs_encrypt = value;
TRETURN;
}
bool
Mail::getNeedsEncrypt () const
{
TSTART;
TRETURN m_needs_encrypt;
}
-std::vector
+std::vector
Mail::getCachedRecipients ()
{
TSTART;
TRETURN m_cached_recipients;
}
+std::vector
+Mail::getCachedRecipientAddresses ()
+{
+ TSTART;
+ std::vector ret;
+ for (const auto &recp: m_cached_recipients)
+ {
+ ret.push_back (recp.mbox());
+ }
+ return ret;
+}
+
void
Mail::appendToInlineBody (const std::string &data)
{
TSTART;
m_inline_body += data;
TRETURN;
}
int
Mail::inlineBodyToBody_o ()
{
TSTART;
if (!m_crypter)
{
log_error ("%s:%s: No crypter.",
SRCNAME, __func__);
TRETURN -1;
}
const auto body = m_crypter->get_inline_data ();
if (body.empty())
{
TRETURN 0;
}
/* For inline we always work with UTF-8 */
put_oom_int (m_mailitem, "InternetCodepage", 65001);
int ret = put_oom_string (m_mailitem, "Body",
body.c_str ());
TRETURN ret;
}
void
Mail::updateCryptMAPI_m ()
{
TSTART;
log_debug ("%s:%s: Update crypt mapi",
SRCNAME, __func__);
if (m_crypt_state != NeedsUpdateInMAPI)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
TRETURN;
}
if (!m_crypter)
{
if (!m_mime_data.empty())
{
log_debug ("%s:%s: Have override mime data creating dummy crypter",
SRCNAME, __func__);
m_crypter = std::shared_ptr (new CryptController (this, false,
false,
GpgME::UnknownProtocol));
}
else
{
log_error ("%s:%s: No crypter.",
SRCNAME, __func__);
m_crypt_state = NoCryptMail;
TRETURN;
}
}
if (m_crypter->update_mail_mapi ())
{
log_error ("%s:%s: Failed to update MAPI after crypt",
SRCNAME, __func__);
m_crypt_state = NoCryptMail;
}
else
{
m_crypt_state = WantsSendMIME;
}
/** If sync we need the crypter in update_crypt_oom */
if (!isAsyncCryptDisabled ())
{
// We don't need the crypter anymore.
resetCrypter ();
}
TRETURN;
}
/** Checks in OOM if the body is either
empty or contains the -----BEGIN tag.
pair.first -> true if body starts with -----BEGIN
pair.second -> true if body is empty. */
static std::pair
has_crypt_or_empty_body_oom (Mail *mail)
{
TSTART;
auto body = mail->getBody_o ();
std::pair ret;
ret.first = false;
ret.second = false;
ltrim (body);
if (body.size() > 10 && !strncmp (body.c_str(), "-----BEGIN", 10))
{
ret.first = true;
TRETURN ret;
}
if (!body.size())
{
ret.second = true;
}
else
{
log_data ("%s:%s: Body found in %p : \"%s\"",
SRCNAME, __func__, mail, body.c_str ());
}
TRETURN ret;
}
void
Mail::updateCryptOOM_o ()
{
TSTART;
log_debug ("%s:%s: Update crypt oom for %p",
SRCNAME, __func__, this);
if (m_crypt_state != NeedsUpdateInOOM)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
resetCrypter ();
TRETURN;
}
if (getDoPGPInline ())
{
if (inlineBodyToBody_o ())
{
log_error ("%s:%s: Inline body to body failed %p.",
SRCNAME, __func__, this);
gpgol_bug (get_active_hwnd(), ERR_INLINE_BODY_TO_BODY);
m_crypt_state = NoCryptMail;
TRETURN;
}
}
if (m_crypter->get_protocol () == GpgME::CMS && m_crypter->is_encrypter ())
{
/* We put the PIDNameContentType headers here for exchange
because this is the only way we found to inject the
smime-type. */
if (put_pa_string (m_mailitem,
PR_PIDNameContentType_DASL,
"application/pkcs7-mime;smime-type=\"enveloped-data\";name=smime.p7m"))
{
log_debug ("%s:%s: Failed to put PIDNameContentType for %p.",
SRCNAME, __func__, this);
}
}
/** When doing async update_crypt_mapi follows and needs
the crypter. */
if (isAsyncCryptDisabled ())
{
resetCrypter ();
}
const auto pair = has_crypt_or_empty_body_oom (this);
if (pair.first)
{
log_debug ("%s:%s: Looks like inline body. You can pass %p.",
SRCNAME, __func__, this);
m_crypt_state = WantsSendInline;
TRETURN;
}
/* Draft encryption we do not want to wipe the oom but we have
to modify it to trigger the wirte / second after write. */
if (m_is_draft_encrypt)
{
char *subject = get_oom_string (m_mailitem, "Subject");
put_oom_string (m_mailitem, "Subject", subject ? subject : "");
xfree (subject);
}
// We are in MIME land. Wipe the body.
if (!m_is_draft_encrypt && wipe_o (true))
{
log_debug ("%s:%s: Cancel send for %p.",
SRCNAME, __func__, this);
wchar_t *title = utf8_to_wchar (_("GpgOL: Encryption not possible!"));
wchar_t *msg = utf8_to_wchar (_(
"Outlook returned an error when trying to send the encrypted mail.\n\n"
"Please restart Outlook and try again.\n\n"
"If it still fails consider using an encrypted attachment or\n"
"switching to PGP/Inline in GpgOL's options."));
MessageBoxW (get_active_hwnd(), msg, title,
MB_ICONERROR | MB_OK);
xfree (msg);
xfree (title);
m_crypt_state = NoCryptMail;
TRETURN;
}
m_crypt_state = NeedsSecondAfterWrite;
TRETURN;
}
void
Mail::enableWindow ()
{
TSTART;
if (!m_window)
{
log_error ("%s:%s:enable window which was not disabled",
SRCNAME, __func__);
}
log_debug ("%s:%s: enable window %p",
SRCNAME, __func__, m_window);
EnableWindow (m_window, TRUE);
TRETURN;
}
void
Mail::disableWindow_o ()
{
TSTART;
m_window = get_active_hwnd ();
log_debug ("%s:%s: disable window %p",
SRCNAME, __func__, m_window);
EnableWindow (m_window, FALSE);
TRETURN;
}
bool
Mail::isActiveInlineResponse_o ()
{
const auto subject = getSubject_o ();
bool ret = false;
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
TRETURN false;
}
LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer");
if (!explorer)
{
TRACEPOINT;
TRETURN false;
}
LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse");
gpgol_release (explorer);
if (!inlineResponse)
{
TRETURN false;
}
// We have inline response
// Check if we are it. It's a bit naive but meh. Worst case
// is that we think inline response too often and do sync
// crypt where we could do async crypt.
char * inlineSubject = get_oom_string (inlineResponse, "Subject");
gpgol_release (inlineResponse);
if (inlineResponse && !subject.empty() && !strcmp (subject.c_str (), inlineSubject))
{
log_debug ("%s:%s: Detected inline response for '%p'",
SRCNAME, __func__, this);
ret = true;
}
xfree (inlineSubject);
TRETURN ret;
}
bool
Mail::checkSyncCrypto_o ()
{
TSTART;
/* Async sending is known to cause instabilities. So we keep
a hidden option to disable it. */
if (opt.sync_enc)
{
m_async_crypt_disabled = true;
TRETURN m_async_crypt_disabled;
}
m_async_crypt_disabled = false;
const auto subject = getSubject_o ();
/* Check for an empty subject. Otherwise the question for it
might be hidden behind our overlay. */
if (subject.empty())
{
log_debug ("%s:%s: Detected empty subject. "
"Disabling async crypt due to T4150.",
SRCNAME, __func__);
m_async_crypt_disabled = true;
TRETURN m_async_crypt_disabled;
}
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (attachments)
{
/* This is horrible. But. For some kinds of attachments (we
got reports about Office attachments the write in the
send event triggered by our crypto done code fails with
an exception. There does not appear to be a detectable
pattern when this happens.
As we can't be sure and do not know for which attachments
this really happens we do not use async crypt for any
mails with attachments. :-/
Better be save (not crash) instead of nice (async).
TODO: Figure this out.
The log goes like this. We pass the send event. That triggers
a write, which we pass. And then that fails. So it looks like
moving to Outbox fails. Because we can save as much as we
like before that.
Using the IMessage::SubmitMessage MAPI interface works, but
as it is unstable in our current implementation we do not
want to use it.
mailitem-events.cpp:Invoke: Passing send event for mime-encrypted message 12B7C6E0.
application-events.cpp:Invoke: Unhandled Event: f002
mailitem-events.cpp:Invoke: Write : 0ED4D058
mailitem-events.cpp:Invoke: Passing write event.
oomhelp.cpp:invoke_oom_method_with_parms_type: Method 'Send' invokation failed: 0x80020009
oomhelp.cpp:dump_excepinfo: Exception:
wCode: 0x1000
wReserved: 0x0
source: Microsoft Outlook
desc: The operation failed. The messaging interfaces have returned an unknown error. If the problem persists, restart Outlook.
help: null
helpCtx: 0x0
deferredFill: 00000000
scode: 0x80040119
*/
int count = get_oom_int (attachments, "Count");
gpgol_release (attachments);
if (count)
{
m_async_crypt_disabled = true;
log_debug ("%s:%s: Detected attachments. "
"Disabling async crypt due to T4131.",
SRCNAME, __func__);
TRETURN m_async_crypt_disabled;
}
}
if (isActiveInlineResponse_o ())
{
m_async_crypt_disabled = true;
}
TRETURN m_async_crypt_disabled;
}
// static
Mail *
Mail::getLastMail ()
{
TSTART;
if (!s_last_mail || !isValidPtr (s_last_mail))
{
s_last_mail = nullptr;
}
TRETURN s_last_mail;
}
// static
void
Mail::clearLastMail ()
{
TSTART;
s_last_mail = nullptr;
TRETURN;
}
// static
void
Mail::locateAllCryptoRecipients_o ()
{
TSTART;
gpgol_lock (&mail_map_lock);
std::map::iterator it;
auto mail_map_copy = s_mail_map;
gpgol_unlock (&mail_map_lock);
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
if (it->second->needs_crypto_m ())
{
it->second->locateKeys_o ();
}
}
TRETURN;
}
int
Mail::removeAllAttachments_o ()
{
TSTART;
int ret = 0;
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
TRACEPOINT;
TRETURN 0;
}
int count = get_oom_int (attachments, "Count");
LPDISPATCH to_delete[count];
/* Populate the array so that we don't get in an index mess */
for (int i = 1; i <= count; i++)
{
auto item_str = std::string("Item(") + std::to_string (i) + ")";
to_delete[i-1] = get_oom_object (attachments, item_str.c_str());
}
gpgol_release (attachments);
/* Now delete all attachments */
for (int i = 0; i < count; i++)
{
LPDISPATCH attachment = to_delete[i];
if (!attachment)
{
log_error ("%s:%s: No such attachment %i",
SRCNAME, __func__, i);
ret = -1;
}
/* Delete the attachments that are marked to delete */
if (invoke_oom_method (attachment, "Delete", NULL))
{
log_error ("%s:%s: Deleting attachment %i",
SRCNAME, __func__, i);
ret = -1;
}
gpgol_release (attachment);
}
TRETURN ret;
}
int
Mail::removeOurAttachments_o ()
{
TSTART;
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
TRACEPOINT;
TRETURN 0;
}
int count = get_oom_int (attachments, "Count");
LPDISPATCH to_delete[count];
int del_cnt = 0;
for (int i = 1; i <= count; i++)
{
auto item_str = std::string("Item(") + std::to_string (i) + ")";
LPDISPATCH attachment = get_oom_object (attachments, item_str.c_str());
if (!attachment)
{
TRACEPOINT;
continue;
}
attachtype_t att_type;
if (get_pa_int (attachment, GPGOL_ATTACHTYPE_DASL, (int*) &att_type))
{
/* Not our attachment. */
gpgol_release (attachment);
continue;
}
if (att_type == ATTACHTYPE_PGPBODY || att_type == ATTACHTYPE_MOSS ||
att_type == ATTACHTYPE_MOSSTEMPL)
{
/* One of ours to delete. */
to_delete[del_cnt++] = attachment;
/* Dont' release yet */
continue;
}
gpgol_release (attachment);
}
gpgol_release (attachments);
int ret = 0;
for (int i = 0; i < del_cnt; i++)
{
LPDISPATCH attachment = to_delete[i];
/* Delete the attachments that are marked to delete */
if (invoke_oom_method (attachment, "Delete", NULL))
{
log_error ("%s:%s: Error: deleting attachment %i",
SRCNAME, __func__, i);
ret = -1;
}
gpgol_release (attachment);
}
TRETURN ret;
}
/* We are very verbose because if we fail it might mean
that we have leaked plaintext -> critical. */
bool
Mail::hasCryptedOrEmptyBody_o ()
{
TSTART;
const auto pair = has_crypt_or_empty_body_oom (this);
if (pair.first /* encrypted marker */)
{
log_debug ("%s:%s: Crypt Marker detected in OOM body. Return true %p.",
SRCNAME, __func__, this);
TRETURN true;
}
if (!pair.second)
{
log_debug ("%s:%s: Unexpected content detected. Return false %p.",
SRCNAME, __func__, this);
TRETURN false;
}
// Pair second == true (is empty) can happen on OOM error.
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message && pair.second)
{
if (message)
{
gpgol_release (message);
}
TRETURN true;
}
size_t r_nbytes = 0;
char *mapi_body = mapi_get_body (message, &r_nbytes);
gpgol_release (message);
if (!mapi_body || !r_nbytes)
{
// Body or bytes are null. we are empty.
xfree (mapi_body);
log_debug ("%s:%s: MAPI error or empty message. Return true. %p.",
SRCNAME, __func__, this);
TRETURN true;
}
if (r_nbytes > 10 && !strncmp (mapi_body, "-----BEGIN", 10))
{
// Body is crypt.
log_debug ("%s:%s: MAPI Crypt marker detected. Return true. %p.",
SRCNAME, __func__, this);
xfree (mapi_body);
TRETURN true;
}
xfree (mapi_body);
log_debug ("%s:%s: Found mapi body. Return false. %p.",
SRCNAME, __func__, this);
TRETURN false;
}
std::string
Mail::getVerificationResultDump ()
{
TSTART;
std::stringstream ss;
ss << m_verify_result;
TRETURN ss.str();
}
void
Mail::setBlockStatus_m ()
{
TSTART;
SPropValue prop;
LPMESSAGE message = get_oom_base_message (m_mailitem);
prop.ulPropTag = PR_BLOCK_STATUS;
prop.Value.l = 1;
HRESULT hr = message->SetProps (1, &prop, NULL);
if (hr)
{
log_error ("%s:%s: can't set block value: hr=%#lx\n",
SRCNAME, __func__, hr);
}
gpgol_release (message);
TRETURN;
}
void
Mail::setBlockHTML (bool value)
{
TSTART;
m_block_html = value;
TRETURN;
}
void
Mail::incrementLocateCount ()
{
TSTART;
m_locate_count++;
TRETURN;
}
void
Mail::decrementLocateCount ()
{
TSTART;
m_locate_count--;
if (m_locate_count < 0)
{
log_error ("%s:%s: locate count mismatch.",
SRCNAME, __func__);
m_locate_count = 0;
}
if (!m_locate_count)
{
autosecureCheck ();
}
TRETURN;
}
void
Mail::autosecureCheck ()
{
TSTART;
if (!opt.autosecure || !opt.autoresolve || m_manual_crypto_opts ||
m_locate_count)
{
TRETURN;
}
bool ret = KeyCache::instance()->isMailResolvable (this);
log_debug ("%s:%s: status %i",
SRCNAME, __func__, ret);
/* As we are safe to call at any time, because we need
* to be triggered by the locator threads finishing we
* need to actually set the draft info flags in
* the ui thread. */
do_in_ui_thread_async (ret ? DO_AUTO_SECURE : DONT_AUTO_SECURE,
this);
TRETURN;
}
void
Mail::setDoAutosecure_m (bool value)
{
TSTART;
TRACEPOINT;
LPMESSAGE msg = get_oom_base_message (m_mailitem);
if (!msg)
{
TRACEPOINT;
TRETURN;
}
/* We need to set a uuid so that autosecure can
be disabled manually */
setUUID_o ();
int old_flags = get_gpgol_draft_info_flags (msg);
if (old_flags && m_first_autosecure_check &&
/* Someone with always sign and autosecure active
* will want to get autoencryption. */
!(old_flags == 2 && opt.sign_default))
{
/* They were set explicily before us. This can be
* because they were a draft (which is bad) or
* because they are a reply/forward to a crypto mail
* or because there are conflicting settings. */
log_debug ("%s:%s: Mail %p had already flags set.",
SRCNAME, __func__, m_mailitem);
m_first_autosecure_check = false;
m_manual_crypto_opts = true;
gpgol_release (msg);
TRETURN;
}
m_first_autosecure_check = false;
set_gpgol_draft_info_flags (msg, value ? 3 : opt.sign_default ? 2 : 0);
gpgol_release (msg);
gpgoladdin_invalidate_ui();
TRETURN;
}
bool
Mail::decryptedSuccessfully () const
{
return m_decrypt_result.isNull() || !m_decrypt_result.error();
}
void
Mail::installFolderEventHandler_o()
{
TSTART;
TRACEPOINT;
LPDISPATCH folder = get_oom_object (m_mailitem, "Parent");
if (!folder)
{
TRACEPOINT;
TRETURN;
}
char *objName = get_object_name (folder);
if (!objName || strcmp (objName, "MAPIFolder"))
{
log_debug ("%s:%s: Mail %p parent is not a mapi folder.",
SRCNAME, __func__, m_mailitem);
xfree (objName);
gpgol_release (folder);
TRETURN;
}
xfree (objName);
char *path = get_oom_string (folder, "FullFolderPath");
if (!path)
{
TRACEPOINT;
path = get_oom_string (folder, "FolderPath");
}
if (!path)
{
log_error ("%s:%s: Mail %p parent has no folder path.",
SRCNAME, __func__, m_mailitem);
gpgol_release (folder);
TRETURN;
}
std::string strPath (path);
xfree (path);
if (s_folder_events_map.find (strPath) == s_folder_events_map.end())
{
log_debug ("%s:%s: Install folder events watcher for %s.",
SRCNAME, __func__, anonstr (strPath.c_str()));
const auto sink = install_FolderEvents_sink (folder);
s_folder_events_map.insert (std::make_pair (strPath, sink));
}
/* Folder already registered */
gpgol_release (folder);
TRETURN;
}
void
Mail::refCurrentItem()
{
TSTART;
if (m_currentItemRef)
{
log_debug ("%s:%s: Current item multi ref. Bug?",
SRCNAME, __func__);
TRETURN;
}
/* This prevents a crash in Outlook 2013 when sending a mail as it
* would unload too early.
*
* As it didn't crash when the mail was opened in Outlook Spy this
* mimics that the mail is inspected somewhere else. */
m_currentItemRef = get_oom_object (m_mailitem, "GetInspector.CurrentItem");
TRETURN;
}
void
Mail::releaseCurrentItem()
{
TSTART;
if (!m_currentItemRef)
{
TRETURN;
}
log_oom ("%s:%s: releasing CurrentItem ref %p",
SRCNAME, __func__, m_currentItemRef);
LPDISPATCH tmp = m_currentItemRef;
m_currentItemRef = nullptr;
/* This can cause our destruction */
gpgol_release (tmp);
TRETURN;
}
void
Mail::decryptPermanently_o()
{
TSTART;
if (!m_needs_wipe)
{
log_debug ("%s:%s: Mail does not yet need wipe. Called to early?",
SRCNAME, __func__);
TRETURN;
}
if (!m_decrypt_result.isNull() && m_decrypt_result.error())
{
log_debug ("%s:%s: Decrypt result had error. Can't decrypt permanently.",
SRCNAME, __func__);
TRETURN;
}
/* Remove the existing categories */
removeCategories_o ();
/* Drop our state variables */
m_decrypt_result = GpgME::DecryptionResult();
m_verify_result = GpgME::VerificationResult();
m_needs_wipe = false;
m_processed = false;
m_is_smime = false;
m_type = MSGTYPE_UNKNOWN;
/* Remove our own attachments */
removeOurAttachments_o ();
updateSigstate();
auto msg = MAKE_SHARED (get_oom_base_message (m_mailitem));
if (!msg)
{
STRANGEPOINT;
TRETURN;
}
mapi_delete_gpgol_tags ((LPMESSAGE)msg.get());
mapi_set_mesage_class ((LPMESSAGE)msg.get(), "IPM.Note");
if (invoke_oom_method (m_mailitem, "Save", NULL))
{
log_error ("Failed to save decrypted mail: %p ", m_mailitem);
}
log_debug ("%s:%s: Delayed invalidate to update sigstate after perm dec.",
SRCNAME, __func__);
CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0,
NULL));
TRETURN;
}
void
Mail::prepareCrypto_o ()
{
TSTART;
// Check inline response state to fill out asynccryptdisabled.
checkSyncCrypto_o ();
if (!isAsyncCryptDisabled())
{
/* Obtain a reference of the current item. This prevents
* an early unload which would crash Outlook 2013
*
* As it didn't crash when the mail was opened in Outlook Spy this
* mimics that the mail is inspected somewhere else. */
refCurrentItem ();
}
// First contact with a mail to encrypt update
// state and oom data.
updateOOMData_o (true);
setCryptState (Mail::NeedsFirstAfterWrite);
TRETURN;
}
/* Printing happens in two steps. First a Mail is loaded after the
BeforePrint event, then it is loaded a second time when the actual print
happens. We have to catch both.
This functions looks over all mails and checks if one is currently
printing. If so we compare our EntryID's and if they match. Bingo,
we are printing, too.*/
bool
Mail::checkIfMailIsChildOfPrintMail_o ()
{
gpgol_lock (&mail_map_lock);
for (auto it = s_mail_map.begin(); it != s_mail_map.end(); ++it)
{
auto mail = it->second;
if (mail->isPrint ())
{
/* This happens so rarely that we only fetch our
entry id if we are in here. */
char *entryID = get_oom_string (mail->item (), "EntryID");
if (!entryID)
{
log_error ("%s:%s: Printing mail %p has no EntryID",
SRCNAME, __func__, mail);
continue;
}
char *ourID = get_oom_string (m_mailitem, "EntryID");
if (!ourID)
{
log_error ("%s:%s: Mail %p has no EntryID",
SRCNAME, __func__, this);
xfree (entryID);
continue;
}
int cmp = strcmp (ourID, entryID);
xfree (ourID);
xfree (entryID);
if (cmp)
{
log_debug ("%s:%s: The current print is not us.",
SRCNAME, __func__);
continue;
}
gpgrt_lock_unlock (&mail_map_lock);
log_debug ("%s:%s: Mail %p is the actual print of %p.",
SRCNAME, __func__, this, mail);
return true;
}
}
gpgrt_lock_unlock (&mail_map_lock);
return false;
}
+
+void
+Mail::setSigningKey (const GpgME::Key &key)
+{
+ m_resolved_signing_key = key;
+}
diff --git a/src/mail.h b/src/mail.h
index bf3cc81..228b5cf 100644
--- a/src/mail.h
+++ b/src/mail.h
@@ -1,716 +1,731 @@
/* @file mail.h
* @brief High level class to work with Outlook Mailitems.
*
* Copyright (C) 2015, 2016 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 MAIL_H
#define MAIL_H
#include "oomhelp.h"
#include "mapihelp.h"
#include "gpgme++/verificationresult.h"
#include "gpgme++/decryptionresult.h"
#include "gpgme++/key.h"
#include
-#include
class ParseController;
class CryptController;
class Attachment;
+class Recipient;
/** @brief Data wrapper around a mailitem.
*
* This class is intended to bundle all that we know about
* a Mail. Due to the restrictions in Outlook we sometimes may
* need additional information that is not available at the time
* like the sender address of an exchange account in the afterWrite
* event.
*
* This class bundles such information and also provides a way to
* access the event handler of a mail.
*
* Naming conventions of the suffixes:
* _o functions that work on OOM and possibly also MAPI.
* _m functions that work on MAPI.
* _s functions that only work on internal data and are safe to call
* from any thread.
*
* O and M functions _must_ only be called from the main thread. Use
* a WindowMessage to signal the Main thread. But be wary. A WindowMessage
* might be handled while an OOM call in the main thread waits for completion.
*
* An example for this is how update_oom_data can work:
*
* Main Thread:
* call update_oom_data
* └> internally invokes an OOM function that might do network access e.g.
* to connect to the exchange server to fetch the address.
*
* Counterintutively the Main thread does not return from that function or
* blocks for it's completion but handles windowmessages.
*
* After a windowmessage was handled and if the OOM invocation is
* completed the invocation returns and normal execution continues.
*
* So if the window message handler's includes for example
* also a call to lookup recipients we crash. Note that it's usually
* safe to do OOM / MAPI calls from a window message.
*
*
* While this seems impossible, remember that we do not work directly
* with functions but everything is handled through COM. Without this
* logic Outlook would probably become unusable because as any long running
* call to the OOM would block it completely and freeze the UI.
* (no windowmessages handled).
*
* So be wary when accessing the OOM from a Window Message.
*/
class Mail
{
public:
enum CryptState
{
NoCryptMail,
NeedsFirstAfterWrite,
NeedsActualCrypt,
NeedsUpdateInOOM,
NeedsSecondAfterWrite,
NeedsUpdateInMAPI,
WantsSendInline,
WantsSendMIME,
};
/** @brief Construct a mail object for the item.
*
* This also installs the event sink for this item.
*
* The mail object takes ownership of the mailitem
* reference. Do not Release it! */
Mail (LPDISPATCH mailitem);
~Mail ();
/** @brief looks for existing Mail objects for the OOM mailitem.
@returns A reference to an existing mailitem or NULL in case none
could be found.
*/
static Mail* getMailForItem (LPDISPATCH mailitem);
/** @brief looks for existing Mail objects in the uuid map.
Only objects for which set_uid has been called can be found
in the uid map. Get the Unique ID of a mailitem thorugh get_unique_id
@returns A reference to an existing mailitem or NULL in case none
could be found.
*/
static Mail* getMailForUUID (const char *uuid);
/** @brief Get the last created mail.
@returns A reference to the last created mail or null.
*/
static Mail* getLastMail ();
/** @brief voids the last mail variable. */
static void clearLastMail ();
/** @brief Lock mail deletion.
Mails are heavily accessed multi threaded. E.g. when locating
keys. Due to bad timing it would be possible that between
a check for "is_valid_ptr" to see if a map is still valid
and the usage of the mail a delete would happen.
This lock can be used to prevent that. Changes made to the
mail will of course have no effect as the mail is already in
the process of beeing unloaded. And calls that access MAPI
or OOM still might crash. But this at least gurantees that
the member variables of the mail exist while the lock is taken.
Use it in your thread like:
Mail::lockDelete ();
Mail::isValidPtr (mail);
mail->set_or_check_something ();
Mail::unlockDelete ();
Still be carefull when it is a mapi or oom function.
*/
static void lockDelete ();
static void unlockDelete ();
/** @brief looks for existing Mail objects.
@returns A reference to an existing mailitem or NULL in case none
could be found. Can be used to check if a mail object was destroyed.
*/
static bool isValidPtr (const Mail *mail);
/** @brief wipe the plaintext from all known Mail objects.
*
* This is intended as a "cleanup" call to be done on unload
* to avoid leaking plaintext in case we are deactivated while
* some mails still have their plaintext inserted.
*
* @returns the number of errors that occured.
*/
static int wipeAllMails_o ();
/** @brief revert all known Mail objects.
*
* Similar to wipe but works on MAPI to revert our attachment
* dance and restore an original MIME mail.
*
* @returns the number of errors that occured.
*/
static int revertAllMails_o ();
/** @brief close all known Mail objects.
*
* Close our mail with discard changes set to true.
* This discards the plaintext / attachments. Afterwards
* it calls save if neccessary to sync back the collected
* property changes.
*
* This is the nicest of our three "Clean plaintext"
* functions. Will fallback to revert if closing fails.
* Closed mails are deleted.
*
* @returns the number of errors that occured.
*/
static int closeAllMails_o ();
/** @brief closes the inspector for a mail
*
* @returns true on success.
*/
static int closeInspector_o (Mail *mail);
/** Call close with discard changes to discard
plaintext. returns the value of the oom close
call. This may have delete the mail if the close
triggers an unload.
Set restoreSMIMEClass to resotre the S/MIME
Message class IPM.Note.SMIME etc. for S/MIME
mails.
*/
int close (bool restoreSMIMEClass = true);
/** @brief locate recipients for all crypto mails
*
* To avoid lookups of recipients for non crypto mails we only
* locate keys when a crypto action is already selected.
*
* As the user can do this after recipients were added but
* we don't know for which mail the crypt button was triggered.
* we march over all mails and if they are crypto mails we check
* that the recipents were located.
*/
static void locateAllCryptoRecipients_o ();
/** @brief Reference to the mailitem. Do not Release! */
LPDISPATCH item () { return m_mailitem; }
/** @brief Pre process the message. Ususally to be called from BeforeRead.
*
* This function assumes that the base message interface can be accessed
* and calles the MAPI Message handling which changes the message class
* to enable our own handling.
*
* @returns 0 on success.
*/
int preProcessMessage_m ();
/** @brief Decrypt / Verify the mail.
*
* Sets the needs_wipe and was_encrypted variable.
*
* @returns 0 on success. */
int decryptVerify_o ();
/** @brief start crypto operations as selected by the user.
*
* Initiates the crypto operations according to the gpgol
* draft info flags.
*
* @returns 0 on success. */
int encryptSignStart_o ();
/** @brief Necessary crypto operations were completed successfully. */
bool wasCryptoSuccessful_m () { return m_crypt_successful || !needs_crypto_m (); }
/** @brief Message should be encrypted and or signed.
0: No
1: Encrypt
2: Sign
3: Encrypt & Sign
*/
int needs_crypto_m () const;
/** @brief wipe the plaintext from the message and encrypt attachments.
*
* @returns 0 on success; */
int wipe_o (bool force = false);
/** @brief revert the message to the original mail before our changes.
*
* @returns 0 on success; */
int revert_o ();
/** @brief update some data collected from the oom
*
* This updates cached values from the OOM that are not available
* in MAPI events like after Write.
*
* For Exchange 2013 at least we don't have any other way to get the
* senders SMTP address then through the object model. So we have to
* store the sender address for later events that do not allow us to
* access the OOM but enable us to work with the underlying MAPI structure.
*
* for_encryption can be used to override that data should be collected
* for encryption. Otherwise it depends on "isCryptoMail" to decide which
* data should be collected.
*
* It also updated the is_html_alternative value.
*
* @returns 0 on success */
int updateOOMData_o (bool for_encryption = false);
/** @brief get sender SMTP address (UTF-8 encoded).
*
* If the sender address has not been set through update_sender this
* calls update_sender before returning the sender.
*
* @returns A reference to the utf8 sender address. Or an empty string. */
std::string getSender_o ();
/** @brief get sender SMTP address (UTF-8 encoded).
*
* Like get_sender but ensures not to touch oom or mapi
*
* @returns A reference to the utf8 sender address. Or an empty string. */
std::string getSender () const;
/** @brief get the subject string (UTF-8 encoded).
*
* @returns the subject or an empty string. */
std::string getSubject_o () const;
/** @brief Is this a crypto mail handled by gpgol.
*
* Calling this is only valid after a message has been processed.
*
* @returns true if the mail was either signed or encrypted and we processed
* it.
*/
bool isCryptoMail () const;
/** @brief This mail needs to be actually written.
*
* @returns true if the next write event should not be canceled.
*/
bool needsSave () { return m_needs_save; }
/** @brief set the needs save state.
*/
void setNeedsSave (bool val) { m_needs_save = val; }
/** @brief is this mail an S/MIME mail.
*
* @returns true for smime messages.
*/
bool isSMIME_m ();
/** @brief get the associated parser.
only valid while the actual parsing happens. */
std::shared_ptr parser () { return m_parser; }
/** @brief get the associated cryptcontroller.
only valid while the crypting happens. */
std::shared_ptr cryper () { return m_crypter; }
/** To be called from outside once the paser was done.
In Qt this would be a slot that is called once it is finished
we hack around that a bit by calling it from our windowmessages
handler.
*/
void parsing_done ();
/** Returns true if the mail was verified and has at least one
signature. Regardless of the validity of the mail */
bool isSigned () const;
/** Returns true if the mail is encrypted to at least one
recipient. Regardless if it could be decrypted. */
bool isEncrypted () const;
/** Are we "green" */
bool isValidSig () const;
/** Get UID gets UniqueID property of this mail. Returns
an empty string if the uid was not set with set uid.*/
const std::string & getUUID () const { return m_uuid; }
/** Returns 0 on success if the mail has a uid alrady or sets
the uid. Setting only succeeds if the OOM is currently
accessible. Returns -1 on error. */
int setUUID_o ();
/** Returns a localized string describing in one or two
words the crypto status of this mail. */
std::string getCryptoSummary () const;
/** Returns a localized string describing the detailed
crypto state of this mail. */
std::string getCryptoDetails_o ();
/** Returns a localized string describing a one line
summary of the crypto state. */
std::string getCryptoOneLine () const;
/** Get the icon id of the appropiate icon for this mail */
int getCryptoIconID () const;
/** Get the fingerprint of an associated signature or null
if it is not signed. */
const char *getSigFpr () const;
/** Remove all categories of this mail */
void removeCategories_o ();
/** Get the body of the mail */
std::string getBody_o () const;
- /** Get the recipients. */
- std::vector getRecipients_o () const;
-
/** Try to locate the keys for all recipients.
This also triggers the Addressbook integration, which we
treat as locate jobs. */
void locateKeys_o ();
/** State variable to check if a close was triggerd by us. */
void setCloseTriggered (bool value);
bool getCloseTriggered () const;
/** Check if the mail should be sent as html alternative mail.
Only valid if update_oom_data was called before. */
bool isHTMLAlternative () const;
/** Get the html body. It is updated in update_oom_data.
Caller takes ownership of the string and has to free it.
*/
char *takeCachedHTMLBody ();
/** Get the plain body. It is updated in update_oom_data.
Caller takes ownership of the string and has to free it.
*/
char *takeCachedPlainBody ();
/** Get the cached recipients. It is updated in update_oom_data.*/
- std::vector getCachedRecipients ();
+ std::vector getCachedRecipients ();
+ /** Only get the Mail addresses from the recipient objects. */
+ std::vector getCachedRecipientAddresses ();
+
+ /** Set an override for recipients. If they have keys the
+ keys will not be resolved again. */
+ void setRecipients (const std::vector &recps);
+
+ /** Get the recipients. */
+ std::vector getRecipients_o () const;
/** Returns 1 if the mail was encrypted, 2 if signed, 3 if both.
Only valid after decrypt_verify.
*/
int getCryptoFlags () const;
/** Returns true if the mail should be encrypted in the
after write event. */
bool getNeedsEncrypt () const;
void setNeedsEncrypt (bool val);
/** Gets the level of the signature. See:
https://wiki.gnupg.org/EasyGpg2016/AutomatedEncryption for
a definition of the levels. */
int get_signature_level () const;
/** Check if all attachments are hidden and show a warning
message appropiate to the crypto state if necessary.
If silent is true it will not show a warning but silently
remove the bad attachments.
*/
int checkAttachments_o (bool silent);
/** Check if the mail should be encrypted "inline" */
bool getDoPGPInline () const {return m_do_inline;}
/** Check if the mail should be encrypted "inline" */
void setDoPGPInline (bool value) {m_do_inline = value;}
/** Append data to a cached inline body. Helper to do this
on MAPI level and later add it through OOM */
void appendToInlineBody (const std::string &data);
/** Set the inline body as OOM body property. */
int inlineBodyToBody_o ();
/** Get the crypt state */
CryptState cryptState () const {return m_crypt_state;}
/** Set the crypt state */
void setCryptState (CryptState state) {m_crypt_state = state;}
/** Update MAPI data after encryption. */
void updateCryptMAPI_m ();
/** Update OOM data after encryption.
Checks for plain text leaks and
does not advance crypt state if body can't be cleaned.
*/
void updateCryptOOM_o ();
/** Enable / Disable the window of this mail.
When the window gets disabled the
handle is stored for a later enable. */
void disableWindow_o ();
void enableWindow ();
/** Determine if the mail is an inline response.
Call check_inline_response first to update the state
from the OOM.
We need synchronous encryption for inline responses. */
bool isAsyncCryptDisabled () { return m_async_crypt_disabled; }
/** Check through OOM if the current mail is an inline
response. Meaning editable in the message list.
*/
bool isActiveInlineResponse_o ();
/* Check if we can't do async crypto. E.g. for inline responses.
Caches the state which can then be queried through
isAsyncCryptDisabled;
*/
bool checkSyncCrypto_o ();
/** Get the window for the mail. Caution! This is only
really valid in the time that the window is disabled.
Use with care and can be null or invalid.
*/
HWND getWindow () { return m_window; }
/** Cleanup any attached crypter object. Useful
on error. */
void resetCrypter () { m_crypter = nullptr; }
/** Set special crypto mime data that should be used as the
mime structure when sending. */
void setOverrideMIMEData (const std::string &data) {m_mime_data = data;}
/** Get the mime data that should be used when sending. */
std::string get_override_mime_data () const { return m_mime_data; }
bool hasOverrideMimeData() const { return !m_mime_data.empty(); }
/** Set if this is a forward of a crypto mail. */
void setIsForwardedCryptoMail (bool value) { m_is_forwarded_crypto_mail = value; }
bool is_forwarded_crypto_mail () { return m_is_forwarded_crypto_mail; }
/** Set if this is a reply of a crypto mail. */
void setIsReplyCryptoMail (bool value) { m_is_reply_crypto_mail = value; }
bool is_reply_crypto_mail () { return m_is_reply_crypto_mail; }
/** Remove the hidden GpgOL attachments. This is needed when forwarding
without encryption so that our attachments are not included in the forward.
Returns 0 on success. Works in OOM. */
int removeOurAttachments_o ();
/** Remove all attachments. Including our own. This is needed for
forwarding of unsigned S/MIME mails (Efail).
Returns 0 on success. Works in OOM. */
int removeAllAttachments_o ();
/** Check both OOM and MAPI if the body is either empty or
encrypted. Won't abort on OOM or MAPI errors, so it can be
used in both states. But will return false if a body
was detected or in the OOM the MAPI Base Message. This
is intended as a saveguard before sending a mail.
This function should not be used to detected the necessity
of encryption and is only an extra check to catch unexpected
errors.
*/
bool hasCryptedOrEmptyBody_o ();
void updateBody_o ();
/** Update information from protected headers in OOM */
void updateHeaders_o ();
/** Set if this mail looks like the send again of a crypto mail.
This will mean that after it is decrypted it is treated
like an unencrypted mail so that it can be encrypted again
or sent unencrypted.
*/
void setIsSendAgain (bool value) { m_is_send_again = value; }
/* Attachment removal state variables. */
bool attachmentRemoveWarningDisabled () { return m_disable_att_remove_warning; }
/* Gets the string dump of the verification result. */
std::string getVerificationResultDump ();
/* Block loading HTML content */
void setBlockHTML (bool value);
bool isBlockHTML () const { return m_block_html; }
/* Remove automatic loading of HTML references setting. */
void setBlockStatus_m ();
/* Crypto options (sign/encrypt) have been set manually. */
void setCryptoSelectedManually (bool v) { m_manual_crypto_opts = v; }
// bool is_crypto_selected_manually () const { return m_manual_crypto_opts; }
/* Reference that a resolver thread is running for this mail. */
void incrementLocateCount ();
/* To be called when a resolver thread is done. If there are no running
resolver threads we can check the recipients to see if we should
toggle / untoggle the secure state.
*/
void decrementLocateCount ();
/* Check if the keys can be resolved automatically and trigger
* setting the crypto flags accordingly.
*/
void autosecureCheck ();
/* Set if a mail should be secured (encrypted and signed)
*
* Only save to call from a place that may access mapi.
*/
void setDoAutosecure_m (bool value);
/* Install an event handler for the folder of this mail. */
void installFolderEventHandler_o ();
/* Marker for a "Move" of this mail */
bool passWrite () { return m_pass_write; }
void setPassWrite(bool value) { m_pass_write = value; }
/* Releases the current item ref obtained in update oom data */
void releaseCurrentItem ();
/* Gets an additional reference for GetInspector.CurrentItem */
void refCurrentItem ();
/* Get the storeID for this mail */
std::string storeID() { return m_store_id; }
/* Remove encryption permanently. */
void decryptPermanently_o ();
/* Prepare for encrypt / sign. Updates data. */
void prepareCrypto_o ();
/* State variable to check if we are about to encrypt a draft. */
void setIsDraftEncrypt (bool value) { m_is_draft_encrypt = value; }
bool isDraftEncrypt () { return m_is_draft_encrypt; }
/* Was this mail decrypted without error. Also returns true
if the mail was not encrypted. */
bool decryptedSuccessfully () const;
/* The mail should be decrypted again after the next
* encryption. So that we can save it for example and
* still work on it. */
void setDecryptAgain (bool value) { m_decrypt_again = value; }
bool isDecryptAgain () const { return m_decrypt_again; }
/* The type of the message. */
msgtype_t msgtype () const { return m_type; }
/* The mail was loaded after we have seen a BeforePrint event. */
void setIsPrint (bool value) { m_printing = value; }
bool isPrint () const { return m_printing; }
/* Update the catgories (encrypted / trust in) based on the
crypt results. */
void updateCategories_o ();
+ /* Set the signing key for the mail. This overrides any other
+ key resolution. */
+ void setSigningKey (const GpgME::Key &key);
+
+ /* Set the encryption keys for this mail. Needs to match the protocol
+ of the signing key. This overrides any other key resolution. */
+ void setEncryptionKeys (std::vector keys);
+
private:
bool checkIfMailIsChildOfPrintMail_o ();
void updateSigstate ();
int add_attachments_o (std::vector > attachments);
LPDISPATCH m_mailitem;
LPDISPATCH m_event_sink;
LPDISPATCH m_currentItemRef;
bool m_processed, /* The message has been porcessed by us. */
m_needs_wipe, /* We have added plaintext to the mesage. */
m_needs_save, /* A property was changed but not by us. */
m_crypt_successful, /* We successfuly performed crypto on the item. */
m_is_smime, /* This is an smime mail. */
m_is_smime_checked, /* it was checked if this is an smime mail */
m_is_signed, /* Mail is signed */
m_is_valid, /* Mail is valid signed. */
m_close_triggered, /* We have programtically triggered a close */
m_is_html_alternative, /* Body Format is not plain text */
m_needs_encrypt; /* Send was triggered we want to encrypt. */
int m_moss_position; /* The number of the original message attachment. */
int m_crypto_flags;
std::string m_sender;
std::string m_sent_on_behalf;
char *m_cached_html_body; /* Cached html body. */
char *m_cached_plain_body; /* Cached plain body. */
- std::vector m_cached_recipients;
+ std::vector m_cached_recipients;
msgtype_t m_type; /* Our messagetype as set in mapi */
std::shared_ptr m_parser;
std::shared_ptr m_crypter;
GpgME::VerificationResult m_verify_result;
GpgME::DecryptionResult m_decrypt_result;
GpgME::Signature m_sig;
GpgME::UserID m_uid;
std::string m_uuid;
std::string m_orig_body;
bool m_do_inline;
bool m_is_gsuite; /* Are we on a gsuite account */
std::string m_inline_body;
CryptState m_crypt_state;
HWND m_window;
bool m_async_crypt_disabled;
std::string m_mime_data;
bool m_is_forwarded_crypto_mail; /* Is this a forward of a crypto mail */
bool m_is_reply_crypto_mail; /* Is this a reply to a crypto mail */
bool m_is_send_again; /* Is this a send again of a crypto mail */
bool m_disable_att_remove_warning; /* Should not warn about attachment removal. */
bool m_block_html; /* Force blocking of html content. e.g for unsigned S/MIME mails. */
bool m_manual_crypto_opts; /* Crypto options (sign/encrypt) have been set manually. */
bool m_first_autosecure_check; /* This is the first autoresolve check */
int m_locate_count; /* The number of key locates pending for this mail. */
bool m_pass_write; /* Danger the next write will be passed. This is for closed mails */
bool m_locate_in_progress; /* Simplified state variable for locate */
std::string m_store_id; /* Store id for categories */
std::string m_verify_category; /* The category string for the verify result */
bool m_is_junk; /* Mail is in the junk folder */
bool m_is_draft_encrypt; /* Mail is a draft that should be encrypted */
bool m_decrypt_again; /* Mail should be decrypted again if it sees
another beforeread */
bool m_printing; /* Mail is decrypted for printing */
std::string m_gpgol_class; /* The GpgOL Message class */
+ GpgME::Key m_resolved_signing_key; /* A prepared / resolved key for signing. */
};
#endif // MAIL_H
diff --git a/src/oomhelp.cpp b/src/oomhelp.cpp
index 8f5b7ea..78ec830 100644
--- a/src/oomhelp.cpp
+++ b/src/oomhelp.cpp
@@ -1,3165 +1,3170 @@
/* 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;
}
/* 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;
}
/* 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 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;
}
/* 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;
}
/* 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_INT;
var.intVal = 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)
+ 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 type = get_oom_int (addrEntry, "AddressEntryUserType");
+ int user_type = get_oom_int (addrEntry, "AddressEntryUserType");
- if (type != DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE)
+ if (user_type != DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE)
{
log_data ("%s:%s: type of %s is %i",
- SRCNAME, __func__, anonstr (name.c_str()), type);
+ 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))
+ if (try_resolve_group (entry.get(), ret, recipient_type))
{
foundOne = true;
continue;
}
}
- std::pair element;
+ 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 = resolved;
+ element.first = Recipient (resolved, 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 = ex_resolved;
+ element.first = Recipient (ex_resolved, 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 >
+std::vector >
get_oom_recipients_with_addrEntry (LPDISPATCH recipients, bool *r_err)
{
TSTART;
int recipientsCnt = get_oom_int (recipients, "Count");
- std::vector > ret;
+ 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");
+
auto addrEntry = MAKE_SHARED (get_oom_object (recipient, "AddressEntry"));
- if (addrEntry && try_resolve_group (addrEntry.get (), ret))
+ 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;
+ std::pair entry;
entry.second = addrEntry;
char *resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL);
if (resolved)
{
- entry.first = resolved;
+ entry.first = Recipient (resolved, recipient_type);
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 = resolved;
+ entry.first = Recipient (resolved, recipient_type);
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 = address;
+ entry.first = Recipient (resolved, recipient_type);
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
+std::vector
get_oom_recipients (LPDISPATCH recipients, bool *r_err)
{
TSTART;
- std::vector ret;
+ 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;
}
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) ||
(var.vt == VT_BOOL && var.boolVal == VARIANT_FALSE))
{
ret++;
}
else
{
gpgol_release (oom_attach);
}
VariantClear (&var);
}
return ret;
}
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]);
return ret;
}
diff --git a/src/oomhelp.h b/src/oomhelp.h
index 3198dd0..691cfbf 100644
--- a/src/oomhelp.h
+++ b/src/oomhelp.h
@@ -1,451 +1,455 @@
/* 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 DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE 11
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);
/* Helper to lookup a dispid. */
DISPID lookup_oom_dispid (LPDISPATCH pDisp, const char *name);
/* Return the OOM object's IDispatch interface described by FULLNAME. */
LPDISPATCH get_oom_object (LPDISPATCH pStart, const char *fullname);
/* Set the Icon of a CommandBarControl. */
int put_oom_icon (LPDISPATCH pDisp, int rsource_id, int size);
/* Set the boolean property NAME to VALUE. */
int put_oom_bool (LPDISPATCH pDisp, const char *name, int value);
/* Set the property NAME to VALUE. */
int put_oom_int (LPDISPATCH pDisp, const char *name, int value);
/* Set the property NAME to STRING. */
int put_oom_string (LPDISPATCH pDisp, const char *name, const char *string);
/* Set the property NAME to DISP. */
int put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH value);
/* Get the boolean property NAME of the object PDISP. */
int get_oom_bool (LPDISPATCH pDisp, const char *name);
/* Get the integer property NAME of the object PDISP. */
int get_oom_int (LPDISPATCH pDisp, const char *name);
/* Get the string property NAME of the object PDISP. */
char *get_oom_string (LPDISPATCH pDisp, const char *name);
/* Get an IUnknown object from property NAME of PDISP. */
LPUNKNOWN get_oom_iunknown (LPDISPATCH pDisp, const char *name);
/* Return the control object with tag property value TAG. */
LPDISPATCH get_oom_control_bytag (LPDISPATCH pObj, const char *tag);
/* Add a new button to an object which supports the add method.
Returns the new object or NULL on error. */
LPDISPATCH add_oom_button (LPDISPATCH pObj);
/* Delete a button. */
void del_oom_button (LPDISPATCH button);
/* Get the HWND of the active window in the current context */
HWND get_oom_context_window (LPDISPATCH context);
/* Get the address of the recipients as string list.
+ 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);
+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 >
+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);
/* 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);
/* 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);
#endif /*OOMHELP_H*/
diff --git a/src/recipient.cpp b/src/recipient.cpp
new file mode 100644
index 0000000..798a99b
--- /dev/null
+++ b/src/recipient.cpp
@@ -0,0 +1,81 @@
+/* @file recipient.cpp
+ * @brief Information about a recipient.
+ *
+ * Copyright (C) 2020, g10 code 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 "recipient.h"
+#include "debug.h"
+
+#include
+
+Recipient::Recipient(const char *addr, int type)
+{
+ TSTART;
+ if (addr)
+ {
+ m_mbox = GpgME::UserID::addrSpecFromString (addr);
+ }
+ setType (type);
+ if (!m_mbox.size ())
+ {
+ log_error ("%s:%s: Recipient constructed without valid addr",
+ SRCNAME, __func__);
+ m_type = invalidType;
+ }
+ TRETURN;
+}
+
+Recipient::Recipient() : m_type (invalidType)
+{
+}
+
+void
+Recipient::setType (int type)
+{
+ if (type > olBCC || type < olOriginator)
+ {
+ log_error ("%s:%s: Invalid recipient type %i",
+ SRCNAME, __func__, type);
+ m_type = invalidType;
+ }
+ m_type = static_cast (type);
+}
+
+void
+Recipient::setKeys (const std::vector &keys)
+{
+ m_keys = keys;
+}
+
+std::string
+Recipient::mbox () const
+{
+ return m_mbox;
+}
+
+Recipient::recipientType
+Recipient::type () const
+{
+ return m_type;
+}
+
+std::vector
+Recipient::keys () const
+{
+ return m_keys;
+}
diff --git a/src/recipient.h b/src/recipient.h
new file mode 100644
index 0000000..5031ab2
--- /dev/null
+++ b/src/recipient.h
@@ -0,0 +1,59 @@
+/* @file recipient.h
+ * @brief Information about a recipient.
+ *
+ * Copyright (C) 2020, g10 code 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 RECIPIENT_H
+#define RECIPIENT_H
+
+#include
+#include
+
+namespace GpgME
+{
+ class Key;
+} // namespace GpgME
+
+class Recipient
+{
+public:
+ Recipient ();
+ explicit Recipient (const char *addr, int type);
+
+ enum recipientType
+ {
+ olOriginator = 0, /* Originator (sender) of the Item */
+ olCC = 2, /* Specified in the CC property */
+ olTo = 1, /* Specified in the To property */
+ olBCC = 3, /* BCC */
+ invalidType = -1, /* indicates that the type was not set or the
+ recipient is somehow invalid */
+ };
+
+ void setKeys (const std::vector &keys);
+ std::vector keys () const;
+
+ std::string mbox () const;
+ recipientType type () const;
+ void setType (int type);
+private:
+ std::string m_mbox;
+ recipientType m_type;
+ std::vector m_keys;
+};
+#endif
diff --git a/src/wks-helper.cpp b/src/wks-helper.cpp
index 96cc23e..b630519 100644
--- a/src/wks-helper.cpp
+++ b/src/wks-helper.cpp
@@ -1,848 +1,849 @@
/* wks-helper.cpp - Web Key Services for GpgOL
* 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 .
*/
#include "wks-helper.h"
#include "common.h"
#include "cpphelp.h"
#include "oomhelp.h"
#include "windowmessages.h"
#include "mail.h"
#include "mapihelp.h"
+#include "recipient.h"
#include