diff --git a/src/addressbook.cpp b/src/addressbook.cpp index b5fbc83..8de852e 100644 --- a/src/addressbook.cpp +++ b/src/addressbook.cpp @@ -1,522 +1,522 @@ /* addressbook.cpp - Functions for the Addressbook * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "addressbook.h" #include "oomhelp.h" #include "keycache.h" #include "mail.h" #include "cpphelp.h" #include "windowmessages.h" #include "recipient.h" #include #include #include #include typedef struct { std::string name; std::string pgp_data; std::string cms_data; std::string entry_id; HWND hwnd; shared_disp_t contact; } keyadder_args_t; static std::set s_checked_entries; static std::vector s_opened_contacts_vec; GPGRT_LOCK_DEFINE (s_opened_contacts_lock); static Addressbook::callback_args_t parse_output (const std::string &output) { Addressbook::callback_args_t ret; std::istringstream ss(output); std::string line; std::string pgp_data; std::string cms_data; bool in_pgp_data = false; bool in_options = false; bool in_cms_data = false; while (std::getline (ss, line)) { rtrim (line); if (in_pgp_data) { if (line == "empty") { pgp_data = ""; } else if (line != "END KEYADDER PGP DATA") { pgp_data += line + std::string("\n"); } else { in_pgp_data = false; } } else if (in_cms_data) { if (line == "empty") { in_cms_data = false; cms_data = ""; } else if (line != "END KEYADDER CMS DATA") { cms_data += line + std::string("\n"); } else { in_cms_data = false; } } else if (in_options) { if (line == "END KEYADDER OPTIONS") { in_options = false; continue; } std::istringstream lss (line); std::string key, value; std::getline (lss, key, '='); std::getline (lss, value, '='); if (key == "secure") { int val = atoi (value.c_str()); if (val > 3 || val < 0) { log_error ("%s:%s: Loading secure value: %s failed", SRCNAME, __func__, value.c_str ()); continue; } ret.crypto_flags = val; } else { log_debug ("%s:%s: Unknown setting: %s", SRCNAME, __func__, key.c_str ()); } continue; } else { if (line == "BEGIN KEYADDER OPTIONS") { in_options = true; } else if (line == "BEGIN KEYADDER CMS DATA") { in_cms_data = true; } else if (line == "BEGIN KEYADDER PGP DATA") { in_pgp_data = true; } else { log_debug ("%s:%s: Unknown line: %s", SRCNAME, __func__, line.c_str ()); } } } ret.pgp_data = xstrdup (pgp_data.c_str ()); ret.cms_data = xstrdup (cms_data.c_str ()); return ret; } static DWORD WINAPI open_keyadder (LPVOID arg) { TSTART; auto adder_args = std::unique_ptr ((keyadder_args_t*) arg); std::vector args; // Collect the arguments char *gpg4win_dir = get_gpg4win_dir (); if (!gpg4win_dir) { TRACEPOINT; TRETURN -1; } const auto keyadder = std::string (gpg4win_dir) + "\\bin\\gpgolkeyadder.exe"; args.push_back (keyadder); args.push_back (std::string ("--hwnd")); args.push_back (std::to_string ((int) (intptr_t) adder_args->hwnd)); args.push_back (std::string ("--username")); args.push_back (adder_args->name); if (opt.enable_smime) { args.push_back (std::string ("--cms")); } auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); if (!ctx) { // can't happen TRACEPOINT; TRETURN -1; } std::string input = adder_args->pgp_data; input += "BEGIN CMS DATA\n"; input += adder_args->cms_data; GpgME::Data mystdin (input.c_str(), input.size(), false); GpgME::Data mystdout, mystderr; char **cargs = vector_to_cArray (args); log_data ("%s:%s: launching keyadder args:", SRCNAME, __func__); for (size_t i = 0; cargs && cargs[i]; i++) { log_data (SIZE_T_FORMAT ": '%s'", i, cargs[i]); } GpgME::Error err = ctx->spawn (cargs[0], const_cast (cargs), mystdin, mystdout, mystderr, (GpgME::Context::SpawnFlags) ( GpgME::Context::SpawnAllowSetFg | GpgME::Context::SpawnShowWindow)); release_cArray (cargs); if (!adder_args->entry_id.empty ()) { log_dbg ("Removing entry from list of opened contacts."); gpgrt_lock_lock (&s_opened_contacts_lock); const auto id = adder_args->entry_id; s_opened_contacts_vec.erase ( std::remove_if (s_opened_contacts_vec.begin(), s_opened_contacts_vec.end (), [id] (const auto &val) { return val == id; })); gpgrt_lock_unlock (&s_opened_contacts_lock); } if (err) { log_error ("%s:%s: Err code: %i asString: %s", SRCNAME, __func__, err.code(), err.asString()); TRETURN 0; } auto output = mystdout.toString (); rtrim(output); if (output.empty()) { log_debug ("%s:%s: keyadder canceled.", SRCNAME, __func__); TRETURN 0; } Addressbook::callback_args_t cb_args = parse_output (output); cb_args.contact = adder_args->contact; do_in_ui_thread (CONFIG_KEY_DONE, (void*) &cb_args); xfree (cb_args.pgp_data); xfree (cb_args.cms_data); TRETURN 0; } void Addressbook::update_key_o (void *callback_args) { TSTART; if (!callback_args) { TRACEPOINT; TRETURN; } callback_args_t *cb_args = static_cast (callback_args); LPDISPATCH contact = cb_args->contact.get(); LPDISPATCH user_props = get_oom_object (contact, "UserProperties"); if (!user_props) { TRACEPOINT; TRETURN; } LPDISPATCH pgp_key = find_or_add_text_prop (user_props, "OpenPGP Key"); if (!pgp_key) { TRACEPOINT; gpgol_release (user_props); TRETURN; } put_oom_string (pgp_key, "Value", cb_args->pgp_data); gpgol_release (pgp_key); log_debug ("%s:%s: PGP key data updated", SRCNAME, __func__); if (opt.enable_smime) { LPDISPATCH cms_data = find_or_add_text_prop (user_props, "GpgOL CMS Cert"); if (!cms_data) { TRACEPOINT; gpgol_release (user_props); TRETURN; } put_oom_string (cms_data, "Value", cb_args->cms_data); gpgol_release (cms_data); log_debug ("%s:%s: CMS key data updated", SRCNAME, __func__); } gpgol_release (user_props); s_checked_entries.clear (); TRETURN; } void Addressbook::edit_key_o (LPDISPATCH contact) { TSTART; if (!contact) { TRACEPOINT; TRETURN; } /* First check that we have not already opened an editor. This can happen when starting the editor takes a while and the user clicks multiple times because nothing happens. */ const auto entry_id = get_oom_string_s (contact, "EntryID"); if (!entry_id.empty()) { gpgrt_lock_lock (&s_opened_contacts_lock); if (std::find (s_opened_contacts_vec.begin (), s_opened_contacts_vec.end (), entry_id) != s_opened_contacts_vec.end ()) { log_dbg ("Contact already opened."); /* TODO: Find the window if it exists and bring it to front. */ gpgrt_lock_unlock (&s_opened_contacts_lock); TRETURN; } gpgrt_lock_unlock (&s_opened_contacts_lock); } else { log_dbg ("Empty EntryID."); } auto user_props = MAKE_SHARED (get_oom_object (contact, "UserProperties")); if (!user_props) { TRACEPOINT; TRETURN; } auto pgp_key = MAKE_SHARED (find_or_add_text_prop (user_props.get (), "OpenPGP Key")); if (!pgp_key) { TRACEPOINT; TRETURN; } char *key_data = get_oom_string (pgp_key.get(), "Value"); if (!key_data) { TRACEPOINT; TRETURN; } char *cms_data = nullptr; if (opt.enable_smime) { auto cms_key = MAKE_SHARED (find_or_add_text_prop (user_props.get (), "GpgOL CMS Cert")); cms_data = get_oom_string (cms_key.get(), "Value"); if (!cms_data) { TRACEPOINT; TRETURN; } } /* Insert into our vec of opened contacts. */ gpgrt_lock_lock (&s_opened_contacts_lock); s_opened_contacts_vec.push_back (entry_id); gpgrt_lock_unlock (&s_opened_contacts_lock); char *name = get_oom_string (contact, "Subject"); if (!name) { TRACEPOINT; name = get_oom_string (contact, "Email1Address"); if (!name) { name = xstrdup (/* TRANSLATORS: Placeholder for a contact without a configured name */ _("Unknown contact")); } } keyadder_args_t *args = new keyadder_args_t; args->name = name; args->pgp_data = key_data; args->cms_data = cms_data ? cms_data : ""; args->hwnd = get_active_hwnd (); contact->AddRef (); memdbg_addRef (contact); args->contact = MAKE_SHARED (contact); args->entry_id = entry_id; CloseHandle (CreateThread (NULL, 0, open_keyadder, (LPVOID) args, 0, NULL)); xfree (name); xfree (key_data); xfree (cms_data); TRETURN; } /* For each new recipient check the address book to look for a potentially configured key for this recipient and import / register it into the keycache. */ void Addressbook::check_o (Mail *mail) { TSTART; if (!mail) { TRACEPOINT; TRETURN; } LPDISPATCH mailitem = mail->item (); if (!mailitem) { TRACEPOINT; TRETURN; } auto recipients_obj = MAKE_SHARED (get_oom_object (mailitem, "Recipients")); if (!recipients_obj) { TRACEPOINT; TRETURN; } bool err = false; const auto recipient_entries = get_oom_recipients_with_addrEntry (recipients_obj.get(), &err); - for (const auto pair: recipient_entries) + for (const auto &pair: recipient_entries) { const auto mbox = pair.first.mbox (); if (s_checked_entries.find (mbox) != s_checked_entries.end ()) { continue; } if (!pair.second) { TRACEPOINT; continue; } auto contact = MAKE_SHARED (get_oom_object (pair.second.get (), "GetContact")); if (!contact) { log_debug ("%s:%s: failed to resolve contact for %s", SRCNAME, __func__, anonstr (mbox.c_str())); continue; } s_checked_entries.insert (mbox); LPDISPATCH user_props = get_oom_object (contact.get (), "UserProperties"); if (!user_props) { TRACEPOINT; continue; } LPDISPATCH pgp_key = find_or_add_text_prop (user_props, "OpenPGP Key"); LPDISPATCH cms_prop = nullptr; if (opt.enable_smime) { cms_prop = find_or_add_text_prop (user_props, "GpgOL CMS Cert"); } gpgol_release (user_props); if (!pgp_key && !cms_prop) { continue; } log_debug ("%s:%s: found configured key for %s", SRCNAME, __func__, anonstr (mbox.c_str())); char *pgp_data = get_oom_string (pgp_key, "Value"); char *cms_data = nullptr; if (cms_prop) { cms_data = get_oom_string (cms_prop, "Value"); } if ((!pgp_data || !strlen (pgp_data)) && (!cms_data || !strlen (cms_data))) { log_debug ("%s:%s: No key data", SRCNAME, __func__); } if (pgp_data && strlen (pgp_data)) { KeyCache::instance ()->importFromAddrBook (mbox, pgp_data, mail, GpgME::OpenPGP); } if (cms_data && strlen (cms_data)) { KeyCache::instance ()->importFromAddrBook (mbox, cms_data, mail, GpgME::CMS); } xfree (pgp_data); xfree (cms_data); gpgol_release (pgp_key); gpgol_release (cms_prop); } TRETURN; } diff --git a/src/keycache.cpp b/src/keycache.cpp index 103d9ca..5f4c5b9 100644 --- a/src/keycache.cpp +++ b/src/keycache.cpp @@ -1,1976 +1,1976 @@ /* @file keycache.cpp * @brief Internal keycache * * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "keycache.h" #include "common.h" #include "cpphelp.h" #include "mail.h" #include #include #include #include #include #include #include #include #include #include #include #include GPGRT_LOCK_DEFINE (keycache_lock); GPGRT_LOCK_DEFINE (fpr_map_lock); GPGRT_LOCK_DEFINE (update_lock); GPGRT_LOCK_DEFINE (import_lock); GPGRT_LOCK_DEFINE (config_lock); static KeyCache* singleton = nullptr; /** At some point we need to set a limit. There seems to be no limit on how many recipients a mail can have in outlook. We would run out of resources or block. 50 Threads already seems a bit excessive but it should really cover most legit use cases. */ #define MAX_LOCATOR_THREADS 50 static int s_thread_cnt; namespace { class LocateArgs { public: LocateArgs (const std::string& mbox, Mail *mail = nullptr): m_mbox (mbox), m_mail (mail) { TSTART; s_thread_cnt++; Mail::lockDelete (); if (Mail::isValidPtr (m_mail)) { m_mail->incrementLocateCount (); } Mail::unlockDelete (); TRETURN; }; ~LocateArgs() { TSTART; s_thread_cnt--; Mail::lockDelete (); if (Mail::isValidPtr (m_mail)) { m_mail->decrementLocateCount (); } Mail::unlockDelete (); TRETURN; } std::string m_mbox; Mail *m_mail; }; } // namespace typedef std::pair update_arg_t; typedef std::pair, std::string> import_arg_t; static std::vector filter_chain (const std::vector &input) { std::vector leaves; std::remove_copy_if(input.begin(), input.end(), std::back_inserter(leaves), [input] (const auto &k) { /* Check if a key has this fingerprint in the * chain ID. Meaning that there is any child of * this certificate. In that case remove it. */ for (const auto &c: input) { if (!c.chainID()) { continue; } if (!k.primaryFingerprint() || !c.primaryFingerprint()) { STRANGEPOINT; continue; } if (!strcmp (c.chainID(), k.primaryFingerprint())) { log_debug ("%s:%s: Filtering %s as non leaf cert", SRCNAME, __func__, k.primaryFingerprint ()); return true; } } return false; }); return leaves; } static DWORD WINAPI do_update (LPVOID arg) { TSTART; auto args = std::unique_ptr ((update_arg_t*) arg); log_debug ("%s:%s updating: \"%s\" with protocol %s", SRCNAME, __func__, anonstr (args->first.c_str ()), to_cstr (args->second)); auto ctx = std::unique_ptr (GpgME::Context::createForProtocol (args->second)); if (!ctx) { TRACEPOINT; KeyCache::instance ()->onUpdateJobDone (args->first.c_str(), GpgME::Key ()); TRETURN 0; } ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Signatures | GpgME::KeyListMode::Validate | GpgME::KeyListMode::WithTofu); GpgME::Error err; const auto newKey = ctx->key (args->first.c_str (), err, false); TRACEPOINT; if (newKey.isNull()) { log_debug ("%s:%s Failed to find key for %s", SRCNAME, __func__, anonstr (args->first.c_str ())); } if (err) { log_debug ("%s:%s Failed to find key for %s err: %s", SRCNAME, __func__, anonstr (args->first.c_str()), err.asString ()); } KeyCache::instance ()->onUpdateJobDone (args->first.c_str(), newKey); log_debug ("%s:%s Update job done", SRCNAME, __func__); TRETURN 0; } static DWORD WINAPI do_import (LPVOID arg) { TSTART; auto args = std::unique_ptr ((import_arg_t*) arg); const std::string mbox = args->first->m_mbox; log_debug ("%s:%s importing for: \"%s\" with data \n%s", SRCNAME, __func__, anonstr (mbox.c_str ()), anonstr (args->second.c_str ())); // We want to avoid unneccessary copies. The c_str will be valid // until args goes out of scope. const char *keyStr = args->second.c_str (); GpgME::Data data (keyStr, strlen (keyStr), /* copy */ false); GpgME::Protocol proto = GpgME::OpenPGP; auto type = data.type(); if (type == GpgME::Data::X509Cert) { proto = GpgME::CMS; } data.rewind (); auto ctx = GpgME::Context::create(proto); if (!ctx) { TRACEPOINT; TRETURN 0; } if (type != GpgME::Data::PGPKey && type != GpgME::Data::X509Cert) { log_debug ("%s:%s Data for: %s is not a PGP Key or Cert ", SRCNAME, __func__, anonstr (mbox.c_str ())); TRETURN 0; } data.rewind (); const auto result = ctx->importKeys (data); std::vector fingerprints; - for (const auto import: result.imports()) + for (const auto &import: result.imports()) { if (import.error()) { log_debug ("%s:%s Error importing: %s", SRCNAME, __func__, import.error().asString()); continue; } const char *fpr = import.fingerprint (); if (!fpr) { TRACEPOINT; continue; } update_arg_t * update_args = new update_arg_t; update_args->first = std::string (fpr); update_args->second = proto; // We do it blocking to be sure that when all imports // are done they are also part of the keycache. do_update ((LPVOID) update_args); if (std::find(fingerprints.begin(), fingerprints.end(), fpr) == fingerprints.end()) { fingerprints.push_back (fpr); } log_debug ("%s:%s Imported: %s from addressbook.", SRCNAME, __func__, anonstr (fpr)); } KeyCache::instance ()->onAddrBookImportJobDone (mbox, fingerprints, proto); log_debug ("%s:%s Import job done for: %s", SRCNAME, __func__, anonstr (mbox.c_str ())); TRETURN 0; } static void do_populate_protocol (GpgME::Protocol proto, bool secret) { log_debug ("%s:%s: Starting keylisting for proto %s", SRCNAME, __func__, to_cstr (proto)); auto ctx = GpgME::Context::create (proto); if (!ctx) { /* Maybe PGP broken and not S/MIME */ log_error ("%s:%s: broken installation no ctx.", SRCNAME, __func__); TRETURN; } ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Validate); ctx->setOffline (true); GpgME::Error err; if ((err = ctx->startKeyListing ((const char*)nullptr, secret))) { log_error ("%s:%s: Failed to start keylisting err: %i: %s", SRCNAME, __func__, err.code (), err.asString()); TRETURN; } while (!err) { const auto key = ctx->nextKey(err); if (err || key.isNull()) { TRACEPOINT; break; } KeyCache::instance()->onUpdateJobDone (key.primaryFingerprint(), key); } TRETURN; } const std::vector< std::pair > gpgagent_transact (std::unique_ptr &ctx, const char *command) { GpgME::Error err; err = ctx->assuanTransact (command); std::unique_ptr t = ctx->takeLastAssuanTransaction(); std::unique_ptr d (dynamic_cast(t.release())); if (d) { return d->statusLines (); } return std::vector< std::pair > (); } static void gpgsm_learn () { TSTART; const GpgME::EngineInfo ei = GpgME::engineInfo (GpgME::CMS); if (!ei.fileName ()) { STRANGEPOINT; TRETURN; } std::vector args; args.push_back (ei.fileName ()); args.push_back ("--learn-card"); // Spawn the process auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); if (!ctx) { STRANGEPOINT; TRETURN; } GpgME::Data mystdin, mystdout, mystderr; char **cargs = vector_to_cArray (args); GpgME::Error err = ctx->spawn (cargs[0], const_cast (cargs), mystdin, mystdout, mystderr, GpgME::Context::SpawnNone); release_cArray (cargs); if (err) { log_debug ("%s:%s: gpgsm learn spawn code: %i asString: %s", SRCNAME, __func__, err.code(), err.asString()); } if ((opt.enable_debug & DBG_DATA)) { log_data ("stdout:\n'%s'\nstderr:\n%s", mystdout.toString ().c_str (), mystderr.toString ().c_str ()); } TRETURN; } static bool already_learned = false; static void do_populate_smartcards (GpgME::Protocol proto) { TSTART; if (already_learned) { /* gpgsm --learn learns for both protocols. */ log_dbg ("Already called gpgsm --learn this run."); TRETURN; } GpgME::Error err; auto ctx = GpgME::Context::createForEngine (GpgME::AssuanEngine, &err); if (err) { log_dbg ("Failed to create assuan engine. %s", err.asString ()); TRETURN; } const auto serials = gpgagent_transact (ctx, "scd serialno"); if (serials.empty ()) { log_dbg ("No smartcard found."); } const auto pairinfo = gpgagent_transact (ctx, "scd learn --keypairinfo"); /* If we have a supported card output looks like this: S KEYPAIRINFO 84DC19DC8A563302DA14FF33D42FBBB815170E19 NKS-NKS3.4531 sa S KEYPAIRINFO CD9C93F2C76CCA935BC47114C5B527491BA4896D NKS-NKS3.45B1 e S KEYPAIRINFO 033829E72E9007213FE3E1D9FD3D286DB5D07599 NKS-NKS3.45B2 e S KEYPAIRINFO 0404417F832BE6266585190843B38204431EE26A NKS-SIGG.4531 sae */ if (pairinfo.empty ()) { log_dbg ("Did not find any smartcards."); TRETURN; } auto search_ctx = GpgME::Context::create (proto); bool need_to_learn = false; for (const auto &info: pairinfo) { if (info.first != "KEYPAIRINFO") { continue; } const auto vec = gpgol_split (info.second, ' '); if (!vec.size ()) { log_dbg ("Unexpected keypairinfo line '%s'", info.second.c_str ()); continue; } if (vec.size () >= 2 && starts_with (vec[1], "OPENPGP") && proto != GpgME::OpenPGP) { log_dbg ("Skipping OpenPGP key %s", anonstr (vec[0].c_str ())); continue; } const auto keygrip = std::string ("&") + vec[0]; const auto key = search_ctx->key (keygrip.c_str (), err, true); if (err || key.isNull ()) { log_debug ("Key for grip: %s not found. Searching.", keygrip.c_str ()); need_to_learn = true; break; } } if (need_to_learn) { /* Note: gpgsm learn also works for openpgp as it learns the keygrips for both. */ already_learned = true; gpgsm_learn (); } TRETURN; } static DWORD WINAPI do_populate (LPVOID) { TSTART; log_dbg ("Populating config"); gpgrt_lock_lock (&config_lock); GpgME::Error err; KeyCache::instance ()->setConfig (GpgME::Configuration::Component::load (err)); gpgrt_lock_unlock (&config_lock); log_debug ("%s:%s: Populating keycache", SRCNAME, __func__); do_populate_protocol (GpgME::OpenPGP, false); do_populate_smartcards (GpgME::OpenPGP); do_populate_protocol (GpgME::OpenPGP, true); if (opt.enable_smime) { do_populate_protocol (GpgME::CMS, false); do_populate_smartcards (GpgME::CMS); do_populate_protocol (GpgME::CMS, true); } log_debug ("%s:%s: Keycache populated", SRCNAME, __func__); TRETURN 0; } class KeyCache::Private { public: Private() : m_use_tofu (false) { } void setPgpKey(const std::string &mbox, const GpgME::Key &key) { TSTART; gpgol_lock (&keycache_lock); auto it = m_pgp_key_map.find (mbox); if (it == m_pgp_key_map.end ()) { m_pgp_key_map.insert (std::pair (mbox, key)); } else { it->second = key; } insertOrUpdateInFprMap (key); gpgol_unlock (&keycache_lock); TRETURN; } void setSmimeKey(const std::string &mbox, const GpgME::Key &key) { TSTART; gpgol_lock (&keycache_lock); auto it = m_smime_key_map.find (mbox); if (it == m_smime_key_map.end ()) { m_smime_key_map.insert (std::pair (mbox, key)); } else { it->second = key; } insertOrUpdateInFprMap (key); gpgol_unlock (&keycache_lock); TRETURN; } time_t getLastSubkeyCreation(const GpgME::Key &k) { TSTART; time_t ret = 0; for (const auto &sub: k.subkeys()) { if (sub.isBad()) { continue; } if (sub.creationTime() > ret) { ret = sub.creationTime(); } } TRETURN ret; } GpgME::Key compareSkeys(const GpgME::Key &old, const GpgME::Key &newKey) { TSTART; if (newKey.isNull()) { return old; } if (old.isNull()) { return newKey; } if (old.primaryFingerprint() && newKey.primaryFingerprint() && !strcmp (old.primaryFingerprint(), newKey.primaryFingerprint())) { // Both are the same. Take the newer one. return newKey; } if (old.canSign() && !newKey.canSign()) { log_debug ("%s:%s Keeping old skey with " "fpr %s over %s because it can sign.", SRCNAME, __func__, anonstr (old.primaryFingerprint()), anonstr (newKey.primaryFingerprint())); TRETURN old; } if (!old.canSign() && newKey.canSign()) { log_debug ("%s:%s Using new skey with " "fpr %s over %s because it can sign.", SRCNAME, __func__, anonstr (newKey.primaryFingerprint()), anonstr (old.primaryFingerprint())); TRETURN newKey; } // Both can or can't sign. Use the newest one. if (getLastSubkeyCreation (old) >= getLastSubkeyCreation (newKey)) { log_debug ("%s:%s Keeping old skey with " "fpr %s over %s because it is newer.", SRCNAME, __func__, anonstr (old.primaryFingerprint()), anonstr (newKey.primaryFingerprint())); TRETURN old; } log_debug ("%s:%s Using new skey with " "fpr %s over %s because it is newer.", SRCNAME, __func__, anonstr (newKey.primaryFingerprint()), anonstr (old.primaryFingerprint())); TRETURN newKey; } void setPgpKeySecret(const std::string &mbox, const GpgME::Key &key, bool insert = true) { TSTART; gpgol_lock (&keycache_lock); auto it = m_pgp_skey_map.find (mbox); if (it == m_pgp_skey_map.end ()) { m_pgp_skey_map.insert (std::pair (mbox, key)); } else { it->second = compareSkeys (it->second, key); } if (insert) { insertOrUpdateInFprMap (key); } gpgol_unlock (&keycache_lock); TRETURN; } void setSmimeKeySecret(const std::string &mbox, const GpgME::Key &key, bool insert = true) { TSTART; gpgol_lock (&keycache_lock); auto it = m_smime_skey_map.find (mbox); if (it == m_smime_skey_map.end ()) { m_smime_skey_map.insert (std::pair (mbox, key)); } else { it->second = compareSkeys (it->second, key); } if (insert) { insertOrUpdateInFprMap (key); } gpgol_unlock (&keycache_lock); TRETURN; } std::vector getOverrides (const char *addr, GpgME::Protocol proto = GpgME::OpenPGP) { TSTART; std::vector ret; if (!addr) { TRETURN ret; } auto mbox = GpgME::UserID::addrSpecFromString (addr); gpgol_lock (&import_lock); const auto job_set = (proto == GpgME::OpenPGP ? &m_pgp_import_jobs : &m_cms_import_jobs); int i = 0; while (job_set->find (mbox) != job_set->end ()) { i++; if (i % 100 == 0) { log_debug ("%s:%s Waiting on import for \"%s\"", SRCNAME, __func__, anonstr (addr)); } gpgol_unlock (&import_lock); Sleep (10); gpgol_lock (&import_lock); if (i == 1000) { /* Just to be on the save side */ log_error ("%s:%s Waiting on import for \"%s\" " "failed! Bug!", SRCNAME, __func__, anonstr (addr)); break; } } gpgol_unlock (&import_lock); auto override_map = (proto == GpgME::OpenPGP ? &m_pgp_overrides : &m_cms_overrides); const auto it = override_map->find (mbox); if (it == override_map->end ()) { gpgol_unlock (&keycache_lock); TRETURN ret; } - for (const auto fpr: it->second) + for (const auto &fpr: it->second) { const auto key = getByFpr (fpr.c_str (), false); if (key.isNull()) { log_debug ("%s:%s: No key for %s in the cache?!", SRCNAME, __func__, anonstr (fpr.c_str())); continue; } ret.push_back (key); } if (proto == GpgME::CMS) { /* Remove root and intermediate ca's */ ret = filter_chain (ret); } gpgol_unlock (&keycache_lock); TRETURN ret; } GpgME::Key getKey (const char *addr, GpgME::Protocol proto) { TSTART; if (!addr) { TRETURN GpgME::Key(); } auto mbox = GpgME::UserID::addrSpecFromString (addr); if (proto == GpgME::OpenPGP) { gpgol_lock (&keycache_lock); const auto it = m_pgp_key_map.find (mbox); if (it == m_pgp_key_map.end ()) { gpgol_unlock (&keycache_lock); TRETURN GpgME::Key(); } const auto ret = it->second; gpgol_unlock (&keycache_lock); TRETURN ret; } gpgol_lock (&keycache_lock); const auto it = m_smime_key_map.find (mbox); if (it == m_smime_key_map.end ()) { gpgol_unlock (&keycache_lock); TRETURN GpgME::Key(); } const auto ret = it->second; gpgol_unlock (&keycache_lock); TRETURN ret; } GpgME::Key getSKey (const char *addr, GpgME::Protocol proto) { TSTART; if (!addr) { TRETURN GpgME::Key(); } auto mbox = GpgME::UserID::addrSpecFromString (addr); if (proto == GpgME::OpenPGP) { gpgol_lock (&keycache_lock); const auto it = m_pgp_skey_map.find (mbox); if (it == m_pgp_skey_map.end ()) { gpgol_unlock (&keycache_lock); TRETURN GpgME::Key(); } const auto ret = it->second; gpgol_unlock (&keycache_lock); TRETURN ret; } gpgol_lock (&keycache_lock); const auto it = m_smime_skey_map.find (mbox); if (it == m_smime_skey_map.end ()) { gpgol_unlock (&keycache_lock); TRETURN GpgME::Key(); } const auto ret = it->second; gpgol_unlock (&keycache_lock); TRETURN ret; } GpgME::Key getSigningKey (const char *addr, GpgME::Protocol proto) { TSTART; const auto key = getSKey (addr, proto); if (key.isNull()) { log_debug ("%s:%s: secret key for %s is null", SRCNAME, __func__, anonstr (addr)); TRETURN key; } if (!key.canReallySign()) { log_debug ("%s:%s: Discarding key for %s because it can't sign", SRCNAME, __func__, anonstr (addr)); TRETURN GpgME::Key(); } if (!key.hasSecret()) { log_debug ("%s:%s: Discarding key for %s because it has no secret", SRCNAME, __func__, anonstr (addr)); TRETURN GpgME::Key(); } if (in_de_vs_mode () && !key.isDeVs()) { log_debug ("%s:%s: signing key for %s is not deVS", SRCNAME, __func__, anonstr (addr)); TRETURN GpgME::Key(); } TRETURN key; } std::vector getEncryptionKeys (const std::vector &recipients, GpgME::Protocol proto) { TSTART; std::vector ret; if (recipients.empty ()) { TRACEPOINT; TRETURN ret; } for (const auto &recip: recipients) { if (recip.empty ()) { continue; } const auto overrides = getOverrides (recip.c_str (), proto); if (!overrides.empty()) { const auto filtered = (proto == GpgME::CMS ? filter_chain(overrides) : overrides); ret.insert (ret.end (), filtered.begin (), filtered.end ()); log_debug ("%s:%s: Using overrides for %s", SRCNAME, __func__, anonstr (recip.c_str ())); continue; } const auto key = getKey (recip.c_str (), proto); if (key.isNull()) { log_debug ("%s:%s: No key for %s in proto %s. no internal encryption", SRCNAME, __func__, anonstr (recip.c_str ()), to_cstr (proto)); TRETURN std::vector(); } if (!key.canEncrypt() || key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) { log_data ("%s:%s: Invalid key for %s. no internal encryption", SRCNAME, __func__, anonstr (recip.c_str ())); TRETURN std::vector(); } if (in_de_vs_mode () && !key.isDeVs ()) { log_data ("%s:%s: key for %s is not deVS", SRCNAME, __func__, anonstr (recip.c_str ())); TRETURN std::vector(); } bool validEnough = false; /* Here we do the check if the key is valid for this recipient */ const auto addrSpec = GpgME::UserID::addrSpecFromString (recip.c_str ()); for (const auto &uid: key.userIDs ()) { if (addrSpec != uid.addrSpec()) { // Ignore unmatching addr specs continue; } if (uid.validity() >= GpgME::UserID::Marginal || uid.origin() == GpgME::Key::OriginWKD) { validEnough = true; break; } if (opt.auto_unstrusted && uid.validity() == GpgME::UserID::Unknown) { log_debug ("%s:%s: Passing unknown trust key for %s because of option", SRCNAME, __func__, anonstr (recip.c_str ())); validEnough = true; break; } } if (!validEnough) { log_debug ("%s:%s: UID for %s does not have at least marginal trust", SRCNAME, __func__, anonstr (recip.c_str ())); TRETURN std::vector(); } // Accepting key ret.push_back (key); } TRETURN ret; } void insertOrUpdateInFprMap (const GpgME::Key &key) { TSTART; if (key.isNull() || !key.primaryFingerprint()) { TRACEPOINT; TRETURN; } gpgol_lock (&fpr_map_lock); /* First ensure that we have the subkeys mapped to the primary fpr */ const char *primaryFpr = key.primaryFingerprint (); #if 0 { std::stringstream ss; ss << key; log_debug ("%s:%s: Inserting key\n%s", SRCNAME, __func__, ss.str().c_str ()); } #endif for (const auto &sub: key.subkeys()) { const char *subFpr = sub.fingerprint(); auto it = m_sub_fpr_map.find (subFpr); if (it == m_sub_fpr_map.end ()) { m_sub_fpr_map.insert (std::make_pair( std::string (subFpr), std::string (primaryFpr))); } } auto it = m_fpr_map.find (primaryFpr); if (it == m_fpr_map.end ()) { m_fpr_map.insert (std::make_pair (primaryFpr, key)); gpgol_unlock (&fpr_map_lock); TRETURN; } for (const auto &uid: key.userIDs()) { if (key.isBad() || uid.isBad()) { continue; } /* Update ultimate keys map */ if (uid.validity() == GpgME::UserID::Validity::Ultimate && uid.id()) { const char *fpr = key.primaryFingerprint(); if (!fpr) { STRANGEPOINT; continue; } TRACEPOINT; m_ultimate_keys.erase (std::remove_if (m_ultimate_keys.begin(), m_ultimate_keys.end(), [fpr] (const GpgME::Key &ult) { return ult.primaryFingerprint() && !strcmp (fpr, ult.primaryFingerprint()); }), m_ultimate_keys.end()); TRACEPOINT; m_ultimate_keys.push_back (key); } /* Update skey maps */ if (key.hasSecret ()) { if (key.protocol () == GpgME::OpenPGP) { setPgpKeySecret (uid.addrSpec(), key, false); } else if (key.protocol () == GpgME::CMS) { setSmimeKeySecret (uid.addrSpec(), key, false); } else { STRANGEPOINT; } } } if (it->second.hasSecret () && !key.hasSecret()) { log_debug ("%s:%s Lost secret info on update. Merging.", SRCNAME, __func__); auto merged = key; merged.mergeWith (it->second); it->second = merged; } else { it->second = key; } gpgol_unlock (&fpr_map_lock); TRETURN; } GpgME::Key getFromMap (const char *fpr) const { TSTART; if (!fpr) { TRACEPOINT; TRETURN GpgME::Key(); } gpgol_lock (&fpr_map_lock); std::string primaryFpr; const auto it = m_sub_fpr_map.find (fpr); if (it != m_sub_fpr_map.end ()) { log_debug ("%s:%s using \"%s\" for \"%s\"", SRCNAME, __func__, anonstr (it->second.c_str()), anonstr (fpr)); primaryFpr = it->second; } else { primaryFpr = fpr; } const auto keyIt = m_fpr_map.find (primaryFpr); if (keyIt != m_fpr_map.end ()) { const auto ret = keyIt->second; gpgol_unlock (&fpr_map_lock); TRETURN ret; } gpgol_unlock (&fpr_map_lock); TRETURN GpgME::Key(); } GpgME::Key getByFpr (const char *fpr, bool block) const { TSTART; if (!fpr) { TRACEPOINT; TRETURN GpgME::Key (); } TRACEPOINT; const auto ret = getFromMap (fpr); if (ret.isNull()) { // If the key was not found we need to check if there is // an update running. if (block) { const std::string sFpr (fpr); int i = 0; gpgol_lock (&update_lock); while (m_update_jobs.find(sFpr) != m_update_jobs.end ()) { i++; if (i % 100 == 0) { log_debug ("%s:%s Waiting on update for \"%s\"", SRCNAME, __func__, anonstr (fpr)); } gpgol_unlock (&update_lock); Sleep (10); gpgol_lock (&update_lock); if (i == 3000) { /* Just to be on the save side */ log_error ("%s:%s Waiting on update for \"%s\" " "failed! Bug!", SRCNAME, __func__, anonstr (fpr)); break; } } gpgol_unlock (&update_lock); TRACEPOINT; const auto ret2 = getFromMap (fpr); if (ret2.isNull ()) { log_debug ("%s:%s Cache miss after blocking check %s.", SRCNAME, __func__, anonstr (fpr)); } else { log_debug ("%s:%s Cache hit after wait for %s.", SRCNAME, __func__, anonstr (fpr)); TRETURN ret2; } } log_debug ("%s:%s Cache miss for %s.", SRCNAME, __func__, anonstr (fpr)); TRETURN GpgME::Key(); } log_debug ("%s:%s Cache hit for %s.", SRCNAME, __func__, anonstr (fpr)); TRETURN ret; } void update (const char *fpr, GpgME::Protocol proto) { TSTART; if (!fpr) { TRETURN; } const std::string sFpr (fpr); gpgol_lock (&update_lock); if (m_update_jobs.find(sFpr) != m_update_jobs.end ()) { log_debug ("%s:%s Update for \"%s\" already in progress.", SRCNAME, __func__, anonstr (fpr)); gpgol_unlock (&update_lock); } m_update_jobs.insert (sFpr); gpgol_unlock (&update_lock); update_arg_t * args = new update_arg_t; args->first = sFpr; args->second = proto; CloseHandle (CreateThread (NULL, 0, do_update, (LPVOID) args, 0, NULL)); TRETURN; } void onUpdateJobDone (const char *fpr, const GpgME::Key &key) { TSTART; if (!fpr) { TRETURN; } TRACEPOINT; insertOrUpdateInFprMap (key); gpgol_lock (&update_lock); const auto it = m_update_jobs.find(fpr); if (it == m_update_jobs.end()) { gpgol_unlock (&update_lock); TRETURN; } m_update_jobs.erase (it); gpgol_unlock (&update_lock); TRACEPOINT; TRETURN; } void importFromAddrBook (const std::string &mbox, const char *data, Mail *mail, GpgME::Protocol proto) { TSTART; if (!data || mbox.empty() || !mail) { TRACEPOINT; TRETURN; } std::string sdata (data); trim (sdata); if (sdata.empty()) { TRETURN; } gpgol_lock (&import_lock); auto job_set = (proto == GpgME::OpenPGP ? &m_pgp_import_jobs : &m_cms_import_jobs); if (job_set->find (mbox) != job_set->end ()) { log_debug ("%s:%s import for \"%s\" %s already in progress.", SRCNAME, __func__, anonstr (mbox.c_str ()), to_cstr (proto)); gpgol_unlock (&import_lock); } job_set->insert (mbox); gpgol_unlock (&import_lock); import_arg_t * args = new import_arg_t; args->first = std::unique_ptr (new LocateArgs (mbox, mail)); args->second = sdata; CloseHandle (CreateThread (NULL, 0, do_import, (LPVOID) args, 0, NULL)); TRETURN; } void onAddrBookImportJobDone (const std::string &mbox, const std::vector &result_fprs, GpgME::Protocol proto) { TSTART; gpgol_lock (&keycache_lock); auto override_map = (proto == GpgME::OpenPGP ? &m_pgp_overrides : &m_cms_overrides); auto job_set = (proto == GpgME::OpenPGP ? &m_pgp_import_jobs : &m_cms_import_jobs); auto it = override_map->find (mbox); if (it != override_map->end ()) { it->second = result_fprs; } else { override_map->insert (std::make_pair (mbox, result_fprs)); } gpgol_unlock (&keycache_lock); gpgol_lock (&import_lock); const auto job_it = job_set->find(mbox); if (job_it == job_set->end()) { log_error ("%s:%s import for \"%s\" %s already finished.", SRCNAME, __func__, anonstr (mbox.c_str ()), to_cstr (proto)); gpgol_unlock (&import_lock); TRETURN; } job_set->erase (job_it); gpgol_unlock (&import_lock); TRETURN; } void populate () { TSTART; gpgrt_lock_lock (&keycache_lock); m_ultimate_keys.clear (); gpgrt_lock_unlock (&keycache_lock); CloseHandle (CreateThread (nullptr, 0, do_populate, nullptr, 0, nullptr)); TRETURN; } const std::vector get_cached_config () const { /* Quick hack to ensure that the config is loaded. */ gpgrt_lock_lock (&config_lock); gpgrt_lock_unlock (&config_lock); return m_cached_config; } void setConfig (const std::vector &conf) { m_cached_config = conf; } std::unordered_map m_pgp_key_map; std::unordered_map m_smime_key_map; std::unordered_map m_pgp_skey_map; std::unordered_map m_smime_skey_map; std::unordered_map m_fpr_map; std::unordered_map m_sub_fpr_map; std::unordered_map > m_pgp_overrides; std::unordered_map > m_cms_overrides; std::vector m_ultimate_keys; std::set m_update_jobs; std::set m_pgp_import_jobs; std::set m_cms_import_jobs; std::vector m_cached_config; bool m_use_tofu; }; KeyCache::KeyCache(): d(new Private) { } KeyCache * KeyCache::instance () { if (!singleton) { singleton = new KeyCache(); } return singleton; } GpgME::Key KeyCache::getSigningKey (const char *addr, GpgME::Protocol proto) const { return d->getSigningKey (addr, proto); } std::vector KeyCache::getEncryptionKeys (const std::vector &recipients, GpgME::Protocol proto) const { return d->getEncryptionKeys (recipients, proto); } std::vector KeyCache::getEncryptionKeys (const std::string &recipient, GpgME::Protocol proto) const { std::vector vec; vec.push_back (recipient); return d->getEncryptionKeys (vec, proto); } static GpgME::Key get_most_valid_key_simple (const std::vector &keys) { GpgME::Key candidate; for (const auto &key: keys) { if (key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) { log_debug ("%s:%s: Skipping invalid S/MIME key", SRCNAME, __func__); continue; } if (candidate.isNull() || !candidate.numUserIDs()) { if (key.numUserIDs() && candidate.userID(0).validity() <= key.userID(0).validity()) { candidate = key; } } } return candidate; } static std::vector get_local_smime_keys (const std::string &addr) { TSTART; std::vector keys; auto ctx = std::unique_ptr ( GpgME::Context::createForProtocol (GpgME::CMS)); if (!ctx) { TRACEPOINT; TRETURN keys; } // We need to validate here to fetch CRL's ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Validate | GpgME::KeyListMode::Signatures); GpgME::Error e = ctx->startKeyListing (addr.c_str()); if (e) { TRACEPOINT; TRETURN keys; } GpgME::Error err; do { keys.push_back(ctx->nextKey(err)); } while (!err); keys.pop_back(); TRETURN keys; } static std::vector get_extern_smime_keys (const std::string &addr, bool import) { TSTART; std::vector keys; auto ctx = std::unique_ptr ( GpgME::Context::createForProtocol (GpgME::CMS)); if (!ctx) { TRACEPOINT; TRETURN keys; } // We need to validate here to fetch CRL's ctx->setKeyListMode (GpgME::KeyListMode::Extern); GpgME::Error e = ctx->startKeyListing (addr.c_str()); if (e) { TRACEPOINT; TRETURN keys; } GpgME::Error err; do { const auto key = ctx->nextKey (err); if (!err && !key.isNull()) { keys.push_back (key); log_debug ("%s:%s: Found extern S/MIME key for %s with fpr: %s", SRCNAME, __func__, anonstr (addr.c_str()), anonstr (key.primaryFingerprint())); } } while (!err); if (import && keys.size ()) { const GpgME::ImportResult res = ctx->importKeys(keys); log_debug ("%s:%s: Import result for %s: err: %s", SRCNAME, __func__, anonstr (addr.c_str()), res.error ().asString ()); } TRETURN keys; } static DWORD WINAPI do_locate (LPVOID arg) { TSTART; if (!arg) { TRETURN 0; } auto args = std::unique_ptr ((LocateArgs *) arg); const auto addr = args->m_mbox; log_debug ("%s:%s searching key for addr: \"%s\"", SRCNAME, __func__, anonstr (addr.c_str())); const auto k = GpgME::Key::locate (addr.c_str()); if (!k.isNull ()) { log_debug ("%s:%s found key for addr: \"%s\":%s", SRCNAME, __func__, anonstr (addr.c_str()), anonstr (k.primaryFingerprint())); KeyCache::instance ()->setPgpKey (addr, k); } log_debug ("%s:%s pgp locate done", SRCNAME, __func__); if (opt.enable_smime) { GpgME::Key candidate = get_most_valid_key_simple ( get_local_smime_keys (addr)); if (!candidate.isNull()) { log_debug ("%s:%s found SMIME key for addr: \"%s\":%s", SRCNAME, __func__, anonstr (addr.c_str()), anonstr (candidate.primaryFingerprint())); KeyCache::instance()->setSmimeKey (addr, candidate); TRETURN 0; } if (!opt.search_smime_servers || (!k.isNull() && !opt.prefer_smime)) { log_debug ("%s:%s Found no S/MIME key locally and external " "search is disabled.", SRCNAME, __func__); TRETURN 0; } /* Search for extern keys and import them */ const auto externs = get_extern_smime_keys (addr, true); if (externs.empty()) { TRETURN 0; } /* We found and imported external keys. We need to get them locally now to ensure that they are valid etc. */ candidate = get_most_valid_key_simple ( get_local_smime_keys (addr)); if (!candidate.isNull()) { log_debug ("%s:%s found ext. SMIME key for addr: \"%s\":%s", SRCNAME, __func__, anonstr (addr.c_str()), anonstr (candidate.primaryFingerprint())); KeyCache::instance()->setSmimeKey (addr, candidate); TRETURN 0; } else { log_debug ("%s:%s: Found no valid key in extern S/MIME certs", SRCNAME, __func__); } } TRETURN 0; } static void locate_secret (const char *addr, GpgME::Protocol proto) { TSTART; auto ctx = std::unique_ptr ( GpgME::Context::createForProtocol (proto)); if (!ctx) { TRACEPOINT; TRETURN; } if (!addr) { TRACEPOINT; TRETURN; } const auto mbox = GpgME::UserID::addrSpecFromString (addr); if (mbox.empty()) { log_debug ("%s:%s: Empty mbox for addr %s", SRCNAME, __func__, anonstr (addr)); TRETURN; } // We need to validate here to fetch CRL's ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Validate); GpgME::Error e = ctx->startKeyListing (mbox.c_str(), true); if (e) { TRACEPOINT; TRETURN; } std::vector keys; GpgME::Error err; do { const auto key = ctx->nextKey(err); if (key.isNull()) { continue; } if (key.isRevoked() || key.isExpired() || key.isDisabled() || key.isInvalid()) { if ((opt.enable_debug & DBG_DATA)) { std::stringstream ss; ss << key; log_data ("%s:%s: Skipping invalid secret key %s", SRCNAME, __func__, ss.str().c_str()); } continue; } if (proto == GpgME::OpenPGP) { log_debug ("%s:%s found pgp skey for addr: \"%s\":%s", SRCNAME, __func__, anonstr (mbox.c_str()), anonstr (key.primaryFingerprint())); KeyCache::instance()->setPgpKeySecret (mbox, key); TRETURN; } if (proto == GpgME::CMS) { log_debug ("%s:%s found cms skey for addr: \"%s\":%s", SRCNAME, __func__, anonstr (mbox.c_str ()), anonstr (key.primaryFingerprint())); KeyCache::instance()->setSmimeKeySecret (mbox, key); TRETURN; } } while (!err); TRETURN; } static DWORD WINAPI do_locate_secret (LPVOID arg) { TSTART; auto args = std::unique_ptr ((LocateArgs *) arg); log_debug ("%s:%s searching secret key for addr: \"%s\"", SRCNAME, __func__, anonstr (args->m_mbox.c_str ())); locate_secret (args->m_mbox.c_str(), GpgME::OpenPGP); if (opt.enable_smime) { locate_secret (args->m_mbox.c_str(), GpgME::CMS); } log_debug ("%s:%s locator sthread thread done", SRCNAME, __func__); TRETURN 0; } void KeyCache::startLocate (const std::vector &addrs, Mail *mail) const { for (const auto &addr: addrs) { startLocate (addr.c_str(), mail); } } void KeyCache::startLocate (const char *addr, Mail *mail) const { TSTART; if (!addr) { TRACEPOINT; TRETURN; } std::string recp = GpgME::UserID::addrSpecFromString (addr); if (recp.empty ()) { TRETURN; } gpgol_lock (&keycache_lock); if (d->m_pgp_key_map.find (recp) == d->m_pgp_key_map.end ()) { // It's enough to look at the PGP Key map. We marked // searched keys there. d->m_pgp_key_map.insert (std::pair (recp, GpgME::Key())); log_debug ("%s:%s Creating a locator thread", SRCNAME, __func__); const auto args = new LocateArgs(recp, mail); HANDLE thread = CreateThread (NULL, 0, do_locate, args, 0, NULL); CloseHandle (thread); } gpgol_unlock (&keycache_lock); TRETURN; } void KeyCache::startLocateSecret (const char *addr, Mail *mail) const { TSTART; if (!addr) { TRACEPOINT; TRETURN; } std::string recp = GpgME::UserID::addrSpecFromString (addr); if (recp.empty ()) { TRETURN; } gpgol_lock (&keycache_lock); if (d->m_pgp_skey_map.find (recp) == d->m_pgp_skey_map.end ()) { // It's enough to look at the PGP Key map. We marked // searched keys there. d->m_pgp_skey_map.insert (std::pair (recp, GpgME::Key())); log_debug ("%s:%s Creating a locator thread", SRCNAME, __func__); const auto args = new LocateArgs(recp, mail); HANDLE thread = CreateThread (NULL, 0, do_locate_secret, (LPVOID) args, 0, NULL); CloseHandle (thread); } gpgol_unlock (&keycache_lock); TRETURN; } void KeyCache::setSmimeKey(const std::string &mbox, const GpgME::Key &key) { d->setSmimeKey(mbox, key); } void KeyCache::setPgpKey(const std::string &mbox, const GpgME::Key &key) { d->setPgpKey(mbox, key); } void KeyCache::setSmimeKeySecret(const std::string &mbox, const GpgME::Key &key) { d->setSmimeKeySecret(mbox, key); } void KeyCache::setPgpKeySecret(const std::string &mbox, const GpgME::Key &key) { d->setPgpKeySecret(mbox, key); } bool KeyCache::isMailResolvable(Mail *mail) { TSTART; /* Get the data from the mail. */ const auto sender = mail->getSender (); auto recps = mail->getCachedRecipientAddresses (); if (sender.empty() || recps.empty()) { log_debug ("%s:%s: Mail has no sender or no recipients.", SRCNAME, __func__); TRETURN false; } GpgME::Key sigKey = getSigningKey (sender.c_str(), GpgME::OpenPGP); std::vector encKeys = getEncryptionKeys (recps, GpgME::OpenPGP); /* If S/MIME is prefrerred we only toggle auto encrypt for PGP if we both have a signing key and encryption keys. */ if (!encKeys.empty() && (!opt.prefer_smime || !sigKey.isNull())) { TRETURN true; } if (!opt.enable_smime) { TRETURN false; } /* Check S/MIME instead here we need to include the sender as we can't just generate a key. */ recps.push_back (sender); encKeys = getEncryptionKeys (recps, GpgME::CMS); sigKey = getSigningKey (sender.c_str(), GpgME::CMS); TRETURN !encKeys.empty() && !sigKey.isNull(); } void KeyCache::update (const char *fpr, GpgME::Protocol proto) { d->update (fpr, proto); } GpgME::Key KeyCache::getByFpr (const char *fpr, bool block) const { return d->getByFpr (fpr, block); } void KeyCache::onUpdateJobDone (const char *fpr, const GpgME::Key &key) { return d->onUpdateJobDone (fpr, key); } void KeyCache::importFromAddrBook (const std::string &mbox, const char *key_data, Mail *mail, GpgME::Protocol proto) const { return d->importFromAddrBook (mbox, key_data, mail, proto); } void KeyCache::onAddrBookImportJobDone (const std::string &mbox, const std::vector &result_fprs, GpgME::Protocol proto) { return d->onAddrBookImportJobDone (mbox, result_fprs, proto); } std::vector KeyCache::getOverrides (const std::string &mbox, GpgME::Protocol proto) { return d->getOverrides (mbox.c_str (), proto); } void KeyCache::populate () { return d->populate (); } std::vector KeyCache::getUltimateKeys () { gpgrt_lock_lock (&fpr_map_lock); const auto ret = d->m_ultimate_keys; gpgrt_lock_unlock (&fpr_map_lock); return ret; } /* static */ bool KeyCache::import_pgp_key_data (const GpgME::Data &data) { TSTART; if (data.isNull()) { STRANGEPOINT; TRETURN false; } auto ctx = GpgME::Context::create(GpgME::OpenPGP); if (!ctx) { STRANGEPOINT; TRETURN false; } const auto type = data.type(); if (type != GpgME::Data::PGPKey) { log_debug ("%s:%s: Data does not look like PGP Keys", SRCNAME, __func__); TRETURN false; } const auto keys = data.toKeys(); if (keys.empty()) { log_debug ("%s:%s: Data does not contain PGP Keys", SRCNAME, __func__); TRETURN false; } if (opt.enable_debug & DBG_DATA) { std::stringstream ss; for (const auto &key: keys) { ss << key << '\n'; } log_debug ("Importing keys: %s", ss.str().c_str()); } const auto result = ctx->importKeys(data); if ((opt.enable_debug & DBG_DATA)) { std::stringstream ss; ss << result; log_debug ("%s:%s: Import result: %s details:\n %s", SRCNAME, __func__, result.error ().asString (), ss.str().c_str()); if (result.error()) { GpgME::Data out; if (ctx->getAuditLog(out, GpgME::Context::DiagnosticAuditLog)) { log_error ("%s:%s: Failed to get diagnostics", SRCNAME, __func__); } else { log_debug ("%s:%s: Diagnostics: \n%s\n", SRCNAME, __func__, out.toString().c_str()); } } } else { log_debug ("%s:%s: Import result: %s", SRCNAME, __func__, result.error ().asString ()); } TRETURN !result.error(); } const std::vector KeyCache::get_cached_config () const { return d->get_cached_config (); } void KeyCache::setConfig (const std::vector& conf) { d->setConfig (conf); for (const auto &component: conf) { if (component.name () && !strcmp (component.name (), "gpg")) { for (const auto &option: component.options ()) { if (option.name () && !strcmp (option.name (), "trust-model")) { const char *val = option.currentValue().stringValue(); if (!val) { return; } if (!strcmp ("tofu", val) || !strcmp ("tofu+pgp", val)) { log_dbg ("Keycache detected tofu mode."); d->m_use_tofu = true; return; } } } return; } } } bool KeyCache::useTofu () const { return d->m_use_tofu; } bool KeyCache::protocolIsOnline (GpgME::Protocol proto) const { TSTART; if (proto == GpgME::OpenPGP && opt.autoretrieve) { TRETURN true; } for (const auto &component: d->get_cached_config ()) { if (proto == GpgME::OpenPGP && component.name () && !strcmp (component.name (), "gpg")) { for (const auto &option: component.options ()) { if (option.name () && !strcmp (option.name (), "auto-key-retrieve") && option.currentValue().boolValue ()) { log_debug ("%s:%s: Detected auto-key-retrieve -> online", SRCNAME, __func__); TRETURN true; } if (option.name () && !strcmp (option.name (), "disable-dirmngr") && option.currentValue().boolValue ()) { log_debug ("%s:%s: Detected disable-dirmngr -> offline", SRCNAME, __func__); TRETURN false; } } } if (proto == GpgME::CMS && component.name () && !strcmp (component.name (), "gpgsm")) { for (const auto &option: component.options ()) { if (option.name () && !strcmp (option.name (), "disable-crl-checks") && option.currentValue().boolValue ()) { log_debug ("%s:%s: Detected disable-crl-checks -> offline", SRCNAME, __func__); TRETURN false; } if (option.name () && !strcmp (option.name (), "disable-dirmngr") && option.currentValue().boolValue ()) { log_debug ("%s:%s: Detected disable-dirmngr -> offline", SRCNAME, __func__); TRETURN false; } } /* By default S/MIME is online. */ log_dbg ("No options found. So assume CRL checks -> online"); TRETURN true; } } log_dbg ("Detected no online options."); TRETURN false; } diff --git a/src/mail.cpp b/src/mail.cpp index 591c93a..463a249 100644 --- a/src/mail.cpp +++ b/src/mail.cpp @@ -1,5264 +1,5264 @@ /* @file mail.cpp * @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 * Copyright (C) 2019, 2020, 2021 g10code GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include "categorymanager.h" #include "dialogs.h" #include "common.h" #include "mail.h" #include "eventsinks.h" #include "attachment.h" #include "mapihelp.h" #include "mimemaker.h" #include "revert.h" #include "gpgoladdin.h" #include "mymapitags.h" #include "parsecontroller.h" #include "cryptcontroller.h" #include "windowmessages.h" #include "mlang-charset.h" #include "wks-helper.h" #include "keycache.h" #include "cpphelp.h" #include "addressbook.h" #include "recipient.h" #include "recipientmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef _ #define _(a) utf8_gettext (a) using namespace GpgME; static std::map s_mail_map; static std::map s_uid_map; static std::map s_folder_events_map; static std::set uids_searched; static std::set s_entry_ids_printing; GPGRT_LOCK_DEFINE (mail_map_lock); GPGRT_LOCK_DEFINE (uid_map_lock); static Mail *s_last_mail; #define COLOR_DARK_GREY "#f0f0f0" #define COLOR_LIGHT_GREY "#f8f8f8" const char *HTML_PREVIEW_PLACEHOLDER = "" "" "" "" "" "" "" "" "
" "

%s %s

" "

%s

" "%s" "
"; const char *TEXT_PREVIEW_PLACEHOLDER = "%s %s %s\n\n%s"; Mail::Mail (LPDISPATCH mailitem) : m_mailitem(mailitem), m_additional_item(nullptr), m_currentItemRef(nullptr), m_processed(false), m_needs_wipe(false), m_needs_save(false), m_crypt_successful(false), m_is_smime(false), m_is_smime_checked(false), m_is_signed(false), m_is_valid(false), m_close_triggered(false), m_is_html_alternative(false), m_needs_encrypt(false), m_moss_position(0), m_crypto_flags(0), m_cached_html_body(nullptr), m_cached_plain_body(nullptr), m_type(MSGTYPE_UNKNOWN), m_do_inline(false), m_is_gsuite(false), m_crypt_state(NotStarted), m_window(nullptr), m_async_crypt_disabled(false), m_is_forwarded_crypto_mail(false), m_is_send_again(false), m_disable_att_remove_warning(false), m_manual_crypto_opts(false), m_first_autosecure_check(true), m_locate_count(0), m_pass_write(false), m_locate_in_progress(false), m_is_junk(false), m_is_draft_encrypt(false), m_decrypt_again(false), m_printing(false), m_recipients_set(false), m_copy_parent(nullptr), m_attachs_added(false) { TSTART; if (getMailForItem (mailitem)) { log_error ("Mail object for item: %p already exists. Bug.", mailitem); TRETURN; } m_event_sink = install_MailItemEvents_sink (mailitem); if (!m_event_sink) { /* Should not happen but in that case we don't add us to the list and just release the Mail item. */ log_error ("%s:%s: Failed to install MailItemEvents sink.", SRCNAME, __func__); gpgol_release(mailitem); TRETURN; } gpgol_lock (&mail_map_lock); s_mail_map.insert (std::pair (mailitem, this)); gpgol_unlock (&mail_map_lock); s_last_mail = this; memdbg_ctor ("Mail"); TRETURN; } GPGRT_LOCK_DEFINE(dtor_lock); // static void Mail::lockDelete () { TSTART; gpgol_lock (&dtor_lock); TRETURN; } // static void Mail::unlockDelete () { TSTART; gpgol_unlock (&dtor_lock); TRETURN; } Mail::~Mail() { TSTART; /* This should fix a race condition where the mail is deleted before the parser is accessed in the decrypt thread. The shared_ptr of the parser then ensures that the parser is alive even if the mail is deleted while parsing. */ gpgol_lock (&dtor_lock); memdbg_dtor ("Mail"); log_oom ("%s:%s: dtor: Mail: %p item: %p", SRCNAME, __func__, this, m_mailitem); std::map::iterator it; log_oom ("%s:%s: Detaching event sink", SRCNAME, __func__); detach_MailItemEvents_sink (m_event_sink); gpgol_release(m_event_sink); log_oom ("%s:%s: Erasing mail", SRCNAME, __func__); gpgol_lock (&mail_map_lock); it = s_mail_map.find(m_mailitem); if (it != s_mail_map.end()) { s_mail_map.erase (it); } gpgol_unlock (&mail_map_lock); if (!m_uuid.empty()) { gpgol_lock (&uid_map_lock); auto it2 = s_uid_map.find(m_uuid); if (it2 != s_uid_map.end()) { s_uid_map.erase (it2); } gpgol_unlock (&uid_map_lock); } log_oom ("%s:%s: removing categories", SRCNAME, __func__); removeCategories_o (); log_oom ("%s:%s: releasing mailitem", SRCNAME, __func__); gpgol_release(m_mailitem); if (m_additional_item) { log_oom ("%s:%s: releasing addional reference", SRCNAME, __func__); gpgol_release (m_additional_item); } xfree (m_cached_html_body); xfree (m_cached_plain_body); if (!m_uuid.empty()) { log_oom ("%s:%s: destroyed: %p uuid: %s", SRCNAME, __func__, this, m_uuid.c_str()); } else { log_oom ("%s:%s: non crypto (or sent) mail: %p destroyed", SRCNAME, __func__, this); } log_oom ("%s:%s: nulling shared pointer", SRCNAME, __func__); m_parser = nullptr; m_crypter = nullptr; releaseCurrentItem(); gpgol_unlock (&dtor_lock); log_oom ("%s:%s: returning", SRCNAME, __func__); TRETURN; } //static Mail * Mail::getMailForItem (LPDISPATCH mailitem) { TSTART; if (!mailitem) { TRETURN NULL; } std::map::iterator it; gpgol_lock (&mail_map_lock); it = s_mail_map.find(mailitem); gpgol_unlock (&mail_map_lock); if (it == s_mail_map.end()) { TRETURN NULL; } TRETURN it->second; } //static Mail * Mail::getMailForUUID (const char *uuid) { TSTART; if (!uuid) { TRETURN NULL; } gpgol_lock (&uid_map_lock); auto it = s_uid_map.find(std::string(uuid)); gpgol_unlock (&uid_map_lock); if (it == s_uid_map.end()) { TRETURN NULL; } TRETURN it->second; } //static std::vector Mail::searchMailsByUUID (const std::string &uuid) { TSTART; std::vector ret; gpgol_lock (&mail_map_lock); auto it = s_mail_map.begin(); while (it != s_mail_map.end()) { if (get_unique_id_s (it->first, 0, nullptr) == uuid) { ret.push_back (it->second); } ++it; } gpgol_unlock (&mail_map_lock); TRETURN ret; } //static bool Mail::isValidPtr (const Mail *mail) { TSTART; gpgol_lock (&mail_map_lock); auto it = s_mail_map.begin(); while (it != s_mail_map.end()) { if (it->second == mail) { gpgol_unlock (&mail_map_lock); TRETURN true; } ++it; } gpgol_unlock (&mail_map_lock); TRETURN false; } int Mail::preProcessMessage_m () { TSTART; if (m_copy_parent) { log_dbg ("Mail was created as a copy by gpgol. Addr: %p", this); TRETURN 0; } LPMESSAGE message = get_oom_base_message (m_mailitem); if (!message) { log_error ("%s:%s: Failed to get base message.", SRCNAME, __func__); TRETURN 0; } log_oom ("%s:%s: GetBaseMessage OK for %p.", SRCNAME, __func__, m_mailitem); /* Change the message class here. It is important that we change the message class in the before read event regardless if it is already set to one of GpgOL's message classes. Changing the message class (even if we set it to the same value again that it already has) causes Outlook to reconsider what it "knows" about a message and reread data from the underlying base message. */ mapi_change_message_class (message, 1, &m_type); if (m_type == MSGTYPE_UNKNOWN) { /* For unknown messages we still need to check for autocrypt headers. If the mails are crypto messages the autocrypt stuff is handled in the parsecontroller. */ if (opt.autoimport) { autocrypt_s ac; parseHeaders_m (); if (m_header_info.acInfo.exists) { log_debug ("%s:%s: Importing autocrypt header from unencrypted " "mail.", SRCNAME, __func__); KeyCache::import_pgp_key_data (m_header_info.acInfo.data); } } gpgol_release (message); TRETURN 0; } /* We could check PR_ACCESS here in MAPI to figure out if we can modify a mail or not. But this strangely does not fully tell us the truth. For example for a read only mail we can modify the body of the mail but "add oom attachments" will fail. Add OOM attachments has error handling and it will show the user that missing rights are an issue. */ /* Create moss attachments here so that they are properly hidden when the item is read into the model. */ LPMESSAGE parsed_message = get_oom_message (m_mailitem); m_moss_position = mapi_mark_or_create_moss_attach (message, parsed_message, m_type); gpgol_release (parsed_message); if (!m_moss_position) { log_error ("%s:%s: Failed to find moss attachment.", SRCNAME, __func__); m_type = MSGTYPE_UNKNOWN; } gpgol_release (message); TRETURN 0; } static LPDISPATCH get_attachment_o (LPDISPATCH mailitem, int pos) { TSTART; LPDISPATCH attachment; LPDISPATCH attachments = get_oom_object (mailitem, "Attachments"); if (!attachments) { log_debug ("%s:%s: Failed to get attachments.", SRCNAME, __func__); TRETURN NULL; } std::string item_str; int count = get_oom_int (attachments, "Count"); if (count < 1) { log_debug ("%s:%s: Invalid attachment count: %i.", SRCNAME, __func__, count); gpgol_release (attachments); TRETURN NULL; } if (pos > 0) { item_str = std::string("Item(") + std::to_string(pos) + ")"; } else { item_str = std::string("Item(") + std::to_string(count) + ")"; } attachment = get_oom_object (attachments, item_str.c_str()); gpgol_release (attachments); TRETURN attachment; } /** Helper to check that all attachments are hidden, to be called before crypto. */ int Mail::checkAttachments_o (bool silent) { TSTART; if (m_attachs_added) { log_dbg ("Not rechecking attachments as " "we already added them."); TRETURN 0; } LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (!attachments) { log_debug ("%s:%s: Failed to get attachments.", SRCNAME, __func__); TRETURN 1; } int count = count_visible_attachments (attachments); if (!count) { gpgol_release (attachments); TRETURN 0; } std::string message; if (isEncrypted () && isSigned ()) { message += _("Not all attachments were encrypted or signed.\n" "The unsigned / unencrypted attachments are:\n\n"); } else if (isSigned ()) { message += _("Not all attachments were signed.\n" "The unsigned attachments are:\n\n"); } else if (isEncrypted ()) { message += _("Not all attachments were encrypted.\n" "The unencrypted attachments are:\n\n"); } else { gpgol_release (attachments); TRETURN 0; } bool foundOne = false; std::vector to_delete; for (int i = 1; i <= count; i++) { std::string item_str; item_str = std::string("Item(") + std::to_string (i) + ")"; LPDISPATCH oom_attach = get_oom_object (attachments, item_str.c_str ()); if (!oom_attach) { log_error ("%s:%s: Failed to get attachment.", SRCNAME, __func__); continue; } VARIANT var; VariantInit (&var); if (get_pa_variant (oom_attach, PR_ATTACHMENT_HIDDEN_DASL, &var) || (var.vt == VT_BOOL && var.boolVal == VARIANT_FALSE)) { foundOne = true; char *dispName = get_oom_string (oom_attach, "DisplayName"); message += dispName ? dispName : "Unknown"; xfree (dispName); message += "\n"; to_delete.push_back (oom_attach); } else { gpgol_release (oom_attach); } VariantClear (&var); } /* In silent mode we remove the unencrypted attachments silently */ if (foundOne) { for (auto attachment: to_delete) { if (silent) { m_disable_att_remove_warning = true; log_debug ("%s:%s: Deleting bad attachment", SRCNAME, __func__); if (invoke_oom_method (attachment, "Delete", NULL)) { log_error ("%s:%s: Error deleting attachment: %i", SRCNAME, __func__, __LINE__); } m_disable_att_remove_warning = false; } gpgol_release (attachment); } } gpgol_release (attachments); if (foundOne && !silent) { message += "\n"; message += _("Note: The attachments may be encrypted or signed " "on a file level but the GpgOL status does not apply to them."); wchar_t *wmsg = utf8_to_wchar (message.c_str ()); wchar_t *wtitle = utf8_to_wchar (_("GpgOL Warning")); MessageBoxW (get_active_hwnd (), wmsg, wtitle, MB_ICONWARNING|MB_OK); xfree (wmsg); xfree (wtitle); } TRETURN 0; } /** Get the cipherstream of the mailitem. */ static LPSTREAM get_attachment_stream_o (LPDISPATCH mailitem, int pos) { TSTART; if (!pos) { log_debug ("%s:%s: Called with zero pos.", SRCNAME, __func__); TRETURN NULL; } LPDISPATCH attachment = get_attachment_o (mailitem, pos); LPSTREAM stream = NULL; if (!attachment) { // For opened messages that have ms-tnef type we // create the moss attachment but don't find it // in the OOM. Try to find it through MAPI. HRESULT hr; log_debug ("%s:%s: Failed to find MOSS Attachment. " "Fallback to MAPI.", SRCNAME, __func__); LPMESSAGE message = get_oom_message (mailitem); if (!message) { log_debug ("%s:%s: Failed to get MAPI Interface.", SRCNAME, __func__); TRETURN NULL; } hr = gpgol_openProperty (message, PR_BODY_A, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); gpgol_release (message); if (hr) { log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr); TRETURN NULL; } TRETURN stream; } LPATTACH mapi_attachment = NULL; mapi_attachment = (LPATTACH) get_oom_iunknown (attachment, "MapiObject"); gpgol_release (attachment); if (!mapi_attachment) { log_debug ("%s:%s: Failed to get MapiObject of attachment: %p", SRCNAME, __func__, attachment); TRETURN NULL; } if (FAILED (gpgol_openProperty (mapi_attachment, PR_ATTACH_DATA_BIN, &IID_IStream, 0, MAPI_MODIFY, (LPUNKNOWN*) &stream))) { log_debug ("%s:%s: Failed to open stream for mapi_attachment: %p", SRCNAME, __func__, mapi_attachment); gpgol_release (mapi_attachment); TRETURN nullptr; } gpgol_release (mapi_attachment); TRETURN stream; } #if 0 This should work. But Outlook says no. See the comment in set_pa_variant about this. I left the code here as an example how to work with safearrays and how this probably should work. static int copy_data_property(LPDISPATCH target, std::shared_ptr attach) { TSTART; VARIANT var; VariantInit (&var); /* Get the size */ off_t size = attach->get_data ().seek (0, SEEK_END); attach->get_data ().seek (0, SEEK_SET); if (!size) { TRACEPOINT; TRETURN 1; } if (!get_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var)) { log_debug("Have variant. type: %x", var.vt); } else { log_debug("failed to get variant."); } /* Set the type to an array of unsigned chars (OLE SAFEARRAY) */ var.vt = VT_ARRAY | VT_UI1; /* Set up the bounds structure */ SAFEARRAYBOUND rgsabound[1]; rgsabound[0].cElements = static_cast (size); rgsabound[0].lLbound = 0; /* Create an OLE SAFEARRAY */ var.parray = SafeArrayCreate (VT_UI1, 1, rgsabound); if (var.parray == NULL) { TRACEPOINT; VariantClear(&var); TRETURN 1; } void *buffer = NULL; /* Get a safe pointer to the array */ if (SafeArrayAccessData(var.parray, &buffer) != S_OK) { TRACEPOINT; VariantClear(&var); TRETURN 1; } /* Copy data to it */ size_t nread = attach->get_data ().read (buffer, static_cast (size)); if (nread != static_cast (size)) { TRACEPOINT; VariantClear(&var); TRETURN 1; } /*/ Unlock the variant data */ if (SafeArrayUnaccessData(var.parray) != S_OK) { TRACEPOINT; VariantClear(&var); TRETURN 1; } if (set_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var)) { TRACEPOINT; VariantClear(&var); TRETURN 1; } VariantClear(&var); TRETURN 0; } #endif /** Sets some meta data on the last attachment added. The meta data is taken from the attachment object. */ static int fixup_last_attachment_o (LPDISPATCH mail, std::shared_ptr attachment) { TSTART; /* Currently we only set content id */ std::string cid = attachment->get_content_id (); if (cid.empty() || cid == "<>") { log_debug ("%s:%s: Content id not found.", SRCNAME, __func__); TRETURN 0; } /* We add this safeguard here was we somehow can't trigger Outlook * not to hide attachments with content id (see below). So we * have to make double sure that the attachment is actually referenced * in the HTML before we hide it. In doubt it is better to "break" * the content id reference but show the Attachment as a hidden * attachment can appear to be data loss. See T4526 T4203. */ char *body = get_oom_string (mail, "HTMLBody"); if (!body) { log_debug ("%s:%s: No HTML Body.", SRCNAME, __func__); TRETURN 0; } if (cid.front () == '<') { cid.erase (0, 1); } if (cid.back() == '>') { cid.pop_back (); } char *cid_pos = strstr (body, cid.c_str ()); xfree (body); if (!cid_pos) { log_debug ("%s:%s: Failed to find cid: '%s' in body. Not setting cid.", SRCNAME, __func__, anonstr (cid.c_str ())); TRETURN 0; } LPDISPATCH attach = get_attachment_o (mail, -1); if (!attach) { log_error ("%s:%s: No attachment.", SRCNAME, __func__); TRETURN 1; } int ret = put_pa_string (attach, PR_ATTACH_CONTENT_ID_DASL, cid.c_str ()); log_debug ("%s:%s: Set attachment content id to: '%s'", SRCNAME, __func__, anonstr (cid.c_str())); if (ret) { log_error ("%s:%s: Failed.", SRCNAME, __func__); gpgol_release (attach); } #if 0 The following was an experiement to delete the ATTACH_FLAGS values so that we are not hiding attachments. LPATTACH mapi_attach = (LPATTACH) get_oom_iunknown (attach, "MAPIOBJECT"); if (mapi_attach) { SPropTagArray proparray; HRESULT hr; proparray.cValues = 1; proparray.aulPropTag[0] = 0x37140003; hr = mapi_attach->DeleteProps (&proparray, NULL); if (hr) { log_error ("%s:%s: can't delete property attach flags: hr=%#lx\n", SRCNAME, __func__, hr); ret = -1; } gpgol_release (mapi_attach); } else { log_error ("%s:%s: Failed to get mapi attachment.", SRCNAME, __func__); } #endif gpgol_release (attach); TRETURN ret; } /** Helper to update the attachments of a mail object in oom. does not modify the underlying mapi structure. */ int Mail::add_attachments_o (std::vector > attachments) { TSTART; m_enc_attachments = attachments; if (m_attachs_added) { log_dbg ("Not adding attachments as they are already there."); TRETURN 0; } bool anyError = false; m_disable_att_remove_warning = true; std::string addErrStr; int addErrCode = 0; std::vector failedNames; for (auto att: attachments) { int err = 0; const auto dispName = att->get_display_name (); if (dispName.empty()) { log_error ("%s:%s: Ignoring attachment without display name.", SRCNAME, __func__); continue; } wchar_t* wchar_name = utf8_to_wchar (dispName.c_str()); if (!wchar_name) { log_error ("%s:%s: Failed to convert '%s' to wchar.", SRCNAME, __func__, anonstr (dispName.c_str())); continue; } HANDLE hFile; wchar_t* wchar_file = get_tmp_outfile (wchar_name, &hFile); if (!wchar_file) { log_error ("%s:%s: Failed to obtain a tmp filename for: %s", SRCNAME, __func__, anonstr (dispName.c_str())); err = 1; } if (!err && att->copy_to (hFile)) { log_error ("%s:%s: Failed to copy attachment %s to temp file", SRCNAME, __func__, anonstr (dispName.c_str())); err = 1; } if (!err && add_oom_attachment (m_mailitem, wchar_file, wchar_name, addErrStr, &addErrCode)) { log_error ("%s:%s: Failed to add attachment: %s", SRCNAME, __func__, anonstr (dispName.c_str())); failedNames.push_back (dispName); err = 1; } if (hFile && hFile != INVALID_HANDLE_VALUE) { CloseHandle (hFile); } if (wchar_file && !DeleteFileW (wchar_file)) { log_error ("%s:%s: Failed to delete tmp attachment for: %s", SRCNAME, __func__, anonstr (dispName.c_str())); err = 1; } xfree (wchar_file); xfree (wchar_name); if (!err) { log_debug ("%s:%s: Added attachment '%s'", SRCNAME, __func__, anonstr (dispName.c_str())); err = fixup_last_attachment_o (m_mailitem, att); } if (err) { anyError = true; } } if (anyError) { std::string msg = _("Not all attachments can be shown.\n\n" "The hidden attachments are:"); msg += "\n"; std::string filenames; join (failedNames, "\n", filenames); msg += filenames; msg += "\n\n"; if (addErrCode == 0x80004005) { msg += _("The mail exceeds the maximum size GpgOL " "can handle on this server."); } else { msg += _("Reason:"); msg += " " + addErrStr; } gpgol_message_box (getWindow (), msg.c_str (), _("GpgOL"), MB_OK); } m_disable_att_remove_warning = false; m_attachs_added = true; TRETURN anyError; } GPGRT_LOCK_DEFINE(parser_lock); static DWORD WINAPI do_parsing (LPVOID arg) { TSTART; gpgol_lock (&dtor_lock); /* We lock with mail dtors so we can be sure the mail->parser call is valid. */ Mail *mail = (Mail *)arg; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: canceling parsing for: %p already deleted", SRCNAME, __func__, arg); gpgol_unlock (&dtor_lock); TRETURN 0; } blockInv (); /* This takes a shared ptr of parser. So the parser is still valid when the mail is deleted. */ auto parser = mail->parser (); gpgol_unlock (&dtor_lock); gpgol_lock (&parser_lock); /* We lock the parser here to avoid too many decryption attempts if there are multiple mailobjects which might have already been deleted (e.g. by quick switches of the mailview.) Let's rather be a bit slower. */ log_debug ("%s:%s: preparing the parser for: %p", SRCNAME, __func__, arg); if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: cancel for: %p already deleted", SRCNAME, __func__, arg); gpgol_unlock (&parser_lock); unblockInv(); TRETURN 0; } if (!parser) { log_error ("%s:%s: no parser found for mail: %p", SRCNAME, __func__, arg); gpgol_unlock (&parser_lock); unblockInv(); TRETURN -1; } bool is_smime = mail->isSMIME (); std::vector senderKey; if (!is_smime) { senderKey = KeyCache::instance ()->getEncryptionKeys (mail->getSender (), GpgME::OpenPGP); } bool has_already_preview = (mail->msgtype () == MSGTYPE_GPGOL_MULTIPART_SIGNED && !mail->getOriginalBody ().empty ()); if (!has_already_preview && (senderKey.empty () || is_smime) && KeyCache::instance ()->protocolIsOnline (is_smime ? GpgME::CMS : GpgME::OpenPGP) && !opt.sync_dec) { log_dbg ("Op might be online. Doing two pass verify."); parser->parse (true); const auto verify_result = parser->verify_result (); if (verify_result.numSignatures ()) { log_dbg ("Have signature, needs second pass."); do_in_ui_thread (SHOW_PREVIEW, arg); if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: canceling parsing for: %p now deleted", SRCNAME, __func__, arg); gpgol_unlock (&parser_lock); unblockInv(); TRETURN 0; } log_dbg ("Preview updated."); /* Sleep (10000); */ parser->parse (false); log_dbg ("Second parse done."); } if (!opt.sync_dec) { do_in_ui_thread (PARSING_DONE, arg); } } else { parser->parse (false); if (!opt.sync_dec) { do_in_ui_thread (PARSING_DONE, arg); } } gpgol_unlock (&parser_lock); unblockInv(); TRETURN 0; } /* How encryption is done: There are two modes of encryption. Synchronous and Async. If async is used depends on the value of mail->async_crypt_disabled. Synchronous crypto: > Send Event < | State NotStarted Needs Crypto ? (get_gpgol_draft_info_flags != 0) -> No: Pass send -> unencrypted mail. -> Yes: mail->update_oom_data State = Mail::NeedsFirstAfterWrite checkSyncCrypto_o sync -> invoke_oom_method (m_object, "Save", NULL); > Write Event < Pass because is_crypto_mail is false (not a decrypted mail) > AfterWrite Event < | State NeedsFirstAfterWrite async -> skips ^ State = NeedsActualCrypo encrypt_sign_start collect_input_data -> Check if Inline PGP should be used do_crypt -> Resolve keys / do crypto State = OOMSynced update_crypt_mapi crypter->update_mail_mapi if (inline) (Meaning PGP/Inline) <-- do nothing. else build MSOXSMIME attachment and clear body / attachments. State = BackendDone <- Back to Send Event update_crypt_oom -> Cleans body or sets PGP/Inline body. (inline_body_to_body) State = CryptoFinished -> Saftey check "has_crypted_or_empty_body" -> If MIME Mail do the T3656 check. Send. State order for "inline_response" (sync) Mails. NotStarted NeedsFirstAfterWrite DataCollected OOMSynced BackendDone CryptFinished (or inline for PGP Inline) -> Send. State order for async Mails NotStarted NeedsFirstAfterWrite DataCollected -> Cancel Send. Windowmessages -> Crypto Done BackendDone OOMUpdated trigger Save. OOMSynced CryptFinished trigger Send. */ static DWORD WINAPI do_crypt (LPVOID arg) { TSTART; gpgol_lock (&dtor_lock); /* We lock with mail dtors so we can be sure the mail->parser call is valid. */ Mail *mail = (Mail *)arg; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: canceling crypt for: %p already deleted", SRCNAME, __func__, arg); gpgol_unlock (&dtor_lock); TRETURN 0; } if (mail->cryptState () != Mail::DataCollected) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, mail->cryptState ()); mail->enableWindow (); gpgol_unlock (&dtor_lock); TRETURN -1; } /* This takes a shared ptr of crypter. So the crypter is still valid when the mail is deleted. */ auto crypter = mail->cryper (); gpgol_unlock (&dtor_lock); if (!crypter) { log_error ("%s:%s: no crypter found for mail: %p", SRCNAME, __func__, arg); gpgol_unlock (&parser_lock); mail->enableWindow (); TRETURN -1; } GpgME::Error err; std::string diag; int rc = crypter->do_crypto(err, diag); gpgol_lock (&dtor_lock); if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: aborting crypt for: %p already deleted", SRCNAME, __func__, arg); wm_abort_pending_ops (); gpgol_unlock (&dtor_lock); TRETURN 0; } mail->enableWindow (); if (rc == -1 || err) { mail->resetCrypter (); crypter = nullptr; if (err) { char *buf = nullptr; gpgrt_asprintf (&buf, _("Crypto operation failed:\n%s"), err.asString()); std::string msg = buf; memdbg_alloc (buf); xfree (buf); if (!diag.empty()) { msg += "\n\n"; msg += _("Diagnostics"); msg += ":\n"; msg += diag; } gpgol_message_box (mail->getWindow (), msg.c_str (), _("GpgOL"), MB_OK); } else { gpgol_bug (mail->getWindow (), ERR_CRYPT_RESOLVER_FAILED); } } if (rc || err.isCanceled()) { log_debug ("%s:%s: crypto failed for: %p with: %i err: %i", SRCNAME, __func__, arg, rc, err.code()); mail->setCryptState (Mail::NotStarted); mail->setIsDraftEncrypt (false); mail->resetCrypter (); crypter = nullptr; gpgol_unlock (&dtor_lock); if (rc != -3) { mail->resetRecipients (); wm_abort_pending_ops (); } TRETURN rc; } if (!mail->isAsyncCryptDisabled ()) { mail->setCryptState (Mail::BackendDone); gpgol_unlock (&dtor_lock); // This deletes the Mail in Outlook 2010 do_in_ui_thread (CRYPTO_DONE, arg); log_debug ("%s:%s: UI thread finished for %p", SRCNAME, __func__, arg); } else if (mail->isDraftEncrypt ()) { mail->setCryptState (Mail::OOMSynced); mail->updateCryptMAPI_m (); mail->setIsDraftEncrypt (false); mail->setCryptState (Mail::NotStarted); log_debug ("%s:%s: Synchronous draft encrypt finished for %p", SRCNAME, __func__, arg); gpgol_unlock (&dtor_lock); } else { mail->setCryptState (Mail::OOMSynced); mail->updateCryptMAPI_m (); if (mail->cryptState () == Mail::CryptFinished) { // For sync crypto we need to switch this. mail->setCryptState (Mail::BackendDone); } else { // A bug! log_debug ("%s:%s: Resetting crypter because of state mismatch. %p", SRCNAME, __func__, arg); crypter = nullptr; mail->resetCrypter (); } gpgol_unlock (&dtor_lock); } /* This works around a bug in pinentry that it might bring the wrong window to front. So after encryption / signing we bring outlook back to front. See GnuPG-Bug-Id: T3732 */ do_in_ui_thread_async (BRING_TO_FRONT, nullptr, 250); log_debug ("%s:%s: crypto thread for %p finished", SRCNAME, __func__, arg); TRETURN 0; } bool Mail::isCryptoMail () const { TSTART; if (m_type == MSGTYPE_UNKNOWN || m_type == MSGTYPE_GPGOL || m_type == MSGTYPE_SMIME) { /* Not a message for us. */ TRETURN false; } TRETURN true; } int Mail::decryptVerify_o () { TSTART; if (!isCryptoMail ()) { log_debug ("%s:%s: Decrypt Verify for non crypto mail: %p.", SRCNAME, __func__, m_mailitem); TRETURN 0; } m_decrypt_again = false; if (isSMIME_m ()) { LPMESSAGE oom_message = get_oom_message (m_mailitem); if (oom_message) { char *old_class = mapi_get_old_message_class (oom_message); char *current_class = mapi_get_message_class (oom_message); if (current_class) { /* Store our own class for an eventual close */ m_gpgol_class = current_class; xfree (current_class); current_class = nullptr; } if (old_class) { const char *new_class = old_class; /* Workaround that our own class might be the original */ if (!strcmp (old_class, "IPM.Note.GpgOL.OpaqueEncrypted")) { new_class = "IPM.Note.SMIME"; } else if (!strcmp (old_class, "IPM.Note.GpgOL.MultipartSigned")) { new_class = "IPM.Note.SMIME.MultipartSigned"; } log_debug ("%s:%s:Restoring message class to %s in decverify.", SRCNAME, __func__, new_class); put_oom_string (m_mailitem, "MessageClass", new_class); xfree (old_class); setPassWrite (true); /* Sync to MAPI */ invoke_oom_method (m_mailitem, "Save", nullptr); setPassWrite (false); } gpgol_release (oom_message); } } check_html_preferred (); auto cipherstream = get_attachment_stream_o (m_mailitem, m_moss_position); if (!cipherstream) { m_is_junk = is_junk_mail (m_mailitem); if (m_is_junk) { log_debug ("%s:%s: Detected: %p as junk", SRCNAME, __func__, m_mailitem); auto mngr = CategoryManager::instance (); m_store_id = mngr->addCategoryToMail (this, CategoryManager::getJunkMailCategory (), 3 /* peach */); installFolderEventHandler_o (); TRETURN 0; } log_debug ("%s:%s: Failed to get cipherstream. Aborting handling.", SRCNAME, __func__); m_type = MSGTYPE_UNKNOWN; TRETURN 1; } setUUID_o (); m_processed = true; m_pass_write = false; /* Insert placeholder */ m_orig_body = get_oom_string_s (m_mailitem, "Body"); rtrim (m_orig_body); if (m_orig_body.empty ()) { log_dbg ("Empty body."); /* We only need to check the HTML body if the plain body is not empty as outlook would convert the html to plain. */ } else if (opt.prefer_html) { m_orig_body = get_oom_string_s (m_mailitem, "HTMLBody"); } char *placeholder_buf = nullptr; if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION) { gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html : decrypt_template, "OpenPGP", _("Pubkey directory confirmation"), _("This is a confirmation request to publish your Pubkey in the " "directory for your domain.\n\n" "

If you did not request to publish your Pubkey in your providers " "directory, simply ignore this message.

\n")); } else if (m_type == MSGTYPE_GPGOL_MULTIPART_SIGNED && !m_orig_body.empty ()) { log_dbg ("Multipart signed inserting body in placeholder."); gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? HTML_PREVIEW_PLACEHOLDER : TEXT_PREVIEW_PLACEHOLDER, isSMIME_m () ? "S/MIME" : "OpenPGP", _("message"), _("Please wait while the message is being verified..."), m_orig_body.c_str ()); } else { gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html : decrypt_template, isSMIME_m () ? "S/MIME" : "OpenPGP", _("message"), _("Please wait while the message is being decrypted / verified...")); } if (opt.prefer_html) { put_oom_int (m_mailitem, "BodyFormat", 2); if (put_oom_string (m_mailitem, "HTMLBody", placeholder_buf)) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } } else { char *tmp = get_oom_string (m_mailitem, "Body"); if (!tmp) { TRACEPOINT; TRETURN 1; } m_orig_body = tmp; xfree (tmp); put_oom_int (m_mailitem, "BodyFormat", 1); if (put_oom_string (m_mailitem, "Body", placeholder_buf)) { log_error ("%s:%s: Failed to modify body of item.", SRCNAME, __func__); } } memdbg_alloc (placeholder_buf); xfree (placeholder_buf); if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION) { if (m_header_info.boundary.empty()) { parseHeaders_m (); } WKSHelper::instance ()->handle_confirmation_read (this, cipherstream); TRETURN 0; } m_parser = std::shared_ptr (new ParseController (cipherstream, m_type)); m_parser->setSender(GpgME::UserID::addrSpecFromString(getSender_o ().c_str())); if (opt.autoimport) { /* Handle autocrypt header. As we want to have the import of the header in the same thread as the parser we leave it to the parser. */ if (m_header_info.boundary.empty()) { parseHeaders_m (); } if (m_header_info.acInfo.exists) { m_parser->setAutocryptInfo (m_header_info.acInfo); } } log_data ("%s:%s: Parser for \"%s\" is %p", SRCNAME, __func__, getSubject_o ().c_str(), m_parser.get()); gpgol_release (cipherstream); /* Printing happens in two steps. First a Mail is loaded after the BeforePrint event, then it is loaded a second time when the actual print happens. We have to catch both. */ if (!m_printing) { m_printing = checkIfMailMightBePrinting_o (); } else { /* Register us for printing. */ s_entry_ids_printing.insert (get_oom_string_s (m_mailitem, "EntryID")); } if (!opt.sync_dec && !m_printing) { HANDLE parser_thread = CreateThread (NULL, 0, do_parsing, (LPVOID) this, 0, NULL); if (!parser_thread) { log_error ("%s:%s: Failed to create decrypt / verify thread.", SRCNAME, __func__); } CloseHandle (parser_thread); TRETURN 0; } else { /* Parse synchronously */ do_parsing ((LPVOID) this); parsingDone_o (); TRETURN 0; } } int Mail::parseHeaders_m () { /* Parse the headers first so that the handler for confirmation read can access them. Should be put in its own function. */ auto message = MAKE_SHARED (get_oom_message (m_mailitem)); if (!message) { /* Hmmm */ STRANGEPOINT; TRETURN -1; } if (!mapi_get_header_info ((LPMESSAGE)message.get(), m_header_info)) { STRANGEPOINT; TRETURN -1; } TRETURN 0; } static void set_body (LPDISPATCH item, const std::string &plain, const std::string &html) { if (opt.prefer_html && !html.empty()) { if (put_oom_string (item, "HTMLBody", html.c_str ())) { log_error ("%s:%s: Failed to set HTML into HTMLBody.", SRCNAME, __func__); if (!plain.empty ()) { put_oom_int (item, "BodyFormat", 1); if (put_oom_string (item, "Body", plain.c_str ())) { log_error ("%s:%s: Failed to put plaintext into body of item.", SRCNAME, __func__); } } else { put_oom_int (item, "BodyFormat", 1); if (put_oom_string (item, "Body", _("GpgOL: Failed to update mail body."))) { log_error ("%s:%s: Failed to insert fallback error into Body.", SRCNAME, __func__); } } } else { put_oom_int (item, "BodyFormat", 2); } } else if (!plain.empty ()) { put_oom_int (item, "BodyFormat", 1); if (put_oom_string (item, "Body", plain.c_str ())) { log_error ("%s:%s: Failed to put plaintext into body of item.", SRCNAME, __func__); } } } void Mail::updateBody_o (bool is_preview) { TSTART; if (!m_parser) { TRACEPOINT; TRETURN; } const auto error = m_parser->get_formatted_error (); if (!error.empty()) { set_body (m_mailitem, error, error); TRETURN; } if (m_verify_result.error() && !m_orig_body.empty ()) { log_error ("%s:%s: Verification failed. Restoring Body.", SRCNAME, __func__); set_body (m_mailitem, m_orig_body, m_orig_body); TRETURN; } // No need to carry body anymore if (!is_preview) { 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() || (is_preview && !m_block_html))) { if (!m_block_html) { 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 = nullptr; if (!html.empty () || !is_preview) { converted = ansi_charset_to_utf8 (charset.c_str(), html.c_str(), html.size(), codepage); } if (is_preview) { char *buf; if (!converted) { /* Convert plaintext to HTML for preview using outlook. */ charset = m_parser->get_body_charset (); converted = ansi_charset_to_utf8 (charset.c_str(), body.c_str(), body.size(), codepage); put_oom_string (m_mailitem, "Body", converted); xfree (converted); converted = get_oom_string (m_mailitem, "HTMLBody"); } if (converted) { gpgrt_asprintf (&buf, HTML_PREVIEW_PLACEHOLDER, isSMIME_m () ? "S/MIME" : "OpenPGP", _("message"), _("Please wait while the message is being verified..."), converted); memdbg_alloc (buf); xfree (converted); converted = buf; } } TRACEPOINT; put_oom_int (m_mailitem, "BodyFormat", 2); int ret = put_oom_string (m_mailitem, "HTMLBody", converted ? converted : ""); xfree (converted); TRACEPOINT; if (ret) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } else { TRETURN; } } else if (!body.empty()) { /* We had a multipart/alternative mail but html should be blocked. So we prefer the text/plain part and warn once about this so that we hopefully don't get too many bugreports about this. */ if (!opt.smime_html_warn_shown) { std::string caption = _("GpgOL") + std::string (": ") + std::string (_("HTML display disabled.")); std::string buf = _("HTML content in unsigned S/MIME mails " "is insecure."); buf += "\n"; buf += _("GpgOL will only show such mails as text."); buf += "\n\n"; buf += _("This message is shown only once."); gpgol_message_box (getWindow (), buf.c_str(), caption.c_str(), MB_OK); opt.smime_html_warn_shown = true; write_options (); } } } if (body.empty () && m_block_html && !html.empty()) { #if 0 Sadly the following code still offers to load external references it might also be too dangerous if Outlook somehow autoloads the references as soon as the Body is put into HTML // Fallback to show HTML as plaintext if HTML display // is blocked. log_error ("%s:%s: No text body. Putting HTML into plaintext.", SRCNAME, __func__); char *converted = ansi_charset_to_utf8 (m_parser->get_html_charset().c_str(), html.c_str(), html.size()); int ret = put_oom_string (m_mailitem, "HTMLBody", converted ? converted : ""); xfree (converted); if (ret) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); body = html; } else { char *plainBody = get_oom_string (m_mailitem, "Body"); if (!plainBody) { log_error ("%s:%s: Failed to obtain converted plain body.", SRCNAME, __func__); body = html; } else { ret = put_oom_string (m_mailitem, "HTMLBody", plainBody); xfree (plainBody); if (ret) { log_error ("%s:%s: Failed to put plain into html body of item.", SRCNAME, __func__); body = html; } else { TRETURN; } } } #endif body = html; std::string caption = _("GpgOL") + std::string (": ") + std::string (_("HTML display disabled.")); std::string buf = _("HTML content in unsigned S/MIME mails " "is insecure."); buf += "\n"; buf += _("GpgOL will only show such mails as text."); buf += "\n\n"; buf += _("Please ask the sender to sign the message or\n" "to send it with a plain text alternative."); gpgol_message_box (getWindow (), buf.c_str(), caption.c_str(), MB_OK); } find_and_replace (body, "\r\r\n", "\r\n"); const auto plain_charset = m_parser->get_body_charset(); int codepage = 0; if (plain_charset.empty()) { codepage = get_oom_int (m_mailitem, "InternetCodepage"); log_debug ("%s:%s: Did not find body charset. " "Using internet Codepage %i.", SRCNAME, __func__, codepage); } char *converted = ansi_charset_to_utf8 (plain_charset.c_str(), body.c_str(), body.size(), codepage); if (is_preview) { char *buf; gpgrt_asprintf (&buf, TEXT_PREVIEW_PLACEHOLDER, isSMIME_m () ? "S/MIME" : "OpenPGP", _("message"), _("Please wait while the message is being verified..."), converted); memdbg_alloc (buf); xfree (converted); converted = buf; } TRACEPOINT; put_oom_int (m_mailitem, "BodyFormat", 1); int ret = put_oom_string (m_mailitem, "Body", converted ? converted : ""); TRACEPOINT; xfree (converted); if (ret) { log_error ("%s:%s: Failed to modify body of item.", SRCNAME, __func__); } TRETURN; } void Mail::updateHeaders_o () { TSTART; if (!m_parser) { STRANGEPOINT; TRETURN; } const auto subject = m_parser->get_protected_header ("Subject"); if (!subject.empty ()) { put_oom_string (m_mailitem, "Subject", subject.c_str ()); } const auto to = m_parser->get_protected_header ("To"); if (!to.empty()) { put_oom_string (m_mailitem, "To", to.c_str ()); } const auto cc = m_parser->get_protected_header ("Cc"); if (!cc.empty()) { put_oom_string (m_mailitem, "CC", cc.c_str ()); } /* TODO: What about Date ? */ const auto reply_to = m_parser->get_protected_header ("Reply-To"); const auto followup_to = m_parser->get_protected_header ("Followup-To"); if (!reply_to.empty () || !followup_to.empty()) { auto recipients = MAKE_SHARED (get_oom_object (m_mailitem, "ReplyRecipents")); if (recipients) { if (!reply_to.empty ()) { invoke_oom_method_with_string (recipients.get (), "Add", reply_to.c_str ()); } if (!followup_to.empty ()) { invoke_oom_method_with_string (recipients.get (), "Add", followup_to.c_str ()); } } } const auto from = m_parser->get_protected_header ("From"); if (!from.empty ()) { LPDISPATCH sender = get_oom_object (m_mailitem, "Sender"); if (!sender) { log_debug ("%s:%s: Sender not found. From not set.", SRCNAME, __func__); TRETURN; } /* Declare that the address is SMTP */ put_oom_int (sender, "AddressEntryUserType", 30); const auto mail_start = from.find (" <"); if (mail_start == std::string::npos) { put_oom_string (sender, "Address", from.c_str ()); put_oom_string (sender, "Name", ""); } else { put_oom_string (sender, "Address", GpgME::UserID::addrSpecFromString (from.c_str ()).c_str ()); put_oom_string (sender, "Name", from.substr (0, mail_start).c_str ()); } gpgol_release (sender); } } static int parsed_count; void Mail::parsingDone_o (bool is_preview) { TSTART; TRACEPOINT; log_oom ("Mail %p Parsing done for parser num %i: %p", this, parsed_count++, m_parser.get()); if (!m_parser) { /* This should not happen but it happens when outlook sends multiple ItemLoad events for the same Mail Object. In that case it could happen that one parser was already done while a second is now returning for the wrong mail (as it's looked up by uuid.) We have a check in get_uuid that the uuid was not in the map before (and the parser is replaced). So this really really should not happen. We handle it anyway as we crash otherwise. It should not happen because the parser is only created in decrypt_verify which is called in the read event. And even in there we check if the parser was set. */ log_error ("%s:%s: No parser obj. For mail: %p", SRCNAME, __func__, this); TRETURN; } /* Store the results. */ m_decrypt_result = m_parser->decrypt_result (); if (is_preview) { log_dbg ("Parser is not completely done. In Preview mode."); } else { m_verify_result = m_parser->verify_result (); } /* Handle protected headers */ updateHeaders_o (); m_crypto_flags = 0; if (!m_decrypt_result.isNull()) { m_crypto_flags |= 1; } if (m_verify_result.numSignatures()) { m_crypto_flags |= 2; } TRACEPOINT; updateSigstate (); m_needs_wipe = !m_is_send_again; TRACEPOINT; /* Set categories according to the result. */ updateCategories_o (); TRACEPOINT; m_block_html = m_parser->shouldBlockHtml (); if (m_block_html) { // Just to be careful. setBlockStatus_m (); } TRACEPOINT; /* Update the body */ updateBody_o (is_preview); TRACEPOINT; m_dec_content_type = m_parser->get_content_type (); log_dbg ("Decrypted mail has content type: '%s'", m_dec_content_type.c_str ()); /* When printing we have already shown the warning. So we should not show it again but silently remove any attachments that are not hidden before our add_attachments. This also fixes an issue that when printing sometimes the child mails which are created for preview and print already have the decrypted attachments. */ checkAttachments_o (isPrint ()); /* Update attachments */ if (add_attachments_o (m_parser->get_attachments())) { log_error ("%s:%s: Failed to update attachments.", SRCNAME, __func__); } if (m_is_send_again) { log_debug ("%s:%s: I think that this is the send again of a crypto mail.", SRCNAME, __func__); /* We no longer want to be treated like a crypto mail. */ m_type = MSGTYPE_UNKNOWN; LPMESSAGE msg = get_oom_base_message (m_mailitem); if (!msg) { TRACEPOINT; } else { set_gpgol_draft_info_flags (msg, m_crypto_flags); gpgol_release (msg); } removeOurAttachments_o (); } installFolderEventHandler_o (); if (!is_preview) { log_debug ("%s:%s: Delayed invalidate to update sigstate.", SRCNAME, __func__); CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0, NULL)); } TRACEPOINT; TRETURN; } int Mail::encryptSignStart_o () { TSTART; if (m_crypt_state != DataCollected) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, m_crypt_state); TRETURN -1; } int flags = 0; if (!needs_crypto_m ()) { TRETURN 0; } LPMESSAGE message = get_oom_base_message (m_mailitem); if (!message) { log_error ("%s:%s: Failed to get base message.", SRCNAME, __func__); TRETURN -1; } flags = get_gpgol_draft_info_flags (message); gpgol_release (message); const auto window = get_active_hwnd (); if (m_is_gsuite) { auto att_table = mapi_create_attach_table (message, 0); int n_att_usable = count_usable_attachments (att_table); mapi_release_attach_table (att_table); /* Check for attachments if we have some abort. */ if (n_att_usable) { wchar_t *w_title = utf8_to_wchar (_( "GpgOL: Oops, G Suite Sync account detected")); wchar_t *msg = utf8_to_wchar ( _("G Suite Sync breaks outgoing crypto mails " "with attachments.\nUsing crypto and attachments " "with G Suite Sync is not supported.\n\n" "See: https://dev.gnupg.org/T3545 for details.")); MessageBoxW (window, msg, w_title, MB_ICONINFORMATION|MB_OK); xfree (msg); xfree (w_title); TRETURN -1; } } /* register / unregister is not yet implemented for error handling in split sending. wm_register_pending_op (this); */ m_do_inline = m_is_draft_encrypt ? false : m_is_gsuite ? true : opt.inline_pgp; GpgME::Protocol proto = opt.enable_smime ? GpgME::UnknownProtocol: GpgME::OpenPGP; m_crypter = std::shared_ptr (new CryptController (this, flags & 1, flags & 2, proto)); // Careful from here on we have to check every // error condition with window enabling again. disableWindow_o (); if (m_crypter->collect_data ()) { log_error ("%s:%s: Crypter for mail %p failed to collect data.", SRCNAME, __func__, this); enableWindow (); TRETURN -1; } if (!m_async_crypt_disabled) { CloseHandle(CreateThread (NULL, 0, do_crypt, (LPVOID) this, 0, NULL)); } else { log_debug ("%s:%s: Starting sync crypt", SRCNAME, __func__); do_crypt (this); } TRETURN 0; } int Mail::needs_crypto_m () const { TSTART; LPMESSAGE message = get_oom_message (m_mailitem); int ret; if (!message) { log_error ("%s:%s: Failed to get message.", SRCNAME, __func__); TRETURN false; } ret = get_gpgol_draft_info_flags (message); gpgol_release(message); TRETURN ret; } int Mail::wipe_o (bool force) { TSTART; if (!m_needs_wipe && !force) { TRETURN 0; } log_debug ("%s:%s: Removing plaintext from mailitem: %p.", SRCNAME, __func__, m_mailitem); if (put_oom_string (m_mailitem, "HTMLBody", "")) { if (put_oom_string (m_mailitem, "Body", "")) { log_debug ("%s:%s: Failed to wipe mailitem: %p.", SRCNAME, __func__, m_mailitem); TRETURN -1; } TRETURN -1; } else { put_oom_string (m_mailitem, "Body", ""); } m_needs_wipe = false; TRETURN 0; } int Mail::updateOOMData_o (bool for_encryption) { TSTART; char *buf = nullptr; log_debug ("%s:%s: Called. For encryption: %i", SRCNAME, __func__, for_encryption); for_encryption |= !isCryptoMail(); if (for_encryption) { /* Write out the mail to a temporary file so that all the properties are set correctly. */ HANDLE hTmpFile = INVALID_HANDLE_VALUE; char *path = get_tmp_outfile_utf8 ("gpgol_enc.dat", &hTmpFile); if (!path || hTmpFile == INVALID_HANDLE_VALUE) { STRANGEPOINT; TRETURN -1; } CloseHandle (hTmpFile); /* Remove our attachments for example if we are a forward of an encrypted message or a previously encrypted draft we do not want to have the gpgol_mime_structure duplicated. */ removeOurAttachments_o (); log_dbg ("Enable pass write for mail serialisation"); setPassWrite (true); if (oom_save_as (m_mailitem, path, olMSG)) { log_dbg ("Failed to call SaveAs."); TRETURN -1; } if (m_pass_write) { log_dbg ("Pass write still set after serialization. Resetting to false."); setPassWrite (false); } wchar_t *wpath = utf8_to_wchar (path); if (FAILED (DeleteFileW (wpath))) { log_err ("Failed to delete file %S", wpath); } xfree (wpath); /* Clear any leftovers */ m_plain_attachments.clear (); auto attachments = get_oom_object_s (m_mailitem, "Attachments"); int count = attachments ? get_oom_int (attachments, "Count") : 0; for (int i = 1; i <= count; i++) { std::string item = asprintf_s ("Item(%i)", i); LPDISPATCH attach = get_oom_object (attachments.get (), item.c_str ()); if (!attach) { STRANGEPOINT; continue; } /* This is for the case where we try to encrypt a mail decrypted by us again. In that case we might not have access to the attachment data because we cannot access the underlying MAPIOBJECT. Since we take care not to write it into MAPI. Usually this is not an issue as the SaveAs call above works. But we can reach this code in the "BeforeAutoSave" event. In which we cannnot trigger another write. */ LPATTACH mapi_attachment = nullptr; mapi_attachment = (LPATTACH) get_oom_iunknown (attach, "MapiObject"); bool file_found = false; if (!mapi_attachment) { log_dbg ("Failed to get MapiObject. Possibly decrypted by us."); const auto fname = get_oom_string_s (attach, "FileName"); for (const auto &ours: m_enc_attachments) { if (!fname.empty() && ours->get_file_name () == fname) { log_dbg ("Using our decrypted variant of attachment '%s'", fname.c_str ()); m_plain_attachments.push_back (ours); file_found = true; break; } } } gpgol_release (mapi_attachment); if (!file_found) { m_plain_attachments.push_back (std::shared_ptr ( new Attachment (attach))); } } /* 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"); if (!m_recipients_set) { m_cached_recipients = getRecipients_o (); } else { log_dbg ("Not updating cached recipients because recipients were " "set explicitly."); } } else { /* This is the case where we are reading a mail and not composing. When composing we need to use the SendUsingAccount because if you send from the folder of userA but change the from to userB outlook will keep the SenderEmailAddress of UserA. This is all so horrible. */ buf = get_sender_SenderEMailAddress (m_mailitem); if (!buf) { /* Try the sender Object */ buf = get_sender_Sender (m_mailitem); } /* We also want to cache sent representing email address so that we can use it for verification information. */ char *buf2 = get_sender_SentRepresentingAddress (m_mailitem); if (buf2) { m_sent_on_behalf = buf2; xfree (buf2); } } if (!buf) { buf = get_sender_SendUsingAccount (m_mailitem, &m_is_gsuite); } if (!isCryptoMail ()) { /* Try the sender Object and handle the case that someone changed the identity but not the account. */ char *buf2 = get_sender_Sender (m_mailitem); char *tmp = buf; if (buf && buf2) { if (*buf == '/' && *buf2 != '/' && *buf2) { log_dbg ("Send account could not be resolved. Using sender."); buf = buf2; xfree (tmp); } else if (*buf && *buf != '/' && *buf2 && *buf2 != '/') { log_dbg ("SendUsingAccount: '%s' Sender: '%s'", anonstr (buf), anonstr (buf2)); auto key = KeyCache::instance()->getSigningKey (buf2, GpgME::OpenPGP); if (!key.isNull()) { log_dbg ("Found OpenPGP Key for %s using that identity", anonstr (buf2)); buf = buf2; xfree (tmp); } else if (opt.enable_smime) { key = KeyCache::instance()->getSigningKey (buf2, GpgME::CMS); log_dbg ("Found S/MIME Key for %s using that identity", anonstr (buf2)); buf = buf2; xfree (tmp); } } } else if (!buf) { buf = buf2; } else if (buf2 && buf != buf2) { log_dbg ("Keeping SendUsingAccount %s", anonstr (buf)); xfree (buf2); } } if (!buf) { /* We don't have s sender object or SendUsingAccount try primary_sender_acct MAPI property */ buf = get_sender_primary_send_acct (m_mailitem); } if (!buf) { /* Still nothing. Fall back to last resort. */ buf = get_sender_CurrentUser (m_mailitem); } if (!buf) { log_debug ("%s:%s: All fallbacks failed.", SRCNAME, __func__); TRETURN -1; } m_sender = buf; if (for_encryption) { bool sender_is_recipient = false; for (const auto &recp: m_cached_recipients) { if (recp.mbox () == m_sender) { sender_is_recipient = true; log_debug ("%s:%s: Sender already recipient.", SRCNAME, __func__); break; } } if (!sender_is_recipient) { m_cached_recipients.push_back (Recipient (buf, Recipient::olOriginator)); } } xfree (buf); TRETURN 0; } std::string Mail::getSender_o () { TSTART; if (m_sender.empty()) updateOOMData_o (); TRETURN m_sender; } std::string Mail::getSender () const { TSTART; TRETURN m_sender; } int Mail::closeAllMails_o () { TSTART; int err = 0; /* Detach Folder sinks */ for (auto fit = s_folder_events_map.begin(); fit != s_folder_events_map.end(); ++fit) { detach_FolderEvents_sink (fit->second); gpgol_release (fit->second); } s_folder_events_map.clear(); std::map::iterator it; TRACEPOINT; gpgol_lock (&mail_map_lock); std::map mail_map_copy = s_mail_map; gpgol_unlock (&mail_map_lock); for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it) { /* XXX For non racy code the is_valid_ptr check should not be necessary but we crashed sometimes closing a destroyed mail. */ if (!isValidPtr (it->second)) { log_debug ("%s:%s: Already deleted mail for %p", SRCNAME, __func__, it->first); continue; } if (!it->second->isCryptoMail ()) { continue; } bool close_failed = false; if (closeInspector_o (it->second)) { log_error ("%s:%s: Failed to close mail inspector: %p ", SRCNAME, __func__, it->first); close_failed = true; } if (isValidPtr (it->second)) { log_debug ("%s:%s: Inspector closed for %p closing object.", SRCNAME, __func__, it->first); if (it->second->close ()) { log_error ("%s:%s: Failed to close mail itself: %p ", SRCNAME, __func__, it->first); close_failed = true; } } else { log_debug ("%s:%s: Mail gone after inspector close.", SRCNAME, __func__); close_failed = false; } /* Beware: The close code removes our NotStarted from the Outlook Object Model and temporary MAPI. If there is an error we might put NotStarted into permanent storage and leak it to the server. So we have an extra safeguard below. The revert is likely to fail if close and closeInspector fails but to guard against a bug in our close code we try it anyway as revert will also try to remove the plaintext from memory and restore the original message. */ if (close_failed) { if (isValidPtr (it->second) && it->second->revert_o ()) { err++; } } } TRETURN err; } int Mail::revertAllMails_o () { TSTART; int err = 0; std::map::iterator it; gpgol_lock (&mail_map_lock); auto mail_map_copy = s_mail_map; gpgol_unlock (&mail_map_lock); for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it) { if (it->second->revert_o ()) { log_error ("Failed to revert mail: %p ", it->first); err++; continue; } it->second->setNeedsSave (true); if (!invoke_oom_method (it->first, "Save", NULL)) { log_error ("Failed to save reverted mail: %p ", it->second); err++; continue; } } TRETURN err; } int Mail::wipeAllMails_o () { TSTART; int err = 0; std::map::iterator it; gpgol_lock (&mail_map_lock); auto mail_map_copy = s_mail_map; gpgol_unlock (&mail_map_lock); for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it) { if (it->second->wipe_o ()) { log_error ("Failed to wipe mail: %p ", it->first); err++; } } TRETURN err; } int Mail::revert_o () { TSTART; int err = 0; if (!m_processed) { TRETURN 0; } m_disable_att_remove_warning = true; err = gpgol_mailitem_revert (m_mailitem); if (err == -1) { log_error ("%s:%s: Message revert failed falling back to wipe.", SRCNAME, __func__); TRETURN wipe_o (); } /* We need to reprocess the mail next time around. */ m_processed = false; m_needs_wipe = false; m_disable_att_remove_warning = false; TRETURN 0; } bool Mail::isSMIME () const { if (!m_is_smime_checked) { log_dbg ("WARNING: SMIME check before isSMIME_m was called."); return false; } return m_is_smime; } bool Mail::isSMIME_m () { TSTART; msgtype_t msgtype; LPMESSAGE message; if (m_is_smime_checked) { TRETURN m_is_smime; } message = get_oom_message (m_mailitem); if (!message) { log_error ("%s:%s: No message?", SRCNAME, __func__); TRETURN false; } msgtype = mapi_get_message_type (message); m_is_smime = msgtype == MSGTYPE_GPGOL_OPAQUE_ENCRYPTED || msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED || msgtype == MSGTYPE_SMIME; /* Check if it is an smime mail. Multipart signed can also be true. */ if (!m_is_smime && msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED) { char *proto; char *ct = mapi_get_message_content_type (message, &proto, NULL); if (ct && proto) { m_is_smime = (!strcmp (proto, "application/pkcs7-signature") || !strcmp (proto, "application/x-pkcs7-signature")); } else { log_error ("%s:%s: No protocol in multipart / signed mail.", SRCNAME, __func__); } xfree (proto); xfree (ct); } gpgol_release (message); m_is_smime_checked = true; log_debug ("%s:%s: Detected %s mail", SRCNAME, __func__, m_is_smime ? "S/MIME" : "not S/MIME"); TRETURN m_is_smime; } static std::string get_string_o (LPDISPATCH item, const char *str) { TSTART; char *buf = get_oom_string (item, str); if (!buf) { TRETURN std::string(); } std::string ret = buf; xfree (buf); TRETURN ret; } std::string Mail::getSubject_o () const { TSTART; TRETURN get_string_o (m_mailitem, "Subject"); } std::string Mail::getBody_o () const { TSTART; TRETURN get_string_o (m_mailitem, "Body"); } std::vector Mail::getRecipients_o () const { TSTART; LPDISPATCH recipients = get_oom_object (m_mailitem, "Recipients"); if (!recipients) { TRACEPOINT; std::vector(); } bool err = false; auto ret = get_oom_recipients (recipients, &err); gpgol_release (recipients); if (err) { log_debug ("%s:%s: Failed to resolve recipients at this time.", SRCNAME, __func__); } TRETURN ret; } int Mail::closeInspector_o (Mail *mail) { TSTART; LPDISPATCH inspector = get_oom_object (mail->item(), "GetInspector"); HRESULT hr; DISPID dispid; if (!inspector) { log_debug ("%s:%s: No inspector.", SRCNAME, __func__); TRETURN -1; } dispid = lookup_oom_dispid (inspector, "Close"); if (dispid != DISPID_UNKNOWN) { VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = 1; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; hr = inspector->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Failed to close inspector: %#lx", SRCNAME, __func__, hr); gpgol_release (inspector); TRETURN -1; } } gpgol_release (inspector); TRETURN 0; } int Mail::close (bool restoreSMIMEClass) { TSTART; wm_after_move_data_t *move_data = nullptr; VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = 1; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; /* Remove the mail from the print mail set. So that categories etc work again. */ const auto ourID = get_oom_string_s (m_mailitem, "EntryID"); if (!ourID.empty ()) { const auto it = s_entry_ids_printing.find (ourID); if (it != s_entry_ids_printing.end ()) { log_dbg ("Found %s in printing map. Removing it", ourID.c_str ()); s_entry_ids_printing.erase (it); } } if (isSMIME_m ()) { LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); LPMESSAGE mapi_msg = get_oom_message (m_mailitem); if (!mapi_msg) { log_error ("%s:%s:Failed to obtain mapi message for %p on close.", SRCNAME, __func__, m_mailitem); } if (m_gpgol_class.empty()) { log_debug ("%s:%s: GpgOL Class empty for S/MIME Mail.", SRCNAME, __func__); } /* This is strangely important. Because Outlook apparently treats * S/MIME Mails differently we need to change our message class * here again so that discard changes works properly. */ if (mapi_msg && !m_gpgol_class.empty ()) { char *current = mapi_get_message_class (mapi_msg); if (current && strcmp (current, m_gpgol_class.c_str ())) { log_debug ("%s:%s:Setting message class to %s on close.", SRCNAME, __func__, m_gpgol_class.c_str ()); mapi_set_mesage_class (mapi_msg, m_gpgol_class.c_str ()); if (restoreSMIMEClass) { /* After the close we need to change the message class back again so as to not break compatibility with other clients. */ move_data = (wm_after_move_data_t *) xmalloc (sizeof (wm_after_move_data_t)); size_t entryIDLen = 0; char *entryID = nullptr; entryID = mapi_get_binary_prop (mapi_msg, PR_ENTRYID, &entryIDLen); LPDISPATCH folder = get_oom_object (m_mailitem, "Parent"); if (folder) { char *name = get_object_name ((LPUNKNOWN) folder); if (!name || strcmp (name, "MAPIFolder")) { log_error ("%s:%s: Failed to obtain folder on close object is %s", SRCNAME, __func__, name ? name : "(null)"); } else { xfree (name); move_data->target_folder = (LPMAPIFOLDER) get_oom_iunknown ( folder, "MAPIOBJECT"); if (!move_data->target_folder) { log_error ("%s:%s: Failed to obtain target folder.", SRCNAME, __func__); xfree (entryID); xfree (current); xfree (move_data); move_data = nullptr; } else { memdbg_addRef (move_data->target_folder); } } } move_data->entry_id = entryID; move_data->entry_id_len = entryIDLen; move_data->old_class = current; } } } if (attachments) { int count = get_oom_int (attachments, "Count"); gpgol_release (attachments); if (count) { /* On exchange Outlook sometimes detects that an S/MIME mail * is an S/MIME Mail. When this mail is then modified by * us and the mail should be moved or closed Outlook will try * to save it. This fails and the user gets an error. * * So we save here, which should not be dangerous as we do not * put plaintext in mapi. * * Still better only do it if it is really * necessary as the changed message class can hurt. * * Tests show no plaintext leaks. The save saves the * message class and in that way outlook no longer * thinks the mails are S/MIME mails and we can * use our own handling. See T4525 */ removeCategories_o (); HRESULT hr = 0; if (mapi_msg) { log_debug ("%s:%s: MAPI Save for: %p", SRCNAME, __func__, m_mailitem); mapi_msg->SaveChanges (KEEP_OPEN_READWRITE); } if (!mapi_msg || hr) { log_error ("%s:%s: Failed to save mapi for %p hr=%#lx", SRCNAME, __func__, this, hr); } /* In case the mail is still visible in a different window */ updateCategories_o (); } } gpgol_release (mapi_msg); } log_oom ("%s:%s: Invoking close for: %p", SRCNAME, __func__, m_mailitem); setCloseTriggered (true); int rc = invoke_oom_method_with_parms (m_mailitem, "Close", nullptr, &dispparams); if (move_data) { log_debug ("%s:%s:Restoring message class to %s after close.", SRCNAME, __func__, move_data->old_class); do_in_ui_thread_async (AFTER_MOVE, move_data, 0); } setCloseTriggered (false); if (!rc) { /* Saveguard against oom writes when our data is in OOM. This * can happen when the mail was opened in a new window. But * also shown in the preview. * We get a close event and discard changes but the data is * still in oom because it is still visible in the opened * window. * * In that case we may not write! Otherwise the plaintext * might be leaked back to the server if the folder is synced. * */ char *body = get_oom_string (m_mailitem, "Body"); LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (body && strlen (body)) { log_debug ("%s:%s: Close successful. But body found. " "Mail still open.", SRCNAME, __func__); } else if (count_visible_attachments (attachments)) { log_debug ("%s:%s: Close successful. But attachments found. " "Mail still open.", SRCNAME, __func__); } else { setPassWrite (true); log_debug ("%s:%s: Close successful. Next write may pass.", SRCNAME, __func__); } gpgol_release (attachments); xfree (body); } log_oom ("%s:%s: returned from close", SRCNAME, __func__); TRETURN rc; } void Mail::setCloseTriggered (bool value) { TSTART; m_close_triggered = value; TRETURN; } bool Mail::getCloseTriggered () const { TSTART; TRETURN m_close_triggered; } static const UserID get_uid_for_sender (const Key &k, const char *sender) { TSTART; UserID ret; if (!sender) { TRETURN ret; } if (!k.numUserIDs()) { log_debug ("%s:%s: Key without uids", SRCNAME, __func__); TRETURN ret; } - for (const auto uid: k.userIDs()) + for (const auto &uid: k.userIDs()) { if (!uid.email() || !*(uid.email())) { /* This happens for S/MIME a lot */ log_debug ("%s:%s: skipping uid without email.", SRCNAME, __func__); continue; } auto normalized_uid = uid.addrSpec(); auto normalized_sender = UserID::addrSpecFromString(sender); if (normalized_sender.empty() || normalized_uid.empty()) { log_error ("%s:%s: normalizing '%s' or '%s' failed.", SRCNAME, __func__, anonstr (uid.email()), anonstr (sender)); continue; } if (normalized_sender == normalized_uid) { ret = uid; } } TRETURN ret; } void Mail::updateSigstate () { TSTART; std::string sender = getSender (); if (sender.empty()) { log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); TRETURN; } if (m_verify_result.isNull()) { log_debug ("%s:%s: No verify result.", SRCNAME, __func__); TRETURN; } if (m_verify_result.error()) { log_debug ("%s:%s: verify error.", SRCNAME, __func__); TRETURN; } - for (const auto sig: m_verify_result.signatures()) + for (const auto &sig: m_verify_result.signatures()) { m_is_signed = true; const auto key = KeyCache::instance ()->getByFpr (sig.fingerprint(), true); m_uid = get_uid_for_sender (key, sender.c_str()); if (m_uid.isNull() && !m_sent_on_behalf.empty ()) { m_uid = get_uid_for_sender (key, m_sent_on_behalf.c_str ()); if (!m_uid.isNull()) { log_debug ("%s:%s: Using sent on behalf '%s' instead of '%s'", SRCNAME, __func__, anonstr (m_sent_on_behalf.c_str()), anonstr (sender.c_str ())); } } /* Sigsum valid or green is somehow not set in this case. */ if (!sig.status() && m_uid.origin() == GpgME::Key::OriginWKD && (sig.validity() == Signature::Validity::Unknown || sig.validity() == Signature::Validity::Marginal)) { // WKD is a shortcut to Level 2 trust. log_debug ("%s:%s: Unknown or marginal from WKD -> Level 2", SRCNAME, __func__); } else if (m_uid.isNull() || (sig.validity() != Signature::Validity::Marginal && sig.validity() != Signature::Validity::Full && sig.validity() != Signature::Validity::Ultimate)) { /* For our category we only care about trusted sigs. And the UID needs to match.*/ continue; } else if (sig.validity() == Signature::Validity::Marginal) { const auto tofu = m_uid.tofuInfo(); if (!tofu.isNull() && (tofu.validity() != TofuInfo::Validity::BasicHistory && tofu.validity() != TofuInfo::Validity::LargeHistory)) { /* Marginal is only good enough without tofu. Otherwise we wait for basic trust. */ log_debug ("%s:%s: Discarding marginal signature." "With too little history.", SRCNAME, __func__); continue; } } log_debug ("%s:%s: Classified sender as verified uid validity: %i origin: %i", SRCNAME, __func__, m_uid.validity(), m_uid.origin()); m_sig = sig; m_is_valid = true; TRETURN; } log_debug ("%s:%s: No signature with enough trust. Using first", SRCNAME, __func__); m_sig = m_verify_result.signature(0); TRETURN; } bool Mail::isValidSig () const { TSTART; TRETURN m_is_valid; } void Mail::removeCategories_o () { TSTART; if (!m_store_id.empty () && !m_verify_category.empty ()) { log_oom ("%s:%s: Unreffing verify category", SRCNAME, __func__); CategoryManager::instance ()->removeCategory (this, m_verify_category); } if (!m_store_id.empty () && !m_decrypt_result.isNull()) { log_oom ("%s:%s: Unreffing dec category", SRCNAME, __func__); CategoryManager::instance ()->removeCategory (this, CategoryManager::getEncMailCategory ()); } if (m_is_junk) { log_oom ("%s:%s: Unreffing junk category", SRCNAME, __func__); CategoryManager::instance ()->removeCategory (this, CategoryManager::getJunkMailCategory ()); } TRETURN; } /* Now for some tasty hack: Outlook sometimes does not show the new categories properly but instead does some weird scrollbar thing. This can be avoided by resizing the message a bit. But somehow this only needs to be done once. Weird isn't it? But as this workaround worked let's do it programatically. Fun. Wan't some tomato sauce with this hack? */ static void resize_active_window () { TSTART; HWND wnd = get_active_hwnd (); static std::vector resized_windows; if(std::find(resized_windows.begin(), resized_windows.end(), wnd) != resized_windows.end()) { /* We only need to do this once per window. XXX But sometimes we also need to do this once per view of the explorer. So for now this might break but we reduce the flicker. A better solution would be to find the current view and track that. */ TRETURN; } if (!wnd) { TRACEPOINT; TRETURN; } RECT oldpos; if (!GetWindowRect (wnd, &oldpos)) { TRACEPOINT; TRETURN; } if (!SetWindowPos (wnd, nullptr, (int)oldpos.left, (int)oldpos.top, /* Anything smaller then 19 was ignored when the window was * maximized on Windows 10 at least with a 1980*1024 * resolution. So I assume it's at least 1 percent. * This is all hackish and ugly but should work for 90%... * hopefully. */ (int)oldpos.right - oldpos.left - 20, (int)oldpos.bottom - oldpos.top, 0)) { TRACEPOINT; TRETURN; } if (!SetWindowPos (wnd, nullptr, (int)oldpos.left, (int)oldpos.top, (int)oldpos.right - oldpos.left, (int)oldpos.bottom - oldpos.top, 0)) { TRACEPOINT; TRETURN; } resized_windows.push_back(wnd); TRETURN; } #if 0 static std::string pretty_id (const char *keyId) { /* Three spaces, four quads and a NULL */ char buf[20]; buf[19] = '\0'; if (!keyId) { return std::string ("null"); } size_t len = strlen (keyId); if (!len) { return std::string ("empty"); } if (len < 16) { return std::string (_("Invalid Key")); } const char *p = keyId + (len - 16); int j = 0; for (size_t i = 0; i < 16; i++) { if (i && i % 4 == 0) { buf[j++] = ' '; } buf[j++] = *(p + i); } return std::string (buf); } #endif void Mail::updateCategories_o () { TSTART; if (m_printing) { log_debug ("%s:%s: Not updating categories as we are printing.", SRCNAME, __func__); return; } auto mngr = CategoryManager::instance (); if (isValidSig ()) { char *buf; /* Resolve to the primary fingerprint */ #if 0 const auto sigKey = KeyCache::instance ()->getByFpr (m_sig.fingerprint (), true); const char *sigFpr; if (sigKey.isNull()) { sigFpr = m_sig.fingerprint (); } else { sigFpr = sigKey.primaryFingerprint (); } #endif /* If m_uid addrSpec would not return a result we would never * have gotten the UID. */ int lvl = get_signature_level (); /* TRANSLATORS: The first placeholder is for tranlsation of "Level". The second one is for the level number. The third is for the translation of "trust in" and the last one is for the mail address used for verification. The result is used as the text on the green bar for signed mails. e.g.: "GpgOL: Level 3 trust in 'john.doe@example.org'" */ gpgrt_asprintf (&buf, "GpgOL: %s %i %s '%s'", _("Level"), lvl, _("trust in"), m_uid.addrSpec ().c_str ()); memdbg_alloc (buf); int color = 0; if (lvl == 2) { color = 7; /* Olive */ } if (lvl == 3) { color = 5; /* Green */ } if (lvl == 4) { color = 20; /* Dark Green */ } m_store_id = mngr->addCategoryToMail (this, buf, color); m_verify_category = buf; xfree (buf); } else { remove_category (m_mailitem, "GpgOL: ", false); } if (!m_decrypt_result.isNull()) { const auto id = mngr->addCategoryToMail (this, CategoryManager::getEncMailCategory (), 8 /* Blue */); if (m_store_id.empty()) { m_store_id = id; } if (m_store_id != id) { log_error ("%s:%s unexpected store mismatch " "between '%s' and dec cat '%s'", SRCNAME, __func__, m_store_id.c_str(), id.c_str()); } } else { /* As a small safeguard against fakes we remove our categories */ remove_category (m_mailitem, CategoryManager::getEncMailCategory ().c_str (), true); } resize_active_window(); TRETURN; } bool Mail::isSigned () const { TSTART; TRETURN m_verify_result.numSignatures() > 0; } bool Mail::isEncrypted () const { TSTART; TRETURN !m_decrypt_result.isNull(); } int Mail::setUUID_o (bool reset) { TSTART; char *uuid; if (reset) { m_uuid = std::string (); uuid = reset_unique_id (m_mailitem); } else 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: Setting uuid for %p to %s again in MAPI", SRCNAME, __func__, this, m_uuid.c_str()); uuid = get_unique_id (m_mailitem, 1, m_uuid.c_str()); } else { uuid = get_unique_id (m_mailitem, 1, nullptr); log_debug ("%s:%s: uuid for %p set to %s", SRCNAME, __func__, this, uuid); } if (!uuid) { log_debug ("%s:%s: Failed to get/set uuid for %p", SRCNAME, __func__, m_mailitem); TRETURN -1; } if (m_uuid.empty()) { m_uuid = uuid; Mail *other = getMailForUUID (uuid); if (other) { /* According to documentation this should not happen as this means that multiple ItemLoad events occured for the same mailobject without unload / destruction of the mail. But it happens. If you invalidate the UI in the selection change event Outlook loads a new mailobject for the mail. Might happen in other surprising cases. We replace in that case as experiments have shown that the last mailobject is the one that is visible. Still troubling state so we log this as an error. */ log_error ("%s:%s: There is another mail for %p " "with uuid: %s replacing it.", SRCNAME, __func__, m_mailitem, uuid); delete other; } gpgol_lock (&uid_map_lock); s_uid_map.insert (std::pair (m_uuid, this)); gpgol_unlock (&uid_map_lock); log_debug ("%s:%s: uuid for %p is now %s", SRCNAME, __func__, this, m_uuid.c_str()); } xfree (uuid); TRETURN 0; } /* TRETURNs 2 if the userid is ultimately trusted. TRETURNs 1 if the userid is fully trusted but has a signature by a key for which we have a secret and which is ultimately trusted. (Direct trust) 0 otherwise */ static int level_4_check (const UserID &uid) { TSTART; if (uid.isNull()) { TRETURN 0; } if (uid.validity () == UserID::Validity::Ultimate) { TRETURN 2; } if (uid.validity () == UserID::Validity::Full) { const auto ultimate_keys = KeyCache::instance()->getUltimateKeys (); - for (const auto sig: uid.signatures ()) + for (const auto &sig: uid.signatures ()) { const char *sigID = sig.signerKeyID (); if (sig.isNull() || !sigID) { /* should not happen */ TRACEPOINT; continue; } /* Direct trust information is not available through gnupg so we cached the keys with ultimate trust during parsing and now see if we find a direct trust path.*/ - for (const auto secKey: ultimate_keys) + for (const auto &secKey: ultimate_keys) { /* Check that the Key id of the key matches */ const char *secKeyID = secKey.keyID (); if (!secKeyID || strcmp (secKeyID, sigID)) { continue; } /* Check that the userID of the signature is the ultimately trusted one. */ const char *sig_uid_str = sig.signerUserID(); if (!sig_uid_str) { /* should not happen */ TRACEPOINT; continue; } - for (const auto signer_uid: secKey.userIDs ()) + for (const auto &signer_uid: secKey.userIDs ()) { if (signer_uid.validity() != UserID::Validity::Ultimate) { TRACEPOINT; continue; } const char *signer_uid_str = signer_uid.id (); if (!sig_uid_str) { /* should not happen */ TRACEPOINT; continue; } if (!strcmp(sig_uid_str, signer_uid_str)) { /* We have a match */ log_debug ("%s:%s: classified %s as ultimate because " "it was signed by uid %s of key %s", SRCNAME, __func__, anonstr (signer_uid_str), anonstr (sig_uid_str), anonstr (secKeyID)); TRETURN 1; } } } } } TRETURN 0; } std::string Mail::getCryptoSummary () const { TSTART; const int level = get_signature_level (); bool enc = isEncrypted (); if (level == 4 && enc) { TRETURN _("Security Level 4"); } if (level == 4) { TRETURN _("Trust Level 4"); } if (level == 3 && enc) { TRETURN _("Security Level 3"); } if (level == 3) { TRETURN _("Trust Level 3"); } if (level == 2 && enc) { TRETURN _("Security Level 2"); } if (level == 2) { TRETURN _("Trust Level 2"); } if (enc) { TRETURN _("Encrypted"); } if (isSigned ()) { /* Even if it is signed, if it is not validly signed it's still completly insecure as anyone could have signed this. So we avoid the label "signed" here as this word already implies some security. */ TRETURN _("Insecure"); } TRETURN _("Insecure"); } std::string Mail::getCryptoOneLine () const { TSTART; bool sig = isSigned (); bool enc = isEncrypted (); if (sig || enc) { if (sig && enc) { TRETURN _("Signed and encrypted message"); } else if (sig) { TRETURN _("Signed message"); } else if (enc) { TRETURN _("Encrypted message"); } } TRETURN _("Insecure message"); } std::string Mail::getCryptoDetails_o () { TSTART; std::string message; /* No signature with keys but error */ if (!isEncrypted () && !isSigned () && m_verify_result.error()) { message = _("You cannot be sure who sent, " "modified and read the message in transit."); message += "\n\n"; message += _("The message was signed but the verification failed with:"); message += "\n"; message += m_verify_result.error().asString(); TRETURN message; } /* No crypo, what are we doing here? */ if (!isEncrypted () && !isSigned ()) { TRETURN _("You cannot be sure who sent, " "modified and read the message in transit."); } /* Handle encrypt only */ if (isEncrypted () && !isSigned ()) { if (in_de_vs_mode ()) { message += compliance_string (false, true, m_decrypt_result.isDeVs ()); } message += "\n\n"; message += _("You cannot be sure who sent the message because " "it is not signed."); TRETURN message; } bool keyFound = true; const auto sigKey = KeyCache::instance ()->getByFpr (m_sig.fingerprint (), true); bool isOpenPGP = sigKey.isNull() ? !isSMIME_m () : sigKey.protocol() == Protocol::OpenPGP; char *buf; bool hasConflict = false; int level = get_signature_level (); log_debug ("%s:%s: Formatting sig. Validity: %x Summary: %x Level: %i", SRCNAME, __func__, m_sig.validity(), m_sig.summary(), level); if (level == 4) { /* level 4 check for direct trust */ int four_check = level_4_check (m_uid); if (four_check == 2 && sigKey.hasSecret ()) { message = _("You signed this message."); } else if (four_check == 1) { message = _("The senders identity was certified by yourself."); } else if (four_check == 2) { message = _("The sender is allowed to certify identities for you."); } else { log_error ("%s:%s:%i BUG: Invalid sigstate.", SRCNAME, __func__, __LINE__); TRETURN message; } } else if (level == 3 && isOpenPGP) { /* Level three is only reachable through web of trust and no direct signature. */ message = _("The senders identity was certified by several trusted people."); } else if (level == 3 && !isOpenPGP) { /* Level three is the only level for trusted S/MIME keys. */ gpgrt_asprintf (&buf, _("The senders identity is certified by the trusted issuer:\n'%s'\n"), sigKey.issuerName()); memdbg_alloc (buf); message = buf; xfree (buf); } else if (level == 2 && m_uid.origin () == GpgME::Key::OriginWKD) { message = _("The mail provider of the recipient served this key."); } else if (level == 2 && m_uid.tofuInfo ().isNull ()) { /* Marginal trust through pgp only */ message = _("Some trusted people " "have certified the senders identity."); } else if (level == 2) { unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(), m_uid.tofuInfo().encrFirst()); char *time = format_date_from_gpgme (first_contact); /* i18n note signcount is always pulral because with signcount 1 we * would not be in this branch. */ gpgrt_asprintf (&buf, _("The senders address is trusted, because " "you have established a communication history " "with this address starting on %s.\n" "You encrypted %i and verified %i messages since."), time, m_uid.tofuInfo().encrCount(), m_uid.tofuInfo().signCount ()); memdbg_alloc (buf); xfree (time); message = buf; xfree (buf); } else if (level == 1) { /* This could be marginal trust through pgp, or tofu with little history. */ if (m_uid.tofuInfo ().signCount() == 1) { message += _("The senders signature was verified for the first time."); } else if (m_uid.tofuInfo ().validity() == TofuInfo::Validity::LittleHistory) { unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(), m_uid.tofuInfo().encrFirst()); char *time = format_date_from_gpgme (first_contact); gpgrt_asprintf (&buf, _("The senders address is not trustworthy yet because " "you only verified %i messages and encrypted %i messages to " "it since %s."), m_uid.tofuInfo().signCount (), m_uid.tofuInfo().encrCount (), time); memdbg_alloc (buf); xfree (time); message = buf; xfree (buf); } } else { /* Now we are in level 0, this could be a technical problem, no key or just unkown. */ message = isEncrypted () ? _("But the sender address is not trustworthy because:") : _("The sender address is not trustworthy because:"); message += "\n"; keyFound = !(m_sig.summary() & Signature::Summary::KeyMissing); bool general_problem = true; /* First the general stuff. */ if (m_sig.summary() & Signature::Summary::Red) { message += _("The signature is invalid: \n"); if (m_sig.status().code() == GPG_ERR_BAD_SIGNATURE) { message += std::string("\n") + _("The signature does not match."); return message; } } else if (m_sig.summary() & Signature::Summary::SysError || m_verify_result.numSignatures() < 1) { message += _("There was an error verifying the signature.\n"); const auto err = m_sig.status (); if (err) { message += err.asString () + std::string ("\n"); } } else if (m_sig.summary() & Signature::Summary::SigExpired) { message += _("The signature is expired.\n"); } else { message += isOpenPGP ? _("The used key") : _("The used certificate"); message += " "; general_problem = false; } /* Now the key problems */ if ((m_sig.summary() & Signature::Summary::KeyMissing)) { message += _("is not available."); } else if ((m_sig.summary() & Signature::Summary::KeyRevoked)) { message += _("is revoked."); } else if ((m_sig.summary() & Signature::Summary::KeyExpired)) { message += _("is expired."); } else if ((m_sig.summary() & Signature::Summary::BadPolicy)) { message += _("is not meant for signing."); } else if ((m_sig.summary() & Signature::Summary::CrlMissing)) { message += _("could not be checked for revocation."); } else if ((m_sig.summary() & Signature::Summary::CrlTooOld)) { message += _("could not be checked for revocation."); } else if ((m_sig.summary() & Signature::Summary::TofuConflict) || m_uid.tofuInfo().validity() == TofuInfo::Conflict) { message += _("is not the same as the key that was used " "for this address in the past."); hasConflict = true; } else if (m_uid.isNull()) { gpgrt_asprintf (&buf, _("does not claim the address: \"%s\"."), getSender_o ().c_str()); memdbg_alloc (buf); message += buf; xfree (buf); } else if (((m_sig.validity() & Signature::Validity::Undefined) || (m_sig.validity() & Signature::Validity::Unknown) || (m_sig.summary() == Signature::Summary::None) || (m_sig.validity() == 0))&& !general_problem) { /* Bit of a catch all for weird results. */ if (isOpenPGP) { message += _("is not certified by any trustworthy key."); } else { message += _("is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown."); } } else if (m_uid.isRevoked()) { message += _("The sender marked this address as revoked."); } else if ((m_sig.validity() & Signature::Validity::Never)) { message += _("is marked as not trustworthy."); } } message += "\n\n"; if (in_de_vs_mode ()) { bool isBoth = isSigned () && m_sig.isDeVs() && isEncrypted () && m_decrypt_result.isDeVs (); if (isBoth) { message += compliance_string (true, true, true); } if (!isBoth && isSigned ()) { message += compliance_string (true, false, m_sig.isDeVs ()); message += "\n"; } if (!isBoth && isEncrypted ()) { message += compliance_string (false, true, m_decrypt_result.isDeVs ()); message += "\n"; message += "\n\n"; } else { message += "\n"; } } if (hasConflict) { message += _("Click here to change the key used for this address."); } else if (keyFound) { message += isOpenPGP ? _("Click here for details about the key.") : _("Click here for details about the certificate."); } else { message += isOpenPGP ? _("Click here to search the key on the configured keyserver.") : _("Click here to search the certificate on the configured X509 keyserver."); } TRETURN message; } int Mail::get_signature_level () const { TSTART; if (!m_is_signed) { TRETURN 0; } if (m_uid.isNull ()) { /* No m_uid matches our sender. */ TRETURN 0; } if (m_is_valid && (m_uid.validity () == UserID::Validity::Ultimate || (m_uid.validity () == UserID::Validity::Full && level_4_check (m_uid))) && (!in_de_vs_mode () || m_sig.isDeVs())) { TRETURN 4; } if (m_is_valid && m_uid.validity () == UserID::Validity::Full && (!in_de_vs_mode () || m_sig.isDeVs())) { TRETURN 3; } if (m_is_valid) { TRETURN 2; } if (m_sig.validity() == Signature::Validity::Marginal) { TRETURN 1; } if (m_sig.summary() & Signature::Summary::TofuConflict || m_uid.tofuInfo().validity() == TofuInfo::Conflict) { TRETURN 0; } TRETURN 0; } int Mail::getCryptoIconID () const { TSTART; int level = get_signature_level (); int offset = isEncrypted () ? ENCRYPT_ICON_OFFSET : 0; TRETURN IDI_LEVEL_0 + level + offset; } const char* Mail::getSigFpr () const { TSTART; if (!m_is_signed || m_sig.isNull()) { TRETURN nullptr; } TRETURN m_sig.fingerprint(); } /** Try to locate the keys for all recipients */ void Mail::locateKeys_o () { TSTART; if (m_locate_in_progress) { /** XXX The strangest thing seems to happen here: In get_recipients the lookup for "AddressEntry" on an unresolved address might cause network traffic. So Outlook somehow "detaches" this call and keeps processing window messages while the call is running. So our do_delayed_locate might trigger a second locate. If we access the OOM in this call while we access the same object in the blocked "detached" call we crash. (T3931) After the window message is handled outlook retunrs in the original lookup. A better fix here might be a non recursive lock of the OOM. But I expect that if we lock the handling of the Windowmessage we might deadlock. */ log_debug ("%s:%s: Locate for %p already in progress.", SRCNAME, __func__, this); TRETURN; } m_locate_in_progress = true; Addressbook::check_o (this); if (opt.autoresolve) { // First update oom data to have recipients and sender updated. updateOOMData_o (); KeyCache::instance()->startLocateSecret (getSender_o ().c_str (), this); KeyCache::instance()->startLocate (getSender_o ().c_str (), this); KeyCache::instance()->startLocate (getCachedRecipientAddresses (), this); } autosecureCheck (); m_locate_in_progress = false; TRETURN; } bool Mail::isHTMLAlternative () const { TSTART; TRETURN m_is_html_alternative; } char * Mail::takeCachedHTMLBody () { TSTART; char *ret = m_cached_html_body; m_cached_html_body = nullptr; TRETURN ret; } char * Mail::takeCachedPlainBody () { TSTART; char *ret = m_cached_plain_body; m_cached_plain_body = nullptr; TRETURN ret; } int Mail::getCryptoFlags () const { TSTART; TRETURN m_crypto_flags; } void Mail::setNeedsEncrypt (bool value) { TSTART; m_needs_encrypt = value; TRETURN; } bool Mail::getNeedsEncrypt () const { TSTART; TRETURN m_needs_encrypt; } std::vector Mail::getCachedRecipients () { TSTART; TRETURN m_cached_recipients; } void Mail::setRecipients (const std::vector &recps) { TSTART; m_recipients_set = !recps.empty (); m_cached_recipients = recps; TRETURN; } std::vector Mail::getCachedRecipientAddresses () { TSTART; std::vector ret; for (const auto &recp: m_cached_recipients) { ret.push_back (recp.mbox()); } return ret; } void Mail::appendToInlineBody (const std::string &data) { TSTART; m_inline_body += data; TRETURN; } int Mail::inlineBodyToBody_o () { TSTART; if (!m_crypter) { log_error ("%s:%s: No crypter.", SRCNAME, __func__); TRETURN -1; } const auto body = m_crypter->get_inline_data (); if (body.empty()) { TRETURN 0; } /* For inline we always work with UTF-8 */ put_oom_int (m_mailitem, "InternetCodepage", 65001); int ret = put_oom_string (m_mailitem, "Body", body.c_str ()); TRETURN ret; } void Mail::updateCryptMAPI_m () { TSTART; log_debug ("%s:%s: Update crypt mapi", SRCNAME, __func__); if (m_crypt_state != OOMSynced) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, m_crypt_state); TRETURN; } if (!m_crypter) { if (!m_mime_data.empty()) { log_debug ("%s:%s: Have override mime data creating dummy crypter", SRCNAME, __func__); m_crypter = std::shared_ptr (new CryptController (this, false, false, GpgME::UnknownProtocol)); } else { log_error ("%s:%s: No crypter.", SRCNAME, __func__); m_crypt_state = NotStarted; TRETURN; } } if (m_crypter->update_mail_mapi ()) { log_error ("%s:%s: Failed to update MAPI after crypt", SRCNAME, __func__); m_crypt_state = NotStarted; } else { m_crypt_state = CryptFinished; } /** If sync we need the crypter in update_crypt_oom */ if (!isAsyncCryptDisabled ()) { // We don't need the crypter anymore. resetCrypter (); } TRETURN; } /** Checks in OOM if the body is either empty or contains the -----BEGIN tag. pair.first -> true if body starts with -----BEGIN pair.second -> true if body is empty. */ static std::pair has_crypt_or_empty_body_oom (Mail *mail) { TSTART; auto body = mail->getBody_o (); std::pair ret; ret.first = false; ret.second = false; ltrim (body); if (body.size() > 10 && !strncmp (body.c_str(), "-----BEGIN", 10)) { ret.first = true; TRETURN ret; } if (!body.size()) { ret.second = true; } else { log_data ("%s:%s: Body found in %p : \"%s\"", SRCNAME, __func__, mail, body.c_str ()); } TRETURN ret; } void Mail::updateCryptOOM_o () { TSTART; log_debug ("%s:%s: Update crypt oom for %p", SRCNAME, __func__, this); if (m_crypt_state != BackendDone) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, m_crypt_state); resetCrypter (); resetRecipients (); TRETURN; } if (getDoPGPInline ()) { if (inlineBodyToBody_o ()) { log_error ("%s:%s: Inline body to body failed %p.", SRCNAME, __func__, this); gpgol_bug (get_active_hwnd(), ERR_INLINE_BODY_TO_BODY); m_crypt_state = NotStarted; TRETURN; } } if (m_crypter->get_protocol () == GpgME::CMS && m_crypter->is_encrypter ()) { /* We put the PIDNameContentType headers here for exchange because this is the only way we found to inject the smime-type. */ if (put_pa_string (m_mailitem, PR_PIDNameContentType_DASL, "application/pkcs7-mime;smime-type=\"enveloped-data\";name=smime.p7m")) { log_debug ("%s:%s: Failed to put PIDNameContentType for %p.", SRCNAME, __func__, this); } } if (get_oom_int (m_mailitem, "BodyFormat") == 3) { log_dbg ("Changing body format from rich text to HTML to " "prevent winmail.dat"); put_oom_int (m_mailitem, "BodyFormat", 2); } /** When doing async update_crypt_mapi follows and needs the crypter. */ if (isAsyncCryptDisabled ()) { resetCrypter (); resetRecipients (); } 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 = CryptFinished; TRETURN; } /* Draft encryption we do not want to wipe the oom but we have to modify it to trigger the wirte / second after write. */ if (m_is_draft_encrypt) { char *subject = get_oom_string (m_mailitem, "Subject"); put_oom_string (m_mailitem, "Subject", subject ? subject : ""); xfree (subject); } // We are in MIME land. Wipe the body. if (!m_is_draft_encrypt && wipe_o (true)) { log_debug ("%s:%s: Cancel send for %p.", SRCNAME, __func__, this); wchar_t *title = utf8_to_wchar (_("GpgOL: Encryption not possible!")); wchar_t *msg = utf8_to_wchar (_( "Outlook returned an error when trying to send the encrypted mail.\n\n" "Please restart Outlook and try again.\n\n" "If it still fails consider using an encrypted attachment or\n" "switching to PGP/Inline in GpgOL's options.")); MessageBoxW (get_active_hwnd(), msg, title, MB_ICONERROR | MB_OK); xfree (msg); xfree (title); m_crypt_state = NotStarted; TRETURN; } m_crypt_state = OOMUpdated; TRETURN; } void Mail::enableWindow () { TSTART; if (!m_window) { log_error ("%s:%s:enable window which was not disabled", SRCNAME, __func__); } log_debug ("%s:%s: enable window %p", SRCNAME, __func__, m_window); EnableWindow (m_window, TRUE); TRETURN; } void Mail::disableWindow_o () { TSTART; m_window = get_active_hwnd (); log_debug ("%s:%s: disable window %p", SRCNAME, __func__, m_window); EnableWindow (m_window, FALSE); TRETURN; } bool Mail::isActiveInlineResponse_o () { const auto subject = getSubject_o (); bool ret = false; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN false; } LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer"); if (!explorer) { TRACEPOINT; TRETURN false; } LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse"); gpgol_release (explorer); if (!inlineResponse) { TRETURN false; } // We have inline response // Check if we are it. It's a bit naive but meh. Worst case // is that we think inline response too often and do sync // crypt where we could do async crypt. char * inlineSubject = get_oom_string (inlineResponse, "Subject"); gpgol_release (inlineResponse); if (inlineResponse && !subject.empty() && !strcmp (subject.c_str (), inlineSubject)) { log_debug ("%s:%s: Detected inline response for '%p'", SRCNAME, __func__, this); ret = true; } xfree (inlineSubject); TRETURN ret; } bool Mail::checkSyncCrypto_o () { TSTART; /* Async sending is known to cause instabilities. So we keep a hidden option to disable it. */ if (opt.sync_enc) { m_async_crypt_disabled = true; TRETURN m_async_crypt_disabled; } m_async_crypt_disabled = false; if (m_is_draft_encrypt) { log_dbg ("Disabling async crypt because of draft encrypt."); m_async_crypt_disabled = true; TRETURN m_async_crypt_disabled; } const auto subject = getSubject_o (); /* Check for an empty subject. Otherwise the question for it might be hidden behind our overlay. */ if (subject.empty()) { log_debug ("%s:%s: Detected empty subject. " "Disabling async crypt due to T4150.", SRCNAME, __func__); m_async_crypt_disabled = true; TRETURN m_async_crypt_disabled; } LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (/* attachments */ false) { /* This is horrible. But. For some kinds of attachments (we got reports about Office attachments the write in the send event triggered by our crypto done code fails with an exception. There does not appear to be a detectable pattern when this happens. As we can't be sure and do not know for which attachments this really happens we do not use async crypt for any mails with attachments. :-/ Better be save (not crash) instead of nice (async). TODO: Figure this out. The log goes like this. We pass the send event. That triggers a write, which we pass. And then that fails. So it looks like moving to Outbox fails. Because we can save as much as we like before that. Using the IMessage::SubmitMessage MAPI interface works, but as it is unstable in our current implementation we do not want to use it. mailitem-events.cpp:Invoke: Passing send event for mime-encrypted message 12B7C6E0. application-events.cpp:Invoke: Unhandled Event: f002 mailitem-events.cpp:Invoke: Write : 0ED4D058 mailitem-events.cpp:Invoke: Passing write event. oomhelp.cpp:invoke_oom_method_with_parms_type: Method 'Send' invokation failed: 0x80020009 oomhelp.cpp:dump_excepinfo: Exception: wCode: 0x1000 wReserved: 0x0 source: Microsoft Outlook desc: The operation failed. The messaging interfaces have returned an unknown error. If the problem persists, restart Outlook. help: null helpCtx: 0x0 deferredFill: 00000000 scode: 0x80040119 */ int count = get_oom_int (attachments, "Count"); gpgol_release (attachments); if (count) { m_async_crypt_disabled = true; log_debug ("%s:%s: Detected attachments. " "Disabling async crypt due to T4131.", SRCNAME, __func__); TRETURN m_async_crypt_disabled; } } if (isActiveInlineResponse_o ()) { m_async_crypt_disabled = true; } TRETURN m_async_crypt_disabled; } // static Mail * Mail::getLastMail () { TSTART; if (!s_last_mail || !isValidPtr (s_last_mail)) { s_last_mail = nullptr; } TRETURN s_last_mail; } // static void Mail::clearLastMail () { TSTART; s_last_mail = nullptr; TRETURN; } // static void Mail::locateAllCryptoRecipients_o () { TSTART; gpgol_lock (&mail_map_lock); std::map::iterator it; auto mail_map_copy = s_mail_map; gpgol_unlock (&mail_map_lock); for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it) { if (it->second->needs_crypto_m ()) { it->second->locateKeys_o (); } } TRETURN; } int Mail::removeAllAttachments_o () { TSTART; int ret = 0; LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (!attachments) { TRACEPOINT; TRETURN 0; } int count = get_oom_int (attachments, "Count"); LPDISPATCH to_delete[count]; /* Populate the array so that we don't get in an index mess */ for (int i = 1; i <= count; i++) { auto item_str = std::string("Item(") + std::to_string (i) + ")"; to_delete[i-1] = get_oom_object (attachments, item_str.c_str()); } gpgol_release (attachments); /* Now delete all attachments */ for (int i = 0; i < count; i++) { LPDISPATCH attachment = to_delete[i]; if (!attachment) { log_error ("%s:%s: No such attachment %i", SRCNAME, __func__, i); ret = -1; } /* Delete the attachments that are marked to delete */ if (invoke_oom_method (attachment, "Delete", NULL)) { log_error ("%s:%s: Deleting attachment %i", SRCNAME, __func__, i); ret = -1; } gpgol_release (attachment); } TRETURN ret; } int Mail::removeOurAttachments_o () { TSTART; LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (!attachments) { TRACEPOINT; TRETURN 0; } int count = get_oom_int (attachments, "Count"); LPDISPATCH to_delete[count]; int del_cnt = 0; for (int i = 1; i <= count; i++) { auto item_str = std::string("Item(") + std::to_string (i) + ")"; LPDISPATCH attachment = get_oom_object (attachments, item_str.c_str()); if (!attachment) { TRACEPOINT; continue; } attachtype_t att_type; if (get_pa_int (attachment, GPGOL_ATTACHTYPE_DASL, (int*) &att_type)) { /* Not our attachment. */ gpgol_release (attachment); continue; } if (att_type == ATTACHTYPE_PGPBODY || att_type == ATTACHTYPE_MOSS || att_type == ATTACHTYPE_MOSSTEMPL) { /* One of ours to delete. */ to_delete[del_cnt++] = attachment; /* Dont' release yet */ continue; } gpgol_release (attachment); } gpgol_release (attachments); int ret = 0; m_disable_att_remove_warning = true; 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); } m_disable_att_remove_warning = false; TRETURN ret; } /* We are very verbose because if we fail it might mean that we have leaked plaintext -> critical. */ bool Mail::hasCryptedOrEmptyBody_o () { TSTART; const auto pair = has_crypt_or_empty_body_oom (this); if (pair.first /* encrypted marker */) { log_debug ("%s:%s: Crypt Marker detected in OOM body. Return true %p.", SRCNAME, __func__, this); TRETURN true; } if (!pair.second) { log_debug ("%s:%s: Unexpected content detected. Return false %p.", SRCNAME, __func__, this); TRETURN false; } // Pair second == true (is empty) can happen on OOM error. LPMESSAGE message = get_oom_base_message (m_mailitem); if (!message && pair.second) { if (message) { gpgol_release (message); } TRETURN true; } size_t r_nbytes = 0; char *mapi_body = mapi_get_body (message, &r_nbytes); gpgol_release (message); if (!mapi_body || !r_nbytes) { // Body or bytes are null. we are empty. xfree (mapi_body); log_debug ("%s:%s: MAPI error or empty message. Return true. %p.", SRCNAME, __func__, this); TRETURN true; } if (r_nbytes > 10 && !strncmp (mapi_body, "-----BEGIN", 10)) { // Body is crypt. log_debug ("%s:%s: MAPI Crypt marker detected. Return true. %p.", SRCNAME, __func__, this); xfree (mapi_body); TRETURN true; } xfree (mapi_body); log_debug ("%s:%s: Found mapi body. Return false. %p.", SRCNAME, __func__, this); TRETURN false; } std::string Mail::getVerificationResultDump () { TSTART; std::stringstream ss; ss << m_verify_result; TRETURN ss.str(); } void Mail::setBlockStatus_m () { TSTART; SPropValue prop; LPMESSAGE message = get_oom_base_message (m_mailitem); prop.ulPropTag = PR_BLOCK_STATUS; prop.Value.l = 1; HRESULT hr = message->SetProps (1, &prop, NULL); if (hr) { log_error ("%s:%s: can't set block value: hr=%#lx\n", SRCNAME, __func__, hr); } gpgol_release (message); TRETURN; } void Mail::setBlockHTML (bool value) { TSTART; m_block_html = value; TRETURN; } void Mail::incrementLocateCount () { TSTART; m_locate_count++; TRETURN; } void Mail::decrementLocateCount () { TSTART; m_locate_count--; if (m_locate_count < 0) { log_error ("%s:%s: locate count mismatch.", SRCNAME, __func__); m_locate_count = 0; } if (!m_locate_count) { autosecureCheck (); } TRETURN; } void Mail::autosecureCheck () { TSTART; if (!opt.autosecure || !opt.autoresolve || m_manual_crypto_opts || m_locate_count) { TRETURN; } bool ret = KeyCache::instance()->isMailResolvable (this); log_debug ("%s:%s: status %i", SRCNAME, __func__, ret); /* As we are safe to call at any time, because we need * to be triggered by the locator threads finishing we * need to actually set the draft info flags in * the ui thread. */ do_in_ui_thread_async (ret ? DO_AUTO_SECURE : DONT_AUTO_SECURE, this); TRETURN; } void Mail::setDoAutosecure_m (bool value) { TSTART; TRACEPOINT; LPMESSAGE msg = get_oom_base_message (m_mailitem); if (!msg) { TRACEPOINT; TRETURN; } /* We need to set a uuid so that autosecure can be disabled manually */ setUUID_o (); int old_flags = get_gpgol_draft_info_flags (msg); if (old_flags && m_first_autosecure_check && /* Someone with always sign and autosecure active * will want to get autoencryption. */ !(old_flags == 2 && opt.sign_default)) { /* They were set explicily before us. This can be * because they were a draft (which is bad) or * because they are a reply/forward to a crypto mail * or because there are conflicting settings. */ log_debug ("%s:%s: Mail %p had already flags set.", SRCNAME, __func__, m_mailitem); m_first_autosecure_check = false; m_manual_crypto_opts = true; gpgol_release (msg); TRETURN; } m_first_autosecure_check = false; set_gpgol_draft_info_flags (msg, value ? 3 : opt.sign_default ? 2 : 0); gpgol_release (msg); gpgoladdin_invalidate_ui(); TRETURN; } bool Mail::decryptedSuccessfully () const { return m_decrypt_result.isNull() || !m_decrypt_result.error(); } void Mail::installFolderEventHandler_o() { TSTART; TRACEPOINT; LPDISPATCH folder = get_oom_object (m_mailitem, "Parent"); if (!folder) { TRACEPOINT; TRETURN; } char *objName = get_object_name (folder); if (!objName || strcmp (objName, "MAPIFolder")) { log_debug ("%s:%s: Mail %p parent is not a mapi folder.", SRCNAME, __func__, m_mailitem); xfree (objName); gpgol_release (folder); TRETURN; } xfree (objName); char *path = get_oom_string (folder, "FullFolderPath"); if (!path) { TRACEPOINT; path = get_oom_string (folder, "FolderPath"); } if (!path) { log_error ("%s:%s: Mail %p parent has no folder path.", SRCNAME, __func__, m_mailitem); gpgol_release (folder); TRETURN; } std::string strPath (path); xfree (path); if (s_folder_events_map.find (strPath) == s_folder_events_map.end()) { log_debug ("%s:%s: Install folder events watcher for %s.", SRCNAME, __func__, anonstr (strPath.c_str())); const auto sink = install_FolderEvents_sink (folder); s_folder_events_map.insert (std::make_pair (strPath, sink)); } /* Folder already registered */ gpgol_release (folder); TRETURN; } void Mail::refCurrentItem() { TSTART; if (m_currentItemRef) { log_debug ("%s:%s: Current item multi ref. Bug?", SRCNAME, __func__); TRETURN; } /* This prevents a crash in Outlook 2013 when sending a mail as it * would unload too early. * * As it didn't crash when the mail was opened in Outlook Spy this * mimics that the mail is inspected somewhere else. */ m_currentItemRef = get_oom_object (m_mailitem, "GetInspector.CurrentItem"); TRETURN; } void Mail::releaseCurrentItem() { TSTART; if (!m_currentItemRef) { TRETURN; } log_oom ("%s:%s: releasing CurrentItem ref %p", SRCNAME, __func__, m_currentItemRef); LPDISPATCH tmp = m_currentItemRef; m_currentItemRef = nullptr; /* This can cause our destruction */ gpgol_release (tmp); TRETURN; } void Mail::decryptPermanently_o() { TSTART; if (!m_needs_wipe) { log_debug ("%s:%s: Mail does not yet need wipe. Called to early?", SRCNAME, __func__); TRETURN; } if (!m_decrypt_result.isNull() && m_decrypt_result.error()) { log_debug ("%s:%s: Decrypt result had error. Can't decrypt permanently.", SRCNAME, __func__); TRETURN; } /* Remove the existing categories */ removeCategories_o (); /* Drop our state variables */ m_decrypt_result = GpgME::DecryptionResult(); m_verify_result = GpgME::VerificationResult(); m_needs_wipe = false; m_processed = false; m_is_smime = false; m_type = MSGTYPE_UNKNOWN; /* Remove our own attachments */ removeOurAttachments_o (); updateSigstate(); auto msg = MAKE_SHARED (get_oom_base_message (m_mailitem)); if (!msg) { STRANGEPOINT; TRETURN; } mapi_delete_gpgol_tags ((LPMESSAGE)msg.get()); /* The content type is wrong now. We remove it.*/ mapi_set_content_type ((LPMESSAGE)msg.get(), m_dec_content_type.empty () ? "text/plain" : m_dec_content_type.c_str ()); mapi_set_mesage_class ((LPMESSAGE)msg.get(), "IPM.Note"); if (invoke_oom_method (m_mailitem, "Save", NULL)) { log_error ("Failed to save decrypted mail: %p ", m_mailitem); } log_debug ("%s:%s: Delayed invalidate to update sigstate after perm dec.", SRCNAME, __func__); CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0, NULL)); TRETURN; } int Mail::prepareCrypto_o () { TSTART; int olFlags = get_oom_crypto_flags (m_mailitem); log_dbg ("Outlook internal crypto flags are: %i", olFlags); if ((olFlags & 3)) { std::string question = _("Should GpgOL override Outlook and continue " "to process the message?"); std::string vs_warning; if (in_de_vs_mode ()) { /* TRANSLATORS %s is compliance name like VS-NfD */ vs_warning = asprintf_s (_("Note: For %s communication you have to select Yes."), de_vs_name ()) + std::string ("\n\n"); } std::string msg = _("A crypto operation was selected both using " "Outlooks internal crypto and with GpgOL / GnuPG.") + std::string("\n\n") + vs_warning + question; int yesno = gpgol_message_box (get_active_hwnd (), msg.c_str (), _("Conflicting crypto settings"), MB_YESNO); if (yesno == IDYES) { log_dbg ("Overriding Outlook internal crypto."); } else { log_dbg ("Aborting send."); TRETURN -1; } } put_pa_int (m_mailitem, PR_SECURITY_FLAGS_DASL, 0); // Check inline response state to fill out asynccryptdisabled. checkSyncCrypto_o (); if (!isAsyncCryptDisabled()) { /* Obtain a reference of the current item. This prevents * an early unload which would crash Outlook 2013 * * As it didn't crash when the mail was opened in Outlook Spy this * mimics that the mail is inspected somewhere else. */ refCurrentItem (); } // First contact with a mail to encrypt update // state and oom data. updateOOMData_o (true); setCryptState (Mail::DataCollected); TRETURN 0; } /* Printing happens in two steps. First a Mail is loaded after the BeforePrint event, then it is loaded a second time when the actual print happens. We have to catch both. This functions looks over all mails and checks if one is currently printing. If so we compare our EntryID's and if they match. Bingo, we are printing, too.*/ bool Mail::checkIfMailMightBePrinting_o () { const auto ourID = get_oom_string_s (m_mailitem, "EntryID"); if (ourID.empty ()) { log_error ("%s:%s: Mail %p has no accessible EntryID", SRCNAME, __func__, this); return false; } if (s_entry_ids_printing.find (ourID) != s_entry_ids_printing.end ()) { log_dbg ("Found %s in printing map. This might be a printing child.", ourID.c_str ()); return true; } return false; } void Mail::setSigningKeys (const std::vector &keys) { m_resolved_signing_keys = keys; } std::vector Mail::getSigningKeys () const { return m_resolved_signing_keys; } Mail * Mail::copy () { TSTART; VARIANT result; VariantInit (&result); int err = invoke_oom_method (m_mailitem, "Copy", &result); if (err) { log_error ("%s:%s Failed to copy mail.", SRCNAME, __func__); TRETURN nullptr; } if (result.vt != VT_DISPATCH || !result.pdispVal) { log_error ("%s:%s Unexpected result type %x.", SRCNAME, __func__, result.vt); TRETURN nullptr; } /* The returned dispatch interface has da different address then the one from the ItemLoad event. So we now need to find the mail object for the copied mail. The copied mail has an entry id but we are not assured that the current mail has an entry id because it might not have been saved *sigh* So we use the GpgOL UUID that is copied in MAPI to identify the copy. */ const auto id = get_unique_id_s (m_mailitem, 0, nullptr); const auto mails = searchMailsByUUID (id); log_dbg ("Found %i mails with id '%s'", (int) mails.size (), id.c_str ()); for (const auto it: mails) { if (it == this) { continue; } /* Transfer the refence from the copy IDispatch to the Mail object IDispatch. This is pretty strange, but doing a VariantClear on the result otherwise results in an unload of the object so we need to track that reference somehow. */ LPDISPATCH copyRef = result.pdispVal; memdbg_addRef (copyRef); it->setAdditionalReference (copyRef); /* Set a new UID for the copy and enter it in the map */ it->setUUID_o (true); TRETURN it; } log_err ("Failed to find copied mail!"); TRETURN nullptr; } void Mail::splitAndSend_o () { TSTART; log_dbg ("Split and send for: %p", this); RecipientManager mngr (m_cached_recipients, m_resolved_signing_keys); int count = mngr.getRequiredMails (); log_dbg ("Need to send %i mails", mngr.getRequiredMails ()); if (count == 1) { /* We have to bug out here because otherwise we would loop endlessly if a split is deteced but no split occurs */ gpgol_bug (get_active_hwnd(), ERR_SPLIT_UNEXPECTED); TRETURN; } /* Build the headers to set on the split mails */ buildProtectedHeaders_o (); for (int i = 0; i < count; i++) { Mail *copiedMail = copy (); if (!copiedMail) { log_err ("Failed to copy mail. Aboring."); TRETURN; } GpgME::Key sigKey; const RecpList recps = mngr.getRecipients (i, sigKey); copiedMail->setRecipients (recps); std::vector sigVec; sigVec.push_back (sigKey); copiedMail->setSigningKeys (sigVec); if (set_oom_recipients (copiedMail->item (), recps)) { log_err ("Failed to update recipients"); gpgol_bug (get_active_hwnd(), ERR_SPLIT_RECIPIENTS); TRETURN; } if (!opt.encryptSubject && !mngr.isSplitByProtocol ()) { /* If subject encryption is on we only need to add the original recipients when there is a BCC recipient. When we have split by protcol we always need to add the headers to restore the original recipients. */ for (const auto &recp: recps) { if (recp.type () == Recipient::olBCC) { copiedMail->setProtectedHeaders (m_protected_headers); break; } } } else { /* When subject encryption is on we always need to add the headers */ copiedMail->setProtectedHeaders (m_protected_headers); } /* Now do the crypto */ copiedMail->setCopyParent (this); copiedMail->prepareCrypto_o (); copiedMail->encryptSignStart_o (); } /* Reset the protected headers */ m_protected_headers = std::string (); TRETURN; } void Mail::resetCrypter () { m_crypter = nullptr; } void Mail::resetRecipients () { /* Clear recipient data */ m_cached_recipients.clear (); m_resolved_signing_keys.clear (); m_recipients_set = false; } void Mail::setCopyParent (Mail *val) { m_copy_parent = val; } Mail * Mail::copyParent() const { return m_copy_parent; } int Mail::buildProtectedHeaders_o () { TSTART; std::stringstream ss; std::vector toRecps; std::vector ccRecps; std::vector bccRecps; ss << "Content-Type: text/plain;\r\n" << "\tprotected-headers=\"v1\"\r\n" << "Content-Disposition: inline\r\n\r\n"; for (const auto &recp: m_cached_recipients) { if (recp.type() == Recipient::olCC) { ccRecps.push_back (recp.encodedDisplayName ()); } else if (recp.type() == Recipient::olTo) { toRecps.push_back (recp.encodedDisplayName ()); } else if (recp.type() == Recipient::olOriginator) { ss << "From: " << recp.encodedDisplayName () << "\r\n"; } } std::string buf; if (toRecps.size ()) { join(toRecps, ";", buf); ss << "To: " << buf << "\r\n"; } if (ccRecps.size ()) { join(toRecps, ";", buf); ss << "CC: " << buf << "\r\n"; } if (opt.encryptSubject) { char *subject = get_oom_string (m_mailitem, "Subject"); if (subject) { char *encodedSubject = utf8_to_rfc2047b (subject); if (encodedSubject) { ss << "Subject: " << encodedSubject << "\r\n"; } xfree (encodedSubject); } if (!put_oom_string (m_mailitem, "Subject", "...")) { STRANGEPOINT; TRETURN -1; } xfree (subject); } m_protected_headers = ss.str (); TRETURN 0; } void Mail::setProtectedHeaders (const std::string &hdr) { m_protected_headers = hdr; } std::string Mail::protectedHeaders () const { return m_protected_headers; } header_info_s Mail::headerInfo () const { return m_header_info; } const std::string& Mail::getOriginalBody () const { return m_orig_body; } int Mail::isMultipartRelated () const { int related = false; int mixed = false; - for (const auto attach: m_plain_attachments) + for (const auto &attach: m_plain_attachments) { if (attach->get_content_id ().size()) { related = 1; } else { mixed = 1; } } return related + mixed; } std::vector > Mail::plainAttachments () const { return m_plain_attachments; } void Mail::setAdditionalReference (LPDISPATCH ref) { m_additional_item = ref; } diff --git a/src/oomhelp.cpp b/src/oomhelp.cpp index 0a58155..0dd8e7b 100644 --- a/src/oomhelp.cpp +++ b/src/oomhelp.cpp @@ -1,3844 +1,3844 @@ /* oomhelp.cpp - Helper functions for the Outlook Object Model * Copyright (C) 2009 g10 Code GmbH * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "common.h" #include "oomhelp.h" #include "cpphelp.h" #include "gpgoladdin.h" #include "categorymanager.h" #include "recipient.h" HRESULT gpgol_queryInterface (LPUNKNOWN pObj, REFIID riid, LPVOID FAR *ppvObj) { HRESULT ret = pObj->QueryInterface (riid, ppvObj); if (ret) { log_debug ("%s:%s: QueryInterface failed hr=%#lx", SRCNAME, __func__, ret); } else if ((opt.enable_debug & DBG_MEMORY) && *ppvObj) { memdbg_addRef (*ppvObj); } return ret; } HRESULT gpgol_openProperty (LPMAPIPROP obj, ULONG ulPropTag, LPCIID lpiid, ULONG ulInterfaceOptions, ULONG ulFlags, LPUNKNOWN FAR * lppUnk) { HRESULT ret = obj->OpenProperty (ulPropTag, lpiid, ulInterfaceOptions, ulFlags, lppUnk); if (ret) { log_debug ("%s:%s: OpenProperty failed hr=%#lx %s", SRCNAME, __func__, ret, mapi_err_to_string (ret)); } else if ((opt.enable_debug & DBG_MEMORY) && *lppUnk) { memdbg_addRef (*lppUnk); log_debug ("%s:%s: OpenProperty on %p prop %lx result %p", SRCNAME, __func__, obj, ulPropTag, *lppUnk); } return ret; } /* Return a malloced string with the utf-8 encoded name of the object or NULL if not available. */ char * get_object_name (LPUNKNOWN obj) { TSTART; HRESULT hr; LPDISPATCH disp = NULL; LPTYPEINFO tinfo = NULL; BSTR bstrname; char *name = NULL; if (!obj) goto leave; /* We can't use gpgol_queryInterface here to avoid recursion */ hr = obj->QueryInterface (IID_IDispatch, (void **)&disp); if (!disp || hr != S_OK) goto leave; disp->GetTypeInfo (0, 0, &tinfo); if (!tinfo) { log_debug ("%s:%s: no typeinfo found for object\n", SRCNAME, __func__); goto leave; } bstrname = NULL; hr = tinfo->GetDocumentation (MEMBERID_NIL, &bstrname, 0, 0, 0); if (hr || !bstrname) log_debug ("%s:%s: GetDocumentation failed: hr=%#lx\n", SRCNAME, __func__, hr); if (bstrname) { name = wchar_to_utf8 (bstrname); SysFreeString (bstrname); } leave: if (tinfo) tinfo->Release (); if (disp) disp->Release (); TRETURN name; } std::string get_object_name_s (LPUNKNOWN obj) { char *name = get_object_name (obj); std::string ret = "(null)"; if (name) { ret = name; } xfree (name); return ret; } std::string get_object_name_s (shared_disp_t obj) { return get_object_name_s (obj.get ()); } /* Lookup the dispid of object PDISP for member NAME. Returns DISPID_UNKNOWN on error. */ DISPID lookup_oom_dispid (LPDISPATCH pDisp, const char *name) { HRESULT hr; DISPID dispid; wchar_t *wname; if (!pDisp || !name) { TRETURN DISPID_UNKNOWN; /* Error: Invalid arg. */ } wname = utf8_to_wchar (name); if (!wname) { TRETURN DISPID_UNKNOWN;/* Error: Out of memory. */ } hr = pDisp->GetIDsOfNames (IID_NULL, &wname, 1, LOCALE_SYSTEM_DEFAULT, &dispid); xfree (wname); if (hr != S_OK || dispid == DISPID_UNKNOWN) log_debug ("%s:%s: error looking up dispid(%s)=%d: hr=0x%x\n", SRCNAME, __func__, name, (int)dispid, (unsigned int)hr); if (hr != S_OK) dispid = DISPID_UNKNOWN; return dispid; } static void init_excepinfo (EXCEPINFO *err) { if (!err) { TRETURN; } err->wCode = 0; err->wReserved = 0; err->bstrSource = nullptr; err->bstrDescription = nullptr; err->bstrHelpFile = nullptr; err->dwHelpContext = 0; err->pvReserved = nullptr; err->pfnDeferredFillIn = nullptr; err->scode = 0; } void dump_excepinfo (EXCEPINFO err) { log_oom ("%s:%s: Exception: \n" " wCode: 0x%x\n" " wReserved: 0x%x\n" " source: %S\n" " desc: %S\n" " help: %S\n" " helpCtx: 0x%x\n" " deferredFill: %p\n" " scode: 0x%x\n", SRCNAME, __func__, (unsigned int) err.wCode, (unsigned int) err.wReserved, err.bstrSource ? err.bstrSource : L"null", err.bstrDescription ? err.bstrDescription : L"null", err.bstrHelpFile ? err.bstrDescription : L"null", (unsigned int) err.dwHelpContext, err.pfnDeferredFillIn, (unsigned int) err.scode); } /* Return the OOM object's IDispatch interface described by FULLNAME. Returns NULL if not found. PSTART is the object where the search starts. FULLNAME is a dot delimited sequence of object names. If an object name has a "(foo)" suffix this passes it as a parameter to the invoke function (i.e. using (DISPATCH|PROPERTYGET)). Object names including the optional suffix are truncated at 127 byte. */ LPDISPATCH get_oom_object (LPDISPATCH pStart, const char *fullname) { TSTART; HRESULT hr; LPDISPATCH pObj = pStart; LPDISPATCH pDisp = NULL; log_oom ("%s:%s: looking for %p->`%s'", SRCNAME, __func__, pStart, fullname); while (pObj) { DISPPARAMS dispparams; VARIANT aVariant[4]; VARIANT vtResult; wchar_t *wname; char name[128]; int n_parms = 0; BSTR parmstr = NULL; INT parmint = 0; DISPID dispid; char *p, *pend; int dispmethod; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); if (pDisp) { gpgol_release (pDisp); pDisp = NULL; } if (gpgol_queryInterface (pObj, IID_IDispatch, (LPVOID*)&pDisp) != S_OK) { log_error ("%s:%s Object does not support IDispatch", SRCNAME, __func__); if (pObj != pStart) gpgol_release (pObj); TRETURN NULL; } /* Confirmed through testing that the retval needs a release */ if (pObj != pStart) gpgol_release (pObj); pObj = NULL; if (!pDisp) { TRETURN NULL; /* The object has no IDispatch interface. */ } if (!*fullname) { if ((opt.enable_debug & DBG_MEMORY)) { pDisp->AddRef (); int ref = pDisp->Release (); log_oom ("%s:%s: got %p with %i refs", SRCNAME, __func__, pDisp, ref); } TRETURN pDisp; /* Ready. */ } /* Break out the next name part. */ { const char *dot; size_t n; dot = strchr (fullname, '.'); if (dot == fullname) { gpgol_release (pDisp); TRETURN NULL; /* Empty name part: error. */ } else if (dot) n = dot - fullname; else n = strlen (fullname); if (n >= sizeof name) n = sizeof name - 1; strncpy (name, fullname, n); name[n] = 0; if (dot) fullname = dot + 1; else fullname += strlen (fullname); } if (!strncmp (name, "get_", 4) && name[4]) { dispmethod = DISPATCH_PROPERTYGET; memmove (name, name+4, strlen (name+4)+1); } else if ((p = strchr (name, '('))) { *p++ = 0; pend = strchr (p, ')'); if (pend) *pend = 0; if (*p == ',' && p[1] != ',') { /* We assume this is "foo(,30007)". I.e. the frst arg is not given and the second one is an integer. */ parmint = (int)strtol (p+1, NULL, 10); n_parms = 4; } else { wname = utf8_to_wchar (p); if (wname) { parmstr = SysAllocString (wname); xfree (wname); } if (!parmstr) { gpgol_release (pDisp); TRETURN NULL; /* Error: Out of memory. */ } n_parms = 1; } dispmethod = DISPATCH_METHOD|DISPATCH_PROPERTYGET; } else dispmethod = DISPATCH_METHOD; /* Lookup the dispid. */ dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { if (parmstr) SysFreeString (parmstr); gpgol_release (pDisp); TRETURN NULL; /* Name not found. */ } /* Invoke the method. */ dispparams.rgvarg = aVariant; dispparams.cArgs = 0; if (n_parms) { if (n_parms == 4) { dispparams.rgvarg[0].vt = VT_ERROR; dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[1].vt = VT_ERROR; dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[2].vt = VT_INT; dispparams.rgvarg[2].intVal = parmint; dispparams.rgvarg[3].vt = VT_ERROR; dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; dispparams.cArgs = n_parms; } else if (n_parms == 1 && parmstr) { dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[0].bstrVal = parmstr; dispparams.cArgs++; } } dispparams.cNamedArgs = 0; VariantInit (&vtResult); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, dispmethod, &dispparams, &vtResult, &execpinfo, &argErr); if (parmstr) SysFreeString (parmstr); if (hr != S_OK || vtResult.vt != VT_DISPATCH) { log_debug ("%s:%s: failure: '%s' p=%p vt=%d hr=0x%x argErr=0x%x dispid=0x%x", SRCNAME, __func__, name, vtResult.pdispVal, vtResult.vt, (unsigned int)hr, (unsigned int)argErr, (unsigned int)dispid); dump_excepinfo (execpinfo); VariantClear (&vtResult); gpgol_release (pDisp); TRETURN NULL; /* Invoke failed. */ } pObj = vtResult.pdispVal; memdbg_addRef (pObj); } gpgol_release (pDisp); log_debug ("%s:%s: no object", SRCNAME, __func__); TRETURN NULL; } shared_disp_t get_oom_object_s (shared_disp_t pStart, const char *fullname) { return MAKE_SHARED (get_oom_object (pStart.get (), fullname)); } shared_disp_t get_oom_object_s (LPDISPATCH pStart, const char *fullname) { return MAKE_SHARED (get_oom_object (pStart, fullname)); } /* Helper for put_oom_icon. */ static int put_picture_or_mask (LPDISPATCH pDisp, int resource, int size, int is_mask) { TSTART; HRESULT hr; PICTDESC pdesc; LPDISPATCH pPict; DISPID dispid_put = DISPID_PROPERTYPUT; UINT fuload; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[2]; /* When loading the mask we need to set the monochrome flag. We better create a DIB section to avoid possible rendering problems. */ fuload = LR_CREATEDIBSECTION | LR_SHARED; if (is_mask) fuload |= LR_MONOCHROME; memset (&pdesc, 0, sizeof pdesc); pdesc.cbSizeofstruct = sizeof pdesc; pdesc.picType = PICTYPE_BITMAP; pdesc.bmp.hbitmap = (HBITMAP) LoadImage (glob_hinst, MAKEINTRESOURCE (resource), IMAGE_BITMAP, size, size, fuload); if (!pdesc.bmp.hbitmap) { log_error_w32 (-1, "%s:%s: LoadImage(%d) failed\n", SRCNAME, __func__, resource); TRETURN -1; } /* Wrap the image into an OLE object. */ hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp, TRUE, (void **) &pPict); if (hr != S_OK || !pPict) { log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n", SRCNAME, __func__, hr); TRETURN -1; } /* Store to the Picture or Mask property of the CommandBarButton. */ dispid = lookup_oom_dispid (pDisp, is_mask? "Mask":"Picture"); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_DISPATCH; dispparams.rgvarg[0].pdispVal = pPict; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting icon failed: %#lx", SRCNAME, __func__, hr); TRETURN -1; } TRETURN 0; } /* Update the icon of PDISP using the bitmap with RESOURCE ID. The function adds the system pixel size to the resource id to compute the actual icon size. The resource id of the mask is the N+1. */ int put_oom_icon (LPDISPATCH pDisp, int resource_id, int size) { TSTART; int rc; /* This code is only relevant for Outlook < 2010. Ideally it should grab the system pixel size and use an icon of the appropiate size (e.g. 32 or 64px) */ rc = put_picture_or_mask (pDisp, resource_id, size, 0); if (!rc) rc = put_picture_or_mask (pDisp, resource_id + 1, size, 1); TRETURN rc; } /* Set the boolean property NAME to VALUE. */ int put_oom_bool (LPDISPATCH pDisp, const char *name, int value) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; dispparams.rgvarg[0].boolVal = value? VARIANT_TRUE:VARIANT_FALSE; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); TRETURN -1; } TRETURN 0; } /* Set the property NAME to VALUE. */ int put_oom_int (LPDISPATCH pDisp, const char *name, int value) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = value; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); TRETURN -1; } TRETURN 0; } /* Set the property NAME to VALUE. */ int put_oom_array (LPDISPATCH pDisp, const char *name, unsigned char *value, size_t size) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); VariantInit (aVariant); dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } /* Prepare the savearray */ SAFEARRAYBOUND saBound; saBound.lLbound = 0; saBound.cElements = size; SAFEARRAY* psa = SafeArrayCreate(VT_UI1, 1, &saBound); if (!psa) { log_err ("Failed to create SafeArray"); TRETURN -1; } hr = SafeArrayLock(psa); if (!SUCCEEDED (hr)) { log_err ("Failed to lock array."); } memcpy (psa->pvData, value, size); SafeArrayUnlock(psa); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_ARRAY; dispparams.rgvarg[0].parray = psa; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, &execpinfo, &argErr); SafeArrayDestroy(psa); if (hr != S_OK) { log_debug ("%s:%s: error: putting %s" " hr=0x%x argErr=0x%x", SRCNAME, __func__, name, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); TRETURN -1; } TRETURN 0; } /* Set the property NAME to STRING. */ int put_oom_string (LPDISPATCH pDisp, const char *name, const char *string) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; BSTR bstring; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } { wchar_t *tmp = utf8_to_wchar (string); bstring = tmp? SysAllocString (tmp):NULL; xfree (tmp); if (!bstring) { log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__); TRETURN -1; } } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[0].bstrVal = bstring; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &dispparams, NULL, &execpinfo, NULL); SysFreeString (bstring); if (hr != S_OK) { log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); dump_excepinfo (execpinfo); TRETURN -1; } TRETURN 0; } /* Set the property NAME to DISP. */ int put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH disp) { TSTART; HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[1]; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispid = lookup_oom_dispid (pDisp, name); if (dispid == DISPID_UNKNOWN) { TRETURN -1; } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_DISPATCH; dispparams.rgvarg[0].pdispVal = disp; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUTREF, &dispparams, NULL, &execpinfo, NULL); if (hr != S_OK) { log_debug ("%s:%s: Putting '%s' failed: %#lx", SRCNAME, __func__, name, hr); dump_excepinfo (execpinfo); TRETURN -1; } TRETURN 0; } /* Get the boolean property NAME of the object PDISP. Returns False if not found or if it is not a boolean property. */ int get_oom_bool (LPDISPATCH pDisp, const char *name) { TSTART; HRESULT hr; int result = 0; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_BOOL) log_debug ("%s:%s: Property `%s' is not a boolean (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else result = !!rVariant.boolVal; VariantClear (&rVariant); } TRETURN result; } /* Get the integer property NAME of the object PDISP. Returns 0 if not found or if it is not an integer property. */ int get_oom_int (LPDISPATCH pDisp, const char *name) { TSTART; HRESULT hr; int result = 0; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_INT && rVariant.vt != VT_I4) log_debug ("%s:%s: Property `%s' is not an integer (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else result = rVariant.intVal; VariantClear (&rVariant); } TRETURN result; } int get_oom_int (shared_disp_t pDisp, const char *name) { return get_oom_int (pDisp.get (), name); } int get_oom_dirty (LPDISPATCH pDisp) { TSTART; HRESULT hr; DISPID dispid = DISPID_DIRTY_RAT; DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET | DISPATCH_METHOD, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Property dirty not found: %#lx", SRCNAME, __func__, hr); TRETURN -1; } return !!rVariant.bVal; } #if 0 int put_oom_dirty (LPDISPATCH pDisp, bool value) { TSTART; /* NOTE: I have found no scenario where this does not return the exception that the property is write protected. But we can never know when we need such an arcane function so I left it in. */ HRESULT hr; DISPID dispid_put = DISPID_PROPERTYPUT; DISPID dispid = DISPID_DIRTY_RAT; DISPPARAMS dispparams; VARIANT aVariant[1]; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; dispparams.rgvarg[0].boolVal = value? VARIANT_TRUE:VARIANT_FALSE; dispparams.cArgs = 1; dispparams.rgdispidNamedArgs = &dispid_put; dispparams.cNamedArgs = 1; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT | DISPATCH_METHOD, &dispparams, NULL, &execpinfo, &argErr); if (hr != S_OK) { log_debug ("%s:%s: error: invoking dirty p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, nullptr, 0, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); TRETURN -1; } TRETURN 0; } #endif /* Get the string property NAME of the object PDISP. Returns NULL if not found or if it is not a string property. */ char * get_oom_string (LPDISPATCH pDisp, const char *name) { TSTART; HRESULT hr; char *result = NULL; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_BSTR) log_debug ("%s:%s: Property `%s' is not a string (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else if (rVariant.bstrVal) result = wchar_to_utf8 (rVariant.bstrVal); VariantClear (&rVariant); } TRETURN result; } std::string get_oom_string_s (LPDISPATCH pDisp, const char *name) { char *ret_c = get_oom_string (pDisp, name); std::string ret; if (ret_c) { ret = ret_c; xfree (ret_c); } return ret; } std::string get_oom_string_s (shared_disp_t pDisp, const char *name) { return get_oom_string_s (pDisp.get (), name); } /* Get the object property NAME of the object PDISP. Returns NULL if not found or if it is not an object perty. */ LPUNKNOWN get_oom_iunknown (LPDISPATCH pDisp, const char *name) { TSTART; HRESULT hr; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { DISPPARAMS dispparams = {NULL, NULL, 0, 0}; VARIANT rVariant; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK) log_debug ("%s:%s: Property '%s' not found: %#lx", SRCNAME, __func__, name, hr); else if (rVariant.vt != VT_UNKNOWN) log_debug ("%s:%s: Property `%s' is not of class IUnknown (vt=%d)", SRCNAME, __func__, name, rVariant.vt); else { memdbg_addRef (rVariant.punkVal); TRETURN rVariant.punkVal; } VariantClear (&rVariant); } TRETURN NULL; } /* Return the control object described by the tag property with value TAG. The object POBJ must support the FindControl method. Returns NULL if not found. */ LPDISPATCH get_oom_control_bytag (LPDISPATCH pDisp, const char *tag) { TSTART; HRESULT hr; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[4]; VARIANT rVariant; BSTR bstring; LPDISPATCH result = NULL; dispid = lookup_oom_dispid (pDisp, "FindControl"); if (dispid == DISPID_UNKNOWN) { log_debug ("%s:%s: Object %p has no FindControl method", SRCNAME, __func__, pDisp); TRETURN NULL; } { wchar_t *tmp = utf8_to_wchar (tag); bstring = tmp? SysAllocString (tmp):NULL; xfree (tmp); if (!bstring) { log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__); TRETURN NULL; } } dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_ERROR; /* Visible */ dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[1].vt = VT_BSTR; /* Tag */ dispparams.rgvarg[1].bstrVal = bstring; dispparams.rgvarg[2].vt = VT_ERROR; /* Id */ dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[3].vt = VT_ERROR;/* Type */ dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; dispparams.cArgs = 4; dispparams.cNamedArgs = 0; VariantInit (&rVariant); hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, NULL, NULL); SysFreeString (bstring); if (hr == S_OK && rVariant.vt == VT_DISPATCH && rVariant.pdispVal) { gpgol_queryInterface (rVariant.pdispVal, IID_IDispatch, (LPVOID*)&result); gpgol_release (rVariant.pdispVal); if (!result) log_debug ("%s:%s: Object with tag `%s' has no dispatch intf.", SRCNAME, __func__, tag); } else { log_debug ("%s:%s: No object with tag `%s' found: vt=%d hr=%#lx", SRCNAME, __func__, tag, rVariant.vt, hr); VariantClear (&rVariant); } TRETURN result; } /* Add a new button to an object which supports the add method. Returns the new object or NULL on error. */ LPDISPATCH add_oom_button (LPDISPATCH pObj) { TSTART; HRESULT hr; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[5]; VARIANT rVariant; dispid = lookup_oom_dispid (pObj, "Add"); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */ dispparams.rgvarg[0].boolVal = VARIANT_TRUE; dispparams.rgvarg[1].vt = VT_ERROR; /* Before */ dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[2].vt = VT_ERROR; /* Parameter */ dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[3].vt = VT_ERROR; /* Id */ dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; dispparams.rgvarg[4].vt = VT_INT; /* Type */ dispparams.rgvarg[4].intVal = MSOCONTROLBUTTON; dispparams.cArgs = 5; dispparams.cNamedArgs = 0; VariantInit (&rVariant); hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, NULL, NULL); if (hr != S_OK || rVariant.vt != VT_DISPATCH || !rVariant.pdispVal) { log_error ("%s:%s: Adding Control failed: %#lx - vt=%d", SRCNAME, __func__, hr, rVariant.vt); VariantClear (&rVariant); TRETURN NULL; } TRETURN rVariant.pdispVal; } /* Add a new button to an object which supports the add method. Returns the new object or NULL on error. */ void del_oom_button (LPDISPATCH pObj) { TSTART; HRESULT hr; DISPID dispid; DISPPARAMS dispparams; VARIANT aVariant[5]; dispid = lookup_oom_dispid (pObj, "Delete"); dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */ dispparams.rgvarg[0].boolVal = VARIANT_FALSE; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, NULL, NULL); if (hr != S_OK) log_error ("%s:%s: Deleting Control failed: %#lx", SRCNAME, __func__, hr); TRETURN; } /* Gets the current contexts HWND. Returns NULL on error */ HWND get_oom_context_window (LPDISPATCH context) { TSTART; LPOLEWINDOW actExplorer; HWND ret = NULL; actExplorer = (LPOLEWINDOW) get_oom_object(context, "Application.ActiveExplorer"); if (actExplorer) actExplorer->GetWindow (&ret); else { log_debug ("%s:%s: Could not find active window", SRCNAME, __func__); } gpgol_release (actExplorer); TRETURN ret; } int put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value) { TSTART; LPDISPATCH propertyAccessor; VARIANT cVariant[2]; VARIANT rVariant; DISPID dispid; DISPPARAMS dispparams; HRESULT hr; EXCEPINFO execpinfo; BSTR b_property; wchar_t *w_property; unsigned int argErr = 0; init_excepinfo (&execpinfo); log_oom ("%s:%s: Looking up property: %s;", SRCNAME, __func__, dasl_id); propertyAccessor = get_oom_object (pDisp, "PropertyAccessor"); if (!propertyAccessor) { log_error ("%s:%s: Failed to look up property accessor.", SRCNAME, __func__); TRETURN -1; } dispid = lookup_oom_dispid (propertyAccessor, "SetProperty"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find SetProperty DISPID", SRCNAME, __func__); TRETURN -1; } /* Prepare the parameter */ w_property = utf8_to_wchar (dasl_id); b_property = SysAllocString (w_property); xfree (w_property); /* Variant 0 carries the data. */ VariantInit (&cVariant[0]); if (VariantCopy (&cVariant[0], value)) { log_error ("%s:%s: Falied to copy value.", SRCNAME, __func__); TRETURN -1; } /* Variant 1 is the DASL as found out by experiments. */ VariantInit (&cVariant[1]); cVariant[1].vt = VT_BSTR; cVariant[1].bstrVal = b_property; dispparams.rgvarg = cVariant; dispparams.cArgs = 2; dispparams.cNamedArgs = 0; VariantInit (&rVariant); hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, &execpinfo, &argErr); VariantClear (&cVariant[0]); VariantClear (&cVariant[1]); gpgol_release (propertyAccessor); if (hr != S_OK) { log_debug ("%s:%s: failure: invoking SetProperty p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, rVariant.pdispVal, rVariant.vt, (unsigned int)hr, (unsigned int)argErr); VariantClear (&rVariant); dump_excepinfo (execpinfo); TRETURN -1; } VariantClear (&rVariant); TRETURN 0; } int put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value) { TSTART; wchar_t *w_value = utf8_to_wchar (value); BSTR b_value = SysAllocString(w_value); xfree (w_value); VARIANT var; VariantInit (&var); var.vt = VT_BSTR; var.bstrVal = b_value; int ret = put_pa_variant (pDisp, dasl_id, &var); VariantClear (&var); TRETURN ret; } int put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value) { TSTART; VARIANT var; VariantInit (&var); var.vt = VT_I4; var.lVal = value; int ret = put_pa_variant (pDisp, dasl_id, &var); VariantClear (&var); TRETURN ret; } /* Get a MAPI property through OOM using the PropertyAccessor * interface and the DASL Uid. Returns -1 on error. * Variant has to be cleared with VariantClear. * rVariant must be a pointer to a Variant. */ int get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant) { TSTART; LPDISPATCH propertyAccessor; VARIANT cVariant[1]; DISPID dispid; DISPPARAMS dispparams; HRESULT hr; EXCEPINFO execpinfo; BSTR b_property; wchar_t *w_property; unsigned int argErr = 0; init_excepinfo (&execpinfo); log_oom ("%s:%s: Looking up property: %s;", SRCNAME, __func__, dasl_id); propertyAccessor = get_oom_object (pDisp, "PropertyAccessor"); if (!propertyAccessor) { log_error ("%s:%s: Failed to look up property accessor.", SRCNAME, __func__); TRETURN -1; } dispid = lookup_oom_dispid (propertyAccessor, "GetProperty"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find GetProperty DISPID", SRCNAME, __func__); TRETURN -1; } /* Prepare the parameter */ w_property = utf8_to_wchar (dasl_id); b_property = SysAllocString (w_property); xfree (w_property); cVariant[0].vt = VT_BSTR; cVariant[0].bstrVal = b_property; dispparams.rgvarg = cVariant; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; VariantInit (rVariant); hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, rVariant, &execpinfo, &argErr); SysFreeString (b_property); gpgol_release (propertyAccessor); if (hr != S_OK && strcmp (GPGOL_UID_DASL, dasl_id)) { /* It often happens that mails don't have a uid by us e.g. if they are not crypto mails or just dont have one. This is not an error. */ log_debug ("%s:%s: error: invoking GetProperty p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, rVariant->pdispVal, rVariant->vt, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); VariantClear (rVariant); TRETURN -1; } TRETURN 0; } /* Get a property string by using the PropertyAccessor of pDisp * Returns NULL on error or a newly allocated result. */ char * get_pa_string (LPDISPATCH pDisp, const char *property) { TSTART; VARIANT rVariant; char *result = NULL; if (get_pa_variant (pDisp, property, &rVariant)) { TRETURN NULL; } if (rVariant.vt == VT_BSTR && rVariant.bstrVal) { result = wchar_to_utf8 (rVariant.bstrVal); } else if (rVariant.vt & VT_ARRAY && !(rVariant.vt & VT_BYREF)) { LONG uBound, lBound; VARTYPE vt; char *data; SafeArrayGetVartype(rVariant.parray, &vt); if (SafeArrayGetUBound (rVariant.parray, 1, &uBound) != S_OK || SafeArrayGetLBound (rVariant.parray, 1, &lBound) != S_OK || vt != VT_UI1) { log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__); VariantClear (&rVariant); TRETURN NULL; } result = (char *)xmalloc (uBound - lBound + 1); data = (char *) rVariant.parray->pvData; memcpy (result, data + lBound, uBound - lBound); result[uBound - lBound] = '\0'; } else if (rVariant.vt != 0) { log_debug ("%s:%s: Property `%s' is not a string (vt=%d)", SRCNAME, __func__, property, rVariant.vt); } VariantClear (&rVariant); TRETURN result; } int get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt) { TSTART; VARIANT rVariant; if (get_pa_variant (pDisp, property, &rVariant)) { TRETURN -1; } if (rVariant.vt != VT_I4) { log_debug ("%s:%s: Property `%s' is not a int (vt=%d)", SRCNAME, __func__, property, rVariant.vt); TRETURN -1; } *rInt = rVariant.lVal; VariantClear (&rVariant); TRETURN 0; } /* Helper for exchange address lookup. */ static char * get_recipient_addr_entry_fallbacks_ex (LPDISPATCH addr_entry) { TSTART; /* Maybe check for type here? We are pretty sure that we are exchange */ /* According to MSDN Message Boards the PR_EMS_AB_PROXY_ADDRESSES_DASL is more avilable then the SMTP Address. */ char *ret = get_pa_string (addr_entry, PR_EMS_AB_PROXY_ADDRESSES_DASL); if (ret) { log_debug ("%s:%s: Found recipient through AB_PROXY: %s", SRCNAME, __func__, anonstr (ret)); char *smtpbegin = strstr(ret, "SMTP:"); if (smtpbegin == ret) { ret += 5; } TRETURN ret; } else { log_debug ("%s:%s: Failed AB_PROXY lookup.", SRCNAME, __func__); } LPDISPATCH ex_user = get_oom_object (addr_entry, "GetExchangeUser"); if (!ex_user) { log_debug ("%s:%s: Failed to find ExchangeUser", SRCNAME, __func__); TRETURN nullptr; } ret = get_oom_string (ex_user, "PrimarySmtpAddress"); gpgol_release (ex_user); if (ret) { log_debug ("%s:%s: Found recipient through exchange user primary smtp address: %s", SRCNAME, __func__, anonstr (ret)); TRETURN ret; } TRETURN nullptr; } /* Helper for additional fallbacks in recipient lookup */ static char * get_recipient_addr_fallbacks (LPDISPATCH recipient) { TSTART; if (!recipient) { TRETURN nullptr; } LPDISPATCH addr_entry = get_oom_object (recipient, "AddressEntry"); if (!addr_entry) { log_debug ("%s:%s: Failed to find AddressEntry", SRCNAME, __func__); TRETURN nullptr; } char *ret = get_recipient_addr_entry_fallbacks_ex (addr_entry); gpgol_release (addr_entry); TRETURN ret; } /* Try to resolve a recipient group and add it to the recipients vector. Returns true on success. */ static bool try_resolve_group (LPDISPATCH addrEntry, std::vector >&ret, int recipient_type) { TSTART; /* Get the name for debugging */ std::string name; char *cname = get_oom_string (addrEntry, "Name"); if (cname) { name = cname; } xfree (cname); int user_type = get_oom_int (addrEntry, "AddressEntryUserType"); if (user_type != DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE) { log_data ("%s:%s: type of %s is %i", SRCNAME, __func__, anonstr (name.c_str()), user_type); TRETURN false; } LPDISPATCH members = get_oom_object (addrEntry, "Members"); addrEntry = nullptr; if (!members) { TRACEPOINT; TRETURN false; } int count = get_oom_int (members, "Count"); if (!count) { TRACEPOINT; gpgol_release (members); TRETURN false; } bool foundOne = false; for (int i = 1; i <= count; i++) { auto item_str = std::string("Item(") + std::to_string (i) + ")"; auto entry = MAKE_SHARED (get_oom_object (members, item_str.c_str())); if (!entry) { TRACEPOINT; continue; } std::string entryName; char *entry_name = get_oom_string (entry.get(), "Name"); if (entry_name) { entryName = entry_name; xfree (entry_name); } int subType = get_oom_int (entry.get(), "AddressEntryUserType"); /* Resolve recursively, yeah fun. */ if (subType == DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE) { log_debug ("%s:%s: recursive address entry %s", SRCNAME, __func__, anonstr (entryName.c_str())); if (try_resolve_group (entry.get(), ret, recipient_type)) { foundOne = true; continue; } } std::pair element; element.second = entry; /* Resolve directly ? */ char *addrtype = get_pa_string (entry.get(), PR_ADDRTYPE_DASL); if (addrtype && !strcmp (addrtype, "SMTP")) { xfree (addrtype); char *resolved = get_pa_string (entry.get(), PR_EMAIL_ADDRESS_DASL); if (resolved) { element.first = Recipient (resolved, entryName.c_str (), recipient_type); ret.push_back (element); foundOne = true; continue; } } xfree (addrtype); /* Resolve through Exchange API */ char *ex_resolved = get_recipient_addr_entry_fallbacks_ex (entry.get()); if (ex_resolved) { element.first = Recipient (ex_resolved, entryName.c_str (), recipient_type); ret.push_back (element); foundOne = true; continue; } log_debug ("%s:%s: failed to resolve name %s", SRCNAME, __func__, anonstr (entryName.c_str())); } gpgol_release (members); if (!foundOne) { log_debug ("%s:%s: failed to resolve group %s", SRCNAME, __func__, anonstr (name.c_str())); } TRETURN foundOne; } /* Get the recipient mbox addresses with the addrEntry object corresponding to the resolved address. */ std::vector > get_oom_recipients_with_addrEntry (LPDISPATCH recipients, bool *r_err) { TSTART; int recipientsCnt = get_oom_int (recipients, "Count"); std::vector > ret; int i; if (!recipientsCnt) { TRETURN ret; } /* Get the recipients */ for (i = 1; i <= recipientsCnt; i++) { char buf[16]; LPDISPATCH recipient; snprintf (buf, sizeof (buf), "Item(%i)", i); recipient = get_oom_object (recipients, buf); if (!recipient) { /* Should be impossible */ log_error ("%s:%s: could not find Item %i;", SRCNAME, __func__, i); if (r_err) { *r_err = true; } break; } int recipient_type = get_oom_int (recipient, "Type"); std::string entryName; char *entry_name = get_oom_string (recipient, "Name"); if (entry_name) { entryName = entry_name; xfree (entry_name); } auto addrEntry = MAKE_SHARED (get_oom_object (recipient, "AddressEntry")); if (addrEntry && try_resolve_group (addrEntry.get (), ret, recipient_type)) { log_debug ("%s:%s: Resolved recipient group", SRCNAME, __func__); gpgol_release (recipient); continue; } std::pair entry; entry.second = addrEntry; char *resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL); if (resolved) { entry.first = Recipient (resolved, entryName.c_str (), recipient_type); entry.first.setIndex (i); xfree (resolved); gpgol_release (recipient); ret.push_back (entry); continue; } /* No PR_SMTP_ADDRESS first fallback */ resolved = get_recipient_addr_fallbacks (recipient); if (resolved) { entry.first = Recipient (resolved, entryName.c_str (), recipient_type); entry.first.setIndex (i); xfree (resolved); gpgol_release (recipient); ret.push_back (entry); continue; } char *address = get_oom_string (recipient, "Address"); gpgol_release (recipient); log_debug ("%s:%s: Failed to look up Address probably " "EX addr is returned", SRCNAME, __func__); if (address) { entry.first = Recipient (resolved, recipient_type); entry.first.setIndex (i); ret.push_back (entry); xfree (address); } else if (r_err) { *r_err = true; } } TRETURN ret; } /* Gets the resolved smtp addresses of the recpients. */ std::vector get_oom_recipients (LPDISPATCH recipients, bool *r_err) { TSTART; std::vector ret; - for (const auto pair: get_oom_recipients_with_addrEntry (recipients, r_err)) + for (const auto &pair: get_oom_recipients_with_addrEntry (recipients, r_err)) { ret.push_back (pair.first); } TRETURN ret; } /* Add an attachment to the outlook dispatcher disp that has an Attachment property. inFile is the path to the attachment. Name is the name that should be used in outlook. */ int add_oom_attachment (LPDISPATCH disp, const wchar_t* inFileW, const wchar_t* displayName, std::string &r_error_str, int *r_err_code) { TSTART; LPDISPATCH attachments = get_oom_object (disp, "Attachments"); DISPID dispid; DISPPARAMS dispparams; VARIANT vtResult; VARIANT aVariant[4]; HRESULT hr; BSTR inFileB = nullptr, dispNameB = nullptr; unsigned int argErr = 0; EXCEPINFO execpinfo; init_excepinfo (&execpinfo); dispid = lookup_oom_dispid (attachments, "Add"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find attachment dispatcher", SRCNAME, __func__); TRETURN -1; } if (inFileW) { inFileB = SysAllocString (inFileW); } if (displayName) { dispNameB = SysAllocString (displayName); } dispparams.rgvarg = aVariant; /* Contrary to the documentation the Source is the last parameter and not the first. Additionally DisplayName is documented but gets ignored by Outlook since Outlook 2003 */ dispparams.rgvarg[0].vt = VT_BSTR; /* DisplayName */ dispparams.rgvarg[0].bstrVal = dispNameB; dispparams.rgvarg[1].vt = VT_INT; /* Position */ dispparams.rgvarg[1].intVal = 1; dispparams.rgvarg[2].vt = VT_INT; /* Type */ dispparams.rgvarg[2].intVal = 1; dispparams.rgvarg[3].vt = VT_BSTR; /* Source */ dispparams.rgvarg[3].bstrVal = inFileB; dispparams.cArgs = 4; dispparams.cNamedArgs = 0; VariantInit (&vtResult); hr = attachments->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &vtResult, &execpinfo, &argErr); if (hr != S_OK) { log_debug ("%s:%s: error: invoking Add p=%p vt=%d hr=0x%x argErr=0x%x", SRCNAME, __func__, vtResult.pdispVal, vtResult.vt, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); if (r_err_code) { *r_err_code = (int) execpinfo.scode; } if (execpinfo.bstrDescription) { char *utf8Err = wchar_to_utf8 (execpinfo.bstrDescription); if (utf8Err) { r_error_str = utf8Err; } xfree (utf8Err); } } if (inFileB) SysFreeString (inFileB); if (dispNameB) SysFreeString (dispNameB); VariantClear (&vtResult); gpgol_release (attachments); TRETURN hr == S_OK ? 0 : -1; } LPDISPATCH get_object_by_id (LPDISPATCH pDisp, REFIID id) { TSTART; LPDISPATCH disp = NULL; if (!pDisp) { TRETURN NULL; } if (gpgol_queryInterface(pDisp, id, (void **)&disp) != S_OK) { TRETURN NULL; } TRETURN disp; } LPDISPATCH get_strong_reference (LPDISPATCH mail) { TSTART; VARIANT var; VariantInit (&var); DISPPARAMS args; VARIANT argvars[2]; VariantInit (&argvars[0]); VariantInit (&argvars[1]); argvars[1].vt = VT_DISPATCH; argvars[1].pdispVal = mail; argvars[0].vt = VT_INT; argvars[0].intVal = 1; args.cArgs = 2; args.cNamedArgs = 0; args.rgvarg = argvars; LPDISPATCH ret = NULL; if (!invoke_oom_method_with_parms ( GpgolAddin::get_instance()->get_application(), "GetObjectReference", &var, &args)) { ret = var.pdispVal; log_oom ("%s:%s: Got strong ref %p for %p", SRCNAME, __func__, ret, mail); memdbg_addRef (ret); } else { log_error ("%s:%s: Failed to get strong ref.", SRCNAME, __func__); } VariantClear (&var); TRETURN ret; } LPMESSAGE get_oom_message (LPDISPATCH mailitem) { TSTART; LPUNKNOWN mapi_obj = get_oom_iunknown (mailitem, "MapiObject"); if (!mapi_obj) { log_error ("%s:%s: Failed to obtain MAPI Message.", SRCNAME, __func__); TRETURN NULL; } TRETURN (LPMESSAGE) mapi_obj; } static LPMESSAGE get_oom_base_message_from_mapi (LPDISPATCH mapi_message) { TSTART; HRESULT hr; LPDISPATCH secureItem = NULL; LPMESSAGE message = NULL; LPMAPISECUREMESSAGE secureMessage = NULL; secureItem = get_object_by_id (mapi_message, IID_IMAPISecureMessage); if (!secureItem) { log_error ("%s:%s: Failed to obtain SecureItem.", SRCNAME, __func__); TRETURN NULL; } secureMessage = (LPMAPISECUREMESSAGE) secureItem; /* The call to GetBaseMessage is pretty much a jump in the dark. So it would not be surprising to get crashes here in the future. */ log_oom("%s:%s: About to call GetBaseMessage.", SRCNAME, __func__); hr = secureMessage->GetBaseMessage (&message); memdbg_addRef (message); gpgol_release (secureMessage); if (hr != S_OK) { log_error_w32 (hr, "Failed to GetBaseMessage."); TRETURN NULL; } TRETURN message; } LPMESSAGE get_oom_base_message (LPDISPATCH mailitem) { TSTART; LPMESSAGE mapi_message = get_oom_message (mailitem); LPMESSAGE ret = NULL; if (!mapi_message) { log_error ("%s:%s: Failed to obtain mapi_message.", SRCNAME, __func__); TRETURN NULL; } ret = get_oom_base_message_from_mapi ((LPDISPATCH)mapi_message); gpgol_release (mapi_message); TRETURN ret; } static int invoke_oom_method_with_parms_type (LPDISPATCH pDisp, const char *name, VARIANT *rVariant, DISPPARAMS *params, int type) { TSTART; HRESULT hr; DISPID dispid; dispid = lookup_oom_dispid (pDisp, name); if (dispid != DISPID_UNKNOWN) { EXCEPINFO execpinfo; init_excepinfo (&execpinfo); DISPPARAMS dispparams = {NULL, NULL, 0, 0}; hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, type, params ? params : &dispparams, rVariant, &execpinfo, NULL); if (hr != S_OK) { log_debug ("%s:%s: Method '%s' invokation failed: %#lx", SRCNAME, __func__, name, hr); dump_excepinfo (execpinfo); TRETURN -1; } } TRETURN 0; } int invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name, VARIANT *rVariant, DISPPARAMS *params) { TSTART; TRETURN invoke_oom_method_with_parms_type (pDisp, name, rVariant, params, DISPATCH_METHOD); } int invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant) { TSTART; TRETURN invoke_oom_method_with_parms (pDisp, name, rVariant, NULL); } LPMAPISESSION get_oom_mapi_session () { TSTART; LPDISPATCH application = GpgolAddin::get_instance ()->get_application (); LPDISPATCH oom_session = NULL; LPMAPISESSION session = NULL; LPUNKNOWN mapiobj = NULL; HRESULT hr; if (!application) { log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__); TRETURN NULL; } oom_session = get_oom_object (application, "Session"); if (!oom_session) { log_error ("%s:%s: session object not found", SRCNAME, __func__); TRETURN NULL; } mapiobj = get_oom_iunknown (oom_session, "MAPIOBJECT"); gpgol_release (oom_session); if (!mapiobj) { log_error ("%s:%s: error getting Session.MAPIOBJECT", SRCNAME, __func__); TRETURN NULL; } session = NULL; hr = gpgol_queryInterface (mapiobj, IID_IMAPISession, (void**)&session); gpgol_release (mapiobj); if (hr != S_OK || !session) { log_error ("%s:%s: error getting IMAPISession: hr=%#lx", SRCNAME, __func__, hr); TRETURN NULL; } TRETURN session; } int create_category (LPDISPATCH categories, const char *category, int color) { TSTART; VARIANT cVariant[3]; VARIANT rVariant; DISPID dispid; DISPPARAMS dispparams; HRESULT hr; EXCEPINFO execpinfo; BSTR b_name; wchar_t *w_name; unsigned int argErr = 0; init_excepinfo (&execpinfo); if (!categories || !category) { TRACEPOINT; TRETURN 1; } dispid = lookup_oom_dispid (categories, "Add"); if (dispid == DISPID_UNKNOWN) { log_error ("%s:%s: could not find Add DISPID", SRCNAME, __func__); TRETURN -1; } /* Do the string dance */ w_name = utf8_to_wchar (category); b_name = SysAllocString (w_name); xfree (w_name); /* Variants are in reverse order ShortcutKey -> 0 / Int Color -> 1 / Int Name -> 2 / Bstr */ VariantInit (&cVariant[2]); cVariant[2].vt = VT_BSTR; cVariant[2].bstrVal = b_name; VariantInit (&cVariant[1]); cVariant[1].vt = VT_INT; cVariant[1].intVal = color; VariantInit (&cVariant[0]); cVariant[0].vt = VT_INT; cVariant[0].intVal = 0; dispparams.cArgs = 3; dispparams.cNamedArgs = 0; dispparams.rgvarg = cVariant; hr = categories->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, &rVariant, &execpinfo, &argErr); VariantClear (&cVariant[0]); VariantClear (&cVariant[1]); VariantClear (&cVariant[2]); if (hr != S_OK) { log_debug ("%s:%s: error: invoking Add p=%p vt=%d" " hr=0x%x argErr=0x%x", SRCNAME, __func__, rVariant.pdispVal, rVariant.vt, (unsigned int)hr, (unsigned int)argErr); dump_excepinfo (execpinfo); VariantClear (&rVariant); TRETURN -1; } VariantClear (&rVariant); log_oom ("%s:%s: Created category '%s'", SRCNAME, __func__, anonstr (category)); TRETURN 0; } LPDISPATCH get_store_for_id (const char *storeID) { TSTART; LPDISPATCH application = GpgolAddin::get_instance ()->get_application (); if (!application || !storeID) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH stores = get_oom_object (application, "Session.Stores"); if (!stores) { log_error ("%s:%s: No stores found.", SRCNAME, __func__); TRETURN nullptr; } auto store_count = get_oom_int (stores, "Count"); for (int n = 1; n <= store_count; n++) { const auto store_str = std::string("Item(") + std::to_string(n) + ")"; LPDISPATCH store = get_oom_object (stores, store_str.c_str()); if (!store) { TRACEPOINT; continue; } char *id = get_oom_string (store, "StoreID"); if (id && !strcmp (id, storeID)) { gpgol_release (stores); xfree (id); return store; } xfree (id); gpgol_release (store); } gpgol_release (stores); TRETURN nullptr; } void ensure_category_exists (const char *category, int color) { TSTART; LPDISPATCH application = GpgolAddin::get_instance ()->get_application (); if (!application || !category) { TRACEPOINT; TRETURN; } log_oom ("%s:%s: Ensure category exists called for %s, %i", SRCNAME, __func__, category, color); LPDISPATCH stores = get_oom_object (application, "Session.Stores"); if (!stores) { log_error ("%s:%s: No stores found.", SRCNAME, __func__); TRETURN; } auto store_count = get_oom_int (stores, "Count"); for (int n = 1; n <= store_count; n++) { const auto store_str = std::string("Item(") + std::to_string(n) + ")"; LPDISPATCH store = get_oom_object (stores, store_str.c_str()); if (!store) { TRACEPOINT; continue; } LPDISPATCH categories = get_oom_object (store, "Categories"); gpgol_release (store); if (!categories) { categories = get_oom_object (application, "Session.Categories"); if (!categories) { TRACEPOINT; continue; } } auto count = get_oom_int (categories, "Count"); bool found = false; for (int i = 1; i <= count && !found; i++) { const auto item_str = std::string("Item(") + std::to_string(i) + ")"; LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str()); if (!category_obj) { TRACEPOINT; gpgol_release (categories); break; } char *name = get_oom_string (category_obj, "Name"); if (name && !strcmp (category, name)) { log_oom ("%s:%s: Found category '%s'", SRCNAME, __func__, name); found = true; } /* We don't check the color here as the user may change that. */ gpgol_release (category_obj); xfree (name); } if (!found) { if (create_category (categories, category, color)) { log_oom ("%s:%s: Found category '%s'", SRCNAME, __func__, category); } } /* Otherwise we have to create the category */ gpgol_release (categories); } gpgol_release (stores); TRETURN; } int add_category (LPDISPATCH mail, const char *category) { TSTART; char *tmp = get_oom_string (mail, "Categories"); if (!tmp) { TRACEPOINT; TRETURN 1; } if (strstr (tmp, category)) { log_oom ("%s:%s: category '%s' already added.", SRCNAME, __func__, category); TRETURN 0; } std::string newstr (tmp); xfree (tmp); if (!newstr.empty ()) { newstr += CategoryManager::getSeperator () + std::string (" "); } newstr += category; TRETURN put_oom_string (mail, "Categories", newstr.c_str ()); } int remove_category (LPDISPATCH mail, const char *category, bool exactMatch) { TSTART; char *tmp = get_oom_string (mail, "Categories"); if (!tmp) { TRACEPOINT; TRETURN 1; } std::vector categories; std::istringstream f(tmp); std::string s; const std::string sep = CategoryManager::getSeperator(); while (std::getline(f, s, *(sep.c_str()))) { ltrim(s); categories.push_back(s); } xfree (tmp); const std::string categoryStr = category; categories.erase (std::remove_if (categories.begin(), categories.end(), [categoryStr, exactMatch] (const std::string &cat) { if (exactMatch) { return cat == categoryStr; } return cat.compare (0, categoryStr.size(), categoryStr) == 0; }), categories.end ()); std::string newCategories; std::string newsep = sep + " "; join (categories, newsep.c_str (), newCategories); TRETURN put_oom_string (mail, "Categories", newCategories.c_str ()); } static int _delete_category (LPDISPATCH categories, int idx) { TSTART; VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = idx; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; TRETURN invoke_oom_method_with_parms (categories, "Remove", NULL, &dispparams); } int delete_category (LPDISPATCH store, const char *category) { TSTART; if (!store || !category) { TRETURN -1; } LPDISPATCH categories = get_oom_object (store, "Categories"); if (!categories) { categories = get_oom_object ( GpgolAddin::get_instance ()->get_application (), "Session.Categories"); if (!categories) { TRACEPOINT; TRETURN -1; } } auto count = get_oom_int (categories, "Count"); int ret = 0; for (int i = 1; i <= count; i++) { const auto item_str = std::string("Item(") + std::to_string(i) + ")"; LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str()); if (!category_obj) { TRACEPOINT; gpgol_release (categories); break; } char *name = get_oom_string (category_obj, "Name"); gpgol_release (category_obj); if (name && !strcmp (category, name)) { if ((ret = _delete_category (categories, i))) { log_error ("%s:%s: Failed to delete category '%s'", SRCNAME, __func__, anonstr (category)); } else { log_debug ("%s:%s: Deleted category '%s'", SRCNAME, __func__, anonstr (category)); } xfree (name); break; } xfree (name); } gpgol_release (categories); TRETURN ret; } void delete_all_categories_starting_with (const char *string) { LPDISPATCH application = GpgolAddin::get_instance ()->get_application (); if (!application || !string) { TRACEPOINT; TRETURN; } log_oom ("%s:%s: Delete categories starting with: \"%s\"", SRCNAME, __func__, string); LPDISPATCH stores = get_oom_object (application, "Session.Stores"); if (!stores) { log_error ("%s:%s: No stores found.", SRCNAME, __func__); TRETURN; } auto store_count = get_oom_int (stores, "Count"); for (int n = 1; n <= store_count; n++) { const auto store_str = std::string("Item(") + std::to_string(n) + ")"; LPDISPATCH store = get_oom_object (stores, store_str.c_str()); if (!store) { TRACEPOINT; continue; } LPDISPATCH categories = get_oom_object (store, "Categories"); if (!categories) { categories = get_oom_object (application, "Session.Categories"); if (!categories) { TRACEPOINT; gpgol_release (store); continue; } } auto count = get_oom_int (categories, "Count"); std::vector to_delete; for (int i = 1; i <= count; i++) { const auto item_str = std::string("Item(") + std::to_string(i) + ")"; LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str()); if (!category_obj) { TRACEPOINT; gpgol_release (categories); break; } char *name = get_oom_string (category_obj, "Name"); if (name && !strncmp (string, name, strlen (string))) { log_oom ("%s:%s: Found category for deletion '%s'", SRCNAME, __func__, anonstr(name)); to_delete.push_back (name); } /* We don't check the color here as the user may change that. */ gpgol_release (category_obj); xfree (name); } /* Do this one after another to avoid messing with indexes. */ for (const auto &str: to_delete) { delete_category (store, str.c_str ()); } gpgol_release (store); /* Otherwise we have to create the category */ gpgol_release (categories); } gpgol_release (stores); TRETURN; } static char * generate_uid () { TSTART; UUID uuid; UuidCreate (&uuid); unsigned char *str; UuidToStringA (&uuid, &str); char *ret = xstrdup ((char*)str); RpcStringFreeA (&str); TRETURN ret; } char * get_unique_id (LPDISPATCH mail, int create, const char *uuid) { TSTART; if (!mail) { TRETURN NULL; } /* Get the User Properties. */ if (!create) { char *uid = get_pa_string (mail, GPGOL_UID_DASL); if (!uid) { log_debug ("%s:%s: No uuid found in oom for '%p'", SRCNAME, __func__, mail); TRETURN NULL; } else { log_debug ("%s:%s: Found uid '%s' for '%p'", SRCNAME, __func__, uid, mail); TRETURN uid; } } char *newuid; if (!uuid) { newuid = generate_uid (); } else { newuid = xstrdup (uuid); } int ret = put_pa_string (mail, GPGOL_UID_DASL, newuid); if (ret) { log_debug ("%s:%s: failed to set uid '%s' for '%p'", SRCNAME, __func__, newuid, mail); xfree (newuid); TRETURN NULL; } log_debug ("%s:%s: '%p' has now the uid: '%s' ", SRCNAME, __func__, mail, newuid); TRETURN newuid; } char * reset_unique_id (LPDISPATCH mail) { TSTART; char *newuid = generate_uid (); int ret = put_pa_string (mail, GPGOL_UID_DASL, newuid); if (ret) { log_debug ("%s:%s: failed to set uid '%s' for '%p'", SRCNAME, __func__, newuid, mail); xfree (newuid); TRETURN NULL; } TRETURN newuid; } std::string get_unique_id_s (LPDISPATCH mail, int create, const char *uuid) { char *val = get_unique_id (mail, create, uuid); if (val) { return val; } return std::string (val); } HWND get_active_hwnd () { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH activeWindow = get_oom_object (app, "ActiveWindow"); if (!activeWindow) { activeWindow = get_oom_object (app, "ActiveInspector"); if (!activeWindow) { activeWindow = get_oom_object (app, "ActiveExplorer"); if (!activeWindow) { TRACEPOINT; TRETURN nullptr; } } } /* Both explorer and inspector have this. */ char *caption = get_oom_string (activeWindow, "Caption"); gpgol_release (activeWindow); if (!caption) { TRACEPOINT; TRETURN nullptr; } /* Might not be completly true for multiple explorers on the same folder but good enugh. */ HWND hwnd = FindWindowExA(NULL, NULL, "rctrl_renwnd32", caption); xfree (caption); TRETURN hwnd; } LPDISPATCH create_mail () { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN nullptr; } VARIANT var; VariantInit (&var); VARIANT argvars[1]; DISPPARAMS args; VariantInit (&argvars[0]); argvars[0].vt = VT_I2; argvars[0].intVal = 0; args.cArgs = 1; args.cNamedArgs = 0; args.rgvarg = argvars; LPDISPATCH ret = nullptr; if (invoke_oom_method_with_parms (app, "CreateItem", &var, &args)) { log_error ("%s:%s: Failed to create mailitem.", SRCNAME, __func__); TRETURN ret; } ret = var.pdispVal; TRETURN ret; } LPDISPATCH get_account_for_mail (const char *mbox) { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH accounts = get_oom_object (app, "Session.Accounts"); if (!accounts) { TRACEPOINT; TRETURN nullptr; } int count = get_oom_int (accounts, "Count"); for (int i = 1; i <= count; i++) { std::string item = std::string ("Item(") + std::to_string (i) + ")"; LPDISPATCH account = get_oom_object (accounts, item.c_str ()); if (!account) { TRACEPOINT; continue; } char *smtpAddr = get_oom_string (account, "SmtpAddress"); if (!smtpAddr) { gpgol_release (account); TRACEPOINT; continue; } if (!stricmp (mbox, smtpAddr)) { gpgol_release (accounts); xfree (smtpAddr); TRETURN account; } gpgol_release (account); xfree (smtpAddr); } gpgol_release (accounts); log_error ("%s:%s: Failed to find account for '%s'.", SRCNAME, __func__, anonstr (mbox)); TRETURN nullptr; } char * get_sender_SendUsingAccount (LPDISPATCH mailitem, bool *r_is_GSuite) { TSTART; LPDISPATCH sender = get_oom_object (mailitem, "SendUsingAccount"); if (!sender) { TRETURN nullptr; } char *buf = get_oom_string (sender, "SmtpAddress"); char *dispName = get_oom_string (sender, "DisplayName"); gpgol_release (sender); /* Check for G Suite account */ if (dispName && !strcmp ("G Suite", dispName) && r_is_GSuite) { *r_is_GSuite = true; } xfree (dispName); if (buf && strlen (buf)) { log_debug ("%s:%s: found sender", SRCNAME, __func__); TRETURN buf; } xfree (buf); TRETURN nullptr; } char * get_sender_Sender (LPDISPATCH mailitem) { TSTART; LPDISPATCH sender = get_oom_object (mailitem, "Sender"); if (!sender) { TRETURN nullptr; } char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL); gpgol_release (sender); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 2", SRCNAME, __func__); TRETURN buf; } xfree (buf); /* We have a sender object but not yet an smtp address likely exchange. Try some more propertys of the message. */ buf = get_pa_string (mailitem, PR_TAG_SENDER_SMTP_ADDRESS); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 3", SRCNAME, __func__); TRETURN buf; } xfree (buf); buf = get_pa_string (mailitem, PR_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 4", SRCNAME, __func__); TRETURN buf; } xfree (buf); TRETURN nullptr; } char * get_sender_primary_send_acct (LPDISPATCH mailitem) { TSTART; char *buf = get_pa_string (mailitem, PR_PRIMARY_SEND_ACCT_W_DASL); if (buf && strlen (buf)) { /* The format of this is documented as implementation dependent for exchange this looks like AccountNumber\01ExchangeAddress\01SMTPAddress */ char *last = strrchr (buf, 1); if (last && ++last) { char *atChar = strchr (last, '@'); if (atChar) { log_debug ("%s:%s Sender fallback 5", SRCNAME, __func__); size_t len = strlen (last) + 1; char *ret = (char *)xmalloc (len); strcpy_s (ret, len, last); xfree (buf); TRETURN ret; } else { log_dbg ("Last part does not contain @ character: %s", anonstr (last)); } } log_dbg ("Failed to parse %s", anonstr (buf)); } xfree (buf); TRETURN nullptr; } char * get_sender_CurrentUser (LPDISPATCH mailitem) { TSTART; LPDISPATCH sender = get_oom_object (mailitem, "Session.CurrentUser"); if (!sender) { TRETURN nullptr; } char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL); gpgol_release (sender); if (buf && strlen (buf)) { log_debug ("%s:%s Sender fallback 6", SRCNAME, __func__); TRETURN buf; } xfree (buf); TRETURN nullptr; } char * get_sender_SenderEMailAddress (LPDISPATCH mailitem) { TSTART; char *type = get_oom_string (mailitem, "SenderEmailType"); if (type && !strcmp ("SMTP", type)) { char *senderMail = get_oom_string (mailitem, "SenderEmailAddress"); if (senderMail) { log_debug ("%s:%s: Sender found", SRCNAME, __func__); xfree (type); TRETURN senderMail; } } xfree (type); TRETURN nullptr; } char * get_sender_SentRepresentingAddress (LPDISPATCH mailitem) { TSTART; char *buf = get_pa_string (mailitem, PR_SENT_REPRESENTING_EMAIL_ADDRESS_W_DASL); if (buf && strlen (buf)) { log_debug ("%s:%s Found sent representing address \"%s\"", SRCNAME, __func__, anonstr (buf)); TRETURN buf; } xfree (buf); TRETURN nullptr; } char * get_inline_body () { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer"); if (!explorer) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse"); gpgol_release (explorer); if (!inlineResponse) { TRETURN nullptr; } char *body = get_oom_string (inlineResponse, "Body"); gpgol_release (inlineResponse); TRETURN body; } int get_ex_major_version_for_addr (const char *mbox) { TSTART; LPDISPATCH account = get_account_for_mail (mbox); if (!account) { TRACEPOINT; TRETURN -1; } char *version_str = get_oom_string (account, "ExchangeMailboxServerVersion"); gpgol_release (account); if (!version_str) { TRETURN -1; } log_debug ("%s:%s: Detected exchange major version: %s", SRCNAME, __func__, version_str); long int version = strtol (version_str, nullptr, 10); xfree (version_str); TRETURN (int) version; } int get_ol_ui_language () { TSTART; LPDISPATCH app = GpgolAddin::get_instance()->get_application(); if (!app) { TRACEPOINT; TRETURN 0; } LPDISPATCH langSettings = get_oom_object (app, "LanguageSettings"); if (!langSettings) { TRACEPOINT; TRETURN 0; } VARIANT var; VariantInit (&var); VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = 2; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; int ret = invoke_oom_method_with_parms_type (langSettings, "LanguageID", &var, &dispparams, DISPATCH_PROPERTYGET); gpgol_release (langSettings); if (ret) { TRACEPOINT; TRETURN 0; } if (var.vt != VT_INT && var.vt != VT_I4) { TRACEPOINT; TRETURN 0; } int result = var.intVal; VariantClear (&var); TRETURN result; } void log_addins () { TSTART; LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN; } LPDISPATCH addins = get_oom_object (app, "COMAddins"); if (!addins) { TRACEPOINT; TRETURN; } std::string activeAddins; int count = get_oom_int (addins, "Count"); for (int i = 1; i <= count; i++) { VARIANT aVariant[1]; VARIANT rVariant; VariantInit (&rVariant); DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = i; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; /* We need this instead of get_oom_object item(1) as usual becase the item method accepts a string or an int. String would be the ProgID and int is just the index. So Fun. */ if (invoke_oom_method_with_parms_type (addins, "Item", &rVariant, &dispparams, DISPATCH_METHOD | DISPATCH_PROPERTYGET)) { log_error ("%s:%s: Failed to invoke item func.", SRCNAME, __func__); continue; } if (rVariant.vt != (VT_DISPATCH)) { log_error ("%s:%s: Invalid ret val", SRCNAME, __func__); continue; } LPDISPATCH addin = rVariant.pdispVal; if (!addin) { TRACEPOINT; continue; } memdbg_addRef (addin); bool connected = get_oom_bool (addin, "Connect"); if (!connected) { gpgol_release (addin); continue; } char *progId = get_oom_string (addin, "ProgId"); gpgol_release (addin); if (!progId) { TRACEPOINT; continue; } activeAddins += std::string (progId) + "\n"; xfree (progId); } gpgol_release (addins); log_debug ("%s:%s:Active Addins:\n%s", SRCNAME, __func__, activeAddins.c_str ()); TRETURN; } bool is_preview_pane_visible (LPDISPATCH explorer) { TSTART; if (!explorer) { TRACEPOINT; TRETURN false; } VARIANT var; VariantInit (&var); VARIANT argvars[1]; DISPPARAMS args; VariantInit (&argvars[0]); argvars[0].vt = VT_INT; argvars[0].intVal = 3; args.cArgs = 1; args.cNamedArgs = 0; args.rgvarg = argvars; if (invoke_oom_method_with_parms (explorer, "IsPaneVisible", &var, &args)) { log_error ("%s:%s: Failed to check visibilty.", SRCNAME, __func__); TRETURN false; } if (var.vt != VT_BOOL) { TRACEPOINT; TRETURN false; } TRETURN !!var.boolVal; } static LPDISPATCH add_user_prop (LPDISPATCH user_props, const char *name) { TSTART; if (!user_props || !name) { TRACEPOINT; TRETURN nullptr; } wchar_t *w_name = utf8_to_wchar (name); BSTR b_name = SysAllocString (w_name); xfree (w_name); /* Args: 0: DisplayFormat int OlUserPropertyType 1: AddToFolderFields Bool Should the filed be added to the folder. 2: Type int OlUserPropertyType Type of the field. 3: Name Bstr Name of the field. Returns the added Property. */ VARIANT var; VariantInit (&var); DISPPARAMS args; VARIANT argvars[4]; VariantInit (&argvars[0]); VariantInit (&argvars[1]); VariantInit (&argvars[2]); VariantInit (&argvars[3]); argvars[0].vt = VT_INT; argvars[0].intVal = 1; // 1 means text. argvars[1].vt = VT_BOOL; argvars[1].boolVal = VARIANT_FALSE; argvars[2].vt = VT_INT; argvars[2].intVal = 1; argvars[3].vt = VT_BSTR; argvars[3].bstrVal = b_name; args.cArgs = 4; args.cNamedArgs = 0; args.rgvarg = argvars; int res = invoke_oom_method_with_parms (user_props, "Add", &var, &args); VariantClear (&argvars[0]); VariantClear (&argvars[1]); VariantClear (&argvars[2]); VariantClear (&argvars[3]); if (res) { log_oom ("%s:%s: Failed to add property %s.", SRCNAME, __func__, name); TRETURN nullptr; } if (var.vt != VT_DISPATCH) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH ret = var.pdispVal; memdbg_addRef (ret); TRETURN ret; } LPDISPATCH find_user_prop (LPDISPATCH user_props, const char *name) { TSTART; if (!user_props || !name) { TRACEPOINT; TRETURN nullptr; } VARIANT var; VariantInit (&var); wchar_t *w_name = utf8_to_wchar (name); BSTR b_name = SysAllocString (w_name); xfree (w_name); /* Name -> 1 / Bstr Custom 0 -> Bool True for search in custom properties. False for builtin properties. */ DISPPARAMS args; VARIANT argvars[2]; VariantInit (&argvars[0]); VariantInit (&argvars[1]); argvars[1].vt = VT_BSTR; argvars[1].bstrVal = b_name; argvars[0].vt = VT_BOOL; argvars[0].boolVal = VARIANT_TRUE; args.cArgs = 2; args.cNamedArgs = 0; args.rgvarg = argvars; int res = invoke_oom_method_with_parms (user_props, "Find", &var, &args); VariantClear (&argvars[0]); VariantClear (&argvars[1]); if (res) { log_oom ("%s:%s: Failed to find property %s.", SRCNAME, __func__, name); TRETURN nullptr; } if (var.vt != VT_DISPATCH) { TRACEPOINT; TRETURN nullptr; } LPDISPATCH ret = var.pdispVal; memdbg_addRef (ret); TRETURN ret; } LPDISPATCH find_or_add_text_prop (LPDISPATCH user_props, const char *name) { TSTART; LPDISPATCH ret = find_user_prop (user_props, name); if (ret) { TRETURN ret; } ret = add_user_prop (user_props, name); TRETURN ret; } void release_disp (LPDISPATCH obj) { TSTART; gpgol_release (obj); TRETURN; } enum FolderID { olFolderCalendar = 9, olFolderConflicts = 19, olFolderContacts = 10, olFolderDeletedItems = 3, olFolderDrafts = 16, olFolderInbox = 6, olFolderJournal = 11, olFolderJunk = 23, olFolderLocalFailures = 21, olFolderManagedEmail = 29, olFolderNotes = 12, olFolderOutbox = 4, olFolderSentMail = 5, olFolderServerFailures = 22, olFolderSuggestedContacts = 30, olFolderSyncIssues = 20, olFolderTasks = 13, olFolderToDo = 28, olPublicFoldersAllPublicFolders = 18, olFolderRssFeeds = 25, }; static bool is_mail_in_folder (LPDISPATCH mailitem, int folder) { TSTART; if (!mailitem) { STRANGEPOINT; TRETURN false; } auto store = MAKE_SHARED (get_oom_object (mailitem, "Parent.Store")); if (!store) { log_debug ("%s:%s: Mail has no parent folder. Probably unsafed", SRCNAME, __func__); TRETURN false; } std::string tmp = std::string("GetDefaultFolder(") + std::to_string (folder) + std::string(")"); auto target_folder = MAKE_SHARED (get_oom_object (store.get(), tmp.c_str())); if (!target_folder) { STRANGEPOINT; TRETURN false; } auto mail_folder = MAKE_SHARED (get_oom_object (mailitem, "Parent")); if (!mail_folder) { STRANGEPOINT; TRETURN false; } char *target_id = get_oom_string (target_folder.get(), "entryID"); if (!target_id) { STRANGEPOINT; TRETURN false; } char *folder_id = get_oom_string (mail_folder.get(), "entryID"); if (!folder_id) { STRANGEPOINT; free (target_id); TRETURN false; } bool ret = !strcmp (target_id, folder_id); free (target_id); free (folder_id); TRETURN ret; } bool is_junk_mail (LPDISPATCH mailitem) { TSTART; TRETURN is_mail_in_folder (mailitem, FolderID::olFolderJunk); } bool is_draft_mail (LPDISPATCH mailitem) { TSTART; TRETURN is_mail_in_folder (mailitem, FolderID::olFolderDrafts); } void format_variant (std::stringstream &stream, VARIANT* var) { if (!var) { stream << " (null) "; } stream << "VT: " << std::hex << var->vt << " Value: "; VARTYPE vt = var->vt; if (vt == VT_BOOL) { stream << (var->boolVal == VARIANT_FALSE ? "false" : "true"); } else if (vt == (VT_BOOL | VT_BYREF)) { stream << (*(var->pboolVal) == VARIANT_FALSE ? "false" : "true"); } else if (vt == VT_BSTR) { char *buf = wchar_to_utf8 (var->bstrVal); stream << "BStr: " << buf; xfree (buf); } else if (vt == VT_INT || vt == VT_I4) { stream << var->intVal; } else if (vt == VT_DISPATCH) { char *buf = get_object_name ((LPUNKNOWN) var->pdispVal); stream << "IDispatch: " << buf; xfree (buf); } else if (vt == (VT_VARIANT | VT_BYREF)) { format_variant (stream, var->pvarVal); } else { stream << "?"; } stream << std::endl; } std::string format_dispparams (DISPPARAMS *p) { if (!p) { return "(null)"; } std::stringstream stream; stream << "Count: " << p->cArgs << " CNamed: " << p->cNamedArgs << std::endl; for (int i = 0; i < p->cArgs; i++) { format_variant (stream, p->rgvarg + i); } return stream.str (); } int count_visible_attachments (LPDISPATCH attachments) { int ret = 0; if (!attachments) { return 0; } int att_count = get_oom_int (attachments, "Count"); for (int i = 1; i <= att_count; i++) { std::string item_str; item_str = std::string("Item(") + std::to_string (i) + ")"; LPDISPATCH oom_attach = get_oom_object (attachments, item_str.c_str ()); if (!oom_attach) { log_error ("%s:%s: Failed to get attachment.", SRCNAME, __func__); continue; } VARIANT var; VariantInit (&var); if (get_pa_variant (oom_attach, PR_ATTACHMENT_HIDDEN_DASL, &var)) { /* SECURITY: Testing has shown that for all mail types GpgOL handles that might contain an unsigned attachment we always have the MAPIOBJECT / get the hidden state. Only the transient MIME attachments. The ones used for the MAPI to MIME conversion and which are hidden by GpgOL and handled by GpgOL will have no MAPIOBJECT when a mail is opened from file. So this will remove the warning that "smime.p7m" or "gpgol_mime_structure.txt" are unsigned and unencrypted attachments. */ log_dbg ("Failed to get hidden state."); LPUNKNOWN mapiobj = get_oom_iunknown (oom_attach, "MAPIOBJECT"); if (!mapiobj) { const auto dispName = get_oom_string_s (oom_attach, "DisplayName"); log_dbg ("Attachment: %s has no mapiobject. Ignoring it.", anonstr (dispName.c_str ())); } else { gpgol_release (mapiobj); const auto dispName = get_oom_string_s (oom_attach, "DisplayName"); log_dbg ("Attachment %s without hidden state but mapiobj. " "Count as visible.", anonstr (dispName.c_str ())); ret++; } } else if (var.vt == VT_BOOL && var.boolVal == VARIANT_FALSE) { ret++; } gpgol_release (oom_attach); VariantClear (&var); } return ret; } int invoke_oom_method_with_int (LPDISPATCH pDisp, const char *name, int arg, VARIANT *rVariant) { TSTART; DISPPARAMS parms; VARIANT argvars[1]; VariantInit (&argvars[0]); argvars[0].vt = VT_INT; argvars[0].intVal = arg; parms.cArgs = 1; parms.cNamedArgs = 0; parms.rgvarg = argvars; TRETURN invoke_oom_method_with_parms (pDisp, name, rVariant, &parms); } int invoke_oom_method_with_string (LPDISPATCH pDisp, const char *name, const char *arg, VARIANT *rVariant) { TSTART; if (!arg) { TRETURN 0; } wchar_t *warg = utf8_to_wchar (arg); if (!warg) { TRETURN 1; } VARIANT aVariant[1]; VariantInit (&aVariant[0]); aVariant[0].vt = VT_BSTR; aVariant[0].bstrVal = SysAllocString (warg); xfree (warg); DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; int ret = invoke_oom_method_with_parms (pDisp, name, rVariant, &dispparams); VariantClear(&aVariant[0]); TRETURN ret; } int set_oom_recipients (LPDISPATCH item, const std::vector &recps) { if (!item) { STRANGEPOINT; TRETURN -1; } auto oom_recps = MAKE_SHARED (get_oom_object (item, "Recipients")); if (!oom_recps) { STRANGEPOINT; TRETURN -1; } int count = get_oom_int (oom_recps.get (), "Count"); for (int i = 1; i <= count; i++) { /* First clear out the current recipients. */ int ret = invoke_oom_method_with_int (oom_recps.get (), "Remove", 1, nullptr); if (ret) { STRANGEPOINT; TRETURN ret; } } for (const auto &recp: recps) { if (recp.type() == Recipient::olOriginator) { /* Skip the originator, we only add it internally but it does not need to be in OOM. */ continue; } VARIANT result; VariantInit (&result); int ret = invoke_oom_method_with_string (oom_recps.get (), "Add", recp.mbox ().c_str (), &result); if (ret) { log_err ("Failed to add recipient."); TRETURN ret; } if (result.vt != VT_DISPATCH || !result.pdispVal) { log_err ("No recipient result."); continue; } if (put_oom_int (result.pdispVal, "Type", recp.type())) { log_err ("Failed to set recipient type."); } /* This releases the recipient. */ VariantClear (&result); } TRETURN 0; } int remove_oom_recipient (LPDISPATCH item, const std::string &mbox) { TSTART; if (!item) { STRANGEPOINT; TRETURN -1; } auto oom_recps = MAKE_SHARED (get_oom_object (item, "Recipients")); if (!oom_recps) { STRANGEPOINT; TRETURN -1; } bool r_err = false; const auto recps = get_oom_recipients (oom_recps.get (), &r_err); if (r_err) { log_debug ("Failure to lookup recipients via OOM"); TRETURN -1; } for (const auto &recp: recps) { if (recp.mbox () == mbox && recp.index () != -1) { TRETURN invoke_oom_method_with_int (oom_recps.get (), "Remove", recp.index (), nullptr); } } TRETURN -1; } void oom_dump_idispatch (LPDISPATCH obj) { log_dbg ("Start infos about %p", obj); if (!obj) { log_dbg ("It's NULL"); return; } log_dbg ("Name: '%s'", get_object_name_s (obj).c_str ()); LPTYPEINFO typeinfo = nullptr; HRESULT hr = obj->GetTypeInfo (0, 0, &typeinfo); if (!typeinfo || FAILED (hr)) { log_dbg ("No typeinfo."); return; } TYPEATTR* pta = NULL; hr = typeinfo->GetTypeAttr(&pta); if (!pta || FAILED (hr)) { log_dbg ("No type attr"); return; } /* First the IID to have it clear */ LPOLESTR lpsz = NULL; hr = StringFromIID(pta->guid, &lpsz); if(FAILED (hr)) { hr = StringFromCLSID (pta->guid, &lpsz); } if(SUCCEEDED (hr)) { log_dbg ("Interface: %S", lpsz); CoTaskMemFree(lpsz); } FUNCDESC *pfd = nullptr; /* Lets see what functions we have. */ for(int i = 0; i < pta->cFuncs; i++) { typeinfo->GetFuncDesc(i, &pfd); BSTR names[1]; unsigned int dumb; typeinfo->GetNames(pfd->memid, names, 1, &dumb); if (!names[0]) { typeinfo->ReleaseFuncDesc(pfd); continue; } log_dbg ("%i: %S id=0x%li With %d param(s)\n", i, (names[0]), pfd->memid, pfd->cParams); typeinfo->ReleaseFuncDesc(pfd); SysFreeString(names[0]); } typeinfo->ReleaseTypeAttr(pta); /* Now for some interesting object relations that many oom objecs have. */ const char * relations[] = { "Parent", "GetInspector", "Session", "Sender", nullptr }; for (int i = 0; relations [i]; i++) { LPDISPATCH rel = get_oom_object (obj, relations[i]); log_dbg ("%s: %s", relations [i], get_object_name_s (rel).c_str ()); gpgol_release (rel); } /* Now for some interesting string values. */ const char * stringVals[] = { "EntryID", "Subject", "MessageClass", "Body", nullptr }; for (int i = 0; stringVals[i]; i++) { const auto str = get_oom_string_s (obj, stringVals[i]); log_dbg ("%s: %s", stringVals[i], str.c_str ()); } log_dbg ("Object dump done"); return; } int get_oom_crypto_flags (LPDISPATCH mailitem) { TSTART; int r_val = 0; int err = get_pa_int (mailitem, PR_SECURITY_FLAGS_DASL, &r_val); if (err) { log_dbg ("Failed to get security flags."); TRETURN 0; } TRETURN r_val; } shared_disp_t show_folder_select () { TSTART; VARIANT var; VariantInit (&var); LPDISPATCH rVal; auto namespace_obj = get_oom_object_s (oApp (), "Session"); if (!namespace_obj) { STRANGEPOINT; TRETURN nullptr; } if (!invoke_oom_method (namespace_obj.get (), "PickFolder", &var)) { if (!(var.vt & VT_DISPATCH)) { log_dbg ("Failed to get disp obj. No folder selected?"); TRETURN nullptr; } rVal = var.pdispVal; log_oom ("%s:%s: Got folder ref %p", SRCNAME, __func__, rVal); memdbg_addRef (rVal); TRETURN MAKE_SHARED (rVal); } log_dbg ("No folder returned."); TRETURN nullptr; } LPDISPATCH oApp () { return GpgolAddin::get_instance()->get_application(); } BSTR utf8_to_bstr (const char *string) { wchar_t *tmp = utf8_to_wchar (string); BSTR bstring = tmp ? SysAllocString (tmp) : NULL; xfree (tmp); if (!bstring) { log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__); TRETURN nullptr; } TRETURN bstring; } int oom_save_as (LPDISPATCH obj, const char *path, oomSaveAsType type) { if (!obj || !path) { /* invalid arguments */ STRANGEPOINT; TRETURN -1; } /* Params are first path and then type as optional. With COM Marshalling this means that param 1 is the path and 0 is the type. */ VARIANT aVariant[2]; VariantInit(aVariant); VariantInit(aVariant + 1); DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = (int) type; dispparams.rgvarg[1].vt = VT_BSTR; dispparams.rgvarg[1].bstrVal = utf8_to_bstr (path); dispparams.cArgs = 2; dispparams.cNamedArgs = 0; int rc = invoke_oom_method_with_parms (obj, "SaveAs", nullptr, &dispparams); if (rc) { log_err ("Failed to call SaveAs"); } VariantClear(aVariant); VariantClear(aVariant + 1); return rc; } int oom_save_as_file (LPDISPATCH obj, const char *path) { if (!obj || !path) { /* invalid arguments */ STRANGEPOINT; TRETURN -1; } VARIANT aVariant[1]; VariantInit(aVariant); DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_BSTR; dispparams.rgvarg[0].bstrVal = utf8_to_bstr (path); dispparams.cArgs = 1; dispparams.cNamedArgs = 0; int rc = invoke_oom_method_with_parms (obj, "SaveAsFile", nullptr, &dispparams); if (rc) { log_err ("Failed to call SaveAsFile"); } VariantClear(aVariant); return rc; } void oom_clear_selections () { TSTART; auto explorers_obj = get_oom_object_s (oApp (), "Explorers"); if (!explorers_obj) { STRANGEPOINT; TRETURN; } int count = get_oom_int (explorers_obj.get (), "Count"); for (int i = 1; i <= count; i++) { auto item_str = std::string("Item(") + std::to_string (i) + ")"; auto explorer = get_oom_object_s (explorers_obj, item_str.c_str ()); if (!explorer) { STRANGEPOINT; TRETURN; } if (invoke_oom_method (explorer.get (), "ClearSelection", NULL)) { log_err ("Clearing Explorers %i", i); } } TRETURN; } diff --git a/src/parsecontroller.cpp b/src/parsecontroller.cpp index 602c866..b088c18 100644 --- a/src/parsecontroller.cpp +++ b/src/parsecontroller.cpp @@ -1,788 +1,788 @@ /* @file parsecontroller.cpp * @brief Parse a mail and decrypt / verify accordingly * * Copyright (C) 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 "config.h" #include "parsecontroller.h" #include "attachment.h" #include "mimedataprovider.h" #include "keycache.h" #include #include #include #include #include "cpphelp.h" #ifdef HAVE_W32_SYSTEM #include "common.h" /* We use UTF-8 internally. */ #undef _ # define _(a) utf8_gettext (a) #else # undef S_ # define S_(a) std::string (a) # define _(a) a #endif const char decrypt_template_html[] = { "" "" "" "" "" "" "
" "

%s %s

" "
" "
%s" "
"}; const char decrypt_template[] = {"%s %s\n\n%s"}; using namespace GpgME; static bool expect_no_headers (msgtype_t type) { TSTART; TRETURN type != MSGTYPE_GPGOL_MULTIPART_SIGNED; } static bool expect_no_mime (msgtype_t type) { TSTART; TRETURN type == MSGTYPE_GPGOL_PGP_MESSAGE || type == MSGTYPE_GPGOL_CLEAR_SIGNED; } #ifdef BUILD_TESTS static void get_and_print_key_test (const char *fingerprint, GpgME::Protocol proto) { if (!fingerprint) { STRANGEPOINT; return; } auto ctx = std::unique_ptr (GpgME::Context::createForProtocol (proto)); if (!ctx) { STRANGEPOINT; return; } ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Signatures | GpgME::KeyListMode::Validate | GpgME::KeyListMode::WithTofu); GpgME::Error err; const auto newKey = ctx->key (fingerprint, err, false); std::stringstream ss; ss << newKey; log_debug ("Key: %s", ss.str().c_str()); return; } #endif #ifdef HAVE_W32_SYSTEM ParseController::ParseController(LPSTREAM instream, msgtype_t type): m_inputprovider (new MimeDataProvider(instream, expect_no_headers(type))), m_outputprovider (new MimeDataProvider(expect_no_mime(type))), m_type (type), m_block_html (false), m_second_pass (false) { TSTART; memdbg_ctor ("ParseController"); log_data ("%s:%s: Creating parser for stream: %p of type %i" " expect no headers: %i expect no mime: %i", SRCNAME, __func__, instream, type, expect_no_headers (type), expect_no_mime (type)); TRETURN; } #endif ParseController::ParseController(FILE *instream, msgtype_t type): m_inputprovider (new MimeDataProvider(instream, expect_no_headers(type))), m_outputprovider (new MimeDataProvider(expect_no_mime(type))), m_type (type), m_block_html (false), m_second_pass (false) { TSTART; memdbg_ctor ("ParseController"); log_data ("%s:%s: Creating parser for stream: %p of type %i", SRCNAME, __func__, instream, type); TRETURN; } ParseController::~ParseController() { TSTART; log_debug ("%s:%s", SRCNAME, __func__); memdbg_dtor ("ParseController"); delete m_inputprovider; delete m_outputprovider; TRETURN; } static void operation_for_type(msgtype_t type, bool *decrypt, bool *verify) { *decrypt = false; *verify = false; switch (type) { case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED: case MSGTYPE_GPGOL_PGP_MESSAGE: *decrypt = true; break; case MSGTYPE_GPGOL_MULTIPART_SIGNED: case MSGTYPE_GPGOL_CLEAR_SIGNED: *verify = true; break; case MSGTYPE_GPGOL_OPAQUE_SIGNED: *verify = true; break; case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED: *decrypt = true; break; default: log_error ("%s:%s: Unknown data type: %i", SRCNAME, __func__, type); } } static bool is_smime (Data &data) { TSTART; data.seek (0, SEEK_SET); auto id = data.type(); data.seek (0, SEEK_SET); TRETURN id == Data::CMSSigned || id == Data::CMSEncrypted; } static std::string format_recipients(GpgME::DecryptionResult result, Protocol protocol) { TSTART; std::string msg; - for (const auto recipient: result.recipients()) + for (const auto &recipient: result.recipients()) { auto ctx = Context::createForProtocol(protocol); Error e; if (!ctx) { /* Can't happen */ TRACEPOINT; continue; } const auto key = ctx->key(recipient.keyID(), e, false); delete ctx; if (!key.isNull() && key.numUserIDs() && !e) { msg += std::string("
") + key.userIDs()[0].id() + " (0x" + recipient.keyID() + ")"; continue; } msg += std::string("
") + _("Unknown Key:") + " 0x" + recipient.keyID(); } TRETURN msg; } static std::string format_error(GpgME::DecryptionResult result, Protocol protocol) { TSTART; char *buf; bool no_sec = false; std::string msg; if (result.error ().isCanceled () || result.error ().code () == GPG_ERR_NO_SECKEY) { msg = _("Decryption canceled or timed out."); } if (result.error ().code () == GPG_ERR_DECRYPT_FAILED || result.error ().code () == GPG_ERR_NO_SECKEY) { no_sec = true; for (const auto &recipient: result.recipients ()) { no_sec &= (recipient.status ().code () == GPG_ERR_NO_SECKEY); } } if (no_sec) { msg = _("No secret key found to decrypt the message. " "It is encrypted to the following keys:"); msg += format_recipients (result, protocol); } else { msg = _("Could not decrypt the data: "); if (result.isNull ()) { msg += _("Failed to parse the mail."); } else if (result.isLegacyCipherNoMDC()) { msg += _("Data is not integrity protected. " "Decrypting it could be a security problem. (no MDC)"); } else { msg += result.error().asString(); } } if (gpgrt_asprintf (&buf, opt.prefer_html ? decrypt_template_html : decrypt_template, protocol == OpenPGP ? "OpenPGP" : "S/MIME", _("Encrypted message (decryption not possible)"), msg.c_str()) == -1) { log_error ("%s:%s:Failed to Format error.", SRCNAME, __func__); TRETURN "Failed to Format error."; } msg = buf; memdbg_alloc (buf); xfree (buf); TRETURN msg; } void ParseController::setSender(const std::string &sender) { TSTART; m_sender = sender; TRETURN; } static void handle_autocrypt_info (const autocrypt_s &info) { TSTART; if (info.pref.size() && info.pref != "mutual") { log_debug ("%s:%s: Autocrypt preference is %s which is unhandled.", SRCNAME, __func__, info.pref.c_str()); TRETURN; } #ifndef BUILD_TESTS if (!KeyCache::import_pgp_key_data (info.data)) { log_error ("%s:%s: Failed to import", SRCNAME, __func__); } #endif /* No need to free b64decoded as gpgme_data_release handles it */ TRETURN; } static bool is_valid_chksum(const GpgME::Signature &sig) { TSTART; const auto sum = sig.summary(); static unsigned int valid_mask = (unsigned int) ( GpgME::Signature::Valid | GpgME::Signature::Green | GpgME::Signature::KeyRevoked | GpgME::Signature::KeyExpired | GpgME::Signature::SigExpired | GpgME::Signature::CrlMissing | GpgME::Signature::CrlTooOld | GpgME::Signature::TofuConflict ); bool valid = (sum & valid_mask); if (!valid) { log_debug ("%s:%s: Treating sig as invalid because status is %x", SRCNAME, __func__, sum); } TRETURN valid; } /* Note on stability: Experiments have shown that we can have a crash if parse Returns at time that is not good for the state of Outlook. This happend in my test instance after a delay of > 1s < 3s with a < 1% chance :-/ So if you have really really bad luck this might still crash although it usually should be either much quicker or much slower (slower e.g. when pinentry is requrired). */ void ParseController::parse(bool offline) { TSTART; // Wrap the input stream in an attachment / GpgME Data Protocol protocol; bool decrypt, verify; Data input (m_inputprovider); auto inputType = input.type (); if (m_autocrypt_info.exists) { handle_autocrypt_info (m_autocrypt_info); } if (inputType == Data::Type::PGPSigned) { verify = true; decrypt = false; } else if (m_second_pass && inputType != Data::Type::PGPEncrypted && /* For CMS there might be combined messages when gnupg supports * this and which is not caught below. */ inputType != Data::Type::CMSOther && inputType != Data::Type::CMSEncrypted) { log_dbg ("Second pass for message. Only doing verify."); verify = true; decrypt = false; } else { operation_for_type (m_type, &decrypt, &verify); } if ((m_inputprovider->signature() && is_smime (*m_inputprovider->signature())) || is_smime (input)) { protocol = Protocol::CMS; if (m_second_pass) { log_dbg ("Second pass parsing for CMS. Only doing verify."); verify = true; decrypt = false; } } else { protocol = Protocol::OpenPGP; } auto ctx = std::unique_ptr (Context::createForProtocol (protocol)); if (!ctx) { log_error ("%s:%s:Failed to create context. Installation broken.", SRCNAME, __func__); char *buf; const char *proto = protocol == OpenPGP ? "OpenPGP" : "S/MIME"; if (gpgrt_asprintf (&buf, opt.prefer_html ? decrypt_template_html : decrypt_template, proto, _("Encrypted message (decryption not possible)"), _("Failed to find GnuPG please ensure that GnuPG or " "Gpg4win is properly installed.")) == -1) { log_error ("%s:%s:Failed format error.", SRCNAME, __func__); /* Should never happen */ m_error = std::string("Bad installation"); } memdbg_alloc (buf); m_error = buf; xfree (buf); TRETURN; } /* Maybe a different option for this ? */ if (opt.autoretrieve) { ctx->setFlag("auto-key-retrieve", "1"); } ctx->setArmor(true); if (!m_sender.empty()) { ctx->setSender(m_sender.c_str()); } if (offline) { ctx->setOffline (true); } if (m_second_pass) { // Always use a fresh output on second pass delete m_outputprovider; m_outputprovider = new MimeDataProvider (expect_no_mime (m_type)); } Data output (m_outputprovider); log_debug ("%s:%s:%p decrypt: %i verify: %i with protocol: %s sender: %s type: %i", SRCNAME, __func__, this, decrypt, verify, protocol == OpenPGP ? "OpenPGP" : protocol == CMS ? "CMS" : "Unknown", m_sender.empty() ? "none" : anonstr (m_sender.c_str()), inputType); if (decrypt) { input.seek (0, SEEK_SET); TRACEPOINT; auto combined_result = ctx->decryptAndVerify(input, output); log_debug ("%s:%s:%p decrypt / verify done.", SRCNAME, __func__, this); m_decrypt_result = combined_result.first; m_verify_result = combined_result.second; if ((!m_decrypt_result.error () && m_verify_result.signatures ().empty() && m_outputprovider->signature ()) || is_smime (output) || output.type() == Data::Type::PGPSigned) { TRACEPOINT; log_dbg ("Did not have combined result parsing output."); /* There is a signature in the output. So we have to verify it now as an extra step. */ input = Data (m_outputprovider); delete m_inputprovider; m_inputprovider = m_outputprovider; m_outputprovider = new MimeDataProvider(); output = Data(m_outputprovider); verify = true; TRACEPOINT; } else { verify = false; } TRACEPOINT; const auto err = m_decrypt_result.error (); if (err || m_decrypt_result.isNull () || err.isCanceled ()) { m_error = format_error (m_decrypt_result, protocol); if (err.code () == GPG_ERR_NO_DATA) { m_error += std::string ("
") +
                 S_ ("If this data does not look like an encrypted message\n"
                     "please see the debug tab in the options on how to report\n"
                     "this for further improvement.") + std::string ("\n\n") +
                 asprintf_s (_("Debug information: Message type: %i Data type: %i"),
                                m_type, inputType) + std::string ("\n\n") +
                 S_ ("The interpreted data was:") + std::string ("\n
");
               m_error += input.toString ();
               m_error += "
"; } } } if (verify) { TRACEPOINT; GpgME::Data *sig = m_inputprovider->signature(); input.seek (0, SEEK_SET); if (sig) { sig->seek (0, SEEK_SET); TRACEPOINT; m_verify_result = ctx->verifyDetachedSignature(*sig, input); log_debug ("%s:%s:%p verify done.", SRCNAME, __func__, this); /* Copy the input to output to do a mime parsing. */ char buf[4096]; input.seek (0, SEEK_SET); // Use a fresh output auto provider = new MimeDataProvider (); // Warning: The dtor of the Data object touches // the provider. So we have to delete it after // the assignment. output = Data (provider); delete m_outputprovider; m_outputprovider = provider; size_t nread; while ((nread = input.read (buf, 4096)) > 0) { output.write (buf, nread); } } else { TRACEPOINT; m_verify_result = ctx->verifyOpaqueSignature(input, output); TRACEPOINT; const auto sigs = m_verify_result.signatures(); bool allBad = sigs.size(); - for (const auto s :sigs) + for (const auto &s:sigs) { if (!(s.summary() & Signature::Red)) { allBad = false; break; } } #ifdef HAVE_W32_SYSTEM if (allBad) { log_debug ("%s:%s:%p inline verify error trying native to utf8.", SRCNAME, __func__, this); /* The proper solution would be to take the encoding from the mail / headers. Then convert the wchar body to that encoding. Verify, and convert it after verifcation to UTF-8 which the rest of the code expects. Or native_body from native ACP to InternetCodepage, then verify and convert the output back to utf8 as the rest expects. But as this is clearsigned and we don't really want that. Meh. */ char *utf8 = native_to_utf8 (input.toString().c_str()); if (utf8) { // Try again after conversion. ctx = std::unique_ptr (Context::createForProtocol (protocol)); ctx->setArmor (true); if (!m_sender.empty()) { ctx->setSender(m_sender.c_str()); } input = Data (utf8, strlen (utf8)); xfree (utf8); // Use a fresh output auto provider = new MimeDataProvider (true); // Warning: The dtor of the Data object touches // the provider. So we have to delete it after // the assignment. output = Data (provider); delete m_outputprovider; m_outputprovider = provider; // Try again m_verify_result = ctx->verifyOpaqueSignature(input, output); } } #else (void)allBad; #endif } } log_debug ("%s:%s:%p: decrypt err: %i verify err: %i", SRCNAME, __func__, this, m_decrypt_result.error().code(), m_verify_result.error().code()); /* If we are called again it is the second pass */ m_second_pass = true; bool has_valid_encrypted_checksum = false; /* Ensure that the Keys for the signatures are available and if it has a valid encrypted checksum. */ - for (const auto sig: m_verify_result.signatures()) + for (const auto &sig: m_verify_result.signatures()) { TRACEPOINT; has_valid_encrypted_checksum = is_valid_chksum (sig); #ifndef BUILD_TESTS /* For TOFU we would need to update here even when offline. */ if (!offline || KeyCache::instance ()->useTofu ()) { KeyCache::instance ()->update (sig.fingerprint (), protocol); } #endif TRACEPOINT; } if (protocol == Protocol::CMS && decrypt && !m_decrypt_result.error() && !has_valid_encrypted_checksum) { log_debug ("%s:%s:%p Encrypted S/MIME without checksum. Block HTML.", SRCNAME, __func__, this); m_block_html = true; } /* Import any application/pgp-keys attachments if the option is set. */ if (opt.autoimport) { for (const auto &attach: get_attachments()) { if (attach->get_content_type () == "application/pgp-keys") { #ifndef BUILD_TESTS KeyCache::import_pgp_key_data (attach->get_data()); #endif } } } if (opt.enable_debug & DBG_DATA) { std::stringstream ss; TRACEPOINT; ss << m_decrypt_result << '\n' << m_verify_result; - for (const auto sig: m_verify_result.signatures()) + for (const auto &sig: m_verify_result.signatures()) { const auto key = sig.key(); if (key.isNull()) { #ifndef BUILD_TESTS ss << '\n' << "Cached key:\n" << KeyCache::instance()->getByFpr( sig.fingerprint(), false); #else get_and_print_key_test (sig.fingerprint (), protocol); #endif } else { ss << '\n' << key; } } log_debug ("Decrypt / Verify result: %s", ss.str().c_str()); } else { log_debug ("%s:%s:%p Decrypt / verify done errs: %i / %i numsigs: %i.", SRCNAME, __func__, this, m_decrypt_result.error().code(), m_verify_result.error().code(), m_verify_result.numSignatures()); } TRACEPOINT; if (m_outputprovider) { m_outputprovider->finalize (); } TRETURN; } const std::string ParseController::get_html_body () const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_html_body (); } else { TRETURN std::string(); } } const std::string ParseController::get_body () const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_body (); } else { TRETURN std::string(); } } const std::string ParseController::get_body_charset() const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_body_charset(); } else { TRETURN std::string(); } } const std::string ParseController::get_html_charset() const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_html_charset(); } else { TRETURN std::string(); } } std::vector > ParseController::get_attachments() const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_attachments(); } else { TRETURN std::vector >(); } } std::string ParseController::get_protected_header (const std::string &which) const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_protected_header (which); } TRETURN std::string (); } std::string ParseController::get_content_type () const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_content_type (); } else { TRETURN std::string(); } }