> attachments)
{
int err = 0;
for (auto att: attachments)
{
if (att->get_display_name().empty())
{
log_error ("%s:%s: Ignoring attachment without display name.",
SRCNAME, __func__);
continue;
}
wchar_t* wchar_name = utf8_to_wchar (att->get_display_name().c_str());
HANDLE hFile;
wchar_t* wchar_file = get_tmp_outfile (wchar_name,
&hFile);
if (copy_attachment_to_file (att, hFile))
{
log_error ("%s:%s: Failed to copy attachment %s to temp file",
SRCNAME, __func__, att->get_display_name().c_str());
err = 1;
}
if (add_oom_attachment (mail, wchar_file, wchar_name))
{
log_error ("%s:%s: Failed to add attachment: %s",
SRCNAME, __func__, att->get_display_name().c_str());
err = 1;
}
CloseHandle (hFile);
if (!DeleteFileW (wchar_file))
{
log_error ("%s:%s: Failed to delete tmp attachment for: %s",
SRCNAME, __func__, att->get_display_name().c_str());
err = 1;
}
xfree (wchar_file);
xfree (wchar_name);
err = fixup_last_attachment (mail, att);
}
return err;
}
GPGRT_LOCK_DEFINE(parser_lock);
static DWORD WINAPI
do_parsing (LPVOID arg)
{
gpgrt_lock_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::is_valid_ptr (mail))
{
log_debug ("%s:%s: canceling parsing for: %p already deleted",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&dtor_lock);
return 0;
}
/* This takes a shared ptr of parser. So the parser is
still valid when the mail is deleted. */
auto parser = mail->parser();
gpgrt_lock_unlock (&dtor_lock);
gpgrt_lock_lock (&parser_lock);
gpgrt_lock_lock (&invalidate_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::is_valid_ptr (mail))
{
log_debug ("%s:%s: cancel for: %p already deleted",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&invalidate_lock);
gpgrt_lock_unlock (&parser_lock);
return 0;
}
if (!parser)
{
log_error ("%s:%s: no parser found for mail: %p",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&invalidate_lock);
gpgrt_lock_unlock (&parser_lock);
return -1;
}
parser->parse();
do_in_ui_thread (PARSING_DONE, arg);
gpgrt_lock_unlock (&invalidate_lock);
gpgrt_lock_unlock (&parser_lock);
return 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
check_inline_response
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)
{
gpgrt_lock_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::is_valid_ptr (mail))
{
log_debug ("%s:%s: canceling crypt for: %p already deleted",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&dtor_lock);
return 0;
}
if (mail->crypt_state() != Mail::NeedsActualCrypt)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, mail->crypt_state ());
mail->set_window_enabled (true);
gpgrt_lock_unlock (&dtor_lock);
return -1;
}
/* This takes a shared ptr of crypter. So the crypter is
still valid when the mail is deleted. */
auto crypter = mail->crypter();
gpgrt_lock_unlock (&dtor_lock);
if (!crypter)
{
log_error ("%s:%s: no crypter found for mail: %p",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&parser_lock);
mail->set_window_enabled (true);
return -1;
}
GpgME::Error err;
int rc = crypter->do_crypto(err);
gpgrt_lock_lock (&dtor_lock);
if (!Mail::is_valid_ptr (mail))
{
log_debug ("%s:%s: aborting crypt for: %p already deleted",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&dtor_lock);
return 0;
}
mail->set_window_enabled (true);
if (rc == -1 || err)
{
mail->reset_crypter ();
crypter = nullptr;
if (err)
{
char *buf = nullptr;
gpgrt_asprintf (&buf, _("Crypto operation failed:\n%s"),
err.asString());
gpgol_message_box (mail->get_window(), buf, _("GpgOL"), MB_OK);
xfree (buf);
}
else
{
gpgol_bug (mail->get_window (),
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->set_crypt_state (Mail::NoCryptMail);
mail->reset_crypter ();
crypter = nullptr;
gpgrt_lock_unlock (&dtor_lock);
return rc;
}
if (!mail->async_crypt_disabled ())
{
mail->set_crypt_state (Mail::NeedsUpdateInOOM);
gpgrt_lock_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
{
mail->set_crypt_state (Mail::NeedsUpdateInMAPI);
mail->update_crypt_mapi ();
if (mail->crypt_state () == Mail::WantsSendMIME)
{
// For sync crypto we need to switch this.
mail->set_crypt_state (Mail::NeedsUpdateInOOM);
}
else
{
// A bug!
log_debug ("%s:%s: Resetting crypter because of state mismatch. %p",
SRCNAME, __func__, arg);
crypter = nullptr;
mail->reset_crypter ();
}
gpgrt_lock_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);
log_debug ("%s:%s: crypto thread for %p finished",
SRCNAME, __func__, arg);
return 0;
}
bool
Mail::is_crypto_mail() const
{
if (m_type == MSGTYPE_UNKNOWN || m_type == MSGTYPE_GPGOL ||
m_type == MSGTYPE_SMIME)
{
/* Not a message for us. */
return false;
}
return true;
}
int
Mail::decrypt_verify()
{
if (!is_crypto_mail())
{
log_debug ("%s:%s: Decrypt Verify for non crypto mail: %p.",
SRCNAME, __func__, m_mailitem);
return 0;
}
if (m_needs_wipe)
{
log_error ("%s:%s: Decrypt verify called for msg that needs wipe: %p",
SRCNAME, __func__, m_mailitem);
return 1;
}
set_uuid ();
m_processed = true;
/* Insert placeholder */
char *placeholder_buf;
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,
is_smime() ? "S/MIME" : "OpenPGP",
_("message"),
_("Please wait while the message is being decrypted / verified...")) == -1)
{
log_error ("%s:%s: Failed to format placeholder.",
SRCNAME, __func__);
return 1;
}
if (opt.prefer_html)
{
m_orig_body = get_oom_string (m_mailitem, "HTMLBody");
if (put_oom_string (m_mailitem, "HTMLBody", placeholder_buf))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
else
{
m_orig_body = get_oom_string (m_mailitem, "Body");
if (put_oom_string (m_mailitem, "Body", placeholder_buf))
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
}
xfree (placeholder_buf);
/* Do the actual parsing */
auto cipherstream = get_attachment_stream (m_mailitem, m_moss_position);
if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION)
{
WKSHelper::instance ()->handle_confirmation_read (this, cipherstream);
return 0;
}
if (!cipherstream)
{
log_debug ("%s:%s: Failed to get cipherstream.",
SRCNAME, __func__);
return 1;
}
m_parser = std::shared_ptr (new ParseController (cipherstream, m_type));
m_parser->setSender(GpgME::UserID::addrSpecFromString(get_sender().c_str()));
log_mime_parser ("%s:%s: Parser for \"%s\" is %p",
SRCNAME, __func__, get_subject ().c_str(), m_parser.get());
gpgol_release (cipherstream);
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);
return 0;
}
void find_and_replace(std::string& source, const std::string &find,
const std::string &replace)
{
for(std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos;)
{
source.replace(i, find.length(), replace);
i += replace.length();
}
}
void
Mail::update_body()
{
if (!m_parser)
{
TRACEPOINT;
return;
}
const auto error = m_parser->get_formatted_error ();
if (!error.empty())
{
if (opt.prefer_html)
{
if (put_oom_string (m_mailitem, "HTMLBody",
error.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
else
{
log_debug ("%s:%s: Set error html to: '%s'",
SRCNAME, __func__, error.c_str ());
}
}
else
{
if (put_oom_string (m_mailitem, "Body",
error.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
else
{
log_debug ("%s:%s: Set error plain to: '%s'",
SRCNAME, __func__, error.c_str ());
}
}
return;
}
if (m_verify_result.error())
{
log_error ("%s:%s: Verification failed. Restoring Body.",
SRCNAME, __func__);
if (opt.prefer_html)
{
if (put_oom_string (m_mailitem, "HTMLBody", m_orig_body.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
else
{
if (put_oom_string (m_mailitem, "Body", m_orig_body.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
return;
}
// 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);
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__);
}
return;
}
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 (get_window(), 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
{
return;
}
}
}
#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 (get_window(), 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);
int ret = put_oom_string (m_mailitem, "Body", converted ? converted : "");
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
return;
}
void
Mail::parsing_done()
{
TRACEPOINT;
log_oom_extra ("Mail %p Parsing done for parser: %p",
this, 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);
return;
}
/* Store the results. */
m_decrypt_result = m_parser->decrypt_result ();
m_verify_result = m_parser->verify_result ();
m_crypto_flags = 0;
if (!m_decrypt_result.isNull())
{
m_crypto_flags |= 1;
}
if (m_verify_result.numSignatures())
{
m_crypto_flags |= 2;
}
update_sigstate ();
m_needs_wipe = !m_is_send_again;
TRACEPOINT;
/* Set categories according to the result. */
update_categories ();
TRACEPOINT;
m_block_html = m_parser->shouldBlockHtml ();
if (m_block_html)
{
// Just to be careful.
set_block_status ();
}
TRACEPOINT;
/* Update the body */
update_body();
TRACEPOINT;
/* Check that there are no unsigned / unencrypted messages. */
check_attachments ();
/* Update attachments */
if (add_attachments (m_mailitem, 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);
}
remove_our_attachments ();
}
log_debug ("%s:%s: Delayed invalidate to update sigstate.",
SRCNAME, __func__);
CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) this, 0,
NULL));
TRACEPOINT;
return;
}
int
Mail::encrypt_sign_start ()
{
if (m_crypt_state != NeedsActualCrypt)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
return -1;
}
int flags = 0;
if (!needs_crypto())
{
return 0;
}
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
return -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);
return -1;
}
}
m_do_inline = 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.
set_window_enabled (false);
if (m_crypter->collect_data ())
{
log_error ("%s:%s: Crypter for mail %p failed to collect data.",
SRCNAME, __func__, this);
set_window_enabled (true);
return -1;
}
if (!m_async_crypt_disabled)
{
CloseHandle(CreateThread (NULL, 0, do_crypt,
(LPVOID) this, 0,
NULL));
}
else
{
do_crypt (this);
}
return 0;
}
int
Mail::needs_crypto ()
{
LPMESSAGE message = get_oom_message (m_mailitem);
bool ret;
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
return false;
}
ret = get_gpgol_draft_info_flags (message);
gpgol_release(message);
return ret;
}
int
Mail::wipe (bool force)
{
if (!m_needs_wipe && !force)
{
return 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);
return -1;
}
return -1;
}
else
{
put_oom_string (m_mailitem, "Body", "");
}
m_needs_wipe = false;
return 0;
}
int
Mail::update_oom_data ()
{
char *buf = nullptr;
log_debug ("%s:%s", SRCNAME, __func__);
if (!is_crypto_mail())
{
/* 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");
- release_cArray (m_cached_recipients);
- m_cached_recipients = get_recipients ();
+ char **recipients = get_recipients ();
+ m_cached_recipients = cArray_to_vector ((const char **)recipients);
+ xfree (recipients);
}
/* For some reason outlook may store the recipient address
in the send using account field. If we have SMTP we prefer
the SenderEmailAddress string. */
if (is_crypto_mail ())
{
/* 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);
}
}
if (!buf)
{
buf = get_sender_SendUsingAccount (m_mailitem, &m_is_gsuite);
}
if (!buf && !is_crypto_mail ())
{
/* 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__);
return -1;
}
m_sender = buf;
xfree (buf);
return 0;
}
std::string
Mail::get_sender ()
{
if (m_sender.empty())
update_oom_data();
return m_sender;
}
std::string
Mail::get_cached_sender ()
{
return m_sender;
}
int
Mail::close_all_mails ()
{
int err = 0;
std::map::iterator it;
TRACEPOINT;
gpgrt_lock_lock (&mail_map_lock);
std::map mail_map_copy = s_mail_map;
gpgrt_lock_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 (!is_valid_ptr (it->second))
{
log_debug ("%s:%s: Already deleted mail for %p",
SRCNAME, __func__, it->first);
continue;
}
if (!it->second->is_crypto_mail())
{
continue;
}
if (close_inspector (it->second) || close (it->second))
{
log_error ("Failed to close mail: %p ", it->first);
/* Should not happen */
if (is_valid_ptr (it->second) && it->second->revert())
{
err++;
}
}
}
return err;
}
int
Mail::revert_all_mails ()
{
int err = 0;
std::map::iterator it;
gpgrt_lock_lock (&mail_map_lock);
for (it = s_mail_map.begin(); it != s_mail_map.end(); ++it)
{
if (it->second->revert ())
{
log_error ("Failed to revert mail: %p ", it->first);
err++;
continue;
}
it->second->set_needs_save (true);
if (!invoke_oom_method (it->first, "Save", NULL))
{
log_error ("Failed to save reverted mail: %p ", it->second);
err++;
continue;
}
}
gpgrt_lock_unlock (&mail_map_lock);
return err;
}
int
Mail::wipe_all_mails ()
{
int err = 0;
std::map::iterator it;
gpgrt_lock_lock (&mail_map_lock);
for (it = s_mail_map.begin(); it != s_mail_map.end(); ++it)
{
if (it->second->wipe ())
{
log_error ("Failed to wipe mail: %p ", it->first);
err++;
}
}
gpgrt_lock_unlock (&mail_map_lock);
return err;
}
int
Mail::revert ()
{
int err = 0;
if (!m_processed)
{
return 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__);
return wipe ();
}
/* We need to reprocess the mail next time around. */
m_processed = false;
m_needs_wipe = false;
m_disable_att_remove_warning = false;
return 0;
}
bool
Mail::is_smime ()
{
msgtype_t msgtype;
LPMESSAGE message;
if (m_is_smime_checked)
{
return m_is_smime;
}
message = get_oom_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: No message?",
SRCNAME, __func__);
return false;
}
msgtype = mapi_get_message_type (message);
m_is_smime = msgtype == MSGTYPE_GPGOL_OPAQUE_ENCRYPTED ||
msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED;
/* 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;
return m_is_smime;
}
static std::string
get_string (LPDISPATCH item, const char *str)
{
char *buf = get_oom_string (item, str);
if (!buf)
return std::string();
std::string ret = buf;
xfree (buf);
return ret;
}
std::string
Mail::get_subject() const
{
return get_string (m_mailitem, "Subject");
}
std::string
Mail::get_body() const
{
return get_string (m_mailitem, "Body");
}
std::string
Mail::get_html_body() const
{
return get_string (m_mailitem, "HTMLBody");
}
char **
Mail::get_recipients() const
{
LPDISPATCH recipients = get_oom_object (m_mailitem, "Recipients");
if (!recipients)
{
TRACEPOINT;
return nullptr;
}
auto ret = get_oom_recipients (recipients);
gpgol_release (recipients);
return ret;
}
int
Mail::close_inspector (Mail *mail)
{
LPDISPATCH inspector = get_oom_object (mail->item(), "GetInspector");
HRESULT hr;
DISPID dispid;
if (!inspector)
{
log_debug ("%s:%s: No inspector.",
SRCNAME, __func__);
return -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);
return -1;
}
}
gpgol_release (inspector);
return 0;
}
/* static */
int
Mail::close (Mail *mail)
{
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;
log_oom_extra ("%s:%s: Invoking close for: %p",
SRCNAME, __func__, mail->item());
mail->set_close_triggered (true);
int rc = invoke_oom_method_with_parms (mail->item(), "Close",
NULL, &dispparams);
log_oom_extra ("%s:%s: Returned from close",
SRCNAME, __func__);
return rc;
}
void
Mail::set_close_triggered (bool value)
{
m_close_triggered = value;
}
bool
Mail::get_close_triggered () const
{
return m_close_triggered;
}
static const UserID
get_uid_for_sender (const Key &k, const char *sender)
{
UserID ret;
if (!sender)
{
return ret;
}
if (!k.numUserIDs())
{
log_debug ("%s:%s: Key without uids",
SRCNAME, __func__);
return ret;
}
for (const auto uid: k.userIDs())
{
if (!uid.email())
{
log_error ("%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__, uid.email(), sender);
continue;
}
if (normalized_sender == normalized_uid)
{
ret = uid;
}
}
return ret;
}
void
Mail::update_sigstate ()
{
std::string sender = get_sender();
if (sender.empty())
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return;
}
if (m_verify_result.isNull())
{
log_debug ("%s:%s: No verify result.",
SRCNAME, __func__);
return;
}
if (m_verify_result.error())
{
log_debug ("%s:%s: verify error.",
SRCNAME, __func__);
return;
}
for (const auto sig: m_verify_result.signatures())
{
m_is_signed = true;
m_uid = get_uid_for_sender (sig.key(), sender.c_str());
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;
}
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",
SRCNAME, __func__, m_uid.validity());
m_sig = sig;
m_is_valid = true;
return;
}
log_debug ("%s:%s: No signature with enough trust. Using first",
SRCNAME, __func__);
m_sig = m_verify_result.signature(0);
return;
}
bool
Mail::is_valid_sig ()
{
return m_is_valid;
}
void
Mail::remove_categories ()
{
const char *decCategory = _("GpgOL: Encrypted Message");
const char *verifyCategory = _("GpgOL: Trusted Sender Address");
remove_category (m_mailitem, decCategory);
remove_category (m_mailitem, verifyCategory);
}
/* 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 ()
{
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. */
return;
}
if (!wnd)
{
TRACEPOINT;
return;
}
RECT oldpos;
if (!GetWindowRect (wnd, &oldpos))
{
TRACEPOINT;
return;
}
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;
return;
}
if (!SetWindowPos (wnd, nullptr,
(int)oldpos.left,
(int)oldpos.top,
(int)oldpos.right - oldpos.left,
(int)oldpos.bottom - oldpos.top, 0))
{
TRACEPOINT;
return;
}
resized_windows.push_back(wnd);
}
void
Mail::update_categories ()
{
const char *decCategory = _("GpgOL: Encrypted Message");
const char *verifyCategory = _("GpgOL: Trusted Sender Address");
if (is_valid_sig())
{
add_category (m_mailitem, verifyCategory);
}
else
{
remove_category (m_mailitem, verifyCategory);
}
if (!m_decrypt_result.isNull())
{
add_category (m_mailitem, decCategory);
}
else
{
/* As a small safeguard against fakes we remove our
categories */
remove_category (m_mailitem, decCategory);
}
resize_active_window();
return;
}
bool
Mail::is_signed() const
{
return m_verify_result.numSignatures() > 0;
}
bool
Mail::is_encrypted() const
{
return !m_decrypt_result.isNull();
}
int
Mail::set_uuid()
{
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);
return -1;
}
if (m_uuid.empty())
{
m_uuid = uuid;
Mail *other = get_mail_for_uuid (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;
}
gpgrt_lock_lock (&uid_map_lock);
s_uid_map.insert (std::pair (m_uuid, this));
gpgrt_lock_unlock (&uid_map_lock);
log_debug ("%s:%s: uuid for %p is now %s",
SRCNAME, __func__, this,
m_uuid.c_str());
}
xfree (uuid);
return 0;
}
/* Returns 2 if the userid is ultimately trusted.
Returns 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)
{
if (uid.isNull())
{
return 0;
}
if (uid.validity () == UserID::Validity::Ultimate)
{
return 2;
}
if (uid.validity () == UserID::Validity::Full)
{
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: ParseController::get_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__, signer_uid_str, sig_uid_str,
secKeyID);
return 1;
}
}
}
}
}
return 0;
}
std::string
Mail::get_crypto_summary ()
{
const int level = get_signature_level ();
bool enc = is_encrypted ();
if (level == 4 && enc)
{
return _("Security Level 4");
}
if (level == 4)
{
return _("Trust Level 4");
}
if (level == 3 && enc)
{
return _("Security Level 3");
}
if (level == 3)
{
return _("Trust Level 3");
}
if (level == 2 && enc)
{
return _("Security Level 2");
}
if (level == 2)
{
return _("Trust Level 2");
}
if (enc)
{
return _("Encrypted");
}
if (is_signed ())
{
/* 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. */
return _("Insecure");
}
return _("Insecure");
}
std::string
Mail::get_crypto_one_line()
{
bool sig = is_signed ();
bool enc = is_encrypted ();
if (sig || enc)
{
if (sig && enc)
{
return _("Signed and encrypted message");
}
else if (sig)
{
return _("Signed message");
}
else if (enc)
{
return _("Encrypted message");
}
}
return _("Insecure message");
}
std::string
Mail::get_crypto_details()
{
std::string message;
/* No signature with keys but error */
if (!is_encrypted() && !is_signed () && 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();
return message;
}
/* No crypo, what are we doing here? */
if (!is_encrypted () && !is_signed ())
{
return _("You cannot be sure who sent, "
"modified and read the message in transit.");
}
/* Handle encrypt only */
if (is_encrypted() && !is_signed ())
{
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.");
return message;
}
bool keyFound = true;
bool isOpenPGP = m_sig.key().isNull() ? !is_smime() :
m_sig.key().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 && m_sig.key().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__);
return 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"),
m_sig.key().issuerName());
message = buf;
xfree (buf);
}
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 ());
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);
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 = is_encrypted () ? _("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");
}
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\"."),
get_sender().c_str());
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 (is_signed ())
{
if (m_sig.isDeVs ())
{
message += _("The signature is VS-NfD-compliant.");
}
else
{
message += _("The signature is not VS-NfD-compliant.");
}
message += "\n";
}
if (is_encrypted ())
{
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.");
}
return message;
}
int
Mail::get_signature_level () const
{
if (!m_is_signed)
{
return 0;
}
if (m_uid.isNull ())
{
/* No m_uid matches our sender. */
return 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()))
{
return 4;
}
if (m_is_valid && m_uid.validity () == UserID::Validity::Full &&
(!in_de_vs_mode () || m_sig.isDeVs()))
{
return 3;
}
if (m_is_valid)
{
return 2;
}
if (m_sig.validity() == Signature::Validity::Marginal)
{
return 1;
}
if (m_sig.summary() & Signature::Summary::TofuConflict ||
m_uid.tofuInfo().validity() == TofuInfo::Conflict)
{
return 0;
}
return 0;
}
int
Mail::get_crypto_icon_id () const
{
int level = get_signature_level ();
int offset = is_encrypted () ? ENCRYPT_ICON_OFFSET : 0;
return IDI_LEVEL_0 + level + offset;
}
const char*
Mail::get_sig_fpr() const
{
if (!m_is_signed || m_sig.isNull())
{
return nullptr;
}
return m_sig.fingerprint();
}
/** Try to locate the keys for all recipients */
void
Mail::locate_keys()
{
static bool locate_in_progress;
if (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);
return;
}
locate_in_progress = true;
// First update oom data to have recipients and sender updated.
update_oom_data ();
- char ** recipients = take_cached_recipients ();
KeyCache::instance()->startLocateSecret (get_sender ().c_str (), this);
KeyCache::instance()->startLocate (get_sender ().c_str (), this);
- KeyCache::instance()->startLocate (recipients, this);
- release_cArray (recipients);
+ KeyCache::instance()->startLocate (get_cached_recipients (), this);
+ autoresolve_check_s ();
+
locate_in_progress = false;
}
bool
Mail::is_html_alternative () const
{
return m_is_html_alternative;
}
char *
Mail::take_cached_html_body ()
{
char *ret = m_cached_html_body;
m_cached_html_body = nullptr;
return ret;
}
char *
Mail::take_cached_plain_body ()
{
char *ret = m_cached_plain_body;
m_cached_plain_body = nullptr;
return ret;
}
int
Mail::get_crypto_flags () const
{
return m_crypto_flags;
}
void
Mail::set_needs_encrypt (bool value)
{
m_needs_encrypt = value;
}
bool
Mail::needs_encrypt() const
{
return m_needs_encrypt;
}
-char **
-Mail::take_cached_recipients()
+std::vector
+Mail::get_cached_recipients()
{
- char **ret = m_cached_recipients;
- m_cached_recipients = nullptr;
- return ret;
+ return m_cached_recipients;
}
void
Mail::append_to_inline_body (const std::string &data)
{
m_inline_body += data;
}
int
Mail::inline_body_to_body()
{
if (!m_crypter)
{
log_error ("%s:%s: No crypter.",
SRCNAME, __func__);
return -1;
}
const auto body = m_crypter->get_inline_data ();
if (body.empty())
{
return 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 ());
return ret;
}
void
Mail::update_crypt_mapi()
{
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);
return;
}
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;
return;
}
}
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 (!async_crypt_disabled ())
{
// We don't need the crypter anymore.
reset_crypter ();
}
}
/** 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)
{
auto body = mail->get_body();
std::pair ret;
ret.first = false;
ret.second = false;
ltrim (body);
if (body.size() > 10 && !strncmp (body.c_str(), "-----BEGIN", 10))
{
ret.first = true;
return ret;
}
if (!body.size())
{
ret.second = true;
}
else
{
log_mime_parser ("%s:%s: Body found in %p : \"%s\"",
SRCNAME, __func__, mail, body.c_str ());
}
return ret;
}
void
Mail::update_crypt_oom()
{
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);
reset_crypter ();
return;
}
if (do_pgp_inline ())
{
if (inline_body_to_body ())
{
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;
return;
}
}
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 (async_crypt_disabled ())
{
reset_crypter ();
}
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;
return;
}
// We are in MIME land. Wipe the body.
if (wipe (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;
return;
}
m_crypt_state = NeedsSecondAfterWrite;
return;
}
void
Mail::set_window_enabled (bool value)
{
if (!value)
{
m_window = get_active_hwnd ();
}
log_debug ("%s:%s: enable window %p %i",
SRCNAME, __func__, m_window, value);
EnableWindow (m_window, value ? TRUE : FALSE);
}
bool
Mail::check_inline_response ()
{
/* Async sending might lead to crashes when the send invocation is done.
* For now we treat every mail as an inline response to disable async
* encryption. :-( For more details see: T3838 */
#ifdef DO_ASYNC_CRYPTO
m_async_crypt_disabled = false;
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return false;
}
LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer");
if (!explorer)
{
TRACEPOINT;
return false;
}
LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse");
gpgol_release (explorer);
if (!inlineResponse)
{
return 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);
const auto subject = get_subject ();
if (inlineResponse && !subject.empty() && !strcmp (subject.c_str (), inlineSubject))
{
log_debug ("%s:%s: Detected inline response for '%p'",
SRCNAME, __func__, this);
m_async_crypt_disabled = true;
}
xfree (inlineSubject);
#else
m_async_crypt_disabled = true;
#endif
return m_async_crypt_disabled;
}
// static
Mail *
Mail::get_last_mail ()
{
if (!s_last_mail || !is_valid_ptr (s_last_mail))
{
s_last_mail = nullptr;
}
return s_last_mail;
}
// static
void
Mail::invalidate_last_mail ()
{
s_last_mail = nullptr;
}
// static
void
Mail::locate_all_crypto_recipients()
{
if (!opt.autoresolve)
{
return;
}
gpgrt_lock_lock (&mail_map_lock);
std::map::iterator it;
for (it = s_mail_map.begin(); it != s_mail_map.end(); ++it)
{
if (it->second->needs_crypto ())
{
it->second->locate_keys ();
}
}
gpgrt_lock_unlock (&mail_map_lock);
}
int
Mail::remove_all_attachments ()
{
int ret = 0;
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
TRACEPOINT;
return 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);
}
return ret;
}
int
Mail::remove_our_attachments ()
{
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
TRACEPOINT;
return 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);
}
return ret;
}
/* We are very verbose because if we fail it might mean
that we have leaked plaintext -> critical. */
bool
Mail::has_crypted_or_empty_body ()
{
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);
return true;
}
if (!pair.second)
{
log_debug ("%s:%s: Unexpected content detected. Return false %p.",
SRCNAME, __func__, this);
return 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);
}
return 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);
return 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);
return true;
}
xfree (mapi_body);
log_debug ("%s:%s: Found mapi body. Return false. %p.",
SRCNAME, __func__, this);
return false;
}
std::string
Mail::get_verification_result_dump()
{
std::stringstream ss;
ss << m_verify_result;
return ss.str();
}
void
Mail::set_block_status()
{
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);
return;
}
void
Mail::set_block_html(bool value)
{
m_block_html = value;
}
void
Mail::increment_locate_count()
{
m_locate_count++;
}
void
Mail::decrement_locate_count()
{
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)
+ {
+ autoresolve_check_s ();
+ }
+}
+
+void
+Mail::autoresolve_check_s()
+{
+ if (!opt.autoresolve || m_manual_crypto_opts)
+ {
+ return;
+ }
+ int 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. */
+ if (ret == 3)
+ {
+ do_in_ui_thread (DO_AUTO_SECURE, this);
+ }
+ else
+ {
+ do_in_ui_thread (DONT_AUTO_SECURE, this);
+ }
+ return;
+}
+
+void
+Mail::set_do_autosecure_mapi(bool value)
+{
+ TRACEPOINT;
+ LPMESSAGE msg = get_oom_base_message (m_mailitem);
+
+ if (!msg)
+ {
+ TRACEPOINT;
+ }
+ int old_flags = get_gpgol_draft_info_flags (msg);
+ if (old_flags && m_first_autosecure_check)
+ {
+ /* 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;
+ return;
+ }
+ m_first_autosecure_check = false;
+ set_gpgol_draft_info_flags (msg, value ? 3 : 0);
+ gpgoladdin_invalidate_ui();
}
diff --git a/src/mail.h b/src/mail.h
index 7e6ea21..afd8d09 100644
--- a/src/mail.h
+++ b/src/mail.h
@@ -1,592 +1,603 @@
/* @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;
/** @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.
*/
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* get_mail_for_item (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* get_mail_for_uuid (const char *uuid);
/** @brief Get the last created mail.
@returns A reference to the last created mail or null.
*/
static Mail* get_last_mail ();
/** @brief voids the last mail variable. */
static void invalidate_last_mail ();
/** @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::delete_lock ();
Mail::is_valid_ptr (mail);
mail->set_or_check_something ();
Mail::delete_unlock ();
Still be carefull when it is a mapi or oom function.
*/
static void delete_lock ();
static void delete_unlock ();
/** @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 is_valid_ptr (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 wipe_all_mails ();
/** @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 revert_all_mails ();
/** @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 close_all_mails ();
/** @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 locate_all_crypto_recipients ();
/** @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 pre_process_message ();
/** @brief Decrypt / Verify the mail.
*
* Sets the needs_wipe and was_encrypted variable.
*
* @returns 0 on success. */
int decrypt_verify ();
/** @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 encrypt_sign_start ();
/** @brief Necessary crypto operations were completed successfully. */
bool crypto_successful () { return !needs_crypto() || m_crypt_successful; }
/** @brief Message should be encrypted and or signed.
0: No
1: Encrypt
2: Sign
3: Encrypt & Sign
*/
int needs_crypto ();
/** @brief wipe the plaintext from the message and encrypt attachments.
*
* @returns 0 on success; */
int wipe (bool force = false);
/** @brief revert the message to the original mail before our changes.
*
* @returns 0 on success; */
int revert ();
/** @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.
*
* It also updated the is_html_alternative value.
*
* @returns 0 on success */
int update_oom_data ();
/** @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 get_sender ();
/** @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 get_cached_sender ();
/** @brief get the subject string (UTF-8 encoded).
*
* @returns the subject or an empty string. */
std::string get_subject () 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 is_crypto_mail () const;
/** @brief This mail needs to be actually written.
*
* @returns true if the next write event should not be canceled.
*/
bool needs_save () { return m_needs_save; }
/** @brief set the needs save state.
*/
void set_needs_save (bool val) { m_needs_save = val; }
/** @brief is this mail an S/MIME mail.
*
* @returns true for smime messages.
*/
bool is_smime ();
/** @brief closes the inspector for a mail
*
* @returns true on success.
*/
static int close_inspector (Mail *mail);
/** @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 crypter () { 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 is_signed () const;
/** Returns true if the mail is encrypted to at least one
recipient. Regardless if it could be decrypted. */
bool is_encrypted () const;
/** Are we "green" */
bool is_valid_sig ();
/** Get UID gets UniqueID property of this mail. Returns
an empty string if the uid was not set with set uid.*/
const std::string & get_uuid () 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 set_uuid ();
/** Returns a localized string describing in one or two
words the crypto status of this mail. */
std::string get_crypto_summary ();
/** Returns a localized string describing the detailed
crypto state of this mail. */
std::string get_crypto_details ();
/** Returns a localized string describing a one line
summary of the crypto state. */
std::string get_crypto_one_line ();
/** Get the icon id of the appropiate icon for this mail */
int get_crypto_icon_id () const;
/** Get the fingerprint of an associated signature or null
if it is not signed. */
const char *get_sig_fpr() const;
/** Remove all categories of this mail */
void remove_categories ();
/** Get the body of the mail */
std::string get_body () const;
/** Get the html of the mail */
std::string get_html_body () const;
/** Get the recipients recipients is a null
terminated array of strings. Needs to be freed
by the caller. */
char ** get_recipients () const;
/** 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.
*/
static int close (Mail *mail);
/** Try to locate the keys for all recipients */
void locate_keys();
/** State variable to check if a close was triggerd by us. */
void set_close_triggered (bool value);
bool get_close_triggered () const;
/** Check if the mail should be sent as html alternative mail.
Only valid if update_oom_data was called before. */
bool is_html_alternative () const;
/** Get the html body. It is updated in update_oom_data.
Caller takes ownership of the string and has to free it.
*/
char *take_cached_html_body ();
/** Get the plain body. It is updated in update_oom_data.
Caller takes ownership of the string and has to free it.
*/
char *take_cached_plain_body ();
- /** Get the cached recipients. It is updated in update_oom_data.
- Caller takes ownership of the list and has to free it.
- */
- char **take_cached_recipients ();
+ /** Get the cached recipients. It is updated in update_oom_data.*/
+ std::vector get_cached_recipients ();
/** Returns 1 if the mail was encrypted, 2 if signed, 3 if both.
Only valid after decrypt_verify.
*/
int get_crypto_flags () const;
/** Returns true if the mail should be encrypted in the
after write event. */
bool needs_encrypt () const;
void set_needs_encrypt (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. */
int check_attachments () const;
/** Check if the mail should be encrypted "inline" */
bool do_pgp_inline () const {return m_do_inline;}
/** Check if the mail should be encrypted "inline" */
void set_do_pgp_inline (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 append_to_inline_body (const std::string &data);
/** Set the inline body as OOM body property. */
int inline_body_to_body ();
/** Get the crypt state */
CryptState crypt_state () const {return m_crypt_state;}
/** Set the crypt state */
void set_crypt_state (CryptState state) {m_crypt_state = state;}
/** Update MAPI data after encryption. */
void update_crypt_mapi ();
/** Update OOM data after encryption.
Checks for plain text leaks and
does not advance crypt state if body can't be cleaned.
*/
void update_crypt_oom ();
/** Enable / Disable the window of this mail.
When value is false the active window will
be disabled and the handle stored for a later
enable. */
void set_window_enabled (bool value);
/** 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 async_crypt_disabled () { return m_async_crypt_disabled; }
/** Check through OOM if the current mail is an inline
response.
Caches the state which can then be queried through
async_crypt_disabled
*/
bool check_inline_response ();
/** 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 get_window () { return m_window; }
/** Cleanup any attached crypter object. Useful
on error. */
void reset_crypter () { m_crypter = nullptr; }
/** Set special crypto mime data that should be used as the
mime structure when sending. */
void set_override_mime_data (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; }
/** Set if this is a forward of a crypto mail. */
void set_is_forwarded_crypto_mail (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 set_is_reply_crypto_mail (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 remove_our_attachments ();
/** 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 remove_all_attachments ();
/** 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 has_crypted_or_empty_body ();
void update_body ();
/** 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 set_is_send_again (bool value) { m_is_send_again = value; }
/* Attachment removal state variables. */
bool attachment_remove_warning_disabled () { return m_disable_att_remove_warning; }
/* Gets the string dump of the verification result. */
std::string get_verification_result_dump ();
/* Block loading HTML content */
void set_block_html (bool value);
bool is_block_html () const { return m_block_html; }
/* Remove automatic loading of HTML references setting. */
void set_block_status ();
/* Crypto options (sign/encrypt) have been set manually. */
void set_crypto_selected_manually (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 increment_locate_count ();
/* 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. */
+ toggle / untoggle the secure state.
+ */
void decrement_locate_count ();
+ /* Check if the keys can be resolved automatically and trigger
+ * setting the crypto flags accordingly.
+ */
+ void autoresolve_check_s ();
+
+ /* Set if a mail should be secured (encrypted and signed)
+ *
+ * Only save to call from a place that may access mapi.
+ */
+ void set_do_autosecure_mapi (bool value);
+
private:
void update_categories ();
void update_sigstate ();
LPDISPATCH m_mailitem;
LPDISPATCH m_event_sink;
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;
char *m_cached_html_body; /* Cached html body. */
char *m_cached_plain_body; /* Cached plain body. */
- char **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. */
};
#endif // MAIL_H
diff --git a/src/mailitem-events.cpp b/src/mailitem-events.cpp
index b499966..51006f6 100644
--- a/src/mailitem-events.cpp
+++ b/src/mailitem-events.cpp
@@ -1,906 +1,906 @@
/* mailitem-events.h - Event handling for mails.
* 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 .
*/
#include "config.h"
#include "common.h"
#include "eventsink.h"
#include "eventsinks.h"
#include "mymapi.h"
#include "mymapitags.h"
#include "oomhelp.h"
#include "ocidl.h"
#include "windowmessages.h"
#include "mail.h"
#include "mapihelp.h"
#include "gpgoladdin.h"
#undef _
#define _(a) utf8_gettext (a)
const wchar_t *prop_blacklist[] = {
L"Body",
L"HTMLBody",
L"To", /* Somehow this is done when a mail is opened */
L"CC", /* Ditto */
L"BCC", /* Ditto */
L"Categories",
L"UnRead",
L"OutlookVersion",
L"OutlookInternalVersion",
L"ReceivedTime",
L"InternetCodepage",
NULL };
typedef enum
{
AfterWrite = 0xFC8D,
AttachmentAdd = 0xF00B,
AttachmentRead = 0xF00C,
AttachmentRemove = 0xFBAE,
BeforeAttachmentAdd = 0xFBB0,
BeforeAttachmentPreview = 0xFBAF,
BeforeAttachmentRead = 0xFBAB,
BeforeAttachmentSave = 0xF00D,
BeforeAttachmentWriteToTempFile = 0xFBB2,
BeforeAutoSave = 0xFC02,
BeforeCheckNames = 0xF00A,
BeforeDelete = 0xFA75,
BeforeRead = 0xFC8C,
Close = 0xF004,
CustomAction = 0xF006,
CustomPropertyChange = 0xF008,
Forward = 0xF468,
Open = 0xF003,
PropertyChange = 0xF009,
Read = 0xF001,
ReadComplete = 0xFC8F,
Reply = 0xF466,
ReplyAll = 0xF467,
Send = 0xF005,
Unload = 0xFBAD,
Write = 0xF002
} MailEvent;
/* Mail Item Events */
BEGIN_EVENT_SINK(MailItemEvents, IDispatch)
/* We are still in the class declaration */
private:
Mail * m_mail; /* The mail object related to this mailitem */
};
MailItemEvents::MailItemEvents() :
m_object(NULL),
m_pCP(NULL),
m_cookie(0),
m_ref(1),
m_mail(NULL)
{
}
MailItemEvents::~MailItemEvents()
{
if (m_pCP)
m_pCP->Unadvise(m_cookie);
if (m_object)
gpgol_release (m_object);
}
static bool propchangeWarnShown = false;
static bool attachRemoveWarnShown = false;
static DWORD WINAPI
do_delayed_locate (LPVOID arg)
{
Sleep(100);
do_in_ui_thread (RECIPIENT_ADDED, arg);
return 0;
}
/* The main Invoke function. The return value of this
function does not appear to have any effect on outlook
although I have read in an example somewhere that you
should return S_OK so that outlook continues to handle
the event I have not yet seen any effect by returning
error values here and no MSDN documentation about the
return values.
*/
EVENT_SINK_INVOKE(MailItemEvents)
{
USE_INVOKE_ARGS
if (!m_mail)
{
m_mail = Mail::get_mail_for_item (m_object);
if (!m_mail)
{
log_error ("%s:%s: mail event without mail object known. Bug.",
SRCNAME, __func__);
return S_OK;
}
}
bool is_reply = false;
switch(dispid)
{
case Open:
{
log_oom_extra ("%s:%s: Open : %p",
SRCNAME, __func__, m_mail);
LPMESSAGE message;
if (g_ol_version_major < 14 && m_mail->set_uuid ())
{
/* In Outlook 2007 we need the uid for every
open mail to track the message in case
it is sent and crypto is required. */
log_debug ("%s:%s: Failed to set uuid.",
SRCNAME, __func__);
delete m_mail; /* deletes this, too */
return S_OK;
}
int draft_flags = 0;
if (!opt.encrypt_default && !opt.sign_default)
{
return S_OK;
}
message = get_oom_base_message (m_object);
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
break;
}
if (opt.encrypt_default)
{
draft_flags = 1;
}
if (opt.sign_default)
{
draft_flags += 2;
}
set_gpgol_draft_info_flags (message, draft_flags);
gpgol_release (message);
break;
}
case BeforeRead:
{
log_oom_extra ("%s:%s: BeforeRead : %p",
SRCNAME, __func__, m_mail);
if (m_mail->pre_process_message ())
{
log_error ("%s:%s: Pre process message failed.",
SRCNAME, __func__);
}
break;
}
case Read:
{
log_oom_extra ("%s:%s: Read : %p",
SRCNAME, __func__, m_mail);
if (!m_mail->is_crypto_mail())
{
log_debug ("%s:%s: Non crypto mail %p opened. Updating sigstatus.",
SRCNAME, __func__, m_mail);
/* Ensure that no wrong sigstatus is shown */
CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) this, 0,
NULL));
break;
}
if (m_mail->set_uuid ())
{
log_debug ("%s:%s: Failed to set uuid.",
SRCNAME, __func__);
delete m_mail; /* deletes this, too */
return S_OK;
}
if (m_mail->decrypt_verify ())
{
log_error ("%s:%s: Decrypt message failed.",
SRCNAME, __func__);
}
if (!opt.enable_smime && m_mail->is_smime ())
{
/* We want to save the mail when it's an smime mail and smime
is disabled to revert it. */
log_debug ("%s:%s: S/MIME mail but S/MIME is disabled."
" Need save.",
SRCNAME, __func__);
m_mail->set_needs_save (true);
}
break;
}
case PropertyChange:
{
if (!parms || parms->cArgs != 1 ||
parms->rgvarg[0].vt != VT_BSTR ||
!parms->rgvarg[0].bstrVal)
{
log_error ("%s:%s: Unexpected params.",
SRCNAME, __func__);
break;
}
const wchar_t *prop_name = parms->rgvarg[0].bstrVal;
if (!m_mail->is_crypto_mail ())
{
if (!opt.autoresolve)
{
break;
}
if (!wcscmp (prop_name, L"To") /* ||
!wcscmp (prop_name, L"BCC") ||
!wcscmp (prop_name, L"CC")
Testing shows that Outlook always sends these three in a row
*/)
{
- if ((m_mail->needs_crypto() & 1))
+ if (opt.autosecure || (m_mail->needs_crypto() & 1))
{
/* XXX Racy race. This is a fix for crashes
that happend if a resolved recipient is copied an pasted.
If we then access the recipients object in the Property
Change event we crash. Thus we do the delay dance. */
HANDLE thread = CreateThread (NULL, 0, do_delayed_locate,
(LPVOID) m_mail, 0,
NULL);
CloseHandle(thread);
}
}
break;
}
for (const wchar_t **cur = prop_blacklist; *cur; cur++)
{
if (!wcscmp (prop_name, *cur))
{
log_oom ("%s:%s: Message %p propchange: %ls discarded.",
SRCNAME, __func__, m_object, prop_name);
return S_OK;
}
}
log_oom ("%s:%s: Message %p propchange: %ls.",
SRCNAME, __func__, m_object, prop_name);
if (!wcscmp (prop_name, L"SendUsingAccount"))
{
bool sent = get_oom_bool (m_object, "Sent");
if (sent)
{
log_debug ("%s:%s: Ignoring SendUsingAccount change for sent %p ",
SRCNAME, __func__, m_object);
return S_OK;
}
log_debug ("%s:%s: Message %p looks like send again.",
SRCNAME, __func__, m_object);
m_mail->set_is_send_again (true);
return S_OK;
}
/* We have tried several scenarios to handle propery changes.
Only save the property in MAPI and call MAPI SaveChanges
worked and did not leak plaintext but this caused outlook
still to break the attachments of PGP/MIME Mails into two
attachments and add them as winmail.dat so other clients
are broken.
Alternatively reverting the mail, saving the property and
then decrypt again also worked a bit but there were some
weird side effects and breakages. But this has the usual
problem of a revert that the mail is created by outlook and
e.g. multipart/signed signatures from most MUA's are broken.
Some things to try out might be the close approach and then
another open or a selection change. But for now we just warn.
As a workardound a user should make property changes when
the mail was not read by us. */
if (propchangeWarnShown)
{
return S_OK;
}
wchar_t *title = utf8_to_wchar (_("Sorry, that's not possible, yet"));
char *fmt;
gpgrt_asprintf (&fmt, _("GpgOL has prevented the change to the \"%s\" property.\n"
"Property changes are not yet handled for crypto messages.\n\n"
"To workaround this limitation please change the property when the "
"message is not open in any window and not selected in the "
"messagelist.\n\nFor example by right clicking but not selecting the message.\n"),
wchar_to_utf8(prop_name));
wchar_t *msg = utf8_to_wchar (fmt);
xfree (fmt);
MessageBoxW (get_active_hwnd(), msg, title,
MB_ICONINFORMATION | MB_OK);
xfree (msg);
xfree (title);
propchangeWarnShown = true;
return S_OK;
}
case CustomPropertyChange:
{
log_oom_extra ("%s:%s: CustomPropertyChange : %p",
SRCNAME, __func__, m_mail);
/* TODO */
break;
}
case Send:
{
/* This is the only event where we can cancel the send of a
mailitem. But it is too early for us to encrypt as the MAPI
structures are not yet filled. Crypto based on the
Outlook Object Model data did not work as the messages
were only sent out empty. See 2b376a48 for a try of
this.
This is why we store send_seen and invoke a save which
may result in an error but only after triggering all the
behavior we need -> filling mapi structures and invoking the
AfterWrite handler where we encrypt.
If this encryption is successful and we pass the send
as then the encrypted data is sent.
*/
log_oom_extra ("%s:%s: Send : %p",
SRCNAME, __func__, m_mail);
if (!m_mail->needs_crypto () && m_mail->crypt_state () == Mail::NoCryptMail)
{
log_debug ("%s:%s: No crypto neccessary. Passing send for %p obj %p",
SRCNAME, __func__, m_mail, m_object);
break;
}
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
log_debug ("%s:%s: Uncancellable send event.",
SRCNAME, __func__);
break;
}
if (m_mail->crypt_state () == Mail::NoCryptMail &&
m_mail->needs_crypto ())
{
// First contact with a mail to encrypt update
// state and oom data.
m_mail->update_oom_data ();
m_mail->set_crypt_state (Mail::NeedsFirstAfterWrite);
// Check inline response state before the write.
m_mail->check_inline_response ();
log_debug ("%s:%s: Send event for crypto mail %p saving and starting.",
SRCNAME, __func__, m_mail);
// Save the Mail
invoke_oom_method (m_object, "Save", NULL);
if (!m_mail->async_crypt_disabled ())
{
// The afterwrite in the save should have triggered
// the encryption. We cancel send for our asyncness.
// Cancel send
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
}
else
{
if (m_mail->crypt_state () == Mail::NoCryptMail)
{
// Crypto failed or was canceled
log_debug ("%s:%s: Message %p mail %p cancelling send - "
"Crypto failed or canceled.",
SRCNAME, __func__, m_object, m_mail);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
}
// For inline response we can't trigger send programatically
// so we do the encryption in sync.
if (m_mail->crypt_state () == Mail::NeedsUpdateInOOM)
{
m_mail->update_crypt_oom ();
}
if (m_mail->crypt_state () == Mail::NeedsSecondAfterWrite)
{
m_mail->set_crypt_state (Mail::WantsSendMIME);
}
if (m_mail->do_pgp_inline () && m_mail->crypt_state () != Mail::WantsSendInline)
{
log_debug ("%s:%s: Message %p mail %p cancelling send - "
"Invalid state.",
SRCNAME, __func__, m_object, m_mail);
gpgol_bug (m_mail->get_window (),
ERR_INLINE_BODY_INV_STATE);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
}
}
}
if (m_mail->crypt_state () == Mail::WantsSendInline)
{
if (!m_mail->has_crypted_or_empty_body())
{
log_debug ("%s:%s: Message %p mail %p cancelling send - "
"not encrypted or not empty body detected.",
SRCNAME, __func__, m_object, m_mail);
gpgol_bug (m_mail->get_window (),
ERR_WANTS_SEND_INLINE_BODY);
m_mail->set_crypt_state (Mail::NoCryptMail);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
}
log_debug ("%s:%s: Passing send event for no-mime message %p.",
SRCNAME, __func__, m_object);
break;
}
if (m_mail->crypt_state () == Mail::WantsSendMIME)
{
if (!m_mail->has_crypted_or_empty_body())
{
/* The safety checks here trigger too often. Somehow for some
users the body is not empty after the encryption but when
it is sent it is still sent with the crypto content because
the encrypted MIME Structure is used because it is
correct in MAPI land.
For safety reasons enabling the checks might be better but
until we figure out why for some users the body replacement
does not work we have to disable them. Otherwise GpgOL
is unusuable for such users. GnuPG-Bug-Id: T3875
*/
#define DISABLE_SAFTEY_CHECKS
#ifndef DISABLE_SAFTEY_CHECKS
gpgol_bug (m_mail->get_window (),
ERR_WANTS_SEND_MIME_BODY);
log_debug ("%s:%s: Message %p mail %p cancelling send mime - "
"not encrypted or not empty body detected.",
SRCNAME, __func__, m_object, m_mail);
m_mail->set_crypt_state (Mail::NoCryptMail);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
#else
log_debug ("%s:%s: Message %p mail %p - "
"not encrypted or not empty body detected - MIME.",
SRCNAME, __func__, m_object, m_mail);
#endif
}
/* Now we adress T3656 if Outlooks internal S/MIME is somehow
* mixed in (even if it is enabled and then disabled) it might
* cause strange behavior in that it sends the plain message
* and not the encrypted message. Tests have shown that we can
* bypass that by calling submit message on our base
* message.
*
* We do this conditionally as our other way of using OOM
* to send is proven to work and we don't want to mess
* with it.
*/
// Get the Message class.
HRESULT hr;
LPSPropValue propval = NULL;
// It's important we use the _not_ base message here.
LPMESSAGE message = get_oom_message (m_object);
hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
gpgol_release (message);
if (FAILED (hr))
{
log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
SRCNAME, __func__, hr);
gpgol_release (message);
break;
}
if (propval->Value.lpszA && !strstr (propval->Value.lpszA, "GpgOL"))
{
// Does not have a message class by us.
log_debug ("%s:%s: Message %p - No GpgOL Message class after encryption. cls is: '%s'",
SRCNAME, __func__, m_object, propval->Value.lpszA);
log_debug ("%s:%s: Message %p - Activating T3656 Workaround",
SRCNAME, __func__, m_object);
message = get_oom_base_message (m_object);
// It's important we use the _base_ message here.
mapi_save_changes (message, KEEP_OPEN_READWRITE | FORCE_SAVE);
message->SubmitMessage(0);
gpgol_release (message);
// Cancel send
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
// Close the composer and trigger unloads
CloseHandle(CreateThread (NULL, 0, close_mail, (LPVOID) m_mail, 0,
NULL));
}
MAPIFreeBuffer (propval);
if (*(parms->rgvarg[0].pboolVal) == VARIANT_TRUE)
{
break;
}
log_debug ("%s:%s: Passing send event for message %p.",
SRCNAME, __func__, m_object);
break;
}
else
{
log_debug ("%s:%s: Message %p cancelling send - "
"crypto or second save failed.",
SRCNAME, __func__, m_object);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
}
return S_OK;
}
case Write:
{
log_oom_extra ("%s:%s: Write : %p",
SRCNAME, __func__, m_mail);
/* This is a bit strange. We sometimes get multiple write events
without a read in between. When we access the message in
the second event it fails and if we cancel the event outlook
crashes. So we have keep the m_needs_wipe state variable
to keep track of that. */
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
/* This happens in the weird case */
log_debug ("%s:%s: Uncancellable write event.",
SRCNAME, __func__);
break;
}
if (m_mail->is_crypto_mail () && !m_mail->needs_save ())
{
Mail *last_mail = Mail::get_last_mail ();
if (Mail::is_valid_ptr (last_mail))
{
/* We want to identify here if there was a mail created that
should receive the contents of this mail. For this we check
for a write in the same loop as a mail creation.
Now when switching from one mail to another this is also what
happens. The new mail is loaded and the old mail is written.
To distinguish the two we check that the new mail does not have
an entryID, a Subject and No Size. Maybe just size or entryID
would be enough but better save then sorry.
Security consideration: Worst case we pass the write here but
an unload follows before we get the scheduled revert. This
would leak plaintext. But does not happen in our tests.
Similarly if we crash or Outlook is closed before we see this
revert. But as we immediately revert after the write this should
also not happen. */
const std::string lastSubject = last_mail->get_subject ();
char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
int lastSize = get_oom_int (last_mail->item (), "Size");
std::string lastEntryStr;
if (lastEntryID)
{
lastEntryStr = lastEntryID;
xfree (lastEntryID);
}
if (!lastSize && !lastEntryStr.size () && !lastSubject.size ())
{
log_debug ("%s:%s: Write in the same loop as empty load."
" Pass but schedule revert.",
SRCNAME, __func__);
/* This might be a forward. So don't invalidate yet. */
// Mail::invalidate_last_mail ();
do_in_ui_thread_async (REVERT_MAIL, m_mail);
return S_OK;
}
}
/* We cancel the write event to stop outlook from excessively
syncing our changes.
if smime support is disabled and we still have an smime
mail we also don't want to cancel the write event
to enable reverting this mails.
*/
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
log_debug ("%s:%s: Canceling write event.",
SRCNAME, __func__);
return S_OK;
}
if (m_mail->is_crypto_mail () && m_mail->needs_save () &&
m_mail->revert ())
{
/* An error cleaning the mail should not happen normally.
But just in case there is an error we cancel the
write here. */
log_debug ("%s:%s: Failed to remove plaintext.",
SRCNAME, __func__);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
}
if (!m_mail->is_crypto_mail () && m_mail->is_forwarded_crypto_mail () &&
!m_mail->needs_crypto () && m_mail->crypt_state () == Mail::NoCryptMail)
{
/* We are sure now that while this is a forward of an encrypted
* mail that the forward should not be signed or encrypted. So
* it's not constructed by us. We need to remove our attachments
* though so that they are not included in the forward. */
log_debug ("%s:%s: Writing unencrypted forward of crypt mail. "
"Removing attachments. mail: %p item: %p",
SRCNAME, __func__, m_mail, m_object);
if (m_mail->remove_our_attachments ())
{
// Worst case we forward some encrypted data here not
// a security problem, so let it pass.
log_error ("%s:%s: Failed to remove our attachments.",
SRCNAME, __func__);
}
/* Remove marker because we did this now. */
m_mail->set_is_forwarded_crypto_mail (false);
}
log_debug ("%s:%s: Passing write event.",
SRCNAME, __func__);
m_mail->set_needs_save (false);
break;
}
case AfterWrite:
{
log_oom_extra ("%s:%s: AfterWrite : %p",
SRCNAME, __func__, m_mail);
if (m_mail->crypt_state () == Mail::NeedsFirstAfterWrite)
{
/* Seen the first after write. Advance the state */
m_mail->set_crypt_state (Mail::NeedsActualCrypt);
if (m_mail->encrypt_sign_start ())
{
log_debug ("%s:%s: Encrypt sign start failes.",
SRCNAME, __func__);
m_mail->set_crypt_state (Mail::NoCryptMail);
}
return S_OK;
}
if (m_mail->crypt_state () == Mail::NeedsSecondAfterWrite)
{
m_mail->set_crypt_state (Mail::NeedsUpdateInMAPI);
m_mail->update_crypt_mapi ();
return S_OK;
}
break;
}
case Close:
{
log_oom_extra ("%s:%s: Close : %p",
SRCNAME, __func__, m_mail);
if (m_mail->is_crypto_mail ())
{
/* Close. This happens when an Opened mail is closed.
To prevent the question of wether or not to save the changes
(Which would save the decrypted data without an event to
prevent it) we cancel the close and then either close it
with discard changes or revert / save it.
Contrary to documentation we can invoke close from
close.
*/
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
/* This happens in the weird case */
log_debug ("%s:%s: Uncancellable close event.",
SRCNAME, __func__);
break;
}
if (m_mail->get_close_triggered ())
{
/* Our close with discard changes, pass through */
m_mail->set_close_triggered (false);
return S_OK;
}
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
log_oom_extra ("%s:%s: Canceling close event.",
SRCNAME, __func__);
if (Mail::close(m_mail))
{
log_debug ("%s:%s: Close request failed.",
SRCNAME, __func__);
}
}
return S_OK;
}
case Unload:
{
log_oom_extra ("%s:%s: Unload : %p",
SRCNAME, __func__, m_mail);
log_debug ("%s:%s: Removing Mail for message: %p.",
SRCNAME, __func__, m_object);
delete m_mail;
log_oom_extra ("%s:%s: deletion done",
SRCNAME, __func__);
return S_OK;
}
/* Fallthrough */
case ReplyAll:
case Reply:
{
is_reply = true;
}
case Forward:
{
log_oom_extra ("%s:%s: %s : %p",
SRCNAME, __func__, is_reply ? "reply" : "forward", m_mail);
int draft_flags = 0;
if (opt.encrypt_default)
{
draft_flags = 1;
}
if (opt.sign_default)
{
draft_flags += 2;
}
bool is_crypto_mail = m_mail->is_crypto_mail();
/* If it is a crypto mail and the settings should not be taken
* from the crypto mail and always encrypt / sign is on. Or
* If it is not a crypto mail and we have automaticalls sign_encrypt. */
if ((is_crypto_mail && !opt.reply_crypt && draft_flags) ||
(!is_crypto_mail && draft_flags))
{
/* Check if we can use the dispval */
if (parms->cArgs == 2 && parms->rgvarg[1].vt == (VT_DISPATCH) &&
parms->rgvarg[0].vt == (VT_BOOL | VT_BYREF))
{
LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal);
if (msg)
{
set_gpgol_draft_info_flags (msg, draft_flags);
gpgol_release (msg);
}
else
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
}
}
else
{
log_error ("%s:%s: Unexpected parameters.",
SRCNAME, __func__);
}
}
if (!is_crypto_mail)
{
/* Replys to non crypto mails do not interest us anymore. */
break;
}
Mail *last_mail = Mail::get_last_mail ();
if (Mail::is_valid_ptr (last_mail))
{
/* We want to identify here if there was a mail created that
should receive the contents of this mail. For this we check
for a forward in the same loop as a mail creation.
We need to do it this complicated and can't just use
get_mail_for_item because the mailitem pointer we get here
is a different one then the one with which the mail was loaded.
*/
char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
int lastSize = get_oom_int (last_mail->item (), "Size");
std::string lastEntryStr;
if (lastEntryID)
{
lastEntryStr = lastEntryID;
xfree (lastEntryID);
}
if (!lastSize && !lastEntryStr.size ())
{
if (!is_reply)
{
log_debug ("%s:%s: Forward in the same loop as empty "
"load Marking %p (item %p) as forwarded.",
SRCNAME, __func__, last_mail,
last_mail->item ());
last_mail->set_is_forwarded_crypto_mail (true);
}
else
{
log_debug ("%s:%s: Reply in the same loop as empty "
"load Marking %p (item %p) as reply.",
SRCNAME, __func__, last_mail,
last_mail->item ());
}
if (m_mail->is_block_html())
{
std::string caption = _("GpgOL") + std::string (": ");
caption += is_reply ? _("Dangerous reply") :
_("Dangerous forward");
std::string buf = _("Unsigned S/MIME mails are not integrity "
"protected.");
buf += "\n\n";
if (is_reply)
{
buf += _("For security reasons no decrypted contents"
" are included in this reply.");
}
else
{
buf += _("For security reasons no decrypted contents"
" are included in the forwarded mail.");
}
gpgol_message_box (get_active_hwnd (), buf.c_str(),
_("GpgOL"), MB_OK);
do_in_ui_thread_async (CLEAR_REPLY_FORWARD, last_mail, 1000);
}
}
// We can now invalidate the last mail
Mail::invalidate_last_mail ();
}
log_oom_extra ("%s:%s: Reply Forward ReplyAll: %p",
SRCNAME, __func__, m_mail);
if (!opt.reply_crypt)
{
break;
}
int crypto_flags = 0;
if (!(crypto_flags = m_mail->get_crypto_flags ()))
{
break;
}
if (parms->cArgs != 2 || parms->rgvarg[1].vt != (VT_DISPATCH) ||
parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
/* This happens in the weird case */
log_debug ("%s:%s: Unexpected args %i %x %x named: %i",
SRCNAME, __func__, parms->cArgs, parms->rgvarg[0].vt, parms->rgvarg[1].vt,
parms->cNamedArgs);
break;
}
LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal);
if (!msg)
{
log_debug ("%s:%s: Failed to get base message",
SRCNAME, __func__);
break;
}
set_gpgol_draft_info_flags (msg, crypto_flags);
gpgol_release (msg);
break;
}
case AttachmentRemove:
{
log_oom_extra ("%s:%s: AttachmentRemove: %p",
SRCNAME, __func__, m_mail);
if (!m_mail->is_crypto_mail () || attachRemoveWarnShown ||
m_mail->attachment_remove_warning_disabled ())
{
return S_OK;
}
gpgol_message_box (get_active_hwnd (),
_("Attachments are part of the crypto message.\nThey "
"can't be permanently removed and will be shown again the next "
"time this message is opened."),
_("Sorry, that's not possible, yet"), MB_OK);
attachRemoveWarnShown = true;
return S_OK;
}
default:
log_oom_extra ("%s:%s: Message:%p Unhandled Event: %lx \n",
SRCNAME, __func__, m_object, dispid);
}
return S_OK;
}
END_EVENT_SINK(MailItemEvents, IID_MailItemEvents)
diff --git a/src/main.c b/src/main.c
index 02c53df..2d3590e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,459 +1,462 @@
/* main.c - DLL entry point
* Copyright (C) 2005, 2007, 2008 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
* 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
#include
#include
#include
#include
#include
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
#include "mymapi.h"
/* Local function prototypes. */
static char *get_locale_dir (void);
static void drop_locale_dir (char *locale_dir);
/* The major version of Outlook we are attached to */
int g_ol_version_major;
/* For certain operations we need to acquire a log on the logging
functions. This lock is controlled by this Mutex. */
HANDLE log_mutex;
/* Early initialization of this module. This is done right at startup
with only one thread running. Should be called only once. Returns
0 on success. */
static int
initialize_main (void)
{
SECURITY_ATTRIBUTES sa;
memset (&sa, 0, sizeof sa);
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof sa;
log_mutex = CreateMutex (&sa, FALSE, NULL);
return log_mutex? 0 : -1;
}
/* Return nbytes of cryptographic strong random. Caller needs to free
the returned buffer. */
static char *
get_crypt_random (size_t nbytes)
{
HCRYPTPROV prov;
char *buffer;
if (!CryptAcquireContext (&prov, NULL, NULL, PROV_RSA_FULL,
(CRYPT_VERIFYCONTEXT|CRYPT_SILENT)) )
return NULL;
buffer = xmalloc (nbytes);
if (!CryptGenRandom (prov, nbytes, buffer))
{
xfree (buffer);
buffer = NULL;
}
CryptReleaseContext (prov, 0);
return buffer;
}
static void
i18n_init (void)
{
char *locale_dir;
#ifdef ENABLE_NLS
# ifdef HAVE_LC_MESSAGES
setlocale (LC_TIME, "");
setlocale (LC_MESSAGES, "");
# else
setlocale (LC_ALL, "" );
# endif
#endif
locale_dir = get_locale_dir ();
if (locale_dir)
{
bindtextdomain (PACKAGE_GT, locale_dir);
drop_locale_dir (locale_dir);
}
textdomain (PACKAGE_GT);
}
static char *
get_gpgme_w32_inst_dir (void)
{
char *gpg4win_dir = get_gpg4win_dir ();
char *tmp;
gpgrt_asprintf (&tmp, "%s\\bin\\gpgme-w32spawn.exe", gpg4win_dir);
if (!access(tmp, R_OK))
{
xfree (tmp);
gpgrt_asprintf (&tmp, "%s\\bin", gpg4win_dir);
xfree (gpg4win_dir);
return tmp;
}
xfree (tmp);
gpgrt_asprintf (&tmp, "%s\\gpgme-w32spawn.exe", gpg4win_dir);
if (!access(tmp, R_OK))
{
xfree (tmp);
return gpg4win_dir;
}
OutputDebugString("Failed to find gpgme-w32spawn.exe!");
return NULL;
}
/* Entry point called by DLL loader. */
int WINAPI
DllMain (HINSTANCE hinst, DWORD reason, LPVOID reserved)
{
(void)reserved;
if (reason == DLL_PROCESS_ATTACH)
{
set_global_hinstance (hinst);
gpg_err_init ();
/* Set the installation directory for GpgME so that
it can find tools like gpgme-w32-spawn correctly. */
char *instdir = get_gpgme_w32_inst_dir();
gpgme_set_global_flag ("w32-inst-dir", instdir);
xfree (instdir);
/* The next call initializes subsystems of gpgme and should be
done as early as possible. The actual return value (the
version string) is not used here. It may be called at any
time later for this. */
gpgme_check_version (NULL);
/* Early initializations of our subsystems. */
if (initialize_main ())
return FALSE;
i18n_init ();
}
else if (reason == DLL_PROCESS_DETACH)
{
gpg_err_deinit (0);
}
return TRUE;
}
/* Return a new allocated IV of size NBYTES. Caller must free it. On
error NULL is returned. */
void *
create_initialization_vector (size_t nbytes)
{
return get_crypt_random (nbytes);
}
static char *
get_locale_dir (void)
{
char *instdir;
char *p;
char *dname;
instdir = get_gpg4win_dir();
if (!instdir)
return NULL;
/* Build the key: "/share/locale". */
#define SLDIR "\\share\\locale"
dname = malloc (strlen (instdir) + strlen (SLDIR) + 1);
if (!dname)
{
free (instdir);
return NULL;
}
p = dname;
strcpy (p, instdir);
p += strlen (instdir);
strcpy (p, SLDIR);
free (instdir);
return dname;
}
static void
drop_locale_dir (char *locale_dir)
{
free (locale_dir);
}
/* Read option settings from the Registry. */
void
read_options (void)
{
static int warnings_shown;
char *val = NULL;
/* Set the log file first so that output from this function is
logged too. */
load_extension_value ("logFile", &val);
set_log_file (val);
xfree (val); val = NULL;
/* Parse the debug flags. */
load_extension_value ("enableDebug", &val);
opt.enable_debug = 0;
if (val)
{
char *p, *pend;
trim_spaces (val);
for (p = val; p; p = pend)
{
pend = strpbrk (p, ", \t\n\r\f");
if (pend)
{
*pend++ = 0;
pend += strspn (pend, ", \t\n\r\f");
}
if (isascii (*p) && isdigit (*p))
opt.enable_debug |= strtoul (p, NULL, 0);
else if (!strcmp (p, "ioworker"))
opt.enable_debug |= DBG_IOWORKER;
else if (!strcmp (p, "ioworker-extra"))
opt.enable_debug |= DBG_IOWORKER_EXTRA;
else if (!strcmp (p, "filter"))
opt.enable_debug |= DBG_FILTER;
else if (!strcmp (p, "filter-extra"))
opt.enable_debug |= DBG_FILTER_EXTRA;
else if (!strcmp (p, "memory"))
opt.enable_debug |= DBG_MEMORY;
else if (!strcmp (p, "commands"))
opt.enable_debug |= DBG_COMMANDS;
else if (!strcmp (p, "mime-parser"))
opt.enable_debug |= DBG_MIME_PARSER;
else if (!strcmp (p, "mime-data"))
opt.enable_debug |= DBG_MIME_DATA;
else if (!strcmp (p, "oom"))
opt.enable_debug |= DBG_OOM;
else if (!strcmp (p, "oom-extra"))
opt.enable_debug |= DBG_OOM_EXTRA;
else
log_debug ("invalid debug flag `%s' ignored", p);
}
}
else
{
/* To help the user enable debugging make sure that the registry
key exists. Note that the other registry keys are stored
after using the configuration dialog. */
store_extension_value ("enableDebug", "0");
}
xfree (val); val = NULL;
if (opt.enable_debug)
log_debug ("enabled debug flags:%s%s%s%s%s%s%s%s%s%s\n",
(opt.enable_debug & DBG_IOWORKER)? " ioworker":"",
(opt.enable_debug & DBG_IOWORKER_EXTRA)? " ioworker-extra":"",
(opt.enable_debug & DBG_FILTER)? " filter":"",
(opt.enable_debug & DBG_FILTER_EXTRA)? " filter-extra":"",
(opt.enable_debug & DBG_MEMORY)? " memory":"",
(opt.enable_debug & DBG_COMMANDS)? " commands":"",
(opt.enable_debug & DBG_MIME_PARSER)? " mime-parser":"",
(opt.enable_debug & DBG_MIME_DATA)? " mime-data":"",
(opt.enable_debug & DBG_OOM)? " oom":"",
(opt.enable_debug & DBG_OOM_EXTRA)? " oom-extra":""
);
load_extension_value ("enableSmime", &val);
opt.enable_smime = !val ? 0 : atoi (val);
xfree (val); val = NULL;
load_extension_value ("encryptDefault", &val);
opt.encrypt_default = val == NULL || *val != '1'? 0 : 1;
xfree (val); val = NULL;
load_extension_value ("signDefault", &val);
opt.sign_default = val == NULL || *val != '1'? 0 : 1;
xfree (val); val = NULL;
load_extension_value ("defaultKey", &val);
set_default_key (val);
xfree (val); val = NULL;
load_extension_value ("gitCommit", &val);
opt.git_commit = val? strtoul (val, NULL, 16) : 0;
xfree (val); val = NULL;
load_extension_value ("formsRevision", &val);
opt.forms_revision = val? atol (val) : 0;
xfree (val); val = NULL;
load_extension_value ("announceNumber", &val);
opt.announce_number = val? atol (val) : 0;
xfree (val); val = NULL;
load_extension_value ("inlinePGP", &val);
opt.inline_pgp = val == NULL || *val != '1'? 0 : 1;
xfree (val); val = NULL;
load_extension_value ("autoresolve", &val);
opt.autoresolve = val == NULL ? 1 : *val != '1' ? 0 : 1;
xfree (val); val = NULL;
load_extension_value ("replyCrypt", &val);
opt.reply_crypt = val == NULL ? 1 : *val != '1' ? 0 : 1;
xfree (val); val = NULL;
load_extension_value ("smimeHtmlWarnShown", &val);
opt.smime_html_warn_shown = val == NULL || *val != '1'? 0 : 1;
xfree (val); val = NULL;
+ load_extension_value ("autosecure", &val);
+ opt.autosecure = val == NULL ? 1 : *val != '1' ? 0 : 1;
+ xfree (val); val = NULL;
/* Note, that on purpose these flags are only Registry changeable.
The format of the entry is a string of of "0" and "1" digits; see
the switch below for a description. */
memset (&opt.compat, 0, sizeof opt.compat);
load_extension_value ("compatFlags", &val);
if (val)
{
const char *s = val;
int i, x;
for (s=val, i=0; *s; s++, i++)
{
x = *s == '1';
switch (i)
{
case 0: opt.compat.no_msgcache = x; break;
case 1: opt.compat.no_pgpmime = x; break;
case 2: opt.compat.no_oom_write = x; break;
case 3: opt.compat.no_preview_info = x; break;
case 4: opt.compat.old_reply_hack = x; break;
case 5: opt.compat.auto_decrypt = x; break;
case 6: opt.compat.no_attestation = x; break;
case 7: opt.compat.use_mwfmo = x; break;
}
}
log_debug ("Note: using compatibility flags: %s", val);
}
if (!warnings_shown)
{
char tmpbuf[512];
warnings_shown = 1;
if (val && *val)
{
snprintf (tmpbuf, sizeof tmpbuf,
_("Note: Using compatibility flags: %s"), val);
MessageBox (NULL, tmpbuf, _("GpgOL"), MB_ICONWARNING|MB_OK);
}
}
xfree (val); val = NULL;
}
/* Write current options back to the Registry. */
int
write_options (void)
{
struct
{
const char *name;
int mode;
int value;
char *s_val;
} table[] = {
{"enableSmime", 0, opt.enable_smime, NULL},
{"encryptDefault", 0, opt.encrypt_default, NULL},
{"signDefault", 0, opt.sign_default, NULL},
{"logFile", 2, 0, (char*) get_log_file ()},
{"gitCommit", 4, opt.git_commit, NULL},
{"formsRevision", 1, opt.forms_revision, NULL},
{"announceNumber", 1, opt.announce_number, NULL},
{"inlinePGP", 0, opt.inline_pgp, NULL},
{"autoresolve", 0, opt.autoresolve, NULL},
{"replyCrypt", 0, opt.reply_crypt, NULL},
{"smimeHtmlWarnShown", 0, opt.smime_html_warn_shown, NULL},
{NULL, 0, 0, NULL}
};
char buf[32];
int rc, i;
const char *string;
for (i=0; table[i].name; i++)
{
switch (table[i].mode)
{
case 0:
string = table[i].value? "1": "0";
log_debug ("storing option `%s' value=`%s'\n",
table[i].name, string);
rc = store_extension_value (table[i].name, string);
break;
case 1:
sprintf (buf, "%d", table[i].value);
log_debug ("storing option `%s' value=`%s'\n",
table[i].name, buf);
rc = store_extension_value (table[i].name, buf);
break;
case 2:
string = table[i].s_val? table[i].s_val : "";
log_debug ("storing option `%s' value=`%s'\n",
table[i].name, string);
rc = store_extension_value (table[i].name, string);
break;
/* case 3: */
/* buf[0] = '0'; */
/* buf[1] = 0; */
/* switch (opt.default_protocol) */
/* { */
/* case PROTOCOL_UNKNOWN: buf[0] = '0'; /\* auto *\/ break; */
/* case PROTOCOL_OPENPGP: buf[0] = '1'; break; */
/* case PROTOCOL_SMIME: buf[0] = '2'; break; */
/* } */
/* log_debug ("storing option `%s' value=`%s'\n", */
/* table[i].name, buf); */
/* rc = store_extension_value (table[i].name, buf); */
/* break; */
case 4:
sprintf (buf, "0x%x", table[i].value);
log_debug ("storing option `%s' value=`%s'\n",
table[i].name, buf);
rc = store_extension_value (table[i].name, buf);
break;
default:
rc = -1;
break;
}
if (rc)
log_error ("error storing option `%s': rc = %d\n", table[i].name, rc);
}
return 0;
}
diff --git a/src/ribbon-callbacks.cpp b/src/ribbon-callbacks.cpp
index 450bc02..87ceab6 100644
--- a/src/ribbon-callbacks.cpp
+++ b/src/ribbon-callbacks.cpp
@@ -1,708 +1,733 @@
/* ribbon-callbacks.h - Callbacks for the ribbon extension interface
* Copyright (C) 2013 Intevation GmbH
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include "ribbon-callbacks.h"
#include "gpgoladdin.h"
#include "common.h"
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
#include "mapihelp.h"
#include "mimemaker.h"
#include "filetype.h"
#include "mail.h"
#include
#include
using namespace GpgME;
/* Gets the context of a ribbon control. And prints some
useful debug output */
HRESULT getContext (LPDISPATCH ctrl, LPDISPATCH *context)
{
*context = get_oom_object (ctrl, "get_Context");
if (*context)
{
char *name = get_object_name (*context);
log_debug ("%s:%s: contextObj: %s",
SRCNAME, __func__, name);
xfree (name);
}
return context ? S_OK : E_FAIL;
}
/* getIcon
Loads a PNG image from the resurce converts it into a Bitmap
and Wraps it in an PictureDispatcher that is returned as result.
Based on documentation from:
http://www.codeproject.com/Articles/3537/Loading-JPG-PNG-resources-using-GDI
*/
HRESULT
getIcon (int id, VARIANT* result)
{
PICTDESC pdesc;
LPDISPATCH pPict;
HRESULT hr;
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::Bitmap* pbitmap;
ULONG_PTR gdiplusToken;
HRSRC hResource;
DWORD imageSize;
const void* pResourceData;
HGLOBAL hBuffer;
memset (&pdesc, 0, sizeof pdesc);
pdesc.cbSizeofstruct = sizeof pdesc;
pdesc.picType = PICTYPE_BITMAP;
if (!result)
{
log_error ("getIcon called without result variant.");
return E_POINTER;
}
/* Initialize GDI */
gdiplusStartupInput.DebugEventCallback = NULL;
gdiplusStartupInput.SuppressBackgroundThread = FALSE;
gdiplusStartupInput.SuppressExternalCodecs = FALSE;
gdiplusStartupInput.GdiplusVersion = 1;
GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL);
/* Get the image from the resource file */
hResource = FindResource (glob_hinst, MAKEINTRESOURCE(id), RT_RCDATA);
if (!hResource)
{
log_error ("%s:%s: failed to find image: %i",
SRCNAME, __func__, id);
return E_FAIL;
}
imageSize = SizeofResource (glob_hinst, hResource);
if (!imageSize)
return E_FAIL;
pResourceData = LockResource (LoadResource(glob_hinst, hResource));
if (!pResourceData)
{
log_error ("%s:%s: failed to load image: %i",
SRCNAME, __func__, id);
return E_FAIL;
}
hBuffer = GlobalAlloc (GMEM_MOVEABLE, imageSize);
if (hBuffer)
{
void* pBuffer = GlobalLock (hBuffer);
if (pBuffer)
{
IStream* pStream = NULL;
CopyMemory (pBuffer, pResourceData, imageSize);
if (CreateStreamOnHGlobal (hBuffer, FALSE, &pStream) == S_OK)
{
pbitmap = Gdiplus::Bitmap::FromStream (pStream);
gpgol_release (pStream);
if (!pbitmap || pbitmap->GetHBITMAP (0, &pdesc.bmp.hbitmap))
{
log_error ("%s:%s: failed to get PNG.",
SRCNAME, __func__);
}
}
}
GlobalUnlock (pBuffer);
}
GlobalFree (hBuffer);
Gdiplus::GdiplusShutdown (gdiplusToken);
/* Wrap the image into an OLE object. */
hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp,
TRUE, (void **) &pPict);
if (hr != S_OK || !pPict)
{
log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
result->pdispVal = pPict;
result->vt = VT_DISPATCH;
return S_OK;
}
HRESULT
mark_mime_action (LPDISPATCH ctrl, int flags, bool is_explorer)
{
- HRESULT hr;
- HRESULT rc = E_FAIL;
- LPDISPATCH context = NULL,
- mailitem = NULL;
LPMESSAGE message = NULL;
int oldflags,
newflags;
log_debug ("%s:%s: enter", SRCNAME, __func__);
- hr = getContext (ctrl, &context);
- if (FAILED(hr))
- return hr;
+ LPDISPATCH context = NULL;
+ if (FAILED(getContext (ctrl, &context)))
+ {
+ TRACEPOINT;
+ return E_FAIL;
+ }
- mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" :
- "CurrentItem");
+ LPDISPATCH mailitem = get_oom_object (context,
+ is_explorer ? "ActiveInlineResponse" :
+ "CurrentItem");
+ gpgol_release (context);
if (!mailitem)
{
log_error ("%s:%s: Failed to get mailitem.",
SRCNAME, __func__);
- goto done;
+ return E_FAIL;
+ }
+
+ /* Get the uid of this item. */
+ char *uid = get_unique_id (mailitem, 0, nullptr);
+ if (!uid)
+ {
+ LPMESSAGE msg = get_oom_base_message (mailitem);
+ uid = mapi_get_uid (msg);
+ gpgol_release (msg);
+ if (!uid)
+ {
+ log_debug ("%s:%s: Failed to get uid for %p",
+ SRCNAME, __func__, mailitem);
+ }
+ }
+ Mail *mail = nullptr;
+ if (uid)
+ {
+ mail = Mail::get_mail_for_uuid (uid);
+ xfree (uid);
+ }
+
+ if (mail)
+ {
+ mail->set_crypto_selected_manually (true);
+ }
+ else
+ {
+ log_debug ("%s:%s: Failed to get mail object.",
+ SRCNAME, __func__);
}
message = get_oom_base_message (mailitem);
+ gpgol_release (mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
- goto done;
+ return S_OK;
}
oldflags = get_gpgol_draft_info_flags (message);
-
if (flags == 3 && oldflags != 3)
{
// If only one sub button is active activate
// both now.
newflags = 3;
}
else
{
newflags = oldflags xor flags;
}
if (set_gpgol_draft_info_flags (message, newflags))
{
log_error ("%s:%s: Failed to set draft flags.",
SRCNAME, __func__);
}
-
- rc = S_OK;
+ gpgol_release (message);
/* We need to invalidate the UI to update the toggle
states of the subbuttons and the top button. Yeah,
we invalidate a lot *sigh* */
gpgoladdin_invalidate_ui ();
if (newflags & 1)
{
Mail::locate_all_crypto_recipients ();
}
-done:
- gpgol_release (context);
- gpgol_release (mailitem);
- gpgol_release (message);
-
- return rc;
+ return S_OK;
}
/* Get the state of encrypt / sign toggle buttons.
flag values: 1 get the state of the encrypt button.
2 get the state of the sign button.
If is_explorer is set to true we look at the inline response.
*/
HRESULT get_crypt_pressed (LPDISPATCH ctrl, int flags, VARIANT *result,
bool is_explorer)
{
HRESULT hr;
bool value;
LPDISPATCH context = NULL,
mailitem = NULL;
LPMESSAGE message = NULL;
result->vt = VT_BOOL | VT_BYREF;
result->pboolVal = (VARIANT_BOOL*) xmalloc (sizeof (VARIANT_BOOL));
*(result->pboolVal) = VARIANT_FALSE;
/* First the usual defensive check about our parameters */
if (!ctrl || !result)
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return E_FAIL;
}
hr = getContext (ctrl, &context);
if (hr)
{
log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
hr);
return E_FAIL;
}
mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" :
"CurrentItem");
if (!mailitem)
{
log_error ("%s:%s: Failed to get mailitem.",
SRCNAME, __func__);
goto done;
}
message = get_oom_base_message (mailitem);
if (!message)
{
log_error ("%s:%s: No message found.",
SRCNAME, __func__);
goto done;
}
value = (get_gpgol_draft_info_flags (message) & flags) == flags;
*(result->pboolVal) = value ? VARIANT_TRUE : VARIANT_FALSE;
done:
gpgol_release (context);
gpgol_release (mailitem);
gpgol_release (message);
return S_OK;
}
static Mail *
get_mail_from_control (LPDISPATCH ctrl, bool *none_selected)
{
HRESULT hr;
LPDISPATCH context = NULL,
mailitem = NULL;
*none_selected = false;
if (!ctrl)
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return NULL;
}
hr = getContext (ctrl, &context);
if (hr)
{
log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
hr);
return NULL;
}
const auto ctx_name = std::string (get_object_name (context));
if (ctx_name.empty())
{
log_error ("%s:%s: Failed to get context name",
SRCNAME, __func__);
gpgol_release (context);
return NULL;
}
if (!strcmp (ctx_name.c_str(), "_Inspector"))
{
mailitem = get_oom_object (context, "CurrentItem");
}
else if (!strcmp (ctx_name.c_str(), "_Explorer"))
{
if (g_ol_version_major >= 16)
{
// Avoid showing wrong crypto state if we don't have a reading
// pane. In that case the parser will finish for a mail which is gone
// and the crypto state will not get updated.
//
//
// Somehow latest Outlook 2016 crashes when accessing the current view
// of the Explorer. This is even reproducible with
// GpgOL disabled and only with Outlook Spy active. If you select
// the explorer of an Outlook.com resource and then access
// the CurrentView and close the CurrentView again in Outlook Spy
// outlook crashes.
LPDISPATCH prevEdit = get_oom_object (context, "PreviewPane.WordEditor");
gpgol_release (prevEdit);
if (!prevEdit)
{
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
}
}
else
{
// Preview Pane is not available in older outlooks
LPDISPATCH tableView = get_oom_object (context, "CurrentView");
if (!tableView)
{
// Woops, should not happen.
TRACEPOINT;
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
}
else
{
int hasReadingPane = get_oom_bool (tableView, "ShowReadingPane");
gpgol_release (tableView);
if (!hasReadingPane)
{
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
}
}
}
if (!*none_selected)
{
/* Accessing the selection item can trigger a load event
so we only do this here if we think that there might be
something visible / selected. To avoid triggering a load
if there is no content shown. */
LPDISPATCH selection = get_oom_object (context, "Selection");
if (!selection)
{
log_error ("%s:%s: Failed to get selection.",
SRCNAME, __func__);
gpgol_release (context);
return NULL;
}
int count = get_oom_int (selection, "Count");
if (count == 1)
{
// If we call this on a selection with more items
// Outlook sends an ItemLoad event for each mail
// in that selection.
mailitem = get_oom_object (selection, "Item(1)");
}
gpgol_release (selection);
if (!mailitem)
{
*none_selected = true;
}
}
}
else if (!strcmp (ctx_name.c_str(), "Selection"))
{
int count = get_oom_int (context, "Count");
if (count == 1)
{
// If we call this on a selection with more items
// Outlook sends an ItemLoad event for each mail
// in that selection.
mailitem = get_oom_object (context, "Item(1)");
}
if (!mailitem)
{
*none_selected = true;
}
}
gpgol_release (context);
if (!mailitem)
{
log_debug ("%s:%s: No mailitem. From %s",
SRCNAME, __func__, ctx_name.c_str());
return NULL;
}
char *uid;
/* Get the uid of this item. */
uid = get_unique_id (mailitem, 0, nullptr);
if (!uid)
{
LPMESSAGE msg = get_oom_base_message (mailitem);
uid = mapi_get_uid (msg);
gpgol_release (msg);
if (!uid)
{
log_debug ("%s:%s: Failed to get uid for %p",
SRCNAME, __func__, mailitem);
gpgol_release (mailitem);
return NULL;
}
}
auto ret = Mail::get_mail_for_uuid (uid);
xfree (uid);
if (!ret)
{
log_error ("%s:%s: Failed to find mail %p in map.",
SRCNAME, __func__, mailitem);
}
gpgol_release (mailitem);
return ret;
}
/* Helper to reduce code duplication.*/
#define MY_MAIL_GETTER \
if (!ctrl) \
{ \
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); \
return E_FAIL; \
} \
bool none_selected; \
const auto mail = get_mail_from_control (ctrl, &none_selected); \
(void)none_selected; \
if (!mail) \
{ \
log_oom ("%s:%s:%i Failed to get mail", \
SRCNAME, __func__, __LINE__); \
}
HRESULT get_is_details_enabled (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
if (!result)
{
TRACEPOINT;
return S_OK;
}
result->vt = VT_BOOL | VT_BYREF;
result->pboolVal = (VARIANT_BOOL*) xmalloc (sizeof (VARIANT_BOOL));
*(result->pboolVal) = none_selected ? VARIANT_FALSE : VARIANT_TRUE;
return S_OK;
}
HRESULT get_sig_label (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BSTR;
wchar_t *w_result;
if (!mail)
{
log_debug ("%s:%s: No mail.",
SRCNAME, __func__);
w_result = utf8_to_wchar (_("Insecure"));
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
return S_OK;
}
w_result = utf8_to_wchar (mail->get_crypto_summary ().c_str ());
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
return S_OK;
}
HRESULT get_sig_ttip (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BSTR;
wchar_t *w_result;
if (mail)
{
w_result = utf8_to_wchar (mail->get_crypto_one_line().c_str());
}
else if (!none_selected)
{
w_result = utf8_to_wchar (_("Insecure message"));
}
else
{
w_result = utf8_to_wchar (_("No message selected"));
}
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
return S_OK;
}
HRESULT get_sig_stip (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BSTR;
if (none_selected)
{
result->bstrVal = SysAllocString (L"");
return S_OK;
}
if (!mail || !mail->is_crypto_mail ())
{
wchar_t *w_result;
w_result = utf8_to_wchar (utf8_gettext ("You cannot be sure who sent, "
"modified and read the message in transit.\n\n"
"Click here to learn more."));
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
return S_OK;
}
const auto message = mail->get_crypto_details ();
wchar_t *w_message = utf8_to_wchar (message.c_str());
result->bstrVal = SysAllocString (w_message);
xfree (w_message);
return S_OK;
}
HRESULT launch_cert_details (LPDISPATCH ctrl)
{
MY_MAIL_GETTER
if (!mail || (!mail->is_signed () && !mail->is_encrypted ()))
{
ShellExecuteA(NULL, NULL, "https://emailselfdefense.fsf.org/infographic",
0, 0, SW_SHOWNORMAL);
return S_OK;
}
if (!mail->is_signed () && mail->is_encrypted ())
{
/* Encrypt only, no information but show something. because
we want the button to be active.
Aheinecke: I don't think we should show to which keys the message
is encrypted here. This would confuse users if they see keyids
of unknown keys and the information can't be "true" because the
sender could have sent the same information to other people or
used throw keyids etc.
*/
char * buf;
gpgrt_asprintf (&buf, _("The message was not cryptographically signed.\n"
"There is no additional information available if it "
"was actually sent by '%s' or if someone faked the sender address."), mail->get_sender ().c_str());
MessageBox (NULL, buf, _("GpgOL"),
MB_ICONINFORMATION|MB_OK);
xfree (buf);
return S_OK;
}
if (!mail->get_sig_fpr())
{
std::string buf = _("There was an error verifying the signature.\n"
"Full details:\n");
buf += mail->get_verification_result_dump();
gpgol_message_box (get_active_hwnd(), buf.c_str(), _("GpgOL"), MB_OK);
}
char *uiserver = get_uiserver_name ();
bool showError = false;
if (uiserver)
{
std::string path (uiserver);
xfree (uiserver);
if (path.find("kleopatra.exe") != std::string::npos)
{
size_t dpos;
if ((dpos = path.find(" --daemon")) != std::string::npos)
{
path.erase(dpos, strlen(" --daemon"));
}
auto ctx = Context::createForEngine(SpawnEngine);
if (!ctx)
{
log_error ("%s:%s: No spawn engine.",
SRCNAME, __func__);
}
std::string parentWid = std::to_string ((int) (intptr_t) get_active_hwnd ());
const char *argv[] = {path.c_str(),
"--query",
mail->get_sig_fpr(),
"--parent-windowid",
parentWid.c_str(),
NULL };
log_debug ("%s:%s: Starting %s %s %s",
SRCNAME, __func__, path.c_str(), argv[1], argv[2]);
Data d(Data::null);
ctx->spawnAsync(path.c_str(), argv, d, d,
d, (GpgME::Context::SpawnFlags) (
GpgME::Context::SpawnAllowSetFg |
GpgME::Context::SpawnShowWindow));
}
else
{
showError = true;
}
}
else
{
showError = true;
}
if (showError)
{
MessageBox (NULL,
_("Could not find Kleopatra.\n"
"Please reinstall Gpg4win with the Kleopatra component enabled."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
}
return S_OK;
}
HRESULT get_crypto_icon (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
if (mail)
{
return getIcon (mail->get_crypto_icon_id (), result);
}
return getIcon (IDI_LEVEL_0, result);
}
HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BOOL | VT_BYREF;
result->pboolVal = (VARIANT_BOOL*) xmalloc (sizeof (VARIANT_BOOL));
*(result->pboolVal) = (mail && (mail->is_signed () || mail->is_encrypted ())) ?
VARIANT_TRUE : VARIANT_FALSE;
return S_OK;
}
HRESULT print_decrypted (LPDISPATCH ctrl)
{
MY_MAIL_GETTER
if (!mail)
{
log_error ("%s:%s: Failed to get mail.",
SRCNAME, __func__);
return S_OK;
}
invoke_oom_method (mail->item(), "PrintOut", NULL);
return S_OK;
}
diff --git a/src/windowmessages.cpp b/src/windowmessages.cpp
index 36c0631..082dfa8 100644
--- a/src/windowmessages.cpp
+++ b/src/windowmessages.cpp
@@ -1,412 +1,436 @@
/* @file windowmessages.h
* @brief Helper class to work with the windowmessage handler thread.
*
* 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 .
*/
#include "windowmessages.h"
#include "common.h"
#include "oomhelp.h"
#include "mail.h"
#include "gpgoladdin.h"
#include "wks-helper.h"
#include
#define RESPONDER_CLASS_NAME "GpgOLResponder"
/* Singleton window */
static HWND g_responder_window = NULL;
LONG_PTR WINAPI
gpgol_window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
if (message == WM_USER + 42)
{
wm_ctx_t *ctx = (wm_ctx_t *) lParam;
log_debug ("%s:%s: Recieved user msg: %i",
SRCNAME, __func__, ctx->wmsg_type);
switch (ctx->wmsg_type)
{
case (PARSING_DONE):
{
auto mail = (Mail*) ctx->data;
if (!Mail::is_valid_ptr (mail))
{
log_debug ("%s:%s: Parsing done for mail which is gone.",
SRCNAME, __func__);
break;
}
mail->parsing_done();
break;
}
case (RECIPIENT_ADDED):
{
auto mail = (Mail*) ctx->data;
if (!Mail::is_valid_ptr (mail))
{
log_debug ("%s:%s: Recipient add for mail which is gone.",
SRCNAME, __func__);
break;
}
mail->locate_keys();
break;
}
case (REVERT_MAIL):
{
auto mail = (Mail*) ctx->data;
if (!Mail::is_valid_ptr (mail))
{
log_debug ("%s:%s: Revert mail for mail which is gone.",
SRCNAME, __func__);
break;
}
mail->set_needs_save (true);
/* Some magic here. Accessing any existing inline body cements
it. Otherwise updating the body through the revert also changes
the body of a inline mail. */
char *inlineBody = get_inline_body ();
xfree (inlineBody);
// Does the revert.
log_debug ("%s:%s: Revert mail. Invoking save.",
SRCNAME, __func__);
invoke_oom_method (mail->item (), "Save", NULL);
log_debug ("%s:%s: Revert mail. Save done. Updating body..",
SRCNAME, __func__);
mail->update_body ();
log_debug ("%s:%s: Revert mail done.",
SRCNAME, __func__);
break;
}
case (INVALIDATE_UI):
{
log_debug ("%s:%s: Invalidating UI",
SRCNAME, __func__);
gpgoladdin_invalidate_ui();
log_debug ("%s:%s: Invalidation done",
SRCNAME, __func__);
break;
}
case (INVALIDATE_LAST_MAIL):
{
log_debug ("%s:%s: Invalidating last mail",
SRCNAME, __func__);
Mail::invalidate_last_mail ();
break;
}
case (CLOSE):
{
auto mail = (Mail*) ctx->data;
if (!Mail::is_valid_ptr (mail))
{
log_debug ("%s:%s: Close for mail which is gone.",
SRCNAME, __func__);
break;
}
Mail::close (mail);
break;
}
case (CRYPTO_DONE):
{
auto mail = (Mail*) ctx->data;
if (!Mail::is_valid_ptr (mail))
{
log_debug ("%s:%s: Crypto done for mail which is gone.",
SRCNAME, __func__);
break;
}
// modify the mail.
if (mail->crypt_state () == Mail::NeedsUpdateInOOM)
{
// Save the Mail
log_debug ("%s:%s: Crypto done for %p updating oom.",
SRCNAME, __func__, mail);
mail->update_crypt_oom();
}
if (mail->crypt_state () == Mail::NeedsSecondAfterWrite)
{
invoke_oom_method (mail->item (), "Save", NULL);
log_debug ("%s:%s: Second save done for %p Invoking second send.",
SRCNAME, __func__, mail);
}
// Finaly this should pass.
invoke_oom_method (mail->item (), "Send", NULL);
log_debug ("%s:%s: Send for %p completed.",
SRCNAME, __func__, mail);
// Allow the WKS helper to queue a notification.
WKSHelper::instance()->allow_notify ();
log_debug ("%s:%s: Crypto done handler completed.",
SRCNAME, __func__);
break;
}
case (BRING_TO_FRONT):
{
// We want to avoid a race where the mail window
// was still the active window.
Sleep (250);
HWND wnd = get_active_hwnd ();
if (wnd)
{
log_debug ("%s:%s: Bringing window %p to front.",
SRCNAME, __func__, wnd);
bring_to_front (wnd);
}
else
{
log_debug ("%s:%s: No active window found for bring to front.",
SRCNAME, __func__);
}
break;
}
case (WKS_NOTIFY):
{
WKSHelper::instance ()->notify ((const char *) ctx->data);
xfree (ctx->data);
break;
}
case (CLEAR_REPLY_FORWARD):
{
auto mail = (Mail*) ctx->data;
if (!Mail::is_valid_ptr (mail))
{
log_debug ("%s:%s: Clear reply forward for mail which is gone.",
SRCNAME, __func__);
break;
}
mail->wipe (true);
mail->remove_all_attachments ();
break;
}
+ case (DO_AUTO_SECURE):
+ {
+ auto mail = (Mail*) ctx->data;
+ if (!Mail::is_valid_ptr (mail))
+ {
+ log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
+ SRCNAME, __func__);
+ break;
+ }
+ mail->set_do_autosecure_mapi (true);
+ break;
+ }
+ case (DONT_AUTO_SECURE):
+ {
+ auto mail = (Mail*) ctx->data;
+ if (!Mail::is_valid_ptr (mail))
+ {
+ log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
+ SRCNAME, __func__);
+ break;
+ }
+ mail->set_do_autosecure_mapi (false);
+ break;
+ }
default:
log_debug ("%s:%s: Unknown msg %x",
SRCNAME, __func__, ctx->wmsg_type);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
HWND
create_responder_window ()
{
size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
char cls_name[cls_name_len];
if (g_responder_window)
{
return g_responder_window;
}
/* Create Window wants a mutable string as the first parameter */
snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
WNDCLASS windowClass;
windowClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
windowClass.lpfnWndProc = gpgol_window_proc;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = (HINSTANCE) GetModuleHandle(NULL);
windowClass.hIcon = 0;
windowClass.hCursor = 0;
windowClass.hbrBackground = 0;
windowClass.lpszMenuName = 0;
windowClass.lpszClassName = cls_name;
RegisterClass(&windowClass);
g_responder_window = CreateWindow (cls_name, RESPONDER_CLASS_NAME, 0, 0, 0,
0, 0, 0, (HMENU) 0,
(HINSTANCE) GetModuleHandle(NULL), 0);
return g_responder_window;
}
int
send_msg_to_ui_thread (wm_ctx_t *ctx)
{
size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
char cls_name[cls_name_len];
snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
HWND responder = FindWindow (cls_name, RESPONDER_CLASS_NAME);
if (!responder)
{
log_error ("%s:%s: Failed to find responder window.",
SRCNAME, __func__);
return -1;
}
SendMessage (responder, WM_USER + 42, 0, (LPARAM) ctx);
return 0;
}
int
do_in_ui_thread (gpgol_wmsg_type type, void *data)
{
wm_ctx_t ctx = {NULL, UNKNOWN, 0, 0};
ctx.wmsg_type = type;
ctx.data = data;
if (send_msg_to_ui_thread (&ctx))
{
return -1;
}
return ctx.err;
}
static DWORD WINAPI
do_async (LPVOID arg)
{
wm_ctx_t *ctx = (wm_ctx_t*) arg;
log_debug ("%s:%s: Do async with type %i after %i ms",
SRCNAME, __func__, ctx ? ctx->wmsg_type : -1,
ctx->delay);
if (ctx->delay)
{
Sleep (ctx->delay);
}
send_msg_to_ui_thread (ctx);
xfree (ctx);
return 0;
}
void
do_in_ui_thread_async (gpgol_wmsg_type type, void *data, int delay)
{
wm_ctx_t *ctx = (wm_ctx_t *) calloc (1, sizeof (wm_ctx_t));
ctx->wmsg_type = type;
ctx->data = data;
ctx->delay = delay;
CloseHandle (CreateThread (NULL, 0, do_async, (LPVOID) ctx, 0, NULL));
}
static std::vector explorers;
void
add_explorer (LPDISPATCH explorer)
{
explorers.push_back (explorer);
}
void remove_explorer (LPDISPATCH explorer)
{
explorers.erase(std::remove(explorers.begin(),
explorers.end(),
explorer),
explorers.end());
}
LRESULT CALLBACK
gpgol_hook(int code, WPARAM wParam, LPARAM lParam)
{
/* Once we are in the close events we don't have enough
control to revert all our changes so we have to do it
with this nice little hack by catching the WM_CLOSE message
before it reaches outlook. */
LPCWPSTRUCT cwp = (LPCWPSTRUCT) lParam;
switch (cwp->message)
{
case WM_CLOSE:
{
HWND lastChild = NULL;
for (const auto explorer: explorers)
{
/* Casting to LPOLEWINDOW and calling GetWindow
succeeded in Outlook 2016 but always returned
the number 1. So we need this hack. */
char *caption = get_oom_string (explorer, "Caption");
if (!caption)
{
log_debug ("%s:%s: No caption.",
SRCNAME, __func__);
continue;
}
/* rctrl_renwnd32 is the window class of outlook. */
HWND hwnd = FindWindowExA(NULL, lastChild, "rctrl_renwnd32",
caption);
xfree (caption);
lastChild = hwnd;
if (hwnd == cwp->hwnd)
{
log_debug ("%s:%s: WM_CLOSE windowmessage for explorer. "
"Closing all mails.",
SRCNAME, __func__);
Mail::close_all_mails();
break;
}
}
break;
}
case WM_SYSCOMMAND:
/*
This comes to often and when we are closed from the icon
we also get WM_CLOSE
if (cwp->wParam == SC_CLOSE)
{
log_debug ("%s:%s: SC_CLOSE syscommand. Closing all mails.",
SRCNAME, __func__);
Mail::close_all_mails();
} */
break;
default:
break;
}
return CallNextHookEx (NULL, code, wParam, lParam);
}
/* Create the message hook for outlook's windowmessages
we are especially interested in WM_QUIT to do cleanups
and prevent the "Item has changed" question. */
HHOOK
create_message_hook()
{
return SetWindowsHookEx (WH_CALLWNDPROC,
gpgol_hook,
NULL,
GetCurrentThreadId());
}
gpgrt_lock_t invalidate_lock = GPGRT_LOCK_INITIALIZER;
static bool invalidation_in_progress;
DWORD WINAPI
delayed_invalidate_ui (LPVOID)
{
if (invalidation_in_progress)
{
log_debug ("%s:%s: Invalidation canceled as it is in progress.",
SRCNAME, __func__);
return 0;
}
gpgrt_lock_lock(&invalidate_lock);
invalidation_in_progress = true;
/* We sleep here a bit to prevent invalidation immediately
after the selection change before we have started processing
the mail. */
Sleep (250);
do_in_ui_thread (INVALIDATE_UI, nullptr);
invalidation_in_progress = false;
gpgrt_lock_unlock(&invalidate_lock);
return 0;
}
DWORD WINAPI
close_mail (LPVOID mail)
{
do_in_ui_thread (CLOSE, mail);
return 0;
}
diff --git a/src/windowmessages.h b/src/windowmessages.h
index dcc372c..6a96a13 100644
--- a/src/windowmessages.h
+++ b/src/windowmessages.h
@@ -1,105 +1,107 @@
/* windowmessages.h - Helper functions for Window message exchange.
* 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 WINDOWMESSAGES_H
#define WINDOWMESSAGES_H
#include
#include "config.h"
#include
/** Window Message handling for GpgOL.
In Outlook only one thread has access to the Outlook Object model
and this is the UI Thread. We can work in other threads but
to do something with outlooks data we neet to be in the UI Thread.
So we create a hidden Window in this thread and use the fact
that SendMessage handles Window messages in the thread where the
Window was created.
This way we can go back to interactct with the Outlook from another
thread without working with COM Multithreading / Marshaling.
The Responder Window should be initalized on startup.
*/
typedef enum _gpgol_wmsg_type
{
UNKNOWN = 1100, /* A large offset to avoid conflicts */
INVALIDATE_UI, /* The UI should be invalidated. */
PARSING_DONE, /* A mail was parsed. Data should be a pointer
to the mail object. */
RECIPIENT_ADDED, /* A recipient was added. Data should be ptr
to mail */
CLOSE, /* Close the message in the next event loop. */
CRYPTO_DONE, /* Sign / Encrypt done. */
WKS_NOTIFY, /* Show a WKS Notification. */
BRING_TO_FRONT, /* Bring the active Outlook window to the front. */
INVALIDATE_LAST_MAIL,
REVERT_MAIL,
CLEAR_REPLY_FORWARD,
+ DO_AUTO_SECURE,
+ DONT_AUTO_SECURE,
} gpgol_wmsg_type;
typedef struct
{
void *data; /* Pointer to arbitrary data depending on msg type */
gpgol_wmsg_type wmsg_type; /* Type of the msg. */
int err; /* Set to true on error */
int delay;
} wm_ctx_t;
/** Create and register the responder window.
The responder window should be */
HWND
create_responder_window ();
/** Send a message to the UI thread through the responder Window.
Returns 0 on success. */
int
send_msg_to_ui_thread (wm_ctx_t *ctx);
/** Uses send_msg_to_ui_thread to execute the request
in the ui thread. Returns the result. */
int
do_in_ui_thread (gpgol_wmsg_type type, void *data);
/** Send a message to the UI thread but returns
immediately without waiting for the execution.
The delay is used in the detached thread to delay
the sending of the actual message. */
void
do_in_ui_thread_async (gpgol_wmsg_type type, void *data, int delay = 0);
/** Create our filter before outlook Window Messages. */
HHOOK
create_message_hook();
DWORD WINAPI
delayed_invalidate_ui (LPVOID);
DWORD WINAPI
close_mail (LPVOID);
void add_explorer (LPDISPATCH explorer);
void remove_explorer (LPDISPATCH explorer);
/* The lock to invalide the ui */
extern gpgrt_lock_t invalidate_lock;
#endif // WINDOWMESSAGES_H