Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/addressbook.cpp b/src/addressbook.cpp
index a61c192..abb0288 100644
--- a/src/addressbook.cpp
+++ b/src/addressbook.cpp
@@ -1,300 +1,298 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#include "addressbook.h"
#include "oomhelp.h"
#include "keycache.h"
#include "mail.h"
#include "cpphelp.h"
#include "windowmessages.h"
#include <gpgme++/context.h>
#include <gpgme++/data.h>
#include <set>
typedef struct
{
std::string name;
std::string data;
HWND hwnd;
shared_disp_t contact;
} keyadder_args_t;
static DWORD WINAPI
open_keyadder (LPVOID arg)
{
auto adder_args = std::unique_ptr<keyadder_args_t> ((keyadder_args_t*) arg);
std::vector<std::string> args;
// Collect the arguments
char *gpg4win_dir = get_gpg4win_dir ();
if (!gpg4win_dir)
{
TRACEPOINT;
return -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);
auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine);
if (!ctx)
{
// can't happen
TRACEPOINT;
return -1;
}
GpgME::Data mystdin (adder_args->data.c_str(), adder_args->data.size(),
false);
GpgME::Data mystdout, mystderr;
char **cargs = vector_to_cArray (args);
- log_debug ("%s:%s: launching keyadder args:", SRCNAME, __func__);
+ log_data ("%s:%s: launching keyadder args:", SRCNAME, __func__);
for (size_t i = 0; cargs && cargs[i]; i++)
{
- log_debug (SIZE_T_FORMAT ": '%s'", i, cargs[i]);
+ log_data (SIZE_T_FORMAT ": '%s'", i, cargs[i]);
}
GpgME::Error err = ctx->spawn (cargs[0], const_cast <const char**> (cargs),
mystdin, mystdout, mystderr,
(GpgME::Context::SpawnFlags) (
GpgME::Context::SpawnAllowSetFg |
GpgME::Context::SpawnShowWindow));
release_cArray (cargs);
if (err)
{
log_error ("%s:%s: Err code: %i asString: %s",
SRCNAME, __func__, err.code(), err.asString());
return 0;
}
auto newKey = mystdout.toString ();
rtrim(newKey);
if (newKey.empty())
{
log_debug ("%s:%s: keyadder canceled.", SRCNAME, __func__);
return 0;
}
if (newKey == "empty")
{
log_debug ("%s:%s: keyadder empty.", SRCNAME, __func__);
newKey = "";
}
Addressbook::callback_args_t cb_args;
/* cb args are valid in the same scope as newKey */
cb_args.data = newKey.c_str();
cb_args.contact = adder_args->contact;
do_in_ui_thread (CONFIG_KEY_DONE, (void*) &cb_args);
return 0;
}
void
Addressbook::update_key_o (void *callback_args)
{
if (!callback_args)
{
TRACEPOINT;
return;
}
callback_args_t *cb_args = static_cast<callback_args_t *> (callback_args);
LPDISPATCH contact = cb_args->contact.get();
LPDISPATCH user_props = get_oom_object (contact, "UserProperties");
if (!user_props)
{
TRACEPOINT;
return;
}
LPDISPATCH pgp_key = find_or_add_text_prop (user_props, "OpenPGP Key");
if (!pgp_key)
{
TRACEPOINT;
return;
}
put_oom_string (pgp_key, "Value", cb_args->data);
log_debug ("%s:%s: PGP key data updated",
SRCNAME, __func__);
gpgol_release (pgp_key);
return;
}
void
Addressbook::edit_key_o (LPDISPATCH contact)
{
if (!contact)
{
TRACEPOINT;
return;
}
LPDISPATCH user_props = get_oom_object (contact, "UserProperties");
if (!user_props)
{
TRACEPOINT;
return;
}
auto pgp_key = MAKE_SHARED (
find_or_add_text_prop (user_props, "OpenPGP Key"));
gpgol_release (user_props);
if (!pgp_key)
{
TRACEPOINT;
return;
}
char *key_data = get_oom_string (pgp_key.get(), "Value");
if (!key_data)
{
TRACEPOINT;
return;
}
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->data = key_data;
args->hwnd = get_active_hwnd ();
contact->AddRef ();
memdbg_addRef (contact);
args->contact = MAKE_SHARED (contact);
CloseHandle (CreateThread (NULL, 0, open_keyadder, (LPVOID) args, 0,
NULL));
xfree (name);
xfree (key_data);
return;
}
static std::set <std::string> s_checked_entries;
/* 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)
{
if (!mail)
{
TRACEPOINT;
return;
}
LPDISPATCH mailitem = mail->item ();
if (!mailitem)
{
TRACEPOINT;
return;
}
auto recipients_obj = MAKE_SHARED (get_oom_object (mailitem, "Recipients"));
if (!recipients_obj)
{
TRACEPOINT;
return;
}
bool err = false;
const auto recipient_entries = get_oom_recipients_with_addrEntry (recipients_obj.get(),
&err);
for (const auto pair: recipient_entries)
{
if (s_checked_entries.find (pair.first) != s_checked_entries.end ())
{
continue;
}
s_checked_entries.insert (pair.first);
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__,
- (opt.enable_debug & DBG_MIME_PARSER) ?
- pair.first.c_str() : "omitted");
+ anonstr (pair.first.c_str()));
continue;
}
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");
gpgol_release (user_props);
if (!pgp_key)
{
continue;
}
log_debug ("%s:%s: found configured pgp key for %s",
SRCNAME, __func__,
- (opt.enable_debug & DBG_MIME_PARSER) ?
- pair.first.c_str() : "omitted");
+ anonstr (pair.first.c_str()));
char *key_data = get_oom_string (pgp_key, "Value");
if (!key_data || !strlen (key_data))
{
log_debug ("%s:%s: No key data",
SRCNAME, __func__);
}
KeyCache::instance ()->importFromAddrBook (pair.first, key_data,
mail);
xfree (key_data);
gpgol_release (pgp_key);
}
}
diff --git a/src/common.cpp b/src/common.cpp
index 9fcecbe..a8d6369 100644
--- a/src/common.cpp
+++ b/src/common.cpp
@@ -1,903 +1,903 @@
/* common.c - Common routines used by GpgOL
* Copyright (C) 2005, 2007, 2008 g10 Code GmbH
* 2015, 2016, 2017 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
* 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#define OEMRESOURCE /* Required for OBM_CHECKBOXES. */
#include <windows.h>
#include <shlobj.h>
#ifndef CSIDL_APPDATA
#define CSIDL_APPDATA 0x001a
#endif
#ifndef CSIDL_LOCAL_APPDATA
#define CSIDL_LOCAL_APPDATA 0x001c
#endif
#ifndef CSIDL_FLAG_CREATE
#define CSIDL_FLAG_CREATE 0x8000
#endif
#include <time.h>
#include <fcntl.h>
#include <ctype.h>
#include "common.h"
#include "dialogs.h"
#include <string>
HINSTANCE glob_hinst = NULL;
void
set_global_hinstance (HINSTANCE hinst)
{
glob_hinst = hinst;
}
void
bring_to_front (HWND wid)
{
if (wid)
{
if (!SetForegroundWindow (wid))
{
log_debug ("%s:%s: SetForegroundWindow failed", SRCNAME, __func__);
/* Yet another fallback which will not work on some
* versions and is not recommended by msdn */
if (!ShowWindow (wid, SW_SHOWNORMAL))
{
log_debug ("%s:%s: ShowWindow failed.", SRCNAME, __func__);
}
}
}
log_debug ("%s:%s: done", SRCNAME, __func__);
}
void
fatal_error (const char *format, ...)
{
va_list arg_ptr;
char buf[512];
va_start (arg_ptr, format);
vsnprintf (buf, sizeof buf -1, format, arg_ptr);
buf[sizeof buf - 1] = 0;
va_end (arg_ptr);
MessageBox (NULL, buf, "Fatal Error", MB_OK);
abort ();
}
/* Helper for read_w32_registry_string(). */
static HKEY
get_root_key(const char *root)
{
HKEY root_key;
if( !root )
root_key = HKEY_CURRENT_USER;
else if( !strcmp( root, "HKEY_CLASSES_ROOT" ) )
root_key = HKEY_CLASSES_ROOT;
else if( !strcmp( root, "HKEY_CURRENT_USER" ) )
root_key = HKEY_CURRENT_USER;
else if( !strcmp( root, "HKEY_LOCAL_MACHINE" ) )
root_key = HKEY_LOCAL_MACHINE;
else if( !strcmp( root, "HKEY_USERS" ) )
root_key = HKEY_USERS;
else if( !strcmp( root, "HKEY_PERFORMANCE_DATA" ) )
root_key = HKEY_PERFORMANCE_DATA;
else if( !strcmp( root, "HKEY_CURRENT_CONFIG" ) )
root_key = HKEY_CURRENT_CONFIG;
else
return NULL;
return root_key;
}
static std::string
readRegStr (const char *root, const char *dir, const char *name)
{
#ifndef _WIN32
(void)root; (void)dir; (void)name;
return std::string();
#else
HKEY root_key, key_handle;
DWORD n1, nbytes, type;
std::string ret;
if (!(root_key = get_root_key(root))) {
return ret;
}
if (RegOpenKeyExA(root_key, dir, 0, KEY_READ, &key_handle)) {
if (root) {
/* no need for a RegClose, so return direct */
return ret;
}
/* Fallback to HKLM */
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle)) {
return ret;
}
}
nbytes = 1;
if (RegQueryValueExA(key_handle, name, 0, nullptr, nullptr, &nbytes)) {
if (root) {
RegCloseKey (key_handle);
return ret;
}
/* Try to fallback to HKLM also vor a missing value. */
RegCloseKey (key_handle);
if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle)) {
return ret;
}
if (RegQueryValueExA(key_handle, name, 0, nullptr, nullptr, &nbytes)) {
RegCloseKey(key_handle);
return ret;
}
}
n1 = nbytes+1;
char result[n1];
if (RegQueryValueExA(key_handle, name, 0, &type, (LPBYTE)result, &n1)) {
RegCloseKey(key_handle);
return ret;
}
RegCloseKey(key_handle);
result[nbytes] = 0; /* make sure it is really a string */
ret = result;
if (type == REG_EXPAND_SZ && strchr (result, '%')) {
n1 += 1000;
char tmp[n1 +1];
nbytes = ExpandEnvironmentStringsA(ret.c_str(), tmp, n1);
if (nbytes && nbytes > n1) {
n1 = nbytes;
char tmp2[n1 +1];
nbytes = ExpandEnvironmentStringsA(result, tmp2, n1);
if (nbytes && nbytes > n1) {
/* oops - truncated, better don't expand at all */
return ret;
}
tmp2[nbytes] = 0;
ret = tmp2;
} else if (nbytes) { /* okay, reduce the length */
tmp[nbytes] = 0;
ret = tmp;
}
}
return ret;
#endif
}
/* Return a string from the Win32 Registry or NULL in case of error.
Caller must release the return value. A NULL for root is an alias
for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. NOTE: The value
is allocated with a plain xmalloc () - use xfree () and not the usual
xfree(). */
char *
read_w32_registry_string (const char *root, const char *dir, const char *name)
{
const auto ret = readRegStr (root, dir, name);
if (ret.empty())
{
return nullptr;
}
return xstrdup (ret.c_str ());
}
/* Return the data dir used for forms etc. Returns NULL on error. */
char *
get_data_dir (void)
{
char *instdir;
char *p;
char *dname;
instdir = get_gpg4win_dir();
if (!instdir)
return NULL;
/* Build the key: "<instdir>/share/gpgol". */
#define SDDIR "\\share\\gpgol"
dname = (char*) xmalloc (strlen (instdir) + strlen (SDDIR) + 1);
if (!dname)
{
xfree (instdir);
return NULL;
}
p = dname;
strcpy (p, instdir);
p += strlen (instdir);
strcpy (p, SDDIR);
xfree (instdir);
#undef SDDIR
return dname;
}
/* Percent-escape the string STR by replacing colons with '%3a'. If
EXTRA is not NULL all characters in it are also escaped. */
char *
percent_escape (const char *str, const char *extra)
{
int i, j;
char *ptr;
if (!str)
return NULL;
for (i=j=0; str[i]; i++)
if (str[i] == ':' || str[i] == '%' || (extra && strchr (extra, str[i])))
j++;
ptr = (char *) xmalloc (i + 2 * j + 1);
i = 0;
while (*str)
{
/* FIXME: Work around a bug in Kleo. */
if (*str == ':')
{
ptr[i++] = '%';
ptr[i++] = '3';
ptr[i++] = 'a';
}
else
{
if (*str == '%')
{
ptr[i++] = '%';
ptr[i++] = '2';
ptr[i++] = '5';
}
else if (extra && strchr (extra, *str))
{
ptr[i++] = '%';
ptr[i++] = tohex_lower ((*str >> 4) & 15);
ptr[i++] = tohex_lower (*str & 15);
}
else
ptr[i++] = *str;
}
str++;
}
ptr[i] = '\0';
return ptr;
}
/* Fix linebreaks.
This replaces all consecutive \r or \n characters
by a single \n.
There can be extremly weird combinations of linebreaks
like \r\r\n\r\r\n at the end of each line when
getting the body of a mail message.
*/
void
fix_linebreaks (char *str, int *len)
{
char *src;
char *dst;
src = str;
dst = str;
while (*src)
{
if (*src == '\r' || *src == '\n')
{
do
src++;
while (*src == '\r' || *src == '\n');
*(dst++) = '\n';
}
else
{
*(dst++) = *(src++);
}
}
*dst = '\0';
*len = dst - str;
}
/* Get a pretty name for the file at path path. File extension
will be set to work for the protocol as provided in protocol and
depends on the signature setting. Set signature to 0 if the
extension should not be a signature extension.
Returns NULL on success.
Caller must free result. */
wchar_t *
get_pretty_attachment_name (wchar_t *path, protocol_t protocol,
int signature)
{
wchar_t* pretty;
wchar_t* buf;
if (!path || !wcslen (path))
{
log_error("%s:%s: No path given", SRCNAME, __func__);
return NULL;
}
pretty = (wchar_t*) xmalloc ((MAX_PATH + 1) * sizeof (wchar_t));
memset (pretty, 0, (MAX_PATH + 1) * sizeof (wchar_t));
buf = wcsrchr (path, '\\') + 1;
if (!buf || !*buf)
{
log_error("%s:%s: No filename found in path", SRCNAME, __func__);
xfree (pretty);
return NULL;
}
wcscpy (pretty, buf);
buf = pretty + wcslen(pretty);
if (signature)
{
if (protocol == PROTOCOL_SMIME)
{
*(buf++) = '.';
*(buf++) = 'p';
*(buf++) = '7';
*(buf++) = 's';
}
else
{
*(buf++) = '.';
*(buf++) = 's';
*(buf++) = 'i';
*(buf++) = 'g';
}
}
else
{
if (protocol == PROTOCOL_SMIME)
{
*(buf++) = '.';
*(buf++) = 'p';
*(buf++) = '7';
*(buf++) = 'm';
}
else
{
*(buf++) = '.';
*(buf++) = 'g';
*(buf++) = 'p';
*(buf++) = 'g';
}
}
return pretty;
}
static HANDLE
CreateFileUtf8 (const char *utf8Name)
{
if (!utf8Name)
{
return INVALID_HANDLE_VALUE;
}
wchar_t *wname = utf8_to_wchar (utf8Name);
if (!wname)
{
TRACEPOINT;
return INVALID_HANDLE_VALUE;
}
auto ret = CreateFileW (wname,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_TEMPORARY,
NULL);
xfree (wname);
return ret;
}
static std::string
getTmpPathUtf8 ()
{
static std::string ret;
if (!ret.empty())
{
return ret;
}
wchar_t tmpPath[MAX_PATH + 2];
if (!GetTempPathW (MAX_PATH, tmpPath))
{
log_error ("%s:%s: Could not get tmp path.",
SRCNAME, __func__);
return ret;
}
char *utf8Name = wchar_to_utf8 (tmpPath);
if (!utf8Name)
{
TRACEPOINT;
return ret;
}
ret = utf8Name;
xfree (utf8Name);
return ret;
}
/* Open a file in a temporary directory, take name as a
suggestion and put the open Handle in outHandle.
Returns the actually used file name in case there
were other files with that name. */
wchar_t*
get_tmp_outfile (wchar_t *name, HANDLE *outHandle)
{
const auto utf8Name = wchar_to_utf8_string (name);
const auto tmpPath = getTmpPathUtf8 ();
if (utf8Name.empty() || tmpPath.empty())
{
TRACEPOINT;
return nullptr;
}
auto outName = tmpPath + utf8Name;
log_data("%s:%s: Attachment candidate is %s",
SRCNAME, __func__, outName.c_str ());
int tries = 1;
while ((*outHandle = CreateFileUtf8 (outName.c_str ())) == INVALID_HANDLE_VALUE)
{
log_debug_w32 (-1, "%s:%s: Failed to open candidate '%s'",
- SRCNAME, __func__, outName.c_str());
+ SRCNAME, __func__, anonstr (outName.c_str()));
char *outNameC = xstrdup (outName.c_str());
const auto lastBackslash = strrchr (outNameC, '\\');
if (!lastBackslash)
{
/* This is an error because tmp name by definition contains one */
log_error ("%s:%s: No backslash in origname '%s'",
SRCNAME, __func__, outNameC);
xfree (outNameC);
return NULL;
}
auto fileExt = strchr (lastBackslash, '.');
if (fileExt)
{
*fileExt = '\0';
++fileExt;
}
// OutNameC is now without an extension and if
// there is a file ext it now points to the extension.
outName = outNameC + std::string("_") + std::to_string(tries++);
if (fileExt)
{
outName += std::string(".") + fileExt;
}
xfree (outNameC);
if (tries > 100)
{
/* You have to know when to give up,.. */
log_error ("%s:%s: Could not get a name out of 100 tries",
SRCNAME, __func__);
return NULL;
}
}
return utf8_to_wchar (outName.c_str ());
}
/** Get the Gpg4win Install directory.
*
* Looks first for the Gpg4win 3.x registry key. Then for the Gpg4win
* 2.x registry key. And checks that the directory can be read.
*
* @returns NULL if no dir could be found. Otherwise a malloced string.
*/
char *
get_gpg4win_dir()
{
const char *g4win_keys[] = {GPG4WIN_REGKEY_3,
GPG4WIN_REGKEY_2,
NULL};
const char **key;
for (key = g4win_keys; *key; key++)
{
char *tmp = read_w32_registry_string (NULL, *key, "Install Directory");
if (!tmp)
{
continue;
}
if (!access(tmp, R_OK))
{
return tmp;
}
else
{
log_debug ("Failed to access: %s\n", tmp);
xfree (tmp);
}
}
return NULL;
}
static void
epoch_to_file_time (unsigned long time, LPFILETIME pft)
{
LONGLONG ll;
ll = Int32x32To64(time, 10000000) + 116444736000000000;
pft->dwLowDateTime = (DWORD)ll;
pft->dwHighDateTime = ll >> 32;
}
char *
format_date_from_gpgme (unsigned long time)
{
wchar_t buf[256];
FILETIME ft;
SYSTEMTIME st;
epoch_to_file_time (time, &ft);
FileTimeToSystemTime(&ft, &st);
int ret = GetDateFormatEx (NULL,
DATE_SHORTDATE,
&st,
NULL,
buf,
256,
NULL);
if (ret == 0)
{
return NULL;
}
return wchar_to_utf8 (buf);
}
/* Return the name of the default UI server. This name is used to
auto start an UI server if an initial connect failed. */
char *
get_uiserver_name (void)
{
char *name = NULL;
char *dir, *uiserver, *p;
int extra_arglen = 9;
const char * server_names[] = {"kleopatra.exe",
"bin\\kleopatra.exe",
"gpa.exe",
"bin\\gpa.exe",
NULL};
const char **tmp = NULL;
dir = get_gpg4win_dir ();
if (!dir)
{
log_error ("Failed to find gpg4win dir");
return NULL;
}
uiserver = read_w32_registry_string (NULL, GPG4WIN_REGKEY_3,
"UI Server");
if (!uiserver)
{
uiserver = read_w32_registry_string (NULL, GPG4WIN_REGKEY_2,
"UI Server");
}
if (uiserver)
{
name = (char*) xmalloc (strlen (dir) + strlen (uiserver) + extra_arglen + 2);
strcpy (stpcpy (stpcpy (name, dir), "\\"), uiserver);
for (p = name; *p; p++)
if (*p == '/')
*p = '\\';
xfree (uiserver);
}
if (name && !access (name, F_OK))
{
/* Set through registry and is accessible */
xfree(dir);
return name;
}
/* Fallbacks */
for (tmp = server_names; *tmp; tmp++)
{
if (name)
{
xfree (name);
}
name = (char *) xmalloc (strlen (dir) + strlen (*tmp) + extra_arglen + 2);
strcpy (stpcpy (stpcpy (name, dir), "\\"), *tmp);
for (p = name; *p; p++)
if (*p == '/')
*p = '\\';
if (!access (name, F_OK))
{
/* Found a viable candidate */
if (strstr (name, "kleopatra.exe"))
{
strcat (name, " --daemon");
}
xfree (dir);
return name;
}
}
xfree (dir);
log_error ("Failed to find a viable UIServer");
return NULL;
}
int
has_high_integrity(HANDLE hToken)
{
PTOKEN_MANDATORY_LABEL integrity_label = NULL;
DWORD integrity_level = 0,
size = 0;
if (hToken == NULL || hToken == INVALID_HANDLE_VALUE)
{
log_debug ("Invalid parameters.");
return 0;
}
/* Get the required size */
if (!GetTokenInformation (hToken, TokenIntegrityLevel,
NULL, 0, &size))
{
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
{
log_debug ("Failed to get required size.\n");
return 0;
}
}
integrity_label = (PTOKEN_MANDATORY_LABEL) LocalAlloc(0, size);
if (integrity_label == NULL)
{
log_debug ("Failed to allocate label. \n");
return 0;
}
if (!GetTokenInformation (hToken, TokenIntegrityLevel,
integrity_label, size, &size))
{
log_debug ("Failed to get integrity level.\n");
LocalFree(integrity_label);
return 0;
}
/* Get the last integrity level */
integrity_level = *GetSidSubAuthority(integrity_label->Label.Sid,
(DWORD)(UCHAR)(*GetSidSubAuthorityCount(
integrity_label->Label.Sid) - 1));
LocalFree (integrity_label);
return integrity_level >= SECURITY_MANDATORY_HIGH_RID;
}
int
is_elevated()
{
int ret = 0;
HANDLE hToken = NULL;
if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &hToken))
{
DWORD elevation;
DWORD cbSize = sizeof (DWORD);
if (GetTokenInformation (hToken, TokenElevation, &elevation,
sizeof (TokenElevation), &cbSize))
{
ret = elevation;
}
}
/* Elevation will be true and ElevationType TokenElevationTypeFull even
if the token is a user token created by SAFER so we additionally
check the integrity level of the token which will only be high in
the real elevated process and medium otherwise. */
ret = ret && has_high_integrity (hToken);
if (hToken)
CloseHandle (hToken);
return ret;
}
int
gpgol_message_box (HWND parent, const char *utf8_text,
const char *utf8_caption, UINT type)
{
wchar_t *w_text = utf8_to_wchar (utf8_text);
wchar_t *w_caption = utf8_to_wchar (utf8_caption);
int ret = 0;
MSGBOXPARAMSW mbp;
mbp.cbSize = sizeof (MSGBOXPARAMS);
mbp.hwndOwner = parent;
mbp.hInstance = glob_hinst;
mbp.lpszText = w_text;
mbp.lpszCaption = w_caption;
mbp.dwStyle = type | MB_USERICON;
mbp.dwLanguageId = MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT);
mbp.lpfnMsgBoxCallback = NULL;
mbp.dwContextHelpId = 0;
mbp.lpszIcon = (LPCWSTR) MAKEINTRESOURCE (IDI_GPGOL_LOCK_ICON);
ret = MessageBoxIndirectW (&mbp);
xfree (w_text);
xfree (w_caption);
return ret;
}
void
gpgol_bug (HWND parent, int code)
{
const char *bugmsg = utf8_gettext ("Operation failed.\n\n"
"This is usually caused by a bug in GpgOL or an error in your setup.\n"
"Please see https://www.gpg4win.org/reporting-bugs.html "
"or ask your Administrator for support.");
char *with_code;
gpgrt_asprintf (&with_code, "%s\nCode: %i", bugmsg, code);
memdbg_alloc (with_code);
gpgol_message_box (parent,
with_code,
_("GpgOL Error"), MB_OK);
xfree (with_code);
return;
}
static char*
expand_path (const char *path)
{
DWORD len;
char *p;
len = ExpandEnvironmentStrings (path, NULL, 0);
if (!len)
{
return NULL;
}
len += 1;
p = (char *) xcalloc (1, len+1);
if (!p)
{
return NULL;
}
len = ExpandEnvironmentStrings (path, p, len);
if (!len)
{
xfree (p);
return NULL;
}
return p;
}
static int
load_config_value (HKEY hk, const char *path, const char *key, char **val)
{
HKEY h;
DWORD size=0, type;
int ec;
*val = NULL;
if (hk == NULL)
{
hk = HKEY_CURRENT_USER;
}
ec = RegOpenKeyEx (hk, path, 0, KEY_READ, &h);
if (ec != ERROR_SUCCESS)
{
return -1;
}
ec = RegQueryValueEx(h, key, NULL, &type, NULL, &size);
if (ec != ERROR_SUCCESS)
{
RegCloseKey (h);
return -1;
}
if (type == REG_EXPAND_SZ)
{
char tmp[256];
RegQueryValueEx (h, key, NULL, NULL, (BYTE*)tmp, &size);
*val = expand_path (tmp);
}
else
{
*val = (char *) xcalloc(1, size+1);
ec = RegQueryValueEx (h, key, NULL, &type, (BYTE*)*val, &size);
if (ec != ERROR_SUCCESS)
{
xfree (*val);
*val = NULL;
RegCloseKey (h);
return -1;
}
}
RegCloseKey (h);
return 0;
}
static int
store_config_value (HKEY hk, const char *path, const char *key, const char *val)
{
HKEY h;
int type;
int ec;
if (hk == NULL)
{
hk = HKEY_CURRENT_USER;
}
ec = RegCreateKeyEx (hk, path, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, &h, NULL);
if (ec != ERROR_SUCCESS)
{
log_debug_w32 (ec, "creating/opening registry key `%s' failed", path);
return -1;
}
type = strchr (val, '%')? REG_EXPAND_SZ : REG_SZ;
ec = RegSetValueEx (h, key, 0, type, (const BYTE*)val, strlen (val));
if (ec != ERROR_SUCCESS)
{
log_debug_w32 (ec, "saving registry key `%s'->`%s' failed", path, key);
RegCloseKey(h);
return -1;
}
RegCloseKey(h);
return 0;
}
/* Store a key in the registry with the key given by @key and the
value @value. */
int
store_extension_value (const char *key, const char *val)
{
return store_config_value (HKEY_CURRENT_USER, GPGOL_REGPATH, key, val);
}
/* Load a key from the registry with the key given by @key. The value is
returned in @val and needs to freed by the caller. */
int
load_extension_value (const char *key, char **val)
{
return load_config_value (HKEY_CURRENT_USER, GPGOL_REGPATH, key, val);
}
int
store_extension_subkey_value (const char *subkey,
const char *key,
const char *val)
{
int ret;
char *path;
gpgrt_asprintf (&path, "%s\\%s", GPGOL_REGPATH, subkey);
memdbg_alloc (path);
ret = store_config_value (HKEY_CURRENT_USER, path, key, val);
xfree (path);
return ret;
}
diff --git a/src/cryptcontroller.cpp b/src/cryptcontroller.cpp
index df35d70..6a85fc3 100644
--- a/src/cryptcontroller.cpp
+++ b/src/cryptcontroller.cpp
@@ -1,1142 +1,1137 @@
/* @file cryptcontroller.cpp
* @brief Helper to do crypto on a mail.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "common.h"
#include "cpphelp.h"
#include "cryptcontroller.h"
#include "mail.h"
#include "mapihelp.h"
#include "mimemaker.h"
#include "wks-helper.h"
#include "overlay.h"
#include "keycache.h"
#include "mymapitags.h"
#include <gpgme++/context.h>
#include <gpgme++/signingresult.h>
#include <gpgme++/encryptionresult.h>
#include "common.h"
#include <sstream>
-#define DEBUG_RESOLVER 1
-
static int
sink_data_write (sink_t sink, const void *data, size_t datalen)
{
GpgME::Data *d = static_cast<GpgME::Data *>(sink->cb_data);
d->write (data, datalen);
return 0;
}
static int
create_sign_attach (sink_t sink, protocol_t protocol,
GpgME::Data &signature,
GpgME::Data &signedData,
const char *micalg);
/** We have some C Style cruft in here as this was historically how
GpgOL worked directly in the MAPI data objects. To reduce the regression
risk the new object oriented way for crypto reused as much as possible
from this.
*/
CryptController::CryptController (Mail *mail, bool encrypt, bool sign,
GpgME::Protocol proto):
m_mail (mail),
m_encrypt (encrypt),
m_sign (sign),
m_crypto_success (false),
m_proto (proto)
{
memdbg_ctor ("CryptController");
log_debug ("%s:%s: CryptController ctor for %p encrypt %i sign %i inline %i.",
SRCNAME, __func__, mail, encrypt, sign, mail->getDoPGPInline ());
m_recipient_addrs = mail->getCachedRecipients ();
}
CryptController::~CryptController()
{
memdbg_dtor ("CryptController");
log_debug ("%s:%s:%p",
SRCNAME, __func__, m_mail);
}
int
CryptController::collect_data ()
{
/* Get the attachment info and the body. We need to do this before
creating the engine's filter because sending the cancel to
the engine with nothing for the engine to process. Will result
in an error. This is actually a bug in our engine code but
we better avoid triggering this bug because the engine
sometimes hangs. Fixme: Needs a proper fix. */
/* Take the Body from the mail if possible. This is a fix for
GnuPG-Bug-ID: T3614 because the body is not always properly
updated in MAPI when sending. */
char *body = m_mail->takeCachedPlainBody ();
if (body && !*body)
{
xfree (body);
body = nullptr;
}
LPMESSAGE message = get_oom_base_message (m_mail->item ());
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
}
auto att_table = mapi_create_attach_table (message, 0);
int n_att_usable = count_usable_attachments (att_table);
if (!n_att_usable && !body)
{
gpgol_message_box (m_mail->getWindow (),
utf8_gettext ("Can't encrypt / sign an empty message."),
utf8_gettext ("GpgOL"), MB_OK);
gpgol_release (message);
mapi_release_attach_table (att_table);
xfree (body);
return -1;
}
bool do_inline = m_mail->getDoPGPInline ();
if (n_att_usable && do_inline)
{
log_debug ("%s:%s: PGP Inline not supported for attachments."
" Using PGP MIME",
SRCNAME, __func__);
do_inline = false;
m_mail->setDoPGPInline (false);
}
else if (do_inline)
{
/* Inline. Use Body as input.
We need to collect also our mime structure for S/MIME
as we don't know yet if we are S/MIME or OpenPGP */
m_bodyInput.write (body, strlen (body));
log_debug ("%s:%s: Inline. Caching body.",
SRCNAME, __func__);
/* Set the input buffer to start. */
m_bodyInput.seek (0, SEEK_SET);
}
/* Set up the sink object to collect the mime structure */
struct sink_s sinkmem;
sink_t sink = &sinkmem;
memset (sink, 0, sizeof *sink);
sink->cb_data = &m_input;
sink->writefnc = sink_data_write;
/* Collect the mime strucutre */
int err = add_body_and_attachments (sink, message, att_table, m_mail,
body, n_att_usable);
xfree (body);
if (err)
{
log_error ("%s:%s: Collecting body and attachments failed.",
SRCNAME, __func__);
gpgol_release (message);
mapi_release_attach_table (att_table);
return -1;
}
/* Message is no longer needed */
gpgol_release (message);
mapi_release_attach_table (att_table);
/* Set the input buffer to start. */
m_input.seek (0, SEEK_SET);
return 0;
}
int
CryptController::lookup_fingerprints (const std::string &sigFpr,
const std::vector<std::string> recpFprs)
{
auto ctx = std::shared_ptr<GpgME::Context> (GpgME::Context::createForProtocol (m_proto));
if (!ctx)
{
log_error ("%s:%s: failed to create context with protocol '%s'",
SRCNAME, __func__,
m_proto == GpgME::CMS ? "smime" :
m_proto == GpgME::OpenPGP ? "openpgp" :
"unknown");
return -1;
}
ctx->setKeyListMode (GpgME::Local);
GpgME::Error err;
if (!sigFpr.empty()) {
m_signer_key = ctx->key (sigFpr.c_str (), err, true);
if (err || m_signer_key.isNull ()) {
log_error ("%s:%s: failed to lookup key for '%s' with protocol '%s'",
- SRCNAME, __func__, sigFpr.c_str (),
+ SRCNAME, __func__, anonstr (sigFpr.c_str ()),
m_proto == GpgME::CMS ? "smime" :
m_proto == GpgME::OpenPGP ? "openpgp" :
"unknown");
return -1;
}
// reset context
ctx = std::shared_ptr<GpgME::Context> (GpgME::Context::createForProtocol (m_proto));
ctx->setKeyListMode (GpgME::Local);
}
if (!recpFprs.size()) {
return 0;
}
// Convert recipient fingerprints
char **cRecps = vector_to_cArray (recpFprs);
err = ctx->startKeyListing (const_cast<const char **> (cRecps));
if (err) {
log_error ("%s:%s: failed to start recipient keylisting",
SRCNAME, __func__);
release_cArray (cRecps);
return -1;
}
do {
m_recipients.push_back(ctx->nextKey(err));
} while (!err);
m_recipients.pop_back();
release_cArray (cRecps);
return 0;
}
int
CryptController::parse_output (GpgME::Data &resolverOutput)
{
// Todo: Use Data::toString
std::istringstream ss(resolverOutput.toString());
std::string line;
std::string sigFpr;
std::vector<std::string> recpFprs;
while (std::getline (ss, line))
{
rtrim (line);
if (line == "cancel")
{
log_debug ("%s:%s: resolver canceled",
SRCNAME, __func__);
return -2;
}
if (line == "unencrypted")
{
log_debug ("%s:%s: FIXME resolver wants unencrypted",
SRCNAME, __func__);
return -1;
}
std::istringstream lss (line);
// First is sig or enc
std::string what;
std::string how;
std::string fingerprint;
std::getline (lss, what, ':');
std::getline (lss, how, ':');
std::getline (lss, fingerprint, ':');
if (m_proto == GpgME::UnknownProtocol)
{
m_proto = (how == "smime") ? GpgME::CMS : GpgME::OpenPGP;
}
if (what == "sig")
{
if (!sigFpr.empty ())
{
log_error ("%s:%s: multiple signing keys not supported",
SRCNAME, __func__);
}
sigFpr = fingerprint;
continue;
}
if (what == "enc")
{
recpFprs.push_back (fingerprint);
}
}
if (m_sign && sigFpr.empty())
{
log_error ("%s:%s: Sign requested but no signing fingerprint - sending unsigned",
SRCNAME, __func__);
m_sign = false;
}
if (m_encrypt && !recpFprs.size())
{
log_error ("%s:%s: Encrypt requested but no recipient fingerprints",
SRCNAME, __func__);
gpgol_message_box (m_mail->getWindow (), _("No recipients for encryption selected."),
_("GpgOL"), MB_OK);
return -2;
}
return lookup_fingerprints (sigFpr, recpFprs);
}
static bool
resolve_through_protocol (const GpgME::Protocol &proto, bool sign,
bool encrypt, const std::string &sender,
const std::vector<std::string> &recps,
std::vector<GpgME::Key> &r_keys,
GpgME::Key &r_sig)
{
bool sig_ok = true;
bool enc_ok = true;
const auto cache = KeyCache::instance();
if (encrypt)
{
r_keys = cache->getEncryptionKeys(recps, proto);
enc_ok = !r_keys.empty();
}
if (sign && enc_ok)
{
r_sig = cache->getSigningKey (sender.c_str (), proto);
sig_ok = !r_sig.isNull();
}
return sig_ok && enc_ok;
}
int
CryptController::resolve_keys_cached()
{
// Prepare variables
const auto cached_sender = m_mail->getSender ();
auto recps = m_recipient_addrs;
if (m_encrypt)
{
recps.push_back (cached_sender);
}
bool resolved = false;
if (opt.enable_smime && opt.prefer_smime)
{
resolved = resolve_through_protocol (GpgME::CMS, m_sign, m_encrypt,
cached_sender, recps, m_recipients,
m_signer_key);
if (resolved)
{
log_debug ("%s:%s: Resolved with CMS due to preference.",
SRCNAME, __func__);
m_proto = GpgME::CMS;
}
}
if (!resolved)
{
resolved = resolve_through_protocol (GpgME::OpenPGP, m_sign, m_encrypt,
cached_sender, recps, m_recipients,
m_signer_key);
if (resolved)
{
log_debug ("%s:%s: Resolved with OpenPGP.",
SRCNAME, __func__);
m_proto = GpgME::OpenPGP;
}
}
if (!resolved && (opt.enable_smime && !opt.prefer_smime))
{
resolved = resolve_through_protocol (GpgME::CMS, m_sign, m_encrypt,
cached_sender, recps, m_recipients,
m_signer_key);
if (resolved)
{
log_debug ("%s:%s: Resolved with CMS as fallback.",
SRCNAME, __func__);
m_proto = GpgME::CMS;
}
}
if (!resolved)
{
log_debug ("%s:%s: Failed to resolve through cache",
SRCNAME, __func__);
m_recipients.clear();
m_signer_key = GpgME::Key();
m_proto = GpgME::UnknownProtocol;
return 1;
}
if (!m_recipients.empty())
{
log_debug ("%s:%s: Encrypting with protocol %s to:",
SRCNAME, __func__, to_cstr (m_proto));
}
for (const auto &key: m_recipients)
{
- log_debug ("%s", key.primaryFingerprint ());
+ log_debug ("%s", anonstr (key.primaryFingerprint ()));
}
if (!m_signer_key.isNull())
{
log_debug ("%s:%s: Signing key: %s:%s",
- SRCNAME, __func__, m_signer_key.primaryFingerprint (),
+ SRCNAME, __func__, anonstr (m_signer_key.primaryFingerprint ()),
to_cstr (m_signer_key.protocol()));
}
return 0;
}
int
CryptController::resolve_keys ()
{
m_recipients.clear();
if (!m_recipient_addrs.size())
{
/* Should not happen. But we add it for better bug reports. */
const char *bugmsg = utf8_gettext ("Operation failed.\n\n"
"This is usually caused by a bug in GpgOL or an error in your setup.\n"
"Please see https://www.gpg4win.org/reporting-bugs.html "
"or ask your Administrator for support.");
char *buf;
gpgrt_asprintf (&buf, "Failed to resolve recipients.\n\n%s\n", bugmsg);
memdbg_alloc (buf);
gpgol_message_box (get_active_hwnd (),
buf,
_("GpgOL"), MB_OK);
xfree(buf);
return -1;
}
if (opt.autoresolve && !resolve_keys_cached ())
{
log_debug ("%s:%s: resolved keys through the cache",
SRCNAME, __func__);
start_crypto_overlay();
return 0;
}
std::vector<std::string> args;
// Collect the arguments
char *gpg4win_dir = get_gpg4win_dir ();
if (!gpg4win_dir)
{
TRACEPOINT;
return -1;
}
const auto resolver = std::string (gpg4win_dir) + "\\bin\\resolver.exe";
args.push_back (resolver);
log_debug ("%s:%s: resolving keys with '%s'",
SRCNAME, __func__, resolver.c_str ());
// We want debug output as OutputDebugString
args.push_back (std::string ("--debug"));
// Yes passing it as int is ok.
auto wnd = m_mail->getWindow ();
if (wnd)
{
// Pass the handle of the active window for raise / overlay.
args.push_back (std::string ("--hwnd"));
args.push_back (std::to_string ((int) (intptr_t) wnd));
}
// Set the overlay caption
args.push_back (std::string ("--overlayText"));
if (m_encrypt)
{
args.push_back (std::string (_("Resolving recipients...")));
}
else if (m_sign)
{
args.push_back (std::string (_("Resolving signers...")));
}
if (!opt.enable_smime)
{
args.push_back (std::string ("--protocol"));
args.push_back (std::string ("pgp"));
}
if (m_sign)
{
args.push_back (std::string ("--sign"));
}
const auto cached_sender = m_mail->getSender ();
if (cached_sender.empty())
{
log_error ("%s:%s: resolve keys without sender.",
SRCNAME, __func__);
}
else
{
args.push_back (std::string ("--sender"));
args.push_back (cached_sender);
}
if (!opt.autoresolve)
{
args.push_back (std::string ("--alwaysShow"));
}
if (m_encrypt)
{
args.push_back (std::string ("--encrypt"));
// Get the recipients that are cached from OOM
for (const auto &addr: m_recipient_addrs)
{
args.push_back (GpgME::UserID::addrSpecFromString (addr.c_str()));
}
}
args.push_back (std::string ("--lang"));
args.push_back (std::string (gettext_localename ()));
// Args are prepared. Spawn the resolver.
auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine);
if (!ctx)
{
// can't happen
TRACEPOINT;
return -1;
}
// Convert our collected vector to c strings
// It's a bit overhead but should be quick for such small
// data.
char **cargs = vector_to_cArray (args);
-#ifdef DEBUG_RESOLVER
- log_debug ("Spawning args:");
+ log_data ("%s:%s: Spawn args:",
+ SRCNAME, __func__);
for (size_t i = 0; cargs && cargs[i]; i++)
{
- log_debug (SIZE_T_FORMAT ": '%s'", i, cargs[i]);
+ log_data (SIZE_T_FORMAT ": '%s'", i, cargs[i]);
}
-#endif
GpgME::Data mystdin (GpgME::Data::null), mystdout, mystderr;
GpgME::Error err = ctx->spawn (cargs[0], const_cast <const char**> (cargs),
mystdin, mystdout, mystderr,
(GpgME::Context::SpawnFlags) (
GpgME::Context::SpawnAllowSetFg |
GpgME::Context::SpawnShowWindow));
// Somehow Qt messes up which window to bring back to front.
// So we do it manually.
bring_to_front (wnd);
// We need to create an overlay while encrypting as pinentry can take a while
start_crypto_overlay();
-#ifdef DEBUG_RESOLVER
- log_debug ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ());
- log_debug ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ());
-#endif
+ log_data ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ());
+ log_data ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ());
release_cArray (cargs);
if (err)
{
log_debug ("%s:%s: Resolver spawn finished Err code: %i asString: %s",
SRCNAME, __func__, err.code(), err.asString());
}
int ret = parse_output (mystdout);
if (ret == -1)
{
log_debug ("%s:%s: Failed to parse / resolve keys.",
SRCNAME, __func__);
- log_debug ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ());
- log_debug ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ());
+ log_data ("Resolver stdout:\n'%s'", mystdout.toString ().c_str ());
+ log_data ("Resolver stderr:\n'%s'", mystderr.toString ().c_str ());
return -1;
}
return ret;
}
int
CryptController::do_crypto (GpgME::Error &err)
{
log_debug ("%s:%s",
SRCNAME, __func__);
/* Start a WKS check if necessary. */
WKSHelper::instance()->start_check (m_mail->getSender ());
int ret = resolve_keys ();
if (ret == -1)
{
//error
log_debug ("%s:%s: Failure to resolve keys.",
SRCNAME, __func__);
return -1;
}
if (ret == -2)
{
// Cancel
return -2;
}
bool do_inline = m_mail->getDoPGPInline ();
if (m_proto == GpgME::CMS && do_inline)
{
log_debug ("%s:%s: Inline for S/MIME not supported. Switching to mime.",
SRCNAME, __func__);
do_inline = false;
m_mail->setDoPGPInline (false);
m_bodyInput = GpgME::Data(GpgME::Data::null);
}
auto ctx = std::shared_ptr<GpgME::Context> (GpgME::Context::createForProtocol(m_proto));
if (!ctx)
{
log_error ("%s:%s: Failure to create context.",
SRCNAME, __func__);
gpgol_message_box (m_mail->getWindow (),
"Failure to create context.",
utf8_gettext ("GpgOL"), MB_OK);
return -1;
}
if (!m_signer_key.isNull())
{
ctx->addSigningKey (m_signer_key);
}
ctx->setTextMode (m_proto == GpgME::OpenPGP);
ctx->setArmor (m_proto == GpgME::OpenPGP);
if (m_encrypt && m_sign && do_inline)
{
// Sign encrypt combined
const auto result_pair = ctx->signAndEncrypt (m_recipients,
do_inline ? m_bodyInput : m_input,
m_output,
GpgME::Context::AlwaysTrust);
const auto err1 = result_pair.first.error();
const auto err2 = result_pair.second.error();
if (err1 || err2)
{
log_error ("%s:%s: Encrypt / Sign error %s %s.",
SRCNAME, __func__, result_pair.first.error().asString(),
result_pair.second.error().asString());
err = err1 ? err1 : err2;
return -1;
}
if (err1.isCanceled() || err2.isCanceled())
{
err = err1.isCanceled() ? err1 : err2;
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
}
else if (m_encrypt && m_sign)
{
// First sign then encrypt
const auto sigResult = ctx->sign (m_input, m_output,
GpgME::Detached);
err = sigResult.error();
if (err)
{
log_error ("%s:%s: Signing error %s.",
SRCNAME, __func__, sigResult.error().asString());
return -1;
}
if (err.isCanceled())
{
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
parse_micalg (sigResult);
// We now have plaintext in m_input
// The detached signature in m_output
// Set up the sink object to construct the multipart/signed
GpgME::Data multipart;
struct sink_s sinkmem;
sink_t sink = &sinkmem;
memset (sink, 0, sizeof *sink);
sink->cb_data = &multipart;
sink->writefnc = sink_data_write;
if (create_sign_attach (sink,
m_proto == GpgME::CMS ?
PROTOCOL_SMIME : PROTOCOL_OPENPGP,
m_output, m_input, m_micalg.c_str ()))
{
TRACEPOINT;
return -1;
}
// Now we have the multipart throw away the rest.
m_output = GpgME::Data ();
m_input = GpgME::Data ();
multipart.seek (0, SEEK_SET);
const auto encResult = ctx->encrypt (m_recipients, multipart,
m_output,
GpgME::Context::AlwaysTrust);
err = encResult.error();
if (err)
{
log_error ("%s:%s: Encryption error %s.",
SRCNAME, __func__, err.asString());
return -1;
}
if (err.isCanceled())
{
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
// Now we have encrypted output just treat it like encrypted.
}
else if (m_encrypt)
{
const auto result = ctx->encrypt (m_recipients, do_inline ? m_bodyInput : m_input,
m_output,
GpgME::Context::AlwaysTrust);
err = result.error();
if (err)
{
log_error ("%s:%s: Encryption error %s.",
SRCNAME, __func__, err.asString());
return -1;
}
if (err.isCanceled())
{
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
}
else if (m_sign)
{
const auto result = ctx->sign (do_inline ? m_bodyInput : m_input, m_output,
do_inline ? GpgME::Clearsigned :
GpgME::Detached);
err = result.error();
if (err)
{
log_error ("%s:%s: Signing error %s.",
SRCNAME, __func__, err.asString());
return -1;
}
if (err.isCanceled())
{
log_debug ("%s:%s: User cancled",
SRCNAME, __func__);
return -2;
}
parse_micalg (result);
}
else
{
// ???
log_error ("%s:%s: unreachable code reached.",
SRCNAME, __func__);
}
log_debug ("%s:%s: Crypto done sucessfuly.",
SRCNAME, __func__);
m_crypto_success = true;
return 0;
}
static int
write_data (sink_t sink, GpgME::Data &data)
{
if (!sink || !sink->writefnc)
{
return -1;
}
char buf[4096];
size_t nread;
data.seek (0, SEEK_SET);
while ((nread = data.read (buf, 4096)) > 0)
{
sink->writefnc (sink, buf, nread);
}
return 0;
}
int
create_sign_attach (sink_t sink, protocol_t protocol,
GpgME::Data &signature,
GpgME::Data &signedData,
const char *micalg)
{
char boundary[BOUNDARYSIZE+1];
char top_header[BOUNDARYSIZE+200];
int rc = 0;
/* Write the top header. */
generate_boundary (boundary);
create_top_signing_header (top_header, sizeof top_header,
protocol, 1, boundary,
micalg);
if ((rc = write_string (sink, top_header)))
{
TRACEPOINT;
return rc;
}
/* Write the boundary so that it is not included in the hashing. */
if ((rc = write_boundary (sink, boundary, 0)))
{
TRACEPOINT;
return rc;
}
/* Write the signed mime structure */
if ((rc = write_data (sink, signedData)))
{
TRACEPOINT;
return rc;
}
/* Write the signature attachment */
if ((rc = write_boundary (sink, boundary, 0)))
{
TRACEPOINT;
return rc;
}
if (protocol == PROTOCOL_OPENPGP)
{
rc = write_string (sink,
"Content-Type: application/pgp-signature\r\n");
}
else
{
rc = write_string (sink,
"Content-Transfer-Encoding: base64\r\n"
"Content-Type: application/pkcs7-signature\r\n");
/* rc = write_string (sink, */
/* "Content-Type: application/x-pkcs7-signature\r\n" */
/* "\tname=\"smime.p7s\"\r\n" */
/* "Content-Transfer-Encoding: base64\r\n" */
/* "Content-Disposition: attachment;\r\n" */
/* "\tfilename=\"smime.p7s\"\r\n"); */
}
if (rc)
{
TRACEPOINT;
return rc;
}
if ((rc = write_string (sink, "\r\n")))
{
TRACEPOINT;
return rc;
}
// Write the signature data
if (protocol == PROTOCOL_SMIME)
{
const std::string sigStr = signature.toString();
if ((rc = write_b64 (sink, (const void *) sigStr.c_str (), sigStr.size())))
{
TRACEPOINT;
return rc;
}
}
else if ((rc = write_data (sink, signature)))
{
TRACEPOINT;
return rc;
}
// Add an extra linefeed with should not harm.
if ((rc = write_string (sink, "\r\n")))
{
TRACEPOINT;
return rc;
}
/* Write the final boundary. */
if ((rc = write_boundary (sink, boundary, 1)))
{
TRACEPOINT;
return rc;
}
return rc;
}
static int
create_encrypt_attach (sink_t sink, protocol_t protocol,
GpgME::Data &encryptedData,
int exchange_major_version)
{
char boundary[BOUNDARYSIZE+1];
int rc = create_top_encryption_header (sink, protocol, boundary,
false, exchange_major_version);
// From here on use goto failure pattern.
if (rc)
{
log_error ("%s:%s: Failed to create top header.",
SRCNAME, __func__);
return rc;
}
if (protocol == PROTOCOL_OPENPGP ||
exchange_major_version >= 15)
{
// With exchange 2016 we have to construct S/MIME
// differently and write the raw data here.
rc = write_data (sink, encryptedData);
}
else
{
const auto encStr = encryptedData.toString();
rc = write_b64 (sink, encStr.c_str(), encStr.size());
}
if (rc)
{
log_error ("%s:%s: Failed to create top header.",
SRCNAME, __func__);
return rc;
}
/* Write the final boundary (for OpenPGP) and finish the attachment. */
if (*boundary && (rc = write_boundary (sink, boundary, 1)))
{
log_error ("%s:%s: Failed to write boundary.",
SRCNAME, __func__);
}
return rc;
}
int
CryptController::update_mail_mapi ()
{
log_debug ("%s:%s", SRCNAME, __func__);
LPMESSAGE message = get_oom_base_message (m_mail->item());
if (!message)
{
log_error ("%s:%s: Failed to obtain message.",
SRCNAME, __func__);
return -1;
}
if (m_mail->getDoPGPInline ())
{
// Nothing to do for inline.
log_debug ("%s:%s: Inline mail. Setting encoding.",
SRCNAME, __func__);
SPropValue prop;
prop.ulPropTag = PR_INTERNET_CPID;
prop.Value.l = 65001;
if (HrSetOneProp (message, &prop))
{
log_error ("%s:%s: Failed to set CPID mapiprop.",
SRCNAME, __func__);
}
return 0;
}
mapi_attach_item_t *att_table = mapi_create_attach_table (message, 0);
// Set up the sink object for our MSOXSMIME attachment.
struct sink_s sinkmem;
sink_t sink = &sinkmem;
memset (sink, 0, sizeof *sink);
sink->cb_data = &m_input;
sink->writefnc = sink_data_write;
// For S/MIME encrypted mails we have to use the application/pkcs7-mime
// content type. Otherwise newer (2016) exchange servers will throw
// an M2MCVT.StorageError.Exeption (See GnuPG-Bug-Id: T3853 )
// This means that the conversion / build of the mime structure also
// happens differently.
int exchange_major_version = get_ex_major_version_for_addr (
m_mail->getSender ().c_str ());
std::string overrideMimeTag;
if (m_proto == GpgME::CMS && m_encrypt && exchange_major_version >= 15)
{
log_debug ("%s:%s: CMS Encrypt with Exchange %i activating alternative.",
SRCNAME, __func__, exchange_major_version);
overrideMimeTag = "application/pkcs7-mime";
}
LPATTACH attach = create_mapi_attachment (message, sink,
overrideMimeTag.empty() ? nullptr :
overrideMimeTag.c_str());
if (!attach)
{
log_error ("%s:%s: Failed to create moss attach.",
SRCNAME, __func__);
gpgol_release (message);
return -1;
}
protocol_t protocol = m_proto == GpgME::CMS ?
PROTOCOL_SMIME :
PROTOCOL_OPENPGP;
int rc = 0;
/* Do we have override MIME ? */
const auto overrideMime = m_mail->get_override_mime_data ();
if (!overrideMime.empty())
{
rc = write_string (sink, overrideMime.c_str ());
}
else if (m_sign && m_encrypt)
{
rc = create_encrypt_attach (sink, protocol, m_output, exchange_major_version);
}
else if (m_encrypt)
{
rc = create_encrypt_attach (sink, protocol, m_output, exchange_major_version);
}
else if (m_sign)
{
rc = create_sign_attach (sink, protocol, m_output, m_input, m_micalg.c_str ());
}
// Close our attachment
if (!rc)
{
rc = close_mapi_attachment (&attach, sink);
}
// Set message class etc.
if (!rc)
{
rc = finalize_message (message, att_table, protocol, m_encrypt ? 1 : 0,
false);
}
// only on error.
if (rc)
{
cancel_mapi_attachment (&attach, sink);
}
// cleanup
mapi_release_attach_table (att_table);
gpgol_release (attach);
gpgol_release (message);
return rc;
}
std::string
CryptController::get_inline_data ()
{
std::string ret;
if (!m_mail->getDoPGPInline ())
{
return ret;
}
m_output.seek (0, SEEK_SET);
char buf[4096];
size_t nread;
while ((nread = m_output.read (buf, 4096)) > 0)
{
ret += std::string (buf, nread);
}
return ret;
}
void
CryptController::parse_micalg (const GpgME::SigningResult &result)
{
if (result.isNull())
{
TRACEPOINT;
return;
}
const auto signature = result.createdSignature(0);
if (signature.isNull())
{
TRACEPOINT;
return;
}
const char *hashAlg = signature.hashAlgorithmAsString ();
if (!hashAlg)
{
TRACEPOINT;
return;
}
if (m_proto == GpgME::OpenPGP)
{
m_micalg = std::string("pgp-") + hashAlg;
}
else
{
m_micalg = hashAlg;
}
std::transform(m_micalg.begin(), m_micalg.end(), m_micalg.begin(), ::tolower);
log_debug ("%s:%s: micalg is: '%s'.",
SRCNAME, __func__, m_micalg.c_str ());
}
void
CryptController::start_crypto_overlay ()
{
auto wid = m_mail->getWindow ();
std::string text;
if (m_encrypt)
{
text = _("Encrypting...");
}
else if (m_sign)
{
text = _("Signing...");
}
m_overlay = std::unique_ptr<Overlay> (new Overlay (wid, text));
}
diff --git a/src/debug.cpp b/src/debug.cpp
index de1108d..454de19 100644
--- a/src/debug.cpp
+++ b/src/debug.cpp
@@ -1,352 +1,352 @@
/* debug.cpp - Debugging / Log helpers for GpgOL
* Copyright (C) 2018 by 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
* 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 <http://www.gnu.org/licenses/>.
*/
#include "common_indep.h"
#include <gpg-error.h>
#include <string>
#include <unordered_map>
/* The malloced name of the logfile and the logging stream. If
LOGFILE is NULL, no logging is done. */
static char *logfile;
static FILE *logfp;
#ifdef HAVE_W32_SYSTEM
/* Acquire the mutex for logging. Returns 0 on success. */
static int
lock_log (void)
{
int code = WaitForSingleObject (log_mutex, 10000);
return code != WAIT_OBJECT_0;
}
/* Release the mutex for logging. No error return is done because this
is a fatal error anyway and we have no means for proper
notification. */
static void
unlock_log (void)
{
ReleaseMutex (log_mutex);
}
#endif
const char *
get_log_file (void)
{
return logfile? logfile : "";
}
void
set_log_file (const char *name)
{
#ifdef HAVE_W32_SYSTEM
if (!lock_log ())
{
#endif
if (logfp)
{
fclose (logfp);
logfp = NULL;
}
xfree (logfile);
if (!name || *name == '\"' || !*name)
logfile = NULL;
else
logfile = xstrdup (name);
#ifdef HAVE_W32_SYSTEM
unlock_log ();
}
#endif
}
static void
do_log (const char *fmt, va_list a, int w32err, int err,
const void *buf, size_t buflen)
{
if (!logfile)
return;
#ifdef HAVE_W32_SYSTEM
if (!opt.enable_debug)
return;
if (lock_log ())
{
OutputDebugStringA ("GpgOL: Failed to log.");
return;
}
#endif
if (!strcmp (logfile, "stdout"))
{
logfp = stdout;
}
else if (!strcmp (logfile, "stderr"))
{
logfp = stderr;
}
if (!logfp)
logfp = fopen (logfile, "a+");
#ifdef HAVE_W32_SYSTEM
if (!logfp)
{
unlock_log ();
return;
}
char time_str[9];
SYSTEMTIME utc_time;
GetSystemTime (&utc_time);
if (GetTimeFormatA (LOCALE_INVARIANT,
TIME_FORCE24HOURFORMAT | LOCALE_USE_CP_ACP,
&utc_time,
"HH:mm:ss",
time_str,
9))
{
fprintf (logfp, "%s/%lu/",
time_str,
(unsigned long)GetCurrentThreadId ());
}
else
{
fprintf (logfp, "unknown/%lu/",
(unsigned long)GetCurrentThreadId ());
}
#endif
if (err == 1)
fputs ("ERROR/", logfp);
vfprintf (logfp, fmt, a);
#ifdef HAVE_W32_SYSTEM
if (w32err)
{
char tmpbuf[256];
FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32err,
MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
tmpbuf, sizeof (tmpbuf)-1, NULL);
fputs (": ", logfp);
if (*tmpbuf && tmpbuf[strlen (tmpbuf)-1] == '\n')
tmpbuf[strlen (tmpbuf)-1] = 0;
if (*tmpbuf && tmpbuf[strlen (tmpbuf)-1] == '\r')
tmpbuf[strlen (tmpbuf)-1] = 0;
fprintf (logfp, "%s (%d)", tmpbuf, w32err);
}
#endif
if (buf)
{
const unsigned char *p = (const unsigned char*)buf;
for ( ; buflen; buflen--, p++)
fprintf (logfp, "%02X", *p);
putc ('\n', logfp);
}
else if ( *fmt && fmt[strlen (fmt) - 1] != '\n')
putc ('\n', logfp);
fflush (logfp);
#ifdef HAVE_W32_SYSTEM
unlock_log ();
#endif
}
const char *
log_srcname (const char *file)
{
const char *s = strrchr (file, '/');
return s? s+1:file;
}
void
log_debug (const char *fmt, ...)
{
va_list a;
va_start (a, fmt);
do_log (fmt, a, 0, 0, NULL, 0);
va_end (a);
}
void
log_error (const char *fmt, ...)
{
va_list a;
va_start (a, fmt);
do_log (fmt, a, 0, 1, NULL, 0);
va_end (a);
}
void
log_vdebug (const char *fmt, va_list a)
{
do_log (fmt, a, 0, 0, NULL, 0);
}
void
log_hexdump (const void *buf, size_t buflen, const char *fmt, ...)
{
va_list a;
va_start (a, fmt);
do_log (fmt, a, 0, 2, buf, buflen);
va_end (a);
}
#ifdef HAVE_W32_SYSTEM
void
log_debug_w32 (int w32err, const char *fmt, ...)
{
va_list a;
if (w32err == -1)
w32err = GetLastError ();
va_start (a, fmt);
do_log (fmt, a, w32err, 0, NULL, 0);
va_end (a);
}
void
log_error_w32 (int w32err, const char *fmt, ...)
{
va_list a;
if (w32err == -1)
w32err = GetLastError ();
va_start (a, fmt);
do_log (fmt, a, w32err, 1, NULL, 0);
va_end (a);
}
static void
do_log_window_info (HWND window, int level)
{
char buf[1024+1];
char name[200];
int nname;
char *pname;
DWORD pid;
if (!window)
return;
GetWindowThreadProcessId (window, &pid);
if (pid != GetCurrentProcessId ())
return;
memset (buf, 0, sizeof (buf));
GetWindowText (window, buf, sizeof (buf)-1);
nname = GetClassName (window, name, sizeof (name)-1);
if (nname)
pname = name;
else
pname = NULL;
if (level == -1)
log_debug (" parent=%p/%lu (%s) `%s'", window, (unsigned long)pid,
pname? pname:"", buf);
else
log_debug (" %*shwnd=%p/%lu (%s) `%s'", level*2, "", window,
(unsigned long)pid, pname? pname:"", buf);
}
/* Helper to log_window_hierarchy. */
static HWND
do_log_window_hierarchy (HWND parent, int level)
{
HWND child;
child = GetWindow (parent, GW_CHILD);
while (child)
{
do_log_window_info (child, level);
do_log_window_hierarchy (child, level+1);
child = GetNextWindow (child, GW_HWNDNEXT);
}
return NULL;
}
/* Print a debug message using the format string FMT followed by the
window hierarchy of WINDOW. */
void
log_window_hierarchy (HWND window, const char *fmt, ...)
{
va_list a;
va_start (a, fmt);
do_log (fmt, a, 0, 0, NULL, 0);
va_end (a);
if (window)
{
do_log_window_info (window, -1);
do_log_window_hierarchy (window, 0);
}
}
#endif
GPGRT_LOCK_DEFINE (anon_str_lock);
/* Weel ok this survives unload but we don't want races
and it makes a bit of sense to keep the strings constant. */
static std::unordered_map<std::string, std::string> str_map;
const char *anonstr (const char *data)
{
static int64_t cnt;
if (opt.enable_debug & DBG_DATA)
{
return data;
}
if (!data)
{
return "gpgol_str_null";
}
if (!strlen (data))
{
return "gpgol_str_empty";
}
gpgrt_lock_lock (&anon_str_lock);
const std::string strData (data);
auto it = str_map.find (strData);
if (it == str_map.end ())
{
- const auto anon = std::string ("gpgol_string_") + std::to_string (cnt);
+ const auto anon = std::string ("gpgol_string_") + std::to_string (++cnt);
str_map.insert (std::make_pair (strData, anon));
it = str_map.find (strData);
}
// As the data is saved in our map we can return
// the c_str as it won't be touched as const.
gpgrt_lock_unlock (&anon_str_lock);
TRACEPOINT;
return it->second.c_str();
}
diff --git a/src/keycache.cpp b/src/keycache.cpp
index 75e317c..ca2a8a5 100644
--- a/src/keycache.cpp
+++ b/src/keycache.cpp
@@ -1,1137 +1,1138 @@
/* @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 <http://www.gnu.org/licenses/>.
*/
#include "keycache.h"
#include "common.h"
#include "cpphelp.h"
#include "mail.h"
#include <gpg-error.h>
#include <gpgme++/context.h>
#include <gpgme++/key.h>
#include <gpgme++/data.h>
#include <gpgme++/importresult.h>
#include <windows.h>
#include <set>
#include <unordered_map>
#include <sstream>
GPGRT_LOCK_DEFINE (keycache_lock);
GPGRT_LOCK_DEFINE (fpr_map_lock);
GPGRT_LOCK_DEFINE (update_lock);
GPGRT_LOCK_DEFINE (import_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)
{
s_thread_cnt++;
Mail::lockDelete ();
if (Mail::isValidPtr (m_mail))
{
m_mail->incrementLocateCount ();
}
Mail::unlockDelete ();
};
~LocateArgs()
{
s_thread_cnt--;
Mail::lockDelete ();
if (Mail::isValidPtr (m_mail))
{
m_mail->decrementLocateCount ();
}
Mail::unlockDelete ();
}
std::string m_mbox;
Mail *m_mail;
};
} // namespace
typedef std::pair<std::string, GpgME::Protocol> update_arg_t;
typedef std::pair<std::unique_ptr<LocateArgs>, std::string> import_arg_t;
static DWORD WINAPI
do_update (LPVOID arg)
{
auto args = std::unique_ptr<update_arg_t> ((update_arg_t*) arg);
- log_data ("%s:%s updating: \"%s\" with protocol %s",
- SRCNAME, __func__, args->first.c_str (),
- to_cstr (args->second));
+ 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> (GpgME::Context::createForProtocol
(args->second));
if (!ctx)
{
TRACEPOINT;
KeyCache::instance ()->onUpdateJobDone (args->first.c_str(),
GpgME::Key ());
return 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__, args->first.c_str ());
+ SRCNAME, __func__, anonstr (args->first.c_str ()));
}
if (err)
{
log_debug ("%s:%s Failed to find key for %s err: ",
SRCNAME, __func__, err.asString ());
}
KeyCache::instance ()->onUpdateJobDone (args->first.c_str(),
newKey);
log_debug ("%s:%s Update job done",
SRCNAME, __func__);
return 0;
}
static DWORD WINAPI
do_import (LPVOID arg)
{
auto args = std::unique_ptr<import_arg_t> ((import_arg_t*) arg);
const std::string mbox = args->first->m_mbox;
- log_data ("%s:%s importing for: \"%s\" with data \n%s",
- SRCNAME, __func__, mbox.c_str (),
- args->second.c_str ());
+ log_debug ("%s:%s importing for: \"%s\" with data \n%s",
+ SRCNAME, __func__, anonstr (mbox.c_str ()),
+ anonstr (args->second.c_str ()));
auto ctx = std::unique_ptr<GpgME::Context> (GpgME::Context::createForProtocol
(GpgME::OpenPGP));
if (!ctx)
{
TRACEPOINT;
return 0;
}
// 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);
if (data.type () != GpgME::Data::PGPKey)
{
log_debug ("%s:%s Data for: %s is not a PGP Key",
- SRCNAME, __func__, mbox.c_str ());
+ SRCNAME, __func__, anonstr (mbox.c_str ()));
return 0;
}
data.rewind ();
const auto result = ctx->importKeys (data);
std::vector<std::string> fingerprints;
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 = GpgME::OpenPGP;
// 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);
fingerprints.push_back (fpr);
log_debug ("%s:%s Imported: %s from addressbook.",
- SRCNAME, __func__, fpr);
+ SRCNAME, __func__, anonstr (fpr));
}
KeyCache::instance ()->onAddrBookImportJobDone (mbox, fingerprints);
log_debug ("%s:%s Import job done for: %s",
- SRCNAME, __func__, mbox.c_str ());
+ SRCNAME, __func__, anonstr (mbox.c_str ()));
return 0;
}
class KeyCache::Private
{
public:
Private()
{
}
void setPgpKey(const std::string &mbox, const GpgME::Key &key)
{
gpgrt_lock_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<std::string, GpgME::Key> (mbox, key));
}
else
{
it->second = key;
}
insertOrUpdateInFprMap (key);
gpgrt_lock_unlock (&keycache_lock);
}
void setSmimeKey(const std::string &mbox, const GpgME::Key &key)
{
gpgrt_lock_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<std::string, GpgME::Key> (mbox, key));
}
else
{
it->second = key;
}
insertOrUpdateInFprMap (key);
gpgrt_lock_unlock (&keycache_lock);
}
void setPgpKeySecret(const std::string &mbox, const GpgME::Key &key)
{
gpgrt_lock_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<std::string, GpgME::Key> (mbox, key));
}
else
{
it->second = key;
}
insertOrUpdateInFprMap (key);
gpgrt_lock_unlock (&keycache_lock);
}
void setSmimeKeySecret(const std::string &mbox, const GpgME::Key &key)
{
gpgrt_lock_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<std::string, GpgME::Key> (mbox, key));
}
else
{
it->second = key;
}
insertOrUpdateInFprMap (key);
gpgrt_lock_unlock (&keycache_lock);
}
std::vector<GpgME::Key> getPGPOverrides (const char *addr)
{
std::vector<GpgME::Key> ret;
if (!addr)
{
return ret;
}
auto mbox = GpgME::UserID::addrSpecFromString (addr);
gpgrt_lock_lock (&keycache_lock);
const auto it = m_addr_book_overrides.find (mbox);
if (it == m_addr_book_overrides.end ())
{
gpgrt_lock_unlock (&keycache_lock);
return ret;
}
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__, fpr.c_str());
+ SRCNAME, __func__, anonstr (fpr.c_str()));
continue;
}
ret.push_back (key);
}
gpgrt_lock_unlock (&keycache_lock);
return ret;
}
GpgME::Key getKey (const char *addr, GpgME::Protocol proto)
{
if (!addr)
{
return GpgME::Key();
}
auto mbox = GpgME::UserID::addrSpecFromString (addr);
if (proto == GpgME::OpenPGP)
{
gpgrt_lock_lock (&keycache_lock);
const auto it = m_pgp_key_map.find (mbox);
if (it == m_pgp_key_map.end ())
{
gpgrt_lock_unlock (&keycache_lock);
return GpgME::Key();
}
const auto ret = it->second;
gpgrt_lock_unlock (&keycache_lock);
return ret;
}
gpgrt_lock_lock (&keycache_lock);
const auto it = m_smime_key_map.find (mbox);
if (it == m_smime_key_map.end ())
{
gpgrt_lock_unlock (&keycache_lock);
return GpgME::Key();
}
const auto ret = it->second;
gpgrt_lock_unlock (&keycache_lock);
return ret;
}
GpgME::Key getSKey (const char *addr, GpgME::Protocol proto)
{
if (!addr)
{
return GpgME::Key();
}
auto mbox = GpgME::UserID::addrSpecFromString (addr);
if (proto == GpgME::OpenPGP)
{
gpgrt_lock_lock (&keycache_lock);
const auto it = m_pgp_skey_map.find (mbox);
if (it == m_pgp_skey_map.end ())
{
gpgrt_lock_unlock (&keycache_lock);
return GpgME::Key();
}
const auto ret = it->second;
gpgrt_lock_unlock (&keycache_lock);
return ret;
}
gpgrt_lock_lock (&keycache_lock);
const auto it = m_smime_skey_map.find (mbox);
if (it == m_smime_skey_map.end ())
{
gpgrt_lock_unlock (&keycache_lock);
return GpgME::Key();
}
const auto ret = it->second;
gpgrt_lock_unlock (&keycache_lock);
return ret;
}
GpgME::Key getSigningKey (const char *addr, GpgME::Protocol proto)
{
const auto key = getSKey (addr, proto);
if (key.isNull())
{
- log_data ("%s:%s: secret key for %s is null",
- SRCNAME, __func__, addr);
+ log_debug ("%s:%s: secret key for %s is null",
+ SRCNAME, __func__, anonstr (addr));
return key;
}
if (!key.canReallySign())
{
- log_data ("%s:%s: Discarding key for %s because it can't sign",
- SRCNAME, __func__, addr);
+ log_debug ("%s:%s: Discarding key for %s because it can't sign",
+ SRCNAME, __func__, anonstr (addr));
return GpgME::Key();
}
if (!key.hasSecret())
{
- log_data ("%s:%s: Discarding key for %s because it has no secret",
- SRCNAME, __func__, addr);
+ log_debug ("%s:%s: Discarding key for %s because it has no secret",
+ SRCNAME, __func__, anonstr (addr));
return GpgME::Key();
}
if (in_de_vs_mode () && !key.isDeVs())
{
- log_data ("%s:%s: signing key for %s is not deVS",
- SRCNAME, __func__, addr);
+ log_debug ("%s:%s: signing key for %s is not deVS",
+ SRCNAME, __func__, anonstr (addr));
return GpgME::Key();
}
return key;
}
std::vector<GpgME::Key> getEncryptionKeys (const std::vector<std::string>
&recipients,
GpgME::Protocol proto)
{
std::vector<GpgME::Key> ret;
if (recipients.empty ())
{
TRACEPOINT;
return ret;
}
for (const auto &recip: recipients)
{
if (proto == GpgME::OpenPGP)
{
const auto overrides = getPGPOverrides (recip.c_str ());
if (!overrides.empty())
{
ret.insert (ret.end (), overrides.begin (), overrides.end ());
- log_data ("%s:%s: Using overides for %s",
- SRCNAME, __func__, recip.c_str ());
+ log_debug ("%s:%s: Using overides for %s",
+ SRCNAME, __func__, anonstr (recip.c_str ()));
continue;
}
}
const auto key = getKey (recip.c_str (), proto);
if (key.isNull())
{
- log_data ("%s:%s: No key for %s. no internal encryption",
- SRCNAME, __func__, recip.c_str ());
+ log_debug ("%s:%s: No key for %s. no internal encryption",
+ SRCNAME, __func__, anonstr (recip.c_str ()));
return std::vector<GpgME::Key>();
}
if (!key.canEncrypt() || key.isRevoked() ||
key.isExpired() || key.isDisabled() || key.isInvalid())
{
log_data ("%s:%s: Invalid key for %s. no internal encryption",
- SRCNAME, __func__, recip.c_str ());
+ SRCNAME, __func__, anonstr (recip.c_str ()));
return std::vector<GpgME::Key>();
}
if (in_de_vs_mode () && !key.isDeVs ())
{
log_data ("%s:%s: key for %s is not deVS",
- SRCNAME, __func__, recip.c_str ());
+ SRCNAME, __func__, anonstr (recip.c_str ()));
return std::vector<GpgME::Key>();
}
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 (!validEnough)
{
- log_data ("%s:%s: UID for %s does not have at least marginal trust",
- SRCNAME, __func__, recip.c_str ());
+ log_debug ("%s:%s: UID for %s does not have at least marginal trust",
+ SRCNAME, __func__, anonstr (recip.c_str ()));
return std::vector<GpgME::Key>();
}
// Accepting key
ret.push_back (key);
}
return ret;
}
void insertOrUpdateInFprMap (const GpgME::Key &key)
{
if (key.isNull() || !key.primaryFingerprint())
{
TRACEPOINT;
return;
}
gpgrt_lock_lock (&fpr_map_lock);
/* First ensure that we have the subkeys mapped to the primary
fpr */
const char *primaryFpr = key.primaryFingerprint ();
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);
- log_data ("%s:%s \"%s\" updated.",
- SRCNAME, __func__, primaryFpr);
+ log_debug ("%s:%s \"%s\" updated.",
+ SRCNAME, __func__, anonstr (primaryFpr));
if (it == m_fpr_map.end ())
{
m_fpr_map.insert (std::make_pair (primaryFpr, key));
gpgrt_lock_unlock (&fpr_map_lock);
return;
}
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;
}
gpgrt_lock_unlock (&fpr_map_lock);
return;
}
GpgME::Key getFromMap (const char *fpr) const
{
if (!fpr)
{
TRACEPOINT;
return GpgME::Key();
}
gpgrt_lock_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__, it->second.c_str(), fpr);
+ 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;
gpgrt_lock_unlock (&fpr_map_lock);
return ret;
}
gpgrt_lock_unlock (&fpr_map_lock);
return GpgME::Key();
}
GpgME::Key getByFpr (const char *fpr, bool block) const
{
if (!fpr)
{
TRACEPOINT;
return 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;
gpgrt_lock_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__, fpr);
+ SRCNAME, __func__, anonstr (fpr));
}
gpgrt_lock_unlock (&update_lock);
Sleep (10);
gpgrt_lock_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__, fpr);
+ SRCNAME, __func__, anonstr (fpr));
break;
}
}
gpgrt_lock_unlock (&update_lock);
TRACEPOINT;
const auto ret2 = getFromMap (fpr);
if (ret2.isNull ())
{
log_debug ("%s:%s Cache miss after blocking check %s.",
- SRCNAME, __func__, fpr);
+ SRCNAME, __func__, anonstr (fpr));
}
else
{
log_debug ("%s:%s Cache hit after wait for %s.",
- SRCNAME, __func__, fpr);
+ SRCNAME, __func__, anonstr (fpr));
return ret2;
}
}
log_debug ("%s:%s Cache miss for %s.",
- SRCNAME, __func__, fpr);
+ SRCNAME, __func__, anonstr (fpr));
return GpgME::Key();
}
log_debug ("%s:%s Cache hit for %s.",
- SRCNAME, __func__, fpr);
+ SRCNAME, __func__, anonstr (fpr));
return ret;
}
void update (const char *fpr, GpgME::Protocol proto)
{
if (!fpr)
{
return;
}
const std::string sFpr (fpr);
gpgrt_lock_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__, fpr);
+ SRCNAME, __func__, anonstr (fpr));
gpgrt_lock_unlock (&update_lock);
}
m_update_jobs.insert (sFpr);
gpgrt_lock_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));
}
void onUpdateJobDone (const char *fpr, const GpgME::Key &key)
{
if (!fpr)
{
return;
}
TRACEPOINT;
insertOrUpdateInFprMap (key);
gpgrt_lock_lock (&update_lock);
const auto it = m_update_jobs.find(fpr);
if (it == m_update_jobs.end())
{
log_error ("%s:%s Update for \"%s\" already finished.",
- SRCNAME, __func__, fpr);
+ SRCNAME, __func__, anonstr (fpr));
gpgrt_lock_unlock (&update_lock);
return;
}
m_update_jobs.erase (it);
gpgrt_lock_unlock (&update_lock);
TRACEPOINT;
return;
}
void importFromAddrBook (const std::string &mbox, const char *data,
Mail *mail)
{
if (!data || mbox.empty() || !mail)
{
TRACEPOINT;
return;
}
gpgrt_lock_lock (&import_lock);
if (m_import_jobs.find (mbox) != m_import_jobs.end ())
{
log_debug ("%s:%s import for \"%s\" already in progress.",
- SRCNAME, __func__, mbox.c_str ());
+ SRCNAME, __func__, anonstr (mbox.c_str ()));
gpgrt_lock_unlock (&import_lock);
}
m_import_jobs.insert (mbox);
gpgrt_lock_unlock (&import_lock);
import_arg_t * args = new import_arg_t;
args->first = std::unique_ptr<LocateArgs> (new LocateArgs (mbox, mail));
args->second = std::string (data);
CloseHandle (CreateThread (NULL, 0, do_import,
(LPVOID) args, 0,
NULL));
}
void onAddrBookImportJobDone (const std::string &mbox,
const std::vector<std::string> &result_fprs)
{
gpgrt_lock_lock (&keycache_lock);
auto it = m_addr_book_overrides.find (mbox);
if (it != m_addr_book_overrides.end ())
{
it->second = result_fprs;
}
else
{
m_addr_book_overrides.insert (
std::make_pair (mbox, result_fprs));
}
gpgrt_lock_unlock (&keycache_lock);
gpgrt_lock_lock (&import_lock);
const auto job_it = m_import_jobs.find(mbox);
if (job_it == m_import_jobs.end())
{
log_error ("%s:%s import for \"%s\" already finished.",
- SRCNAME, __func__, mbox.c_str ());
+ SRCNAME, __func__, anonstr (mbox.c_str ()));
gpgrt_lock_unlock (&import_lock);
return;
}
m_import_jobs.erase (job_it);
gpgrt_lock_unlock (&import_lock);
return;
}
std::unordered_map<std::string, GpgME::Key> m_pgp_key_map;
std::unordered_map<std::string, GpgME::Key> m_smime_key_map;
std::unordered_map<std::string, GpgME::Key> m_pgp_skey_map;
std::unordered_map<std::string, GpgME::Key> m_smime_skey_map;
std::unordered_map<std::string, GpgME::Key> m_fpr_map;
std::unordered_map<std::string, std::string> m_sub_fpr_map;
std::unordered_map<std::string, std::vector<std::string> >
m_addr_book_overrides;
std::set<std::string> m_update_jobs;
std::set<std::string> m_import_jobs;
};
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<GpgME::Key>
KeyCache::getEncryptionKeys (const std::vector<std::string> &recipients, GpgME::Protocol proto) const
{
return d->getEncryptionKeys (recipients, proto);
}
static DWORD WINAPI
do_locate (LPVOID arg)
{
if (!arg)
{
return 0;
}
auto args = std::unique_ptr<LocateArgs> ((LocateArgs *) arg);
const auto addr = args->m_mbox;
- log_data ("%s:%s searching key for addr: \"%s\"",
- SRCNAME, __func__, addr.c_str());
+ 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_data ("%s:%s found key for addr: \"%s\":%s",
- SRCNAME, __func__, addr.c_str(),
- k.primaryFingerprint());
+ log_debug ("%s:%s found key for addr: \"%s\":%s",
+ SRCNAME, __func__, anonstr (addr.c_str()),
+ anonstr (k.primaryFingerprint()));
KeyCache::instance ()->setPgpKey (addr, k);
}
if (opt.enable_smime)
{
auto ctx = std::unique_ptr<GpgME::Context> (
GpgME::Context::createForProtocol (GpgME::CMS));
if (!ctx)
{
TRACEPOINT;
return 0;
}
// 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;
return 0;
}
std::vector<GpgME::Key> keys;
GpgME::Error err;
do {
keys.push_back(ctx->nextKey(err));
} while (!err);
keys.pop_back();
GpgME::Key candidate;
for (const auto &key: keys)
{
if (key.isRevoked() || key.isExpired() ||
key.isDisabled() || key.isInvalid())
{
- log_data ("%s:%s: Skipping invalid S/MIME key",
- SRCNAME, __func__);
+ 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;
}
}
}
if (!candidate.isNull())
{
- log_data ("%s:%s found SMIME key for addr: \"%s\":%s",
- SRCNAME, __func__, addr.c_str(),
- candidate.primaryFingerprint());
+ 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);
}
}
log_debug ("%s:%s locator thread done",
SRCNAME, __func__);
return 0;
}
static void
locate_secret (const char *addr, GpgME::Protocol proto)
{
auto ctx = std::unique_ptr<GpgME::Context> (
GpgME::Context::createForProtocol (proto));
if (!ctx)
{
TRACEPOINT;
return;
}
if (!addr)
{
TRACEPOINT;
return;
}
const auto mbox = GpgME::UserID::addrSpecFromString (addr);
if (mbox.empty())
{
log_debug ("%s:%s: Empty mbox for addr %s",
- SRCNAME, __func__, addr);
+ SRCNAME, __func__, anonstr (addr));
return;
}
// 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;
return;
}
std::vector<GpgME::Key> 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_MIME_PARSER))
{
std::stringstream ss;
ss << key;
log_data ("%s:%s: Skipping invalid secret key %s",
- SRCNAME, __func__, ss.str().c_str());
+ SRCNAME, __func__, ss.str().c_str());
}
continue;
}
if (proto == GpgME::OpenPGP)
{
- log_data ("%s:%s found pgp skey for addr: \"%s\":%s",
- SRCNAME, __func__, mbox.c_str(),
- key.primaryFingerprint());
+ 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);
return;
}
if (proto == GpgME::CMS)
{
- log_data ("%s:%s found cms skey for addr: \"%s\":%s",
- SRCNAME, __func__, mbox.c_str (),
- key.primaryFingerprint());
+ 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);
return;
}
} while (!err);
return;
}
static DWORD WINAPI
do_locate_secret (LPVOID arg)
{
auto args = std::unique_ptr<LocateArgs> ((LocateArgs *) arg);
- log_data ("%s:%s searching secret key for addr: \"%s\"",
- SRCNAME, __func__, args->m_mbox.c_str ());
+ 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__);
return 0;
}
void
KeyCache::startLocate (const std::vector<std::string> &addrs, Mail *mail) const
{
for (const auto &addr: addrs)
{
startLocate (addr.c_str(), mail);
}
}
void
KeyCache::startLocate (const char *addr, Mail *mail) const
{
if (!addr)
{
TRACEPOINT;
return;
}
std::string recp = GpgME::UserID::addrSpecFromString (addr);
if (recp.empty ())
{
return;
}
gpgrt_lock_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<std::string, GpgME::Key> (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);
}
gpgrt_lock_unlock (&keycache_lock);
}
void
KeyCache::startLocateSecret (const char *addr, Mail *mail) const
{
if (!addr)
{
TRACEPOINT;
return;
}
std::string recp = GpgME::UserID::addrSpecFromString (addr);
if (recp.empty ())
{
return;
}
gpgrt_lock_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<std::string, GpgME::Key> (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);
}
gpgrt_lock_unlock (&keycache_lock);
}
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)
{
/* Get the data from the mail. */
const auto sender = mail->getSender ();
auto recps = mail->getCachedRecipients ();
if (sender.empty() || recps.empty())
{
log_debug ("%s:%s: Mail has no sender or no recipients.",
SRCNAME, __func__);
return false;
}
std::vector<GpgME::Key> encKeys = getEncryptionKeys (recps,
GpgME::OpenPGP);
if (!encKeys.empty())
{
return true;
}
if (!opt.enable_smime)
{
return false;
}
/* Check S/MIME instead here we need to include the sender
as we can't just generate a key. */
recps.push_back (sender);
GpgME::Key sigKey= getSigningKey (sender.c_str(), GpgME::CMS);
encKeys = getEncryptionKeys (recps, GpgME::CMS);
return !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) const
{
return d->importFromAddrBook (mbox, key_data, mail);
}
void
KeyCache::onAddrBookImportJobDone (const std::string &mbox,
const std::vector<std::string> &result_fprs)
{
return d->onAddrBookImportJobDone (mbox, result_fprs);
}
diff --git a/src/mail.cpp b/src/mail.cpp
index f9dfe0c..00c41fd 100644
--- a/src/mail.cpp
+++ b/src/mail.cpp
@@ -1,3564 +1,3566 @@
/* @file mail.h
* @brief High level class to work with Outlook Mailitems.
*
* Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.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 <gpgme++/configuration.h>
#include <gpgme++/tofuinfo.h>
#include <gpgme++/verificationresult.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/key.h>
#include <gpgme++/context.h>
#include <gpgme++/keylistresult.h>
#include <gpg-error.h>
#include <map>
#include <set>
#include <vector>
#include <memory>
#include <sstream>
#undef _
#define _(a) utf8_gettext (a)
using namespace GpgME;
static std::map<LPDISPATCH, Mail*> s_mail_map;
static std::map<std::string, Mail*> s_uid_map;
static std::map<std::string, LPDISPATCH> s_folder_events_map;
static std::set<std::string> uids_searched;
GPGRT_LOCK_DEFINE (mail_map_lock);
GPGRT_LOCK_DEFINE (uid_map_lock);
static Mail *s_last_mail;
#define COPYBUFSIZE (8 * 1024)
Mail::Mail (LPDISPATCH mailitem) :
m_mailitem(mailitem),
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(NoCryptMail),
m_window(nullptr),
m_async_crypt_disabled(false),
m_is_forwarded_crypto_mail(false),
m_is_reply_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_is_about_to_be_moved(false),
m_locate_in_progress(false)
{
if (getMailForItem (mailitem))
{
log_error ("Mail object for item: %p already exists. Bug.",
mailitem);
return;
}
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);
return;
}
gpgrt_lock_lock (&mail_map_lock);
s_mail_map.insert (std::pair<LPDISPATCH, Mail *> (mailitem, this));
gpgrt_lock_unlock (&mail_map_lock);
s_last_mail = this;
memdbg_ctor ("Mail");
}
GPGRT_LOCK_DEFINE(dtor_lock);
// static
void
Mail::lockDelete ()
{
gpgrt_lock_lock (&dtor_lock);
}
// static
void
Mail::unlockDelete ()
{
gpgrt_lock_unlock (&dtor_lock);
}
Mail::~Mail()
{
/* 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. */
gpgrt_lock_lock (&dtor_lock);
memdbg_dtor ("Mail");
log_oom ("%s:%s: dtor: Mail: %p item: %p",
SRCNAME, __func__, this, m_mailitem);
std::map<LPDISPATCH, Mail *>::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__);
gpgrt_lock_lock (&mail_map_lock);
it = s_mail_map.find(m_mailitem);
if (it != s_mail_map.end())
{
s_mail_map.erase (it);
}
gpgrt_lock_unlock (&mail_map_lock);
if (!m_uuid.empty())
{
gpgrt_lock_lock (&uid_map_lock);
auto it2 = s_uid_map.find(m_uuid);
if (it2 != s_uid_map.end())
{
s_uid_map.erase (it2);
}
gpgrt_lock_unlock (&uid_map_lock);
}
log_oom ("%s:%s: releasing mailitem",
SRCNAME, __func__);
gpgol_release(m_mailitem);
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();
gpgrt_lock_unlock (&dtor_lock);
log_oom ("%s:%s: returning",
SRCNAME, __func__);
}
//static
Mail *
Mail::getMailForItem (LPDISPATCH mailitem)
{
if (!mailitem)
{
return NULL;
}
std::map<LPDISPATCH, Mail *>::iterator it;
gpgrt_lock_lock (&mail_map_lock);
it = s_mail_map.find(mailitem);
gpgrt_lock_unlock (&mail_map_lock);
if (it == s_mail_map.end())
{
return NULL;
}
return it->second;
}
//static
Mail *
Mail::getMailForUUID (const char *uuid)
{
if (!uuid)
{
return NULL;
}
gpgrt_lock_lock (&uid_map_lock);
auto it = s_uid_map.find(std::string(uuid));
gpgrt_lock_unlock (&uid_map_lock);
if (it == s_uid_map.end())
{
return NULL;
}
return it->second;
}
//static
bool
Mail::isValidPtr (const Mail *mail)
{
gpgrt_lock_lock (&mail_map_lock);
auto it = s_mail_map.begin();
while (it != s_mail_map.end())
{
if (it->second == mail)
{
gpgrt_lock_unlock (&mail_map_lock);
return true;
}
++it;
}
gpgrt_lock_unlock (&mail_map_lock);
return false;
}
int
Mail::preProcessMessage_m ()
{
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
return 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)
{
gpgol_release (message);
return 0;
}
/* Create moss attachments here so that they are properly
hidden when the item is read into the model. */
m_moss_position = mapi_mark_or_create_moss_attach (message, m_type);
if (!m_moss_position)
{
log_error ("%s:%s: Failed to find moss attachment.",
SRCNAME, __func__);
m_type = MSGTYPE_UNKNOWN;
}
gpgol_release (message);
return 0;
}
static LPDISPATCH
get_attachment_o (LPDISPATCH mailitem, int pos)
{
LPDISPATCH attachment;
LPDISPATCH attachments = get_oom_object (mailitem, "Attachments");
if (!attachments)
{
log_debug ("%s:%s: Failed to get attachments.",
SRCNAME, __func__);
return 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);
return 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);
return attachment;
}
/** Helper to check that all attachments are hidden, to be
called before crypto. */
int
Mail::checkAttachments_o () const
{
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
log_debug ("%s:%s: Failed to get attachments.",
SRCNAME, __func__);
return 1;
}
int count = get_oom_int (attachments, "Count");
if (!count)
{
gpgol_release (attachments);
return 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);
return 0;
}
bool foundOne = false;
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";
}
VariantClear (&var);
gpgol_release (oom_attach);
}
gpgol_release (attachments);
if (foundOne)
{
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);
}
return 0;
}
/** Get the cipherstream of the mailitem. */
static LPSTREAM
get_attachment_stream_o (LPDISPATCH mailitem, int pos)
{
if (!pos)
{
log_debug ("%s:%s: Called with zero pos.",
SRCNAME, __func__);
return 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__);
return 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);
return NULL;
}
return 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);
return 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);
}
gpgol_release (mapi_attachment);
return 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<Attachment> attach)
{
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;
return 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<unsigned long> (size);
rgsabound[0].lLbound = 0;
/* Create an OLE SAFEARRAY */
var.parray = SafeArrayCreate (VT_UI1, 1, rgsabound);
if (var.parray == NULL)
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
void *buffer = NULL;
/* Get a safe pointer to the array */
if (SafeArrayAccessData(var.parray, &buffer) != S_OK)
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
/* Copy data to it */
size_t nread = attach->get_data ().read (buffer, static_cast<size_t> (size));
if (nread != static_cast<size_t> (size))
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
/*/ Unlock the variant data */
if (SafeArrayUnaccessData(var.parray) != S_OK)
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
if (set_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var))
{
TRACEPOINT;
VariantClear(&var);
return 1;
}
VariantClear(&var);
return 0;
}
#endif
static int
copy_attachment_to_file (std::shared_ptr<Attachment> att, HANDLE hFile)
{
char copybuf[COPYBUFSIZE];
size_t nread;
/* Security considerations: Writing the data to a temporary
file is necessary as neither MAPI manipulation works in the
read event to transmit the data nor Property Accessor
works (see above). From a security standpoint there is a
short time where the temporary files are on disk. Tempdir
should be protected so that only the user can read it. Thus
we have a local attack that could also take the data out
of Outlook. FILE_SHARE_READ is necessary so that outlook
can read the file.
A bigger concern is that the file is manipulated
by another software to fake the signature state. So
we keep the write exlusive to us.
We delete the file before closing the write file handle.
*/
/* Make sure we start at the beginning */
att->get_data ().seek (0, SEEK_SET);
while ((nread = att->get_data ().read (copybuf, COPYBUFSIZE)))
{
DWORD nwritten;
if (!WriteFile (hFile, copybuf, nread, &nwritten, NULL))
{
log_error ("%s:%s: Failed to write in tmp attachment.",
SRCNAME, __func__);
return 1;
}
if (nread != nwritten)
{
log_error ("%s:%s: Write truncated.",
SRCNAME, __func__);
return 1;
}
}
return 0;
}
/** 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> attachment)
{
/* Currently we only set content id */
if (attachment->get_content_id ().empty())
{
log_debug ("%s:%s: Content id not found.",
SRCNAME, __func__);
return 0;
}
LPDISPATCH attach = get_attachment_o (mail, -1);
if (!attach)
{
log_error ("%s:%s: No attachment.",
SRCNAME, __func__);
return 1;
}
int ret = put_pa_string (attach,
PR_ATTACH_CONTENT_ID_DASL,
attachment->get_content_id ().c_str());
gpgol_release (attach);
return ret;
}
/** Helper to update the attachments of a mail object in oom.
does not modify the underlying mapi structure. */
static int
add_attachments_o(LPDISPATCH mail,
std::vector<std::shared_ptr<Attachment> > attachments)
{
bool anyError = false;
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__, dispName.c_str());
+ 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__, dispName.c_str());
+ SRCNAME, __func__, anonstr (dispName.c_str()));
err = 1;
}
if (!err && copy_attachment_to_file (att, hFile))
{
log_error ("%s:%s: Failed to copy attachment %s to temp file",
- SRCNAME, __func__, dispName.c_str());
+ SRCNAME, __func__, anonstr (dispName.c_str()));
err = 1;
}
if (!err && add_oom_attachment (mail, wchar_file, wchar_name))
{
log_error ("%s:%s: Failed to add attachment: %s",
- SRCNAME, __func__, dispName.c_str());
+ SRCNAME, __func__, anonstr (dispName.c_str()));
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__, dispName.c_str());
+ SRCNAME, __func__, anonstr (dispName.c_str()));
err = 1;
}
xfree (wchar_file);
xfree (wchar_name);
if (!err)
{
err = fixup_last_attachment_o (mail, att);
}
if (err)
{
anyError = true;
}
}
return anyError;
}
GPGRT_LOCK_DEFINE(parser_lock);
static DWORD WINAPI
do_parsing (LPVOID arg)
{
gpgrt_lock_lock (&dtor_lock);
/* We lock with mail dtors so we can be sure the mail->parser
call is valid. */
Mail *mail = (Mail *)arg;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: canceling parsing for: %p already deleted",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&dtor_lock);
return 0;
}
blockInv ();
/* This takes a shared ptr of parser. So the parser is
still valid when the mail is deleted. */
auto parser = mail->parser ();
gpgrt_lock_unlock (&dtor_lock);
gpgrt_lock_lock (&parser_lock);
/* 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);
gpgrt_lock_unlock (&parser_lock);
unblockInv();
return 0;
}
if (!parser)
{
log_error ("%s:%s: no parser found for mail: %p",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&parser_lock);
unblockInv();
return -1;
}
parser->parse();
do_in_ui_thread (PARSING_DONE, arg);
gpgrt_lock_unlock (&parser_lock);
unblockInv();
return 0;
}
/* How encryption is done:
There are two modes of encryption. Synchronous and Async.
If async is used depends on the value of mail->async_crypt_disabled.
Synchronous crypto:
> Send Event < | State NoCryptMail
Needs Crypto ? (get_gpgol_draft_info_flags != 0)
-> No:
Pass send -> unencrypted mail.
-> Yes:
mail->update_oom_data
State = Mail::NeedsFirstAfterWrite
check_inline_response
invoke_oom_method (m_object, "Save", NULL);
> Write Event <
Pass because is_crypto_mail is false (not a decrypted mail)
> AfterWrite Event < | State NeedsFirstAfterWrite
State = NeedsActualCrypo
encrypt_sign_start
collect_input_data
-> Check if Inline PGP should be used
do_crypt
-> Resolve keys / do crypto
State = NeedsUpdateInMAPI
update_crypt_mapi
crypter->update_mail_mapi
if (inline) (Meaning PGP/Inline)
<-- do nothing.
else
build MSOXSMIME attachment and clear body / attachments.
State = NeedsUpdateInOOM
<- Back to Send Event
update_crypt_oom
-> Cleans body or sets PGP/Inline body. (inline_body_to_body)
State = WantsSendMIME or WantsSendInline
-> Saftey check "has_crypted_or_empty_body"
-> If MIME Mail do the T3656 check.
Send.
State order for "inline_response" (sync) Mails.
NoCryptMail
NeedsFirstAfterWrite
NeedsActualCrypto
NeedsUpdateInMAPI
NeedsUpdateInOOM
WantsSendMIME (or inline for PGP Inline)
-> Send.
State order for async Mails
NoCryptMail
NeedsFirstAfterWrite
NeedsActualCrypto
-> Cancel Send.
Windowmessages -> Crypto Done
NeedsUpdateInOOM
NeedsSecondAfterWrite
trigger Save.
NeedsUpdateInMAPI
WantsSendMIME
trigger Send.
*/
static DWORD WINAPI
do_crypt (LPVOID arg)
{
gpgrt_lock_lock (&dtor_lock);
/* We lock with mail dtors so we can be sure the mail->parser
call is valid. */
Mail *mail = (Mail *)arg;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: canceling crypt for: %p already deleted",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&dtor_lock);
return 0;
}
if (mail->cryptState () != Mail::NeedsActualCrypt)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, mail->cryptState ());
mail->setWindowEnabled_o (true);
gpgrt_lock_unlock (&dtor_lock);
return -1;
}
/* This takes a shared ptr of crypter. So the crypter is
still valid when the mail is deleted. */
auto crypter = mail->cryper ();
gpgrt_lock_unlock (&dtor_lock);
if (!crypter)
{
log_error ("%s:%s: no crypter found for mail: %p",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&parser_lock);
mail->setWindowEnabled_o (true);
return -1;
}
GpgME::Error err;
int rc = crypter->do_crypto(err);
gpgrt_lock_lock (&dtor_lock);
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: aborting crypt for: %p already deleted",
SRCNAME, __func__, arg);
gpgrt_lock_unlock (&dtor_lock);
return 0;
}
mail->setWindowEnabled_o (true);
if (rc == -1 || err)
{
mail->resetCrypter ();
crypter = nullptr;
if (err)
{
char *buf = nullptr;
gpgrt_asprintf (&buf, _("Crypto operation failed:\n%s"),
err.asString());
memdbg_alloc (buf);
gpgol_message_box (mail->getWindow (), buf, _("GpgOL"), MB_OK);
xfree (buf);
}
else
{
gpgol_bug (mail->getWindow (),
ERR_CRYPT_RESOLVER_FAILED);
}
}
if (rc || err.isCanceled())
{
log_debug ("%s:%s: crypto failed for: %p with: %i err: %i",
SRCNAME, __func__, arg, rc, err.code());
mail->setCryptState (Mail::NoCryptMail);
mail->resetCrypter ();
crypter = nullptr;
gpgrt_lock_unlock (&dtor_lock);
return rc;
}
if (!mail->isAsyncCryptDisabled ())
{
mail->setCryptState (Mail::NeedsUpdateInOOM);
gpgrt_lock_unlock (&dtor_lock);
// This deletes the Mail in Outlook 2010
do_in_ui_thread (CRYPTO_DONE, arg);
log_debug ("%s:%s: UI thread finished for %p",
SRCNAME, __func__, arg);
}
else
{
mail->setCryptState (Mail::NeedsUpdateInMAPI);
mail->updateCryptMAPI_m ();
if (mail->cryptState () == Mail::WantsSendMIME)
{
// For sync crypto we need to switch this.
mail->setCryptState (Mail::NeedsUpdateInOOM);
}
else
{
// A bug!
log_debug ("%s:%s: Resetting crypter because of state mismatch. %p",
SRCNAME, __func__, arg);
crypter = nullptr;
mail->resetCrypter ();
}
gpgrt_lock_unlock (&dtor_lock);
}
/* This works around a bug in pinentry that it might
bring the wrong window to front. So after encryption /
signing we bring outlook back to front.
See GnuPG-Bug-Id: T3732
*/
do_in_ui_thread_async (BRING_TO_FRONT, nullptr, 250);
log_debug ("%s:%s: crypto thread for %p finished",
SRCNAME, __func__, arg);
return 0;
}
bool
Mail::isCryptoMail () const
{
if (m_type == MSGTYPE_UNKNOWN || m_type == MSGTYPE_GPGOL ||
m_type == MSGTYPE_SMIME)
{
/* Not a message for us. */
return false;
}
return true;
}
int
Mail::decryptVerify_o ()
{
if (!isCryptoMail ())
{
log_debug ("%s:%s: Decrypt Verify for non crypto mail: %p.",
SRCNAME, __func__, m_mailitem);
return 0;
}
if (m_needs_wipe)
{
log_error ("%s:%s: Decrypt verify called for msg that needs wipe: %p",
SRCNAME, __func__, m_mailitem);
return 1;
}
setUUID_o ();
m_processed = true;
/* Insert placeholder */
char *placeholder_buf = nullptr;
if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION)
{
gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html :
decrypt_template,
"OpenPGP",
_("Pubkey directory confirmation"),
_("This is a confirmation request to publish your Pubkey in the "
"directory for your domain.\n\n"
"<p>If you did not request to publish your Pubkey in your providers "
"directory, simply ignore this message.</p>\n"));
}
else if (gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html :
decrypt_template,
isSMIME_m () ? "S/MIME" : "OpenPGP",
_("message"),
_("Please wait while the message is being decrypted / verified...")) == -1)
{
log_error ("%s:%s: Failed to format placeholder.",
SRCNAME, __func__);
return 1;
}
if (opt.prefer_html)
{
char *tmp = get_oom_string (m_mailitem, "HTMLBody");
if (!tmp)
{
TRACEPOINT;
return 1;
}
m_orig_body = tmp;
xfree (tmp);
if (put_oom_string (m_mailitem, "HTMLBody", placeholder_buf))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
else
{
char *tmp = get_oom_string (m_mailitem, "Body");
if (!tmp)
{
TRACEPOINT;
return 1;
}
m_orig_body = tmp;
xfree (tmp);
if (put_oom_string (m_mailitem, "Body", placeholder_buf))
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
}
memdbg_alloc (placeholder_buf);
xfree (placeholder_buf);
/* Do the actual parsing */
auto cipherstream = get_attachment_stream_o (m_mailitem, m_moss_position);
if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION)
{
WKSHelper::instance ()->handle_confirmation_read (this, cipherstream);
return 0;
}
if (!cipherstream)
{
log_debug ("%s:%s: Failed to get cipherstream.",
SRCNAME, __func__);
return 1;
}
m_parser = std::shared_ptr <ParseController> (new ParseController (cipherstream, m_type));
m_parser->setSender(GpgME::UserID::addrSpecFromString(getSender_o ().c_str()));
log_data ("%s:%s: Parser for \"%s\" is %p",
SRCNAME, __func__, getSubject_o ().c_str(), m_parser.get());
gpgol_release (cipherstream);
HANDLE parser_thread = CreateThread (NULL, 0, do_parsing, (LPVOID) this, 0,
NULL);
if (!parser_thread)
{
log_error ("%s:%s: Failed to create decrypt / verify thread.",
SRCNAME, __func__);
}
CloseHandle (parser_thread);
return 0;
}
void find_and_replace(std::string& source, const std::string &find,
const std::string &replace)
{
for(std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos;)
{
source.replace(i, find.length(), replace);
i += replace.length();
}
}
void
Mail::updateBody_o ()
{
if (!m_parser)
{
TRACEPOINT;
return;
}
const auto error = m_parser->get_formatted_error ();
if (!error.empty())
{
if (opt.prefer_html)
{
if (put_oom_string (m_mailitem, "HTMLBody",
error.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
else
{
log_debug ("%s:%s: Set error html to: '%s'",
SRCNAME, __func__, error.c_str ());
}
}
else
{
if (put_oom_string (m_mailitem, "Body",
error.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
else
{
log_debug ("%s:%s: Set error plain to: '%s'",
SRCNAME, __func__, error.c_str ());
}
}
return;
}
if (m_verify_result.error())
{
log_error ("%s:%s: Verification failed. Restoring Body.",
SRCNAME, __func__);
if (opt.prefer_html)
{
if (put_oom_string (m_mailitem, "HTMLBody", m_orig_body.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
else
{
if (put_oom_string (m_mailitem, "Body", m_orig_body.c_str ()))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
return;
}
// No need to carry body anymore
m_orig_body = std::string();
auto html = m_parser->get_html_body ();
auto body = m_parser->get_body ();
/** Outlook does not show newlines if \r\r\n is a newline. We replace
these as apparently some other buggy MUA sends this. */
find_and_replace (html, "\r\r\n", "\r\n");
if (opt.prefer_html && !html.empty())
{
if (!m_block_html)
{
const auto charset = m_parser->get_html_charset();
int codepage = 0;
if (charset.empty())
{
codepage = get_oom_int (m_mailitem, "InternetCodepage");
log_debug ("%s:%s: Did not find html charset."
" Using internet Codepage %i.",
SRCNAME, __func__, codepage);
}
char *converted = ansi_charset_to_utf8 (charset.c_str(), html.c_str(),
html.size(), codepage);
TRACEPOINT;
int ret = put_oom_string (m_mailitem, "HTMLBody", converted ?
converted : "");
TRACEPOINT;
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
return;
}
else if (!body.empty())
{
/* We had a multipart/alternative mail but html should be
blocked. So we prefer the text/plain part and warn
once about this so that we hopefully don't get too
many bugreports about this. */
if (!opt.smime_html_warn_shown)
{
std::string caption = _("GpgOL") + std::string (": ") +
std::string (_("HTML display disabled."));
std::string buf = _("HTML content in unsigned S/MIME mails "
"is insecure.");
buf += "\n";
buf += _("GpgOL will only show such mails as text.");
buf += "\n\n";
buf += _("This message is shown only once.");
gpgol_message_box (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
{
return;
}
}
}
#endif
body = html;
std::string caption = _("GpgOL") + std::string (": ") +
std::string (_("HTML display disabled."));
std::string buf = _("HTML content in unsigned S/MIME mails "
"is insecure.");
buf += "\n";
buf += _("GpgOL will only show such mails as text.");
buf += "\n\n";
buf += _("Please ask the sender to sign the message or\n"
"to send it with a plain text alternative.");
gpgol_message_box (getWindow (), buf.c_str(), caption.c_str(),
MB_OK);
}
find_and_replace (body, "\r\r\n", "\r\n");
const auto plain_charset = m_parser->get_body_charset();
int codepage = 0;
if (plain_charset.empty())
{
codepage = get_oom_int (m_mailitem, "InternetCodepage");
log_debug ("%s:%s: Did not find body charset. "
"Using internet Codepage %i.",
SRCNAME, __func__, codepage);
}
char *converted = ansi_charset_to_utf8 (plain_charset.c_str(),
body.c_str(), body.size(),
codepage);
TRACEPOINT;
int ret = put_oom_string (m_mailitem, "Body", converted ? converted : "");
TRACEPOINT;
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
return;
}
static int parsed_count;
void
Mail::parsing_done()
{
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);
return;
}
/* Store the results. */
m_decrypt_result = m_parser->decrypt_result ();
m_verify_result = m_parser->verify_result ();
m_crypto_flags = 0;
if (!m_decrypt_result.isNull())
{
m_crypto_flags |= 1;
}
if (m_verify_result.numSignatures())
{
m_crypto_flags |= 2;
}
TRACEPOINT;
updateSigstate ();
m_needs_wipe = !m_is_send_again;
TRACEPOINT;
/* Set categories according to the result. */
updateCategories_o ();
TRACEPOINT;
m_block_html = m_parser->shouldBlockHtml ();
if (m_block_html)
{
// Just to be careful.
setBlockStatus_m ();
}
TRACEPOINT;
/* Update the body */
updateBody_o ();
TRACEPOINT;
/* Check that there are no unsigned / unencrypted messages. */
checkAttachments_o ();
/* Update attachments */
if (add_attachments_o (m_mailitem, m_parser->get_attachments()))
{
log_error ("%s:%s: Failed to update attachments.",
SRCNAME, __func__);
}
if (m_is_send_again)
{
log_debug ("%s:%s: I think that this is the send again of a crypto mail.",
SRCNAME, __func__);
/* We no longer want to be treated like a crypto mail. */
m_type = MSGTYPE_UNKNOWN;
LPMESSAGE msg = get_oom_base_message (m_mailitem);
if (!msg)
{
TRACEPOINT;
}
else
{
set_gpgol_draft_info_flags (msg, m_crypto_flags);
gpgol_release (msg);
}
removeOurAttachments_o ();
}
installFolderEventHandler_o ();
log_debug ("%s:%s: Delayed invalidate to update sigstate.",
SRCNAME, __func__);
CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0,
NULL));
TRACEPOINT;
return;
}
int
Mail::encryptSignStart_o ()
{
if (m_crypt_state != NeedsActualCrypt)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
return -1;
}
int flags = 0;
if (!needs_crypto_m ())
{
return 0;
}
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
return -1;
}
flags = get_gpgol_draft_info_flags (message);
gpgol_release (message);
const auto window = get_active_hwnd ();
if (m_is_gsuite)
{
auto att_table = mapi_create_attach_table (message, 0);
int n_att_usable = count_usable_attachments (att_table);
mapi_release_attach_table (att_table);
/* Check for attachments if we have some abort. */
if (n_att_usable)
{
wchar_t *w_title = utf8_to_wchar (_(
"GpgOL: Oops, G Suite Sync account detected"));
wchar_t *msg = utf8_to_wchar (
_("G Suite Sync breaks outgoing crypto mails "
"with attachments.\nUsing crypto and attachments "
"with G Suite Sync is not supported.\n\n"
"See: https://dev.gnupg.org/T3545 for details."));
MessageBoxW (window,
msg,
w_title,
MB_ICONINFORMATION|MB_OK);
xfree (msg);
xfree (w_title);
return -1;
}
}
m_do_inline = m_is_gsuite ? true : opt.inline_pgp;
GpgME::Protocol proto = opt.enable_smime ? GpgME::UnknownProtocol: GpgME::OpenPGP;
m_crypter = std::shared_ptr <CryptController> (new CryptController (this, flags & 1,
flags & 2,
proto));
// Careful from here on we have to check every
// error condition with window enabling again.
setWindowEnabled_o (false);
if (m_crypter->collect_data ())
{
log_error ("%s:%s: Crypter for mail %p failed to collect data.",
SRCNAME, __func__, this);
setWindowEnabled_o (true);
return -1;
}
if (!m_async_crypt_disabled)
{
CloseHandle(CreateThread (NULL, 0, do_crypt,
(LPVOID) this, 0,
NULL));
}
else
{
do_crypt (this);
}
return 0;
}
int
Mail::needs_crypto_m () const
{
LPMESSAGE message = get_oom_message (m_mailitem);
int ret;
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
return false;
}
ret = get_gpgol_draft_info_flags (message);
gpgol_release(message);
return ret;
}
int
Mail::wipe_o (bool force)
{
if (!m_needs_wipe && !force)
{
return 0;
}
log_debug ("%s:%s: Removing plaintext from mailitem: %p.",
SRCNAME, __func__, m_mailitem);
if (put_oom_string (m_mailitem, "HTMLBody",
""))
{
if (put_oom_string (m_mailitem, "Body",
""))
{
log_debug ("%s:%s: Failed to wipe mailitem: %p.",
SRCNAME, __func__, m_mailitem);
return -1;
}
return -1;
}
else
{
put_oom_string (m_mailitem, "Body", "");
}
m_needs_wipe = false;
return 0;
}
int
Mail::updateOOMData_o ()
{
char *buf = nullptr;
log_debug ("%s:%s", SRCNAME, __func__);
if (!isCryptoMail ())
{
/* Update the body format. */
m_is_html_alternative = get_oom_int (m_mailitem, "BodyFormat") > 1;
/* Store the body. It was not obvious for me (aheinecke) how
to access this through MAPI. */
if (m_is_html_alternative)
{
log_debug ("%s:%s: Is html alternative mail.", SRCNAME, __func__);
xfree (m_cached_html_body);
m_cached_html_body = get_oom_string (m_mailitem, "HTMLBody");
}
xfree (m_cached_plain_body);
m_cached_plain_body = get_oom_string (m_mailitem, "Body");
m_cached_recipients = getRecipients_o ();
}
/* For some reason outlook may store the recipient address
in the send using account field. If we have SMTP we prefer
the SenderEmailAddress string. */
if (isCryptoMail ())
{
/* This is the case where we are reading a mail and not composing.
When composing we need to use the SendUsingAccount because if
you send from the folder of userA but change the from to userB
outlook will keep the SenderEmailAddress of UserA. This is all
so horrible. */
buf = get_sender_SenderEMailAddress (m_mailitem);
if (!buf)
{
/* Try the sender Object */
buf = get_sender_Sender (m_mailitem);
}
/* We also want to cache sent representing email address so that
we can use it for verification information. */
char *buf2 = get_sender_SentRepresentingAddress (m_mailitem);
if (buf2)
{
m_sent_on_behalf = buf2;
xfree (buf2);
}
}
if (!buf)
{
buf = get_sender_SendUsingAccount (m_mailitem, &m_is_gsuite);
}
if (!buf && !isCryptoMail ())
{
/* Try the sender Object */
buf = get_sender_Sender (m_mailitem);
}
if (!buf)
{
/* We don't have s sender object or SendUsingAccount,
well, in that case fall back to the current user. */
buf = get_sender_CurrentUser (m_mailitem);
}
if (!buf)
{
log_debug ("%s:%s: All fallbacks failed.",
SRCNAME, __func__);
return -1;
}
m_sender = buf;
xfree (buf);
return 0;
}
std::string
Mail::getSender_o ()
{
if (m_sender.empty())
updateOOMData_o ();
return m_sender;
}
std::string
Mail::getSender () const
{
return m_sender;
}
int
Mail::closeAllMails_o ()
{
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<LPDISPATCH, Mail *>::iterator it;
TRACEPOINT;
gpgrt_lock_lock (&mail_map_lock);
std::map<LPDISPATCH, Mail *> mail_map_copy = s_mail_map;
gpgrt_lock_unlock (&mail_map_lock);
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
/* XXX For non racy code the is_valid_ptr check should not
be necessary but we crashed sometimes closing a destroyed
mail. */
if (!isValidPtr (it->second))
{
log_debug ("%s:%s: Already deleted mail for %p",
SRCNAME, __func__, it->first);
continue;
}
if (!it->second->isCryptoMail ())
{
continue;
}
if (closeInspector_o (it->second) || close (it->second))
{
log_error ("Failed to close mail: %p ", it->first);
/* Should not happen */
if (isValidPtr (it->second) && it->second->revert_o ())
{
err++;
}
}
}
return err;
}
int
Mail::revertAllMails_o ()
{
int err = 0;
std::map<LPDISPATCH, Mail *>::iterator it;
gpgrt_lock_lock (&mail_map_lock);
for (it = s_mail_map.begin(); it != s_mail_map.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;
}
}
gpgrt_lock_unlock (&mail_map_lock);
return err;
}
int
Mail::wipeAllMails_o ()
{
int err = 0;
std::map<LPDISPATCH, Mail *>::iterator it;
gpgrt_lock_lock (&mail_map_lock);
for (it = s_mail_map.begin(); it != s_mail_map.end(); ++it)
{
if (it->second->wipe_o ())
{
log_error ("Failed to wipe mail: %p ", it->first);
err++;
}
}
gpgrt_lock_unlock (&mail_map_lock);
return err;
}
int
Mail::revert_o ()
{
int err = 0;
if (!m_processed)
{
return 0;
}
m_disable_att_remove_warning = true;
err = gpgol_mailitem_revert (m_mailitem);
if (err == -1)
{
log_error ("%s:%s: Message revert failed falling back to wipe.",
SRCNAME, __func__);
return wipe_o ();
}
/* We need to reprocess the mail next time around. */
m_processed = false;
m_needs_wipe = false;
m_disable_att_remove_warning = false;
return 0;
}
bool
Mail::isSMIME_m ()
{
msgtype_t msgtype;
LPMESSAGE message;
if (m_is_smime_checked)
{
return m_is_smime;
}
message = get_oom_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: No message?",
SRCNAME, __func__);
return false;
}
msgtype = mapi_get_message_type (message);
m_is_smime = msgtype == MSGTYPE_GPGOL_OPAQUE_ENCRYPTED ||
msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED;
/* Check if it is an smime mail. Multipart signed can
also be true. */
if (!m_is_smime && msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED)
{
char *proto;
char *ct = mapi_get_message_content_type (message, &proto, NULL);
if (ct && proto)
{
m_is_smime = (!strcmp (proto, "application/pkcs7-signature") ||
!strcmp (proto, "application/x-pkcs7-signature"));
}
else
{
log_error ("%s:%s: No protocol in multipart / signed mail.",
SRCNAME, __func__);
}
xfree (proto);
xfree (ct);
}
gpgol_release (message);
m_is_smime_checked = true;
return m_is_smime;
}
static std::string
get_string_o (LPDISPATCH item, const char *str)
{
char *buf = get_oom_string (item, str);
if (!buf)
return std::string();
std::string ret = buf;
xfree (buf);
return ret;
}
std::string
Mail::getSubject_o () const
{
return get_string_o (m_mailitem, "Subject");
}
std::string
Mail::getBody_o () const
{
return get_string_o (m_mailitem, "Body");
}
std::vector<std::string>
Mail::getRecipients_o () const
{
LPDISPATCH recipients = get_oom_object (m_mailitem, "Recipients");
if (!recipients)
{
TRACEPOINT;
std::vector<std::string>();
}
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__);
}
return ret;
}
int
Mail::closeInspector_o (Mail *mail)
{
LPDISPATCH inspector = get_oom_object (mail->item(), "GetInspector");
HRESULT hr;
DISPID dispid;
if (!inspector)
{
log_debug ("%s:%s: No inspector.",
SRCNAME, __func__);
return -1;
}
dispid = lookup_oom_dispid (inspector, "Close");
if (dispid != DISPID_UNKNOWN)
{
VARIANT aVariant[1];
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = 1;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
hr = inspector->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Failed to close inspector: %#lx",
SRCNAME, __func__, hr);
gpgol_release (inspector);
return -1;
}
}
gpgol_release (inspector);
return 0;
}
/* static */
int
Mail::close (Mail *mail)
{
VARIANT aVariant[1];
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = 1;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
log_oom ("%s:%s: Invoking close for: %p",
SRCNAME, __func__, mail->item());
mail->setCloseTriggered (true);
int rc = invoke_oom_method_with_parms (mail->item(), "Close",
NULL, &dispparams);
log_oom ("%s:%s: Returned from close",
SRCNAME, __func__);
return rc;
}
void
Mail::setCloseTriggered (bool value)
{
m_close_triggered = value;
}
bool
Mail::getCloseTriggered () const
{
return m_close_triggered;
}
static const UserID
get_uid_for_sender (const Key &k, const char *sender)
{
UserID ret;
if (!sender)
{
return ret;
}
if (!k.numUserIDs())
{
log_debug ("%s:%s: Key without uids",
SRCNAME, __func__);
return ret;
}
for (const auto uid: k.userIDs())
{
if (!uid.email() || !*(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__, uid.email(), sender);
+ SRCNAME, __func__, anonstr (uid.email()),
+ anonstr (sender));
continue;
}
if (normalized_sender == normalized_uid)
{
ret = uid;
}
}
return ret;
}
void
Mail::updateSigstate ()
{
std::string sender = getSender ();
if (sender.empty())
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return;
}
if (m_verify_result.isNull())
{
log_debug ("%s:%s: No verify result.",
SRCNAME, __func__);
return;
}
if (m_verify_result.error())
{
log_debug ("%s:%s: verify error.",
SRCNAME, __func__);
return;
}
for (const auto sig: m_verify_result.signatures())
{
m_is_signed = true;
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__, m_sent_on_behalf.c_str(),
- sender.c_str ());
+ SRCNAME, __func__, anonstr (m_sent_on_behalf.c_str()),
+ anonstr (sender.c_str ()));
}
}
if ((sig.summary() & Signature::Summary::Valid) &&
m_uid.origin() == GpgME::Key::OriginWKD &&
(sig.validity() == Signature::Validity::Unknown ||
sig.validity() == Signature::Validity::Marginal))
{
// WKD is a shortcut to Level 2 trust.
log_debug ("%s:%s: Unknown or marginal from WKD -> Level 2",
SRCNAME, __func__);
}
else if (m_uid.isNull() || (sig.validity() != Signature::Validity::Marginal &&
sig.validity() != Signature::Validity::Full &&
sig.validity() != Signature::Validity::Ultimate))
{
/* For our category we only care about trusted sigs. And
the UID needs to match.*/
continue;
}
else if (sig.validity() == Signature::Validity::Marginal)
{
const auto tofu = m_uid.tofuInfo();
if (!tofu.isNull() &&
(tofu.validity() != TofuInfo::Validity::BasicHistory &&
tofu.validity() != TofuInfo::Validity::LargeHistory))
{
/* Marginal is only good enough without tofu.
Otherwise we wait for basic trust. */
log_debug ("%s:%s: Discarding marginal signature."
"With too little history.",
SRCNAME, __func__);
continue;
}
}
log_debug ("%s:%s: Classified sender as verified uid validity: %i origin: %i",
SRCNAME, __func__, m_uid.validity(), m_uid.origin());
m_sig = sig;
m_is_valid = true;
return;
}
log_debug ("%s:%s: No signature with enough trust. Using first",
SRCNAME, __func__);
m_sig = m_verify_result.signature(0);
return;
}
bool
Mail::isValidSig () const
{
return m_is_valid;
}
void
Mail::removeCategories_o ()
{
const char *decCategory = _("GpgOL: Encrypted Message");
const char *verifyCategory = _("GpgOL: Trusted Sender Address");
remove_category (m_mailitem, decCategory);
remove_category (m_mailitem, verifyCategory);
}
/* Now for some tasty hack: Outlook sometimes does
not show the new categories properly but instead
does some weird scrollbar thing. This can be
avoided by resizing the message a bit. But somehow
this only needs to be done once.
Weird isn't it? But as this workaround worked let's
do it programatically. Fun. Wan't some tomato sauce
with this hack? */
static void
resize_active_window ()
{
HWND wnd = get_active_hwnd ();
static std::vector<HWND> resized_windows;
if(std::find(resized_windows.begin(), resized_windows.end(), wnd) != resized_windows.end()) {
/* We only need to do this once per window. XXX But sometimes we also
need to do this once per view of the explorer. So for now this might
break but we reduce the flicker. A better solution would be to find
the current view and track that. */
return;
}
if (!wnd)
{
TRACEPOINT;
return;
}
RECT oldpos;
if (!GetWindowRect (wnd, &oldpos))
{
TRACEPOINT;
return;
}
if (!SetWindowPos (wnd, nullptr,
(int)oldpos.left,
(int)oldpos.top,
/* Anything smaller then 19 was ignored when the window was
* maximized on Windows 10 at least with a 1980*1024
* resolution. So I assume it's at least 1 percent.
* This is all hackish and ugly but should work for 90%...
* hopefully.
*/
(int)oldpos.right - oldpos.left - 20,
(int)oldpos.bottom - oldpos.top, 0))
{
TRACEPOINT;
return;
}
if (!SetWindowPos (wnd, nullptr,
(int)oldpos.left,
(int)oldpos.top,
(int)oldpos.right - oldpos.left,
(int)oldpos.bottom - oldpos.top, 0))
{
TRACEPOINT;
return;
}
resized_windows.push_back(wnd);
}
void
Mail::updateCategories_o ()
{
const char *decCategory = _("GpgOL: Encrypted Message");
const char *verifyCategory = _("GpgOL: Trusted Sender Address");
if (isValidSig ())
{
add_category (m_mailitem, verifyCategory);
}
else
{
remove_category (m_mailitem, verifyCategory);
}
if (!m_decrypt_result.isNull())
{
add_category (m_mailitem, decCategory);
}
else
{
/* As a small safeguard against fakes we remove our
categories */
remove_category (m_mailitem, decCategory);
}
resize_active_window();
return;
}
bool
Mail::isSigned () const
{
return m_verify_result.numSignatures() > 0;
}
bool
Mail::isEncrypted () const
{
return !m_decrypt_result.isNull();
}
int
Mail::setUUID_o ()
{
char *uuid;
if (!m_uuid.empty())
{
/* This codepath is reached by decrypt again after a
close with discard changes. The close discarded
the uuid on the OOM object so we have to set
it again. */
log_debug ("%s:%s: Resetting uuid for %p to %s",
SRCNAME, __func__, this,
m_uuid.c_str());
uuid = get_unique_id (m_mailitem, 1, m_uuid.c_str());
}
else
{
uuid = get_unique_id (m_mailitem, 1, nullptr);
log_debug ("%s:%s: uuid for %p set to %s",
SRCNAME, __func__, this, uuid);
}
if (!uuid)
{
log_debug ("%s:%s: Failed to get/set uuid for %p",
SRCNAME, __func__, m_mailitem);
return -1;
}
if (m_uuid.empty())
{
m_uuid = uuid;
Mail *other = 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;
}
gpgrt_lock_lock (&uid_map_lock);
s_uid_map.insert (std::pair<std::string, Mail *> (m_uuid, this));
gpgrt_lock_unlock (&uid_map_lock);
log_debug ("%s:%s: uuid for %p is now %s",
SRCNAME, __func__, this,
m_uuid.c_str());
}
xfree (uuid);
return 0;
}
/* Returns 2 if the userid is ultimately trusted.
Returns 1 if the userid is fully trusted but has
a signature by a key for which we have a secret
and which is ultimately trusted. (Direct trust)
0 otherwise */
static int
level_4_check (const UserID &uid)
{
if (uid.isNull())
{
return 0;
}
if (uid.validity () == UserID::Validity::Ultimate)
{
return 2;
}
if (uid.validity () == UserID::Validity::Full)
{
const auto ultimate_keys = ParseController::get_ultimate_keys ();
for (const auto sig: uid.signatures ())
{
const char *sigID = sig.signerKeyID ();
if (sig.isNull() || !sigID)
{
/* should not happen */
TRACEPOINT;
continue;
}
/* Direct trust information is not available
through gnupg so we cached the keys with ultimate
trust during parsing and now see if we find a direct
trust path.*/
for (const auto secKey: ultimate_keys)
{
/* Check that the Key id of the key matches */
const char *secKeyID = secKey.keyID ();
if (!secKeyID || strcmp (secKeyID, sigID))
{
continue;
}
/* Check that the userID of the signature is the ultimately
trusted one. */
const char *sig_uid_str = sig.signerUserID();
if (!sig_uid_str)
{
/* should not happen */
TRACEPOINT;
continue;
}
for (const auto signer_uid: secKey.userIDs ())
{
if (signer_uid.validity() != UserID::Validity::Ultimate)
{
TRACEPOINT;
continue;
}
const char *signer_uid_str = signer_uid.id ();
if (!sig_uid_str)
{
/* should not happen */
TRACEPOINT;
continue;
}
if (!strcmp(sig_uid_str, signer_uid_str))
{
/* We have a match */
log_debug ("%s:%s: classified %s as ultimate because "
"it was signed by uid %s of key %s",
- SRCNAME, __func__, signer_uid_str, sig_uid_str,
- secKeyID);
+ SRCNAME, __func__, anonstr (signer_uid_str),
+ anonstr (sig_uid_str),
+ anonstr (secKeyID));
return 1;
}
}
}
}
}
return 0;
}
std::string
Mail::getCryptoSummary () const
{
const int level = get_signature_level ();
bool enc = isEncrypted ();
if (level == 4 && enc)
{
return _("Security Level 4");
}
if (level == 4)
{
return _("Trust Level 4");
}
if (level == 3 && enc)
{
return _("Security Level 3");
}
if (level == 3)
{
return _("Trust Level 3");
}
if (level == 2 && enc)
{
return _("Security Level 2");
}
if (level == 2)
{
return _("Trust Level 2");
}
if (enc)
{
return _("Encrypted");
}
if (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. */
return _("Insecure");
}
return _("Insecure");
}
std::string
Mail::getCryptoOneLine () const
{
bool sig = isSigned ();
bool enc = isEncrypted ();
if (sig || enc)
{
if (sig && enc)
{
return _("Signed and encrypted message");
}
else if (sig)
{
return _("Signed message");
}
else if (enc)
{
return _("Encrypted message");
}
}
return _("Insecure message");
}
std::string
Mail::getCryptoDetails_o ()
{
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();
return message;
}
/* No crypo, what are we doing here? */
if (!isEncrypted () && !isSigned ())
{
return _("You cannot be sure who sent, "
"modified and read the message in transit.");
}
/* Handle encrypt only */
if (isEncrypted () && !isSigned ())
{
if (in_de_vs_mode ())
{
if (m_sig.isDeVs())
{
message += _("The encryption was VS-NfD-compliant.");
}
else
{
message += _("The encryption was not VS-NfD-compliant.");
}
}
message += "\n\n";
message += _("You cannot be sure who sent the message because "
"it is not signed.");
return 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__);
return message;
}
}
else if (level == 3 && isOpenPGP)
{
/* Level three is only reachable through web of trust and no
direct signature. */
message = _("The senders identity was certified by several trusted people.");
}
else if (level == 3 && !isOpenPGP)
{
/* Level three is the only level for trusted S/MIME keys. */
gpgrt_asprintf (&buf, _("The senders identity is certified by the trusted issuer:\n'%s'\n"),
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");
}
else if (m_sig.summary() & Signature::Summary::SysError ||
m_verify_result.numSignatures() < 1)
{
message += _("There was an error verifying the signature.\n");
const auto err = m_sig.status ();
if (err)
{
message += err.asString () + std::string ("\n");
}
}
else if (m_sig.summary() & Signature::Summary::SigExpired)
{
message += _("The signature is expired.\n");
}
else
{
message += isOpenPGP ? _("The used key") : _("The used certificate");
message += " ";
general_problem = false;
}
/* Now the key problems */
if ((m_sig.summary() & Signature::Summary::KeyMissing))
{
message += _("is not available.");
}
else if ((m_sig.summary() & Signature::Summary::KeyRevoked))
{
message += _("is revoked.");
}
else if ((m_sig.summary() & Signature::Summary::KeyExpired))
{
message += _("is expired.");
}
else if ((m_sig.summary() & Signature::Summary::BadPolicy))
{
message += _("is not meant for signing.");
}
else if ((m_sig.summary() & Signature::Summary::CrlMissing))
{
message += _("could not be checked for revocation.");
}
else if ((m_sig.summary() & Signature::Summary::CrlTooOld))
{
message += _("could not be checked for revocation.");
}
else if ((m_sig.summary() & Signature::Summary::TofuConflict) ||
m_uid.tofuInfo().validity() == TofuInfo::Conflict)
{
message += _("is not the same as the key that was used "
"for this address in the past.");
hasConflict = true;
}
else if (m_uid.isNull())
{
gpgrt_asprintf (&buf, _("does not claim the address: \"%s\"."),
getSender_o ().c_str());
memdbg_alloc (buf);
message += buf;
xfree (buf);
}
else if (((m_sig.validity() & Signature::Validity::Undefined) ||
(m_sig.validity() & Signature::Validity::Unknown) ||
(m_sig.summary() == Signature::Summary::None) ||
(m_sig.validity() == 0))&& !general_problem)
{
/* Bit of a catch all for weird results. */
if (isOpenPGP)
{
message += _("is not certified by any trustworthy key.");
}
else
{
message += _("is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown.");
}
}
else if (m_uid.isRevoked())
{
message += _("The sender marked this address as revoked.");
}
else if ((m_sig.validity() & Signature::Validity::Never))
{
message += _("is marked as not trustworthy.");
}
}
message += "\n\n";
if (in_de_vs_mode ())
{
if (isSigned ())
{
if (m_sig.isDeVs ())
{
message += _("The signature is VS-NfD-compliant.");
}
else
{
message += _("The signature is not VS-NfD-compliant.");
}
message += "\n";
}
if (isEncrypted ())
{
if (m_decrypt_result.isDeVs ())
{
message += _("The encryption is VS-NfD-compliant.");
}
else
{
message += _("The encryption is not VS-NfD-compliant.");
}
message += "\n\n";
}
else
{
message += "\n";
}
}
if (hasConflict)
{
message += _("Click here to change the key used for this address.");
}
else if (keyFound)
{
message += isOpenPGP ? _("Click here for details about the key.") :
_("Click here for details about the certificate.");
}
else
{
message += isOpenPGP ? _("Click here to search the key on the configured keyserver.") :
_("Click here to search the certificate on the configured X509 keyserver.");
}
return message;
}
int
Mail::get_signature_level () const
{
if (!m_is_signed)
{
return 0;
}
if (m_uid.isNull ())
{
/* No m_uid matches our sender. */
return 0;
}
if (m_is_valid && (m_uid.validity () == UserID::Validity::Ultimate ||
(m_uid.validity () == UserID::Validity::Full &&
level_4_check (m_uid))) && (!in_de_vs_mode () || m_sig.isDeVs()))
{
return 4;
}
if (m_is_valid && m_uid.validity () == UserID::Validity::Full &&
(!in_de_vs_mode () || m_sig.isDeVs()))
{
return 3;
}
if (m_is_valid)
{
return 2;
}
if (m_sig.validity() == Signature::Validity::Marginal)
{
return 1;
}
if (m_sig.summary() & Signature::Summary::TofuConflict ||
m_uid.tofuInfo().validity() == TofuInfo::Conflict)
{
return 0;
}
return 0;
}
int
Mail::getCryptoIconID () const
{
int level = get_signature_level ();
int offset = isEncrypted () ? ENCRYPT_ICON_OFFSET : 0;
return IDI_LEVEL_0 + level + offset;
}
const char*
Mail::getSigFpr () const
{
if (!m_is_signed || m_sig.isNull())
{
return nullptr;
}
return m_sig.fingerprint();
}
/** Try to locate the keys for all recipients */
void
Mail::locateKeys_o ()
{
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);
return;
}
m_locate_in_progress = true;
Addressbook::check_o (this);
if (opt.autoresolve)
{
// First update oom data to have recipients and sender updated.
updateOOMData_o ();
KeyCache::instance()->startLocateSecret (getSender_o ().c_str (), this);
KeyCache::instance()->startLocate (getSender_o ().c_str (), this);
KeyCache::instance()->startLocate (getCachedRecipients (), this);
}
autosecureCheck ();
m_locate_in_progress = false;
}
bool
Mail::isHTMLAlternative () const
{
return m_is_html_alternative;
}
char *
Mail::takeCachedHTMLBody ()
{
char *ret = m_cached_html_body;
m_cached_html_body = nullptr;
return ret;
}
char *
Mail::takeCachedPlainBody ()
{
char *ret = m_cached_plain_body;
m_cached_plain_body = nullptr;
return ret;
}
int
Mail::getCryptoFlags () const
{
return m_crypto_flags;
}
void
Mail::setNeedsEncrypt (bool value)
{
m_needs_encrypt = value;
}
bool
Mail::getNeedsEncrypt () const
{
return m_needs_encrypt;
}
std::vector<std::string>
Mail::getCachedRecipients ()
{
return m_cached_recipients;
}
void
Mail::appendToInlineBody (const std::string &data)
{
m_inline_body += data;
}
int
Mail::inlineBodyToBody_o ()
{
if (!m_crypter)
{
log_error ("%s:%s: No crypter.",
SRCNAME, __func__);
return -1;
}
const auto body = m_crypter->get_inline_data ();
if (body.empty())
{
return 0;
}
/* For inline we always work with UTF-8 */
put_oom_int (m_mailitem, "InternetCodepage", 65001);
int ret = put_oom_string (m_mailitem, "Body",
body.c_str ());
return ret;
}
void
Mail::updateCryptMAPI_m ()
{
log_debug ("%s:%s: Update crypt mapi",
SRCNAME, __func__);
if (m_crypt_state != NeedsUpdateInMAPI)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
return;
}
if (!m_crypter)
{
if (!m_mime_data.empty())
{
log_debug ("%s:%s: Have override mime data creating dummy crypter",
SRCNAME, __func__);
m_crypter = std::shared_ptr <CryptController> (new CryptController (this, false,
false,
GpgME::UnknownProtocol));
}
else
{
log_error ("%s:%s: No crypter.",
SRCNAME, __func__);
m_crypt_state = NoCryptMail;
return;
}
}
if (m_crypter->update_mail_mapi ())
{
log_error ("%s:%s: Failed to update MAPI after crypt",
SRCNAME, __func__);
m_crypt_state = NoCryptMail;
}
else
{
m_crypt_state = WantsSendMIME;
}
/** If sync we need the crypter in update_crypt_oom */
if (!isAsyncCryptDisabled ())
{
// We don't need the crypter anymore.
resetCrypter ();
}
}
/** 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<bool, bool>
has_crypt_or_empty_body_oom (Mail *mail)
{
auto body = mail->getBody_o ();
std::pair<bool, bool> ret;
ret.first = false;
ret.second = false;
ltrim (body);
if (body.size() > 10 && !strncmp (body.c_str(), "-----BEGIN", 10))
{
ret.first = true;
return ret;
}
if (!body.size())
{
ret.second = true;
}
else
{
log_data ("%s:%s: Body found in %p : \"%s\"",
SRCNAME, __func__, mail, body.c_str ());
}
return ret;
}
void
Mail::updateCryptOOM_o ()
{
log_debug ("%s:%s: Update crypt oom for %p",
SRCNAME, __func__, this);
if (m_crypt_state != NeedsUpdateInOOM)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
resetCrypter ();
return;
}
if (getDoPGPInline ())
{
if (inlineBodyToBody_o ())
{
log_error ("%s:%s: Inline body to body failed %p.",
SRCNAME, __func__, this);
gpgol_bug (get_active_hwnd(), ERR_INLINE_BODY_TO_BODY);
m_crypt_state = NoCryptMail;
return;
}
}
if (m_crypter->get_protocol () == GpgME::CMS && m_crypter->is_encrypter ())
{
/* We put the PIDNameContentType headers here for exchange
because this is the only way we found to inject the
smime-type. */
if (put_pa_string (m_mailitem,
PR_PIDNameContentType_DASL,
"application/pkcs7-mime;smime-type=\"enveloped-data\";name=smime.p7m"))
{
log_debug ("%s:%s: Failed to put PIDNameContentType for %p.",
SRCNAME, __func__, this);
}
}
/** When doing async update_crypt_mapi follows and needs
the crypter. */
if (isAsyncCryptDisabled ())
{
resetCrypter ();
}
const auto pair = has_crypt_or_empty_body_oom (this);
if (pair.first)
{
log_debug ("%s:%s: Looks like inline body. You can pass %p.",
SRCNAME, __func__, this);
m_crypt_state = WantsSendInline;
return;
}
// We are in MIME land. Wipe the body.
if (wipe_o (true))
{
log_debug ("%s:%s: Cancel send for %p.",
SRCNAME, __func__, this);
wchar_t *title = utf8_to_wchar (_("GpgOL: Encryption not possible!"));
wchar_t *msg = utf8_to_wchar (_(
"Outlook returned an error when trying to send the encrypted mail.\n\n"
"Please restart Outlook and try again.\n\n"
"If it still fails consider using an encrypted attachment or\n"
"switching to PGP/Inline in GpgOL's options."));
MessageBoxW (get_active_hwnd(), msg, title,
MB_ICONERROR | MB_OK);
xfree (msg);
xfree (title);
m_crypt_state = NoCryptMail;
return;
}
m_crypt_state = NeedsSecondAfterWrite;
return;
}
void
Mail::setWindowEnabled_o (bool value)
{
if (!value)
{
m_window = get_active_hwnd ();
}
log_debug ("%s:%s: enable window %p %i",
SRCNAME, __func__, m_window, value);
EnableWindow (m_window, value ? TRUE : FALSE);
}
bool
Mail::check_inline_response ()
{
/* Async sending is known to cause instabilities. So we keep
a hidden option to disable it. */
if (opt.sync_enc)
{
m_async_crypt_disabled = true;
return m_async_crypt_disabled;
}
m_async_crypt_disabled = false;
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (attachments)
{
/* This is horrible. But. For some kinds of attachments (we
got reports about Office attachments the write in the
send event triggered by our crypto done code fails with
an exception. There does not appear to be a detectable
pattern when this happens.
As we can't be sure and do not know for which attachments
this really happens we do not use async crypt for any
mails with attachments. :-/
Better be save (not crash) instead of nice (async).
TODO: Figure this out.
The log goes like this. We pass the send event. That triggers
a write, which we pass. And then that fails. So it looks like
moving to Outbox fails. Because we can save as much as we
like before that.
Using the IMessage::SubmitMessage MAPI interface works, but
as it is unstable in our current implementation we do not
want to use it.
mailitem-events.cpp:Invoke: Passing send event for mime-encrypted message 12B7C6E0.
application-events.cpp:Invoke: Unhandled Event: f002
mailitem-events.cpp:Invoke: Write : 0ED4D058
mailitem-events.cpp:Invoke: Passing write event.
oomhelp.cpp:invoke_oom_method_with_parms_type: Method 'Send' invokation failed: 0x80020009
oomhelp.cpp:dump_excepinfo: Exception:
wCode: 0x1000
wReserved: 0x0
source: Microsoft Outlook
desc: The operation failed. The messaging interfaces have returned an unknown error. If the problem persists, restart Outlook.
help: null
helpCtx: 0x0
deferredFill: 00000000
scode: 0x80040119
*/
int count = get_oom_int (attachments, "Count");
gpgol_release (attachments);
if (count)
{
m_async_crypt_disabled = true;
log_debug ("%s:%s: Detected attachments. "
"Disabling async crypt due to T4131.",
SRCNAME, __func__);
return m_async_crypt_disabled;
}
}
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return false;
}
LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer");
if (!explorer)
{
TRACEPOINT;
return false;
}
LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse");
gpgol_release (explorer);
if (!inlineResponse)
{
return false;
}
// We have inline response
// Check if we are it. It's a bit naive but meh. Worst case
// is that we think inline response too often and do sync
// crypt where we could do async crypt.
char * inlineSubject = get_oom_string (inlineResponse, "Subject");
gpgol_release (inlineResponse);
const auto subject = getSubject_o ();
if (inlineResponse && !subject.empty() && !strcmp (subject.c_str (), inlineSubject))
{
log_debug ("%s:%s: Detected inline response for '%p'",
SRCNAME, __func__, this);
m_async_crypt_disabled = true;
}
xfree (inlineSubject);
return m_async_crypt_disabled;
}
// static
Mail *
Mail::getLastMail ()
{
if (!s_last_mail || !isValidPtr (s_last_mail))
{
s_last_mail = nullptr;
}
return s_last_mail;
}
// static
void
Mail::clearLastMail ()
{
s_last_mail = nullptr;
}
// static
void
Mail::locateAllCryptoRecipients_o ()
{
gpgrt_lock_lock (&mail_map_lock);
std::map<LPDISPATCH, Mail *>::iterator it;
for (it = s_mail_map.begin(); it != s_mail_map.end(); ++it)
{
if (it->second->needs_crypto_m ())
{
it->second->locateKeys_o ();
}
}
gpgrt_lock_unlock (&mail_map_lock);
}
int
Mail::removeAllAttachments_o ()
{
int ret = 0;
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
TRACEPOINT;
return 0;
}
int count = get_oom_int (attachments, "Count");
LPDISPATCH to_delete[count];
/* Populate the array so that we don't get in an index mess */
for (int i = 1; i <= count; i++)
{
auto item_str = std::string("Item(") + std::to_string (i) + ")";
to_delete[i-1] = get_oom_object (attachments, item_str.c_str());
}
gpgol_release (attachments);
/* Now delete all attachments */
for (int i = 0; i < count; i++)
{
LPDISPATCH attachment = to_delete[i];
if (!attachment)
{
log_error ("%s:%s: No such attachment %i",
SRCNAME, __func__, i);
ret = -1;
}
/* Delete the attachments that are marked to delete */
if (invoke_oom_method (attachment, "Delete", NULL))
{
log_error ("%s:%s: Deleting attachment %i",
SRCNAME, __func__, i);
ret = -1;
}
gpgol_release (attachment);
}
return ret;
}
int
Mail::removeOurAttachments_o ()
{
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
TRACEPOINT;
return 0;
}
int count = get_oom_int (attachments, "Count");
LPDISPATCH to_delete[count];
int del_cnt = 0;
for (int i = 1; i <= count; i++)
{
auto item_str = std::string("Item(") + std::to_string (i) + ")";
LPDISPATCH attachment = get_oom_object (attachments, item_str.c_str());
if (!attachment)
{
TRACEPOINT;
continue;
}
attachtype_t att_type;
if (get_pa_int (attachment, GPGOL_ATTACHTYPE_DASL, (int*) &att_type))
{
/* Not our attachment. */
gpgol_release (attachment);
continue;
}
if (att_type == ATTACHTYPE_PGPBODY || att_type == ATTACHTYPE_MOSS ||
att_type == ATTACHTYPE_MOSSTEMPL)
{
/* One of ours to delete. */
to_delete[del_cnt++] = attachment;
/* Dont' release yet */
continue;
}
gpgol_release (attachment);
}
gpgol_release (attachments);
int ret = 0;
for (int i = 0; i < del_cnt; i++)
{
LPDISPATCH attachment = to_delete[i];
/* Delete the attachments that are marked to delete */
if (invoke_oom_method (attachment, "Delete", NULL))
{
log_error ("%s:%s: Error: deleting attachment %i",
SRCNAME, __func__, i);
ret = -1;
}
gpgol_release (attachment);
}
return ret;
}
/* We are very verbose because if we fail it might mean
that we have leaked plaintext -> critical. */
bool
Mail::hasCryptedOrEmptyBody_o ()
{
const auto pair = has_crypt_or_empty_body_oom (this);
if (pair.first /* encrypted marker */)
{
log_debug ("%s:%s: Crypt Marker detected in OOM body. Return true %p.",
SRCNAME, __func__, this);
return true;
}
if (!pair.second)
{
log_debug ("%s:%s: Unexpected content detected. Return false %p.",
SRCNAME, __func__, this);
return false;
}
// Pair second == true (is empty) can happen on OOM error.
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message && pair.second)
{
if (message)
{
gpgol_release (message);
}
return true;
}
size_t r_nbytes = 0;
char *mapi_body = mapi_get_body (message, &r_nbytes);
gpgol_release (message);
if (!mapi_body || !r_nbytes)
{
// Body or bytes are null. we are empty.
xfree (mapi_body);
log_debug ("%s:%s: MAPI error or empty message. Return true. %p.",
SRCNAME, __func__, this);
return true;
}
if (r_nbytes > 10 && !strncmp (mapi_body, "-----BEGIN", 10))
{
// Body is crypt.
log_debug ("%s:%s: MAPI Crypt marker detected. Return true. %p.",
SRCNAME, __func__, this);
xfree (mapi_body);
return true;
}
xfree (mapi_body);
log_debug ("%s:%s: Found mapi body. Return false. %p.",
SRCNAME, __func__, this);
return false;
}
std::string
Mail::getVerificationResultDump ()
{
std::stringstream ss;
ss << m_verify_result;
return ss.str();
}
void
Mail::setBlockStatus_m ()
{
SPropValue prop;
LPMESSAGE message = get_oom_base_message (m_mailitem);
prop.ulPropTag = PR_BLOCK_STATUS;
prop.Value.l = 1;
HRESULT hr = message->SetProps (1, &prop, NULL);
if (hr)
{
log_error ("%s:%s: can't set block value: hr=%#lx\n",
SRCNAME, __func__, hr);
}
gpgol_release (message);
return;
}
void
Mail::setBlockHTML (bool value)
{
m_block_html = value;
}
void
Mail::incrementLocateCount ()
{
m_locate_count++;
}
void
Mail::decrementLocateCount ()
{
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 ();
}
}
void
Mail::autosecureCheck ()
{
if (!opt.autosecure || !opt.autoresolve || m_manual_crypto_opts ||
m_locate_count)
{
return;
}
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 (ret ? DO_AUTO_SECURE : DONT_AUTO_SECURE,
this);
return;
}
void
Mail::setDoAutosecure_m (bool value)
{
TRACEPOINT;
LPMESSAGE msg = get_oom_base_message (m_mailitem);
if (!msg)
{
TRACEPOINT;
return;
}
/* 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);
return;
}
m_first_autosecure_check = false;
set_gpgol_draft_info_flags (msg, value ? 3 : opt.sign_default ? 2 : 0);
gpgol_release (msg);
gpgoladdin_invalidate_ui();
}
void
Mail::installFolderEventHandler_o()
{
TRACEPOINT;
LPDISPATCH folder = get_oom_object (m_mailitem, "Parent");
if (!folder)
{
TRACEPOINT;
return;
}
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);
return;
}
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);
return;
}
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__, strPath.c_str());
+ 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);
}
void
Mail::refCurrentItem()
{
if (m_currentItemRef)
{
log_debug ("%s:%s: Current item multi ref. Bug?",
SRCNAME, __func__);
return;
}
/* 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");
}
void
Mail::releaseCurrentItem()
{
if (!m_currentItemRef)
{
return;
}
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);
}
diff --git a/src/mailitem-events.cpp b/src/mailitem-events.cpp
index 86af6a1..af4f6b3 100644
--- a/src/mailitem-events.cpp
+++ b/src/mailitem-events.cpp
@@ -1,925 +1,925 @@
/* mailitem-events.h - Event handling for mails.
* Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "common.h"
#include "eventsink.h"
#include "eventsinks.h"
#include "mymapi.h"
#include "mymapitags.h"
#include "oomhelp.h"
#include "ocidl.h"
#include "windowmessages.h"
#include "mail.h"
#include "mapihelp.h"
#include "gpgoladdin.h"
#include "wks-helper.h"
#undef _
#define _(a) utf8_gettext (a)
const wchar_t *prop_blacklist[] = {
L"Body",
L"HTMLBody",
L"To", /* Somehow this is done when a mail is opened */
L"CC", /* Ditto */
L"BCC", /* Ditto */
L"Categories",
L"UnRead",
L"OutlookVersion",
L"OutlookInternalVersion",
L"ReceivedTime",
L"InternetCodepage",
NULL };
typedef enum
{
AfterWrite = 0xFC8D,
AttachmentAdd = 0xF00B,
AttachmentRead = 0xF00C,
AttachmentRemove = 0xFBAE,
BeforeAttachmentAdd = 0xFBB0,
BeforeAttachmentPreview = 0xFBAF,
BeforeAttachmentRead = 0xFBAB,
BeforeAttachmentSave = 0xF00D,
BeforeAttachmentWriteToTempFile = 0xFBB2,
BeforeAutoSave = 0xFC02,
BeforeCheckNames = 0xF00A,
BeforeDelete = 0xFA75,
BeforeRead = 0xFC8C,
Close = 0xF004,
CustomAction = 0xF006,
CustomPropertyChange = 0xF008,
Forward = 0xF468,
Open = 0xF003,
PropertyChange = 0xF009,
Read = 0xF001,
ReadComplete = 0xFC8F,
Reply = 0xF466,
ReplyAll = 0xF467,
Send = 0xF005,
Unload = 0xFBAD,
Write = 0xF002
} MailEvent;
/* Mail Item Events */
BEGIN_EVENT_SINK(MailItemEvents, IDispatch)
/* We are still in the class declaration */
private:
Mail * m_mail; /* The mail object related to this mailitem */
};
MailItemEvents::MailItemEvents() :
m_object(NULL),
m_pCP(NULL),
m_cookie(0),
m_ref(1),
m_mail(NULL)
{
}
MailItemEvents::~MailItemEvents()
{
if (m_pCP)
m_pCP->Unadvise(m_cookie);
if (m_object)
gpgol_release (m_object);
}
static bool propchangeWarnShown = false;
static bool attachRemoveWarnShown = false;
static DWORD WINAPI
do_delayed_locate (LPVOID arg)
{
Sleep(100);
do_in_ui_thread (RECIPIENT_ADDED, arg);
return 0;
}
/* The main Invoke function. The return value of this
function does not appear to have any effect on outlook
although I have read in an example somewhere that you
should return S_OK so that outlook continues to handle
the event I have not yet seen any effect by returning
error values here and no MSDN documentation about the
return values.
*/
EVENT_SINK_INVOKE(MailItemEvents)
{
USE_INVOKE_ARGS
if (!m_mail)
{
m_mail = Mail::getMailForItem (m_object);
if (!m_mail)
{
log_error ("%s:%s: mail event without mail object known. Bug.",
SRCNAME, __func__);
return S_OK;
}
}
bool is_reply = false;
switch(dispid)
{
case Open:
{
log_oom ("%s:%s: Open : %p",
SRCNAME, __func__, m_mail);
int draft_flags = 0;
if (!opt.encrypt_default && !opt.sign_default)
{
return S_OK;
}
LPMESSAGE message = get_oom_base_message (m_object);
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
break;
}
if (opt.encrypt_default)
{
draft_flags = 1;
}
if (opt.sign_default)
{
draft_flags += 2;
}
set_gpgol_draft_info_flags (message, draft_flags);
gpgol_release (message);
break;
}
case BeforeRead:
{
log_oom ("%s:%s: BeforeRead : %p",
SRCNAME, __func__, m_mail);
if (m_mail->preProcessMessage_m ())
{
log_error ("%s:%s: Pre process message failed.",
SRCNAME, __func__);
}
break;
}
case Read:
{
log_oom ("%s:%s: Read : %p",
SRCNAME, __func__, m_mail);
if (!m_mail->isCryptoMail ())
{
log_debug ("%s:%s: Non crypto mail %p opened. Updating sigstatus.",
SRCNAME, __func__, m_mail);
/* Ensure that no wrong sigstatus is shown */
CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0,
NULL));
break;
}
if (m_mail->setUUID_o ())
{
log_debug ("%s:%s: Failed to set uuid.",
SRCNAME, __func__);
delete m_mail; /* deletes this, too */
return S_OK;
}
if (m_mail->decryptVerify_o ())
{
log_error ("%s:%s: Decrypt message failed.",
SRCNAME, __func__);
}
if (!opt.enable_smime && m_mail->isSMIME_m ())
{
/* We want to save the mail when it's an smime mail and smime
is disabled to revert it. */
log_debug ("%s:%s: S/MIME mail but S/MIME is disabled."
" Need save.",
SRCNAME, __func__);
m_mail->setNeedsSave (true);
}
break;
}
case PropertyChange:
{
if (!parms || parms->cArgs != 1 ||
parms->rgvarg[0].vt != VT_BSTR ||
!parms->rgvarg[0].bstrVal)
{
log_error ("%s:%s: Unexpected params.",
SRCNAME, __func__);
break;
}
const wchar_t *prop_name = parms->rgvarg[0].bstrVal;
if (!m_mail->isCryptoMail ())
{
if (m_mail->hasOverrideMimeData())
{
/* This is a mail created by us. Ignore propchanges. */
break;
}
if (!wcscmp (prop_name, L"To") /* ||
!wcscmp (prop_name, L"BCC") ||
!wcscmp (prop_name, L"CC")
Testing shows that Outlook always sends these three in a row
*/)
{
if (opt.autosecure || (m_mail->needs_crypto_m () & 1))
{
/* XXX Racy race. This is a fix for crashes
that happend if a resolved recipient is copied an pasted.
If we then access the recipients object in the Property
Change event we crash. Thus we do the delay dance. */
HANDLE thread = CreateThread (NULL, 0, do_delayed_locate,
(LPVOID) m_mail, 0,
NULL);
CloseHandle(thread);
}
}
break;
}
for (const wchar_t **cur = prop_blacklist; *cur; cur++)
{
if (!wcscmp (prop_name, *cur))
{
log_oom ("%s:%s: Message %p propchange: %ls discarded.",
SRCNAME, __func__, m_object, prop_name);
return S_OK;
}
}
log_oom ("%s:%s: Message %p propchange: %ls.",
SRCNAME, __func__, m_object, prop_name);
if (!wcscmp (prop_name, L"SendUsingAccount"))
{
bool sent = get_oom_bool (m_object, "Sent");
if (sent)
{
log_debug ("%s:%s: Ignoring SendUsingAccount change for sent %p ",
SRCNAME, __func__, m_object);
return S_OK;
}
log_debug ("%s:%s: Message %p looks like send again.",
SRCNAME, __func__, m_object);
m_mail->setIsSendAgain (true);
return S_OK;
}
/* We have tried several scenarios to handle propery changes.
Only save the property in MAPI and call MAPI SaveChanges
worked and did not leak plaintext but this caused outlook
still to break the attachments of PGP/MIME Mails into two
attachments and add them as winmail.dat so other clients
are broken.
Alternatively reverting the mail, saving the property and
then decrypt again also worked a bit but there were some
weird side effects and breakages. But this has the usual
problem of a revert that the mail is created by outlook and
e.g. multipart/signed signatures from most MUA's are broken.
Some things to try out might be the close approach and then
another open or a selection change. But for now we just warn.
As a workardound a user should make property changes when
the mail was not read by us. */
if (propchangeWarnShown)
{
return S_OK;
}
wchar_t *title = utf8_to_wchar (_("Sorry, that's not possible, yet"));
char *fmt;
gpgrt_asprintf (&fmt, _("GpgOL has prevented the change to the \"%s\" property.\n"
"Property changes are not yet handled for crypto messages.\n\n"
"To workaround this limitation please change the property when the "
"message is not open in any window and not selected in the "
"messagelist.\n\nFor example by right clicking but not selecting the message.\n"),
wchar_to_utf8(prop_name));
memdbg_alloc (fmt);
wchar_t *msg = utf8_to_wchar (fmt);
xfree (fmt);
MessageBoxW (get_active_hwnd(), msg, title,
MB_ICONINFORMATION | MB_OK);
xfree (msg);
xfree (title);
propchangeWarnShown = true;
return S_OK;
}
case CustomPropertyChange:
{
log_oom ("%s:%s: CustomPropertyChange : %p",
SRCNAME, __func__, m_mail);
/* TODO */
break;
}
case Send:
{
/* This is the only event where we can cancel the send of a
mailitem. But it is too early for us to encrypt as the MAPI
structures are not yet filled. Crypto based on the
Outlook Object Model data did not work as the messages
were only sent out empty. See 2b376a48 for a try of
this.
This is why we store send_seen and invoke a save which
may result in an error but only after triggering all the
behavior we need -> filling mapi structures and invoking the
AfterWrite handler where we encrypt.
If this encryption is successful and we pass the send
as then the encrypted data is sent.
*/
log_oom ("%s:%s: Send : %p",
SRCNAME, __func__, m_mail);
if (!m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NoCryptMail)
{
log_debug ("%s:%s: No crypto neccessary. Passing send for %p obj %p",
SRCNAME, __func__, m_mail, m_object);
break;
}
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
log_debug ("%s:%s: Uncancellable send event.",
SRCNAME, __func__);
break;
}
if (m_mail->cryptState () == Mail::NoCryptMail &&
m_mail->needs_crypto_m ())
{
log_debug ("%s:%s: Send event for crypto mail %p saving and starting.",
SRCNAME, __func__, m_mail);
if (!m_mail->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. */
m_mail->refCurrentItem ();
}
// First contact with a mail to encrypt update
// state and oom data.
m_mail->updateOOMData_o ();
m_mail->setCryptState (Mail::NeedsFirstAfterWrite);
// Check inline response state before the write.
m_mail->check_inline_response ();
// Save the Mail
invoke_oom_method (m_object, "Save", NULL);
if (!m_mail->isAsyncCryptDisabled ())
{
// The afterwrite in the save should have triggered
// the encryption. We cancel send for our asyncness.
// Cancel send
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
}
else
{
if (m_mail->cryptState () == Mail::NoCryptMail)
{
// Crypto failed or was canceled
log_debug ("%s:%s: Message %p mail %p cancelling send - "
"Crypto failed or canceled.",
SRCNAME, __func__, m_object, m_mail);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
}
// For inline response we can't trigger send programatically
// so we do the encryption in sync.
if (m_mail->cryptState () == Mail::NeedsUpdateInOOM)
{
m_mail->updateCryptOOM_o ();
}
if (m_mail->cryptState () == Mail::NeedsSecondAfterWrite)
{
m_mail->setCryptState (Mail::WantsSendMIME);
}
if (m_mail->getDoPGPInline () && m_mail->cryptState () != Mail::WantsSendInline)
{
log_debug ("%s:%s: Message %p mail %p cancelling send - "
"Invalid state.",
SRCNAME, __func__, m_object, m_mail);
gpgol_bug (m_mail->getWindow (),
ERR_INLINE_BODY_INV_STATE);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
}
}
}
if (m_mail->cryptState () == Mail::WantsSendInline)
{
if (!m_mail->hasCryptedOrEmptyBody_o ())
{
log_debug ("%s:%s: Message %p mail %p cancelling send - "
"not encrypted or not empty body detected.",
SRCNAME, __func__, m_object, m_mail);
gpgol_bug (m_mail->getWindow (),
ERR_WANTS_SEND_INLINE_BODY);
m_mail->setCryptState (Mail::NoCryptMail);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
}
log_debug ("%s:%s: Passing send event for no-mime message %p.",
SRCNAME, __func__, m_object);
WKSHelper::instance()->allow_notify (1000);
break;
}
if (m_mail->cryptState () == Mail::WantsSendMIME)
{
if (!m_mail->hasCryptedOrEmptyBody_o ())
{
/* The safety checks here trigger too often. Somehow for some
users the body is not empty after the encryption but when
it is sent it is still sent with the crypto content because
the encrypted MIME Structure is used because it is
correct in MAPI land.
For safety reasons enabling the checks might be better but
until we figure out why for some users the body replacement
does not work we have to disable them. Otherwise GpgOL
is unusuable for such users. GnuPG-Bug-Id: T3875
*/
#define DISABLE_SAFTEY_CHECKS
#ifndef DISABLE_SAFTEY_CHECKS
gpgol_bug (m_mail->getWindow (),
ERR_WANTS_SEND_MIME_BODY);
log_debug ("%s:%s: Message %p mail %p cancelling send mime - "
"not encrypted or not empty body detected.",
SRCNAME, __func__, m_object, m_mail);
m_mail->setCryptState (Mail::NoCryptMail);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
break;
#else
log_debug ("%s:%s: Message %p mail %p - "
"not encrypted or not empty body detected - MIME.",
SRCNAME, __func__, m_object, m_mail);
#endif
}
/* Now we adress T3656 if Outlooks internal S/MIME is somehow
* mixed in (even if it is enabled and then disabled) it might
* cause strange behavior in that it sends the plain message
* and not the encrypted message. Tests have shown that we can
* bypass that by calling submit message on our base
* message.
*
* We do this conditionally as our other way of using OOM
* to send is proven to work and we don't want to mess
* with it.
*/
// Get the Message class.
HRESULT hr;
LPSPropValue propval = NULL;
// It's important we use the _not_ base message here.
LPMESSAGE message = get_oom_message (m_object);
hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
gpgol_release (message);
if (FAILED (hr))
{
log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
SRCNAME, __func__, hr);
gpgol_release (message);
break;
}
if (propval->Value.lpszA && !strstr (propval->Value.lpszA, "GpgOL"))
{
// Does not have a message class by us.
log_debug ("%s:%s: Message %p - No GpgOL Message class after encryption. cls is: '%s'",
SRCNAME, __func__, m_object, propval->Value.lpszA);
log_debug ("%s:%s: Message %p - Activating T3656 Workaround",
SRCNAME, __func__, m_object);
message = get_oom_base_message (m_object);
if (message)
{
// It's important we use the _base_ message here.
mapi_save_changes (message, FORCE_SAVE);
message->SubmitMessage(0);
gpgol_release (message);
// Close the composer and trigger unloads
CloseHandle(CreateThread (NULL, 0, close_mail, (LPVOID) m_mail, 0,
NULL));
}
else
{
gpgol_bug (nullptr,
ERR_GET_BASE_MSG_FAILED);
}
// Cancel send
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
}
MAPIFreeBuffer (propval);
if (*(parms->rgvarg[0].pboolVal) == VARIANT_TRUE)
{
break;
}
log_debug ("%s:%s: Passing send event for mime-encrypted message %p.",
SRCNAME, __func__, m_object);
WKSHelper::instance()->allow_notify (1000);
break;
}
else
{
log_debug ("%s:%s: Message %p cancelling send - "
"crypto or second save failed.",
SRCNAME, __func__, m_object);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
}
return S_OK;
}
case Write:
{
log_oom ("%s:%s: Write : %p",
SRCNAME, __func__, m_mail);
/* This is a bit strange. We sometimes get multiple write events
without a read in between. When we access the message in
the second event it fails and if we cancel the event outlook
crashes. So we have keep the m_needs_wipe state variable
to keep track of that. */
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
/* This happens in the weird case */
log_debug ("%s:%s: Uncancellable write event.",
SRCNAME, __func__);
break;
}
if (m_mail->isAboutToBeMoved())
{
log_debug ("%s:%s: Mail is about to be moved. Passing write for %p",
SRCNAME, __func__, m_mail);
break;
}
if (m_mail->isCryptoMail () && !m_mail->needsSave ())
{
Mail *last_mail = Mail::getLastMail ();
if (Mail::isValidPtr (last_mail))
{
/* We want to identify here if there was a mail created that
should receive the contents of this mail. For this we check
for a write in the same loop as a mail creation.
Now when switching from one mail to another this is also what
happens. The new mail is loaded and the old mail is written.
To distinguish the two we check that the new mail does not have
an entryID, a Subject and No Size. Maybe just size or entryID
would be enough but better save then sorry.
Security consideration: Worst case we pass the write here but
an unload follows before we get the scheduled revert. This
would leak plaintext. But does not happen in our tests.
Similarly if we crash or Outlook is closed before we see this
revert. But as we immediately revert after the write this should
also not happen. */
const std::string lastSubject = last_mail->getSubject_o ();
char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
int lastSize = get_oom_int (last_mail->item (), "Size");
std::string lastEntryStr;
if (lastEntryID)
{
lastEntryStr = lastEntryID;
xfree (lastEntryID);
}
if (!lastSize && !lastEntryStr.size () && !lastSubject.size ())
{
log_debug ("%s:%s: Write in the same loop as empty load."
" Pass but schedule revert.",
SRCNAME, __func__);
/* This might be a forward. So don't invalidate yet. */
// Mail::clearLastMail ();
do_in_ui_thread_async (REVERT_MAIL, m_mail);
return S_OK;
}
}
/* We cancel the write event to stop outlook from excessively
syncing our changes.
if smime support is disabled and we still have an smime
mail we also don't want to cancel the write event
to enable reverting this mails.
*/
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
log_debug ("%s:%s: Canceling write event.",
SRCNAME, __func__);
return S_OK;
}
if (m_mail->isCryptoMail () && m_mail->needsSave () &&
m_mail->revert_o ())
{
/* An error cleaning the mail should not happen normally.
But just in case there is an error we cancel the
write here. */
log_debug ("%s:%s: Failed to remove plaintext.",
SRCNAME, __func__);
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
}
if (!m_mail->isCryptoMail () && m_mail->is_forwarded_crypto_mail () &&
!m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NoCryptMail)
{
/* We are sure now that while this is a forward of an encrypted
* mail that the forward should not be signed or encrypted. So
* it's not constructed by us. We need to remove our attachments
* though so that they are not included in the forward. */
log_debug ("%s:%s: Writing unencrypted forward of crypt mail. "
"Removing attachments. mail: %p item: %p",
SRCNAME, __func__, m_mail, m_object);
if (m_mail->removeOurAttachments_o ())
{
// Worst case we forward some encrypted data here not
// a security problem, so let it pass.
log_error ("%s:%s: Failed to remove our attachments.",
SRCNAME, __func__);
}
/* Remove marker because we did this now. */
m_mail->setIsForwardedCryptoMail (false);
}
log_debug ("%s:%s: Passing write event.",
SRCNAME, __func__);
m_mail->setNeedsSave (false);
break;
}
case AfterWrite:
{
log_oom ("%s:%s: AfterWrite : %p",
SRCNAME, __func__, m_mail);
if (m_mail->cryptState () == Mail::NeedsFirstAfterWrite)
{
/* Seen the first after write. Advance the state */
m_mail->setCryptState (Mail::NeedsActualCrypt);
if (m_mail->encryptSignStart_o ())
{
- log_debug ("%s:%s: Encrypt sign start failes.",
+ log_debug ("%s:%s: Encrypt sign start failed.",
SRCNAME, __func__);
m_mail->setCryptState (Mail::NoCryptMail);
}
return S_OK;
}
if (m_mail->cryptState () == Mail::NeedsSecondAfterWrite)
{
m_mail->setCryptState (Mail::NeedsUpdateInMAPI);
m_mail->updateCryptMAPI_m ();
return S_OK;
}
break;
}
case Close:
{
log_oom ("%s:%s: Close : %p",
SRCNAME, __func__, m_mail);
if (m_mail->isCryptoMail ())
{
/* Close. This happens when an Opened mail is closed.
To prevent the question of wether or not to save the changes
(Which would save the decrypted data without an event to
prevent it) we cancel the close and then either close it
with discard changes or revert / save it.
Contrary to documentation we can invoke close from
close.
*/
if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
/* This happens in the weird case */
log_debug ("%s:%s: Uncancellable close event.",
SRCNAME, __func__);
break;
}
if (m_mail->getCloseTriggered ())
{
/* Our close with discard changes, pass through */
m_mail->setCloseTriggered (false);
return S_OK;
}
*(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
log_oom ("%s:%s: Canceling close event.",
SRCNAME, __func__);
if (Mail::close(m_mail))
{
log_debug ("%s:%s: Close request failed.",
SRCNAME, __func__);
}
}
return S_OK;
}
case Unload:
{
log_oom ("%s:%s: Unload : %p",
SRCNAME, __func__, m_mail);
log_debug ("%s:%s: Removing Mail for message: %p.",
SRCNAME, __func__, m_object);
delete m_mail;
log_oom ("%s:%s: deletion done",
SRCNAME, __func__);
memdbg_dump ();
return S_OK;
}
/* Fallthrough */
case ReplyAll:
case Reply:
{
is_reply = true;
}
case Forward:
{
log_oom ("%s:%s: %s : %p",
SRCNAME, __func__, is_reply ? "reply" : "forward", m_mail);
int draft_flags = 0;
if (opt.encrypt_default)
{
draft_flags = 1;
}
if (opt.sign_default)
{
draft_flags += 2;
}
bool is_crypto_mail = m_mail->isCryptoMail ();
/* If it is a crypto mail and the settings should not be taken
* from the crypto mail and always encrypt / sign is on. Or
* If it is not a crypto mail and we have automaticalls sign_encrypt. */
if ((is_crypto_mail && !opt.reply_crypt && draft_flags) ||
(!is_crypto_mail && draft_flags))
{
/* Check if we can use the dispval */
if (parms->cArgs == 2 && parms->rgvarg[1].vt == (VT_DISPATCH) &&
parms->rgvarg[0].vt == (VT_BOOL | VT_BYREF))
{
LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal);
if (msg)
{
set_gpgol_draft_info_flags (msg, draft_flags);
gpgol_release (msg);
}
else
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
}
}
else
{
log_error ("%s:%s: Unexpected parameters.",
SRCNAME, __func__);
}
}
if (!is_crypto_mail)
{
/* Replys to non crypto mails do not interest us anymore. */
break;
}
Mail *last_mail = Mail::getLastMail ();
if (Mail::isValidPtr (last_mail))
{
/* We want to identify here if there was a mail created that
should receive the contents of this mail. For this we check
for a forward in the same loop as a mail creation.
We need to do it this complicated and can't just use
get_mail_for_item because the mailitem pointer we get here
is a different one then the one with which the mail was loaded.
*/
char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
int lastSize = get_oom_int (last_mail->item (), "Size");
std::string lastEntryStr;
if (lastEntryID)
{
lastEntryStr = lastEntryID;
xfree (lastEntryID);
}
if (!lastSize && !lastEntryStr.size ())
{
if (!is_reply)
{
log_debug ("%s:%s: Forward in the same loop as empty "
"load Marking %p (item %p) as forwarded.",
SRCNAME, __func__, last_mail,
last_mail->item ());
last_mail->setIsForwardedCryptoMail (true);
}
else
{
log_debug ("%s:%s: Reply in the same loop as empty "
"load Marking %p (item %p) as reply.",
SRCNAME, __func__, last_mail,
last_mail->item ());
}
if (m_mail->isBlockHTML ())
{
std::string caption = _("GpgOL") + std::string (": ");
caption += is_reply ? _("Dangerous reply") :
_("Dangerous forward");
std::string buf = _("Unsigned S/MIME mails are not integrity "
"protected.");
buf += "\n\n";
if (is_reply)
{
buf += _("For security reasons no decrypted contents"
" are included in this reply.");
}
else
{
buf += _("For security reasons no decrypted contents"
" are included in the forwarded mail.");
}
gpgol_message_box (get_active_hwnd (), buf.c_str(),
_("GpgOL"), MB_OK);
do_in_ui_thread_async (CLEAR_REPLY_FORWARD, last_mail, 1000);
}
}
// We can now invalidate the last mail
Mail::clearLastMail ();
}
log_oom ("%s:%s: Reply Forward ReplyAll: %p",
SRCNAME, __func__, m_mail);
if (!opt.reply_crypt)
{
break;
}
int crypto_flags = 0;
if (!(crypto_flags = m_mail->getCryptoFlags ()))
{
break;
}
if (parms->cArgs != 2 || parms->rgvarg[1].vt != (VT_DISPATCH) ||
parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
{
/* This happens in the weird case */
log_debug ("%s:%s: Unexpected args %i %x %x named: %i",
SRCNAME, __func__, parms->cArgs, parms->rgvarg[0].vt, parms->rgvarg[1].vt,
parms->cNamedArgs);
break;
}
LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal);
if (!msg)
{
log_debug ("%s:%s: Failed to get base message",
SRCNAME, __func__);
break;
}
set_gpgol_draft_info_flags (msg, crypto_flags);
gpgol_release (msg);
break;
}
case AttachmentRemove:
{
log_oom ("%s:%s: AttachmentRemove: %p",
SRCNAME, __func__, m_mail);
if (!m_mail->isCryptoMail () || attachRemoveWarnShown ||
m_mail->attachmentRemoveWarningDisabled ())
{
return S_OK;
}
gpgol_message_box (get_active_hwnd (),
_("Attachments are part of the crypto message.\nThey "
"can't be permanently removed and will be shown again the next "
"time this message is opened."),
_("Sorry, that's not possible, yet"), MB_OK);
attachRemoveWarnShown = true;
return S_OK;
}
default:
log_oom ("%s:%s: Message:%p Unhandled Event: %lx \n",
SRCNAME, __func__, m_object, dispid);
}
return S_OK;
}
END_EVENT_SINK(MailItemEvents, IID_MailItemEvents)
diff --git a/src/mapihelp.cpp b/src/mapihelp.cpp
index 562a903..0ee9619 100644
--- a/src/mapihelp.cpp
+++ b/src/mapihelp.cpp
@@ -1,3512 +1,3513 @@
/* mapihelp.cpp - Helper functions for MAPI
* Copyright (C) 2005, 2007, 2008 g10 Code GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>
#include <windows.h>
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
#include "rfc822parse.h"
#include "mapihelp.h"
#include "parsetlv.h"
#include "oomhelp.h"
#include <string>
#ifndef CRYPT_E_STREAM_INSUFFICIENT_DATA
#define CRYPT_E_STREAM_INSUFFICIENT_DATA 0x80091011
#endif
#ifndef CRYPT_E_ASN1_BADTAG
#define CRYPT_E_ASN1_BADTAG 0x8009310B
#endif
static int get_attach_method (LPATTACH obj);
static int has_smime_filename (LPATTACH obj);
static char *get_attach_mime_tag (LPATTACH obj);
/* Print a MAPI property to the log stream. */
void
log_mapi_property (LPMESSAGE message, ULONG prop, const char *propname)
{
HRESULT hr;
LPSPropValue propval = NULL;
size_t keylen;
void *key;
char *buf;
if (!message)
return; /* No message: Nop. */
hr = HrGetOneProp ((LPMAPIPROP)message, prop, &propval);
if (FAILED (hr))
{
log_debug ("%s:%s: HrGetOneProp(%s) failed: hr=%#lx\n",
SRCNAME, __func__, propname, hr);
return;
}
switch ( PROP_TYPE (propval->ulPropTag) )
{
case PT_BINARY:
keylen = propval->Value.bin.cb;
key = propval->Value.bin.lpb;
log_hexdump (key, keylen, "%s: %20s=", __func__, propname);
break;
case PT_UNICODE:
buf = wchar_to_utf8 (propval->Value.lpszW);
if (!buf)
log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
else
log_debug ("%s: %20s=`%s'", __func__, propname, buf);
xfree (buf);
break;
case PT_STRING8:
log_debug ("%s: %20s=`%s'", __func__, propname, propval->Value.lpszA);
break;
case PT_LONG:
log_debug ("%s: %20s=%ld", __func__, propname, propval->Value.l);
break;
default:
log_debug ("%s:%s: HrGetOneProp(%s) property type %lu not supported\n",
SRCNAME, __func__, propname,
PROP_TYPE (propval->ulPropTag) );
return;
}
MAPIFreeBuffer (propval);
}
/* Helper to create a named property. */
static ULONG
create_gpgol_tag (LPMESSAGE message, const wchar_t *name, const char *func)
{
HRESULT hr;
LPSPropTagArray proparr = NULL;
MAPINAMEID mnid, *pmnid;
wchar_t *propname = xwcsdup (name);
/* {31805ab8-3e92-11dc-879c-00061b031004}: GpgOL custom properties. */
GUID guid = {0x31805ab8, 0x3e92, 0x11dc, {0x87, 0x9c, 0x00, 0x06,
0x1b, 0x03, 0x10, 0x04}};
ULONG result;
memset (&mnid, 0, sizeof mnid);
mnid.lpguid = &guid;
mnid.ulKind = MNID_STRING;
mnid.Kind.lpwstrName = propname;
pmnid = &mnid;
hr = message->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &proparr);
xfree (propname);
if (FAILED (hr))
proparr = NULL;
if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) )
{
log_error ("%s:%s: can't map GpgOL property: hr=%#lx\n",
SRCNAME, func, hr);
result = 0;
}
else
result = (proparr->aulPropTag[0] & 0xFFFF0000);
if (proparr)
MAPIFreeBuffer (proparr);
return result;
}
/* Return the property tag for GpgOL Msg Class. */
int
get_gpgolmsgclass_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Msg Class", __func__)))
return -1;
*r_tag |= PT_STRING8;
return 0;
}
/* Return the property tag for GpgOL Old Msg Class. The Old Msg Class
saves the message class as seen before we changed it the first
time. */
int
get_gpgololdmsgclass_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Old Msg Class", __func__)))
return -1;
*r_tag |= PT_STRING8;
return 0;
}
/* Return the property tag for GpgOL Attach Type. */
int
get_gpgolattachtype_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Attach Type", __func__)))
return -1;
*r_tag |= PT_LONG;
return 0;
}
/* Return the property tag for GpgOL Sig Status. */
int
get_gpgolsigstatus_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Sig Status", __func__)))
return -1;
*r_tag |= PT_STRING8;
return 0;
}
/* Return the property tag for GpgOL Protect IV. */
int
get_gpgolprotectiv_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Protect IV", __func__)))
return -1;
*r_tag |= PT_BINARY;
return 0;
}
/* Return the property tag for GpgOL Last Decrypted. */
int
get_gpgollastdecrypted_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Last Decrypted",__func__)))
return -1;
*r_tag |= PT_BINARY;
return 0;
}
/* Return the property tag for GpgOL MIME structure. */
int
get_gpgolmimeinfo_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL MIME Info", __func__)))
return -1;
*r_tag |= PT_STRING8;
return 0;
}
/* Return the property tag for GpgOL Charset. */
int
get_gpgolcharset_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Charset", __func__)))
return -1;
*r_tag |= PT_STRING8;
return 0;
}
/* Return the property tag for GpgOL Draft Info. */
int
get_gpgoldraftinfo_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Draft Info", __func__)))
return -1;
*r_tag |= PT_STRING8;
return 0;
}
/* Return the tag of the Internet Charset Body property which seems to
hold the PR_BODY as received and thus before charset
conversion. */
int
get_internetcharsetbody_tag (LPMESSAGE message, ULONG *r_tag)
{
HRESULT hr;
LPSPropTagArray proparr = NULL;
MAPINAMEID mnid, *pmnid;
/* {4E3A7680-B77A-11D0-9DA5-00C04FD65685} */
GUID guid = {0x4E3A7680, 0xB77A, 0x11D0, {0x9D, 0xA5, 0x00, 0xC0,
0x4F, 0xD6, 0x56, 0x85}};
wchar_t propname[] = L"Internet Charset Body";
int result;
memset (&mnid, 0, sizeof mnid);
mnid.lpguid = &guid;
mnid.ulKind = MNID_STRING;
mnid.Kind.lpwstrName = propname;
pmnid = &mnid;
hr = message->GetIDsFromNames (1, &pmnid, 0, &proparr);
if (FAILED (hr))
proparr = NULL;
if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) )
{
log_debug ("%s:%s: can't get the Internet Charset Body property:"
" hr=%#lx\n", SRCNAME, __func__, hr);
result = -1;
}
else
{
result = 0;
*r_tag = ((proparr->aulPropTag[0] & 0xFFFF0000) | PT_BINARY);
}
if (proparr)
MAPIFreeBuffer (proparr);
return result;
}
/* Return the property tag for GpgOL UUID Info. */
static int
get_gpgoluid_tag (LPMESSAGE message, ULONG *r_tag)
{
if (!(*r_tag = create_gpgol_tag (message, L"GpgOL UID", __func__)))
return -1;
*r_tag |= PT_UNICODE;
return 0;
}
char *
mapi_get_uid (LPMESSAGE msg)
{
/* If the UUID is not in OOM maybe we find it in mapi. */
if (!msg)
{
log_error ("%s:%s: Called without message",
SRCNAME, __func__);
return NULL;
}
ULONG tag;
if (get_gpgoluid_tag (msg, &tag))
{
log_debug ("%s:%s: Failed to get tag for '%p'",
SRCNAME, __func__, msg);
return NULL;
}
LPSPropValue propval = NULL;
HRESULT hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
if (hr)
{
log_debug ("%s:%s: Failed to get prop for '%p'",
SRCNAME, __func__, msg);
return NULL;
}
char *ret = NULL;
if (PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
{
ret = wchar_to_utf8 (propval->Value.lpszW);
log_debug ("%s:%s: Fund uuid in MAPI for %p",
SRCNAME, __func__, msg);
}
else if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
{
ret = xstrdup (propval->Value.lpszA);
log_debug ("%s:%s: Fund uuid in MAPI for %p",
SRCNAME, __func__, msg);
}
MAPIFreeBuffer (propval);
return ret;
}
/* A Wrapper around the SaveChanges method. This function should be
called indirect through the mapi_save_changes macro. Returns 0 on
success. */
int
mapi_do_save_changes (LPMESSAGE message, ULONG flags, int only_del_body,
const char *dbg_file, const char *dbg_func)
{
HRESULT hr;
SPropTagArray proparray;
int any = 0;
if (mapi_has_last_decrypted (message))
{
proparray.cValues = 1;
proparray.aulPropTag[0] = PR_BODY;
hr = message->DeleteProps (&proparray, NULL);
if (hr)
log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed",
log_srcname (dbg_file), dbg_func);
else
any = 1;
proparray.cValues = 1;
proparray.aulPropTag[0] = PR_BODY_HTML;
hr = message->DeleteProps (&proparray, NULL);
if (hr)
log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed",
log_srcname (dbg_file), dbg_func);
else
any = 1;
}
if (!only_del_body || any)
{
int i;
for (i = 0, hr = 0; hr && i < 10; i++)
{
hr = message->SaveChanges (flags);
if (hr)
{
log_debug ("%s:%s: Failed try to save.",
SRCNAME, __func__);
Sleep (1000);
}
}
if (hr)
{
log_error ("%s:%s: SaveChanges(%lu) failed: hr=%#lx\n",
log_srcname (dbg_file), dbg_func,
(unsigned long)flags, hr);
return -1;
}
}
return 0;
}
/* Set an arbitary header in the message MSG with NAME to the value
VAL. */
int
mapi_set_header (LPMESSAGE msg, const char *name, const char *val)
{
HRESULT hr;
LPSPropTagArray pProps = NULL;
SPropValue pv;
MAPINAMEID mnid, *pmnid;
/* {00020386-0000-0000-C000-000000000046} -> GUID For X-Headers */
GUID guid = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x46} };
int result;
if (!msg)
return -1;
memset (&mnid, 0, sizeof mnid);
mnid.lpguid = &guid;
mnid.ulKind = MNID_STRING;
mnid.Kind.lpwstrName = utf8_to_wchar (name);
pmnid = &mnid;
hr = msg->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &pProps);
xfree (mnid.Kind.lpwstrName);
if (FAILED (hr))
{
pProps = NULL;
log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n",
SRCNAME, __func__, name, hr);
result = -1;
}
else
{
pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8;
pv.Value.lpszA = (char *)val;
hr = HrSetOneProp(msg, &pv);
if (hr)
{
log_error ("%s:%s: can't set header `%s': hr=%#lx\n",
SRCNAME, __func__, name, hr);
result = -1;
}
else
result = 0;
}
if (pProps)
MAPIFreeBuffer (pProps);
return result;
}
/* Return the headers as ASCII string. Returns empty
string on failure. */
std::string
mapi_get_header (LPMESSAGE message)
{
HRESULT hr;
LPSTREAM stream;
ULONG bRead;
std::string ret;
if (!message)
return ret;
hr = gpgol_openProperty (message, PR_TRANSPORT_MESSAGE_HEADERS_A, &IID_IStream, 0, 0,
(LPUNKNOWN*)&stream);
if (hr)
{
log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr);
return ret;
}
char buf[8192];
while ((hr = stream->Read (buf, 8192, &bRead)) == S_OK ||
hr == S_FALSE)
{
if (!bRead)
{
// EOF
break;
}
ret += std::string (buf, bRead);
}
gpgol_release (stream);
return ret;
}
/* Return the body as a new IStream object. Returns NULL on failure.
The stream returns the body as an ASCII stream (Use mapi_get_body
for an UTF-8 value). */
LPSTREAM
mapi_get_body_as_stream (LPMESSAGE message)
{
HRESULT hr;
ULONG tag;
LPSTREAM stream;
if (!message)
return NULL;
if (!get_internetcharsetbody_tag (message, &tag) )
{
/* The store knows about the Internet Charset Body property,
thus try to get the body from this property if it exists. */
hr = gpgol_openProperty (message, tag, &IID_IStream, 0, 0,
(LPUNKNOWN*)&stream);
if (!hr)
return stream;
log_debug ("%s:%s: OpenProperty tag=%lx failed: hr=%#lx",
SRCNAME, __func__, tag, hr);
}
/* We try to get it as an ASCII body. If this fails we would either
need to implement some kind of stream filter to translated to
utf-8 or read everyting into a memory buffer and [provide an
istream from that memory buffer. */
hr = gpgol_openProperty (message, PR_BODY_A, &IID_IStream, 0, 0,
(LPUNKNOWN*)&stream);
if (hr)
{
log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr);
return NULL;
}
return stream;
}
/* Return the body of the message in an allocated buffer. The buffer
is guaranteed to be Nul terminated. The actual length (ie. the
strlen()) will be stored at R_NBYTES. The body will be returned in
UTF-8 encoding. Returns NULL if no body is available. */
char *
mapi_get_body (LPMESSAGE message, size_t *r_nbytes)
{
HRESULT hr;
LPSPropValue lpspvFEID = NULL;
LPSTREAM stream;
STATSTG statInfo;
ULONG nread;
char *body = NULL;
if (r_nbytes)
*r_nbytes = 0;
hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
if (SUCCEEDED (hr)) /* Message is small enough to be retrieved directly. */
{
switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
{
case PT_UNICODE:
body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
if (!body)
log_debug ("%s: error converting to utf8\n", __func__);
break;
case PT_STRING8:
body = xstrdup (lpspvFEID->Value.lpszA);
break;
default:
log_debug ("%s: proptag=0x%08lx not supported\n",
__func__, lpspvFEID->ulPropTag);
break;
}
MAPIFreeBuffer (lpspvFEID);
}
else /* Message is large; use an IStream to read it. */
{
hr = gpgol_openProperty (message, PR_BODY, &IID_IStream, 0, 0,
(LPUNKNOWN*)&stream);
if (hr)
{
log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
hr = stream->Stat (&statInfo, STATFLAG_NONAME);
if (hr)
{
log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
gpgol_release (stream);
return NULL;
}
/* Fixme: We might want to read only the first 1k to decide
whether this is actually an OpenPGP message and only then
continue reading. */
body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
if (hr)
{
log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
xfree (body);
gpgol_release (stream);
return NULL;
}
body[nread] = 0;
body[nread+1] = 0;
if (nread != statInfo.cbSize.QuadPart)
{
log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
xfree (body);
gpgol_release (stream);
return NULL;
}
gpgol_release (stream);
{
char *tmp;
tmp = wchar_to_utf8 ((wchar_t*)body);
if (!tmp)
log_debug ("%s: error converting to utf8\n", __func__);
else
{
xfree (body);
body = tmp;
}
}
}
if (r_nbytes)
*r_nbytes = strlen (body);
return body;
}
/* Look at the body of the MESSAGE and try to figure out whether this
is a supported PGP message. Returns the new message class or NULL
if it does not look like a PGP message.
If r_nobody is not null it is set to true if no body was found.
*/
static char *
get_msgcls_from_pgp_lines (LPMESSAGE message, bool *r_nobody = nullptr)
{
HRESULT hr;
LPSTREAM stream;
STATSTG statInfo;
ULONG nread;
size_t nbytes;
char *body = NULL;
char *p;
char *msgcls = NULL;
int is_wchar = 0;
if (r_nobody)
{
*r_nobody = false;
}
stream = mapi_get_body_as_stream (message);
if (!stream)
{
log_debug ("%s:%s: Failed to get body ASCII stream.",
SRCNAME, __func__);
hr = gpgol_openProperty (message, PR_BODY_W, &IID_IStream, 0, 0,
(LPUNKNOWN*)&stream);
if (hr)
{
log_error ("%s:%s: Failed to get w_body stream. : hr=%#lx",
SRCNAME, __func__, hr);
if (r_nobody)
{
*r_nobody = true;
}
return NULL;
}
else
{
is_wchar = 1;
}
}
hr = stream->Stat (&statInfo, STATFLAG_NONAME);
if (hr)
{
log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
gpgol_release (stream);
return NULL;
}
/* We read only the first 1k to decide whether this is actually an
OpenPGP armored message . */
nbytes = (size_t)statInfo.cbSize.QuadPart;
if (nbytes > 1024*2)
nbytes = 1024*2;
body = (char*)xmalloc (nbytes + 2);
hr = stream->Read (body, nbytes, &nread);
if (hr)
{
log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
xfree (body);
gpgol_release (stream);
return NULL;
}
body[nread] = 0;
body[nread+1] = 0;
if (nread != nbytes)
{
log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
xfree (body);
gpgol_release (stream);
return NULL;
}
gpgol_release (stream);
if (is_wchar)
{
char *tmp;
tmp = wchar_to_utf8 ((wchar_t*)body);
if (!tmp)
log_debug ("%s: error converting to utf8\n", __func__);
else
{
xfree (body);
body = tmp;
}
}
/* The first ~1k of the body of the message is now available in the
utf-8 string BODY. Walk over it to figure out its type. */
for (p=body; p && *p; p = ((p=strchr (p+1, '\n')) ? (p+1) : NULL))
{
if (!strncmp (p, "-----BEGIN PGP ", 15))
{
/* Enabling clearsigned detection for Outlook 2010 and later
would result in data loss as the signature is not reverted. */
if (!strncmp (p+15, "SIGNED MESSAGE-----", 19)
&& trailing_ws_p (p+15+19))
msgcls = xstrdup ("IPM.Note.GpgOL.ClearSigned");
else if (!strncmp (p+15, "MESSAGE-----", 12)
&& trailing_ws_p (p+15+12))
msgcls = xstrdup ("IPM.Note.GpgOL.PGPMessage");
break;
}
else if (!trailing_ws_p (p))
{
/* We have text before the message. In that case we need
to break because some bad MUA's like Outlook do not insert
quote characters before a replied to message. In that case
the reply to an inline Mail from an Outlook without GpgOL
enabled could cause the behavior that we would detect
the original message.
*/
log_debug ("%s:%s: Detected non whitespace %c before a PGP Marker",
SRCNAME, __func__, *p);
break;
}
}
xfree (body);
return msgcls;
}
/* Check whether the message is really a CMS encrypted message.
We check here whether the message is really encrypted by looking at
the object identifier inside the CMS data. Returns:
-1 := Unknown message type,
0 := The message is signed,
1 := The message is encrypted.
This function is required for two reasons:
1. Due to a bug in CryptoEx which sometimes assignes the *.CexEnc
message class to signed messages and only updates the message
class after accessing them. Thus in old stores there may be a
lot of *.CexEnc message which are actually just signed.
2. If the smime-type parameter is missing we need another way to
decide whether to decrypt or to verify.
3. Some messages lack a PR_TRANSPORT_MESSAGE_HEADERS and thus it is
not possible to deduce the message type from the mail headers.
This function may be used to identify the message anyway.
*/
static int
is_really_cms_encrypted (LPMESSAGE message)
{
HRESULT hr;
SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
LPMAPITABLE mapitable;
LPSRowSet mapirows;
unsigned int pos, n_attach;
int result = -1; /* Unknown. */
LPATTACH att = NULL;
LPSTREAM stream = NULL;
char buffer[24]; /* 24 bytes are more than enough to peek at.
Cf. ksba_cms_identify() from the libksba
package. */
const char *p;
ULONG nread;
size_t n;
tlvinfo_t ti;
hr = message->GetAttachmentTable (0, &mapitable);
if (FAILED (hr))
{
log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
SRCNAME, __func__, hr);
return -1;
}
hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
NULL, NULL, 0, &mapirows);
if (FAILED (hr))
{
log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
SRCNAME, __func__, hr);
gpgol_release (mapitable);
return -1;
}
n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
if (n_attach != 1)
{
FreeProws (mapirows);
gpgol_release (mapitable);
log_debug ("%s:%s: not just one attachment", SRCNAME, __func__);
return -1;
}
pos = 0;
if (mapirows->aRow[pos].cValues < 1)
{
log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
goto leave;
}
if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
{
log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
goto leave;
}
hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
NULL, MAPI_BEST_ACCESS, &att);
memdbg_addRef (att);
if (FAILED (hr))
{
log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
SRCNAME, __func__, pos,
mapirows->aRow[pos].lpProps[0].Value.l, hr);
goto leave;
}
if (!has_smime_filename (att))
{
log_debug ("%s:%s: no smime filename", SRCNAME, __func__);
goto leave;
}
if (get_attach_method (att) != ATTACH_BY_VALUE)
{
log_debug ("%s:%s: wrong attach method", SRCNAME, __func__);
goto leave;
}
hr = gpgol_openProperty (att, PR_ATTACH_DATA_BIN, &IID_IStream,
0, 0, (LPUNKNOWN*) &stream);
if (FAILED (hr))
{
log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
SRCNAME, __func__, hr);
goto leave;
}
hr = stream->Read (buffer, sizeof buffer, &nread);
if ( hr != S_OK )
{
log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
goto leave;
}
if (nread < sizeof buffer)
{
log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
goto leave;
}
p = buffer;
n = nread;
if (parse_tlv (&p, &n, &ti))
goto leave;
if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE
&& ti.is_cons) )
goto leave;
if (parse_tlv (&p, &n, &ti))
goto leave;
if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_OBJECT_ID
&& !ti.is_cons && ti.length) || ti.length > n)
goto leave;
/* Now is this enveloped data (1.2.840.113549.1.7.3)
or signed data (1.2.840.113549.1.7.2) ? */
if (ti.length == 9)
{
if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9))
result = 1; /* Encrypted. */
else if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02", 9))
result = 0; /* Signed. */
}
leave:
if (stream)
gpgol_release (stream);
if (att)
gpgol_release (att);
FreeProws (mapirows);
gpgol_release (mapitable);
return result;
}
/* Return the content-type of the first and only attachment of MESSAGE
or NULL if it does not exists. Caller must free. */
static char *
get_first_attach_mime_tag (LPMESSAGE message)
{
HRESULT hr;
SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
LPMAPITABLE mapitable;
LPSRowSet mapirows;
unsigned int pos, n_attach;
LPATTACH att = NULL;
char *result = NULL;
hr = message->GetAttachmentTable (0, &mapitable);
if (FAILED (hr))
{
log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
NULL, NULL, 0, &mapirows);
if (FAILED (hr))
{
log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
SRCNAME, __func__, hr);
gpgol_release (mapitable);
return NULL;
}
n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
if (n_attach < 1)
{
FreeProws (mapirows);
gpgol_release (mapitable);
log_debug ("%s:%s: less then one attachment", SRCNAME, __func__);
return NULL;
}
pos = 0;
if (mapirows->aRow[pos].cValues < 1)
{
log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
goto leave;
}
if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
{
log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
goto leave;
}
hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
NULL, MAPI_BEST_ACCESS, &att);
memdbg_addRef (att);
if (FAILED (hr))
{
log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
SRCNAME, __func__, pos,
mapirows->aRow[pos].lpProps[0].Value.l, hr);
goto leave;
}
/* Note: We do not expect a filename. */
if (get_attach_method (att) != ATTACH_BY_VALUE)
{
log_debug ("%s:%s: wrong attach method", SRCNAME, __func__);
goto leave;
}
result = get_attach_mime_tag (att);
leave:
if (att)
gpgol_release (att);
FreeProws (mapirows);
gpgol_release (mapitable);
return result;
}
/* Look at the first attachment's content type to determine the
messageclass. */
static char *
get_msgcls_from_first_attachment (LPMESSAGE message)
{
char *ret = nullptr;
char *attach_mime = get_first_attach_mime_tag (message);
if (!attach_mime)
{
return nullptr;
}
if (!strcmp (attach_mime, "application/pgp-encrypted"))
{
ret = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
xfree (attach_mime);
}
else if (!strcmp (attach_mime, "application/pgp-signature"))
{
ret = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
xfree (attach_mime);
}
return ret;
}
/* Helper for mapi_change_message_class. Returns the new message
class as an allocated string.
Most message today are of the message class "IPM.Note". However a
PGP/MIME encrypted message also has this class. We need to see
whether we can detect such a mail right here and change the message
class accordingly. */
static char *
change_message_class_ipm_note (LPMESSAGE message)
{
char *newvalue = NULL;
char *ct, *proto;
ct = mapi_get_message_content_type (message, &proto, NULL);
log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__,
ct ? ct : "null");
if (ct && proto)
{
log_debug ("%s:%s: protocol is '%s'", SRCNAME, __func__, proto);
if (!strcmp (ct, "multipart/encrypted")
&& !strcmp (proto, "application/pgp-encrypted"))
{
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
}
else if (!strcmp (ct, "multipart/signed")
&& !strcmp (proto, "application/pgp-signature"))
{
/* Sometimes we receive a PGP/MIME signed message with a
class IPM.Note. */
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
}
else if (!strcmp (ct, "multipart/mixed")
&& !strcmp (proto, "application/pgp-encrypted"))
{
/* This case happens if an encrypted mail is moved
by outlook local filter rules.
*/
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
}
xfree (proto);
}
else if (ct && !strcmp (ct, "application/ms-tnef"))
{
/* ms-tnef can either be inline PGP or PGP/MIME. First check
for inline and then look at the attachments if they look
like PGP /MIME .*/
newvalue = get_msgcls_from_pgp_lines (message);
if (!newvalue)
{
/* So no PGP Inline. Lets look at the attachment. */
newvalue = get_msgcls_from_first_attachment (message);
}
}
else if (!ct || !strcmp (ct, "text/plain") ||
!strcmp (ct, "multipart/mixed") ||
!strcmp (ct, "multipart/alternative") ||
!strcmp (ct, "multipart/related") ||
!strcmp (ct, "text/html"))
{
bool has_no_body = false;
/* It is quite common to have a multipart/mixed or alternative
mail with separate encrypted PGP parts. Look at the body to
decide. */
newvalue = get_msgcls_from_pgp_lines (message, &has_no_body);
if (!newvalue && has_no_body && ct && !strcmp (ct, "multipart/mixed"))
{
/* This is uncommon. But some Exchanges might break a PGP/MIME mail
this way. Let's take a look at the attachments. Maybe it's
a PGP/MIME mail. */
log_debug ("%s:%s: Multipart mixed without body found. Looking at attachments.",
SRCNAME, __func__);
newvalue = get_msgcls_from_first_attachment (message);
}
}
xfree (ct);
return newvalue;
}
/* Helper for mapi_change_message_class. Returns the new message
class as an allocated string.
This function is used for the message class "IPM.Note.SMIME". It
indicates an S/MIME opaque encrypted or signed message. This may
also be an PGP/MIME mail. */
static char *
change_message_class_ipm_note_smime (LPMESSAGE message)
{
char *newvalue = NULL;
char *ct, *proto, *smtype;
ct = mapi_get_message_content_type (message, &proto, &smtype);
if (ct)
{
log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
if (proto
&& !strcmp (ct, "multipart/signed")
&& !strcmp (proto, "application/pgp-signature"))
{
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
}
else if (ct && !strcmp (ct, "application/ms-tnef"))
{
/* So no PGP Inline. Lets look at the attachment. */
char *attach_mime = get_first_attach_mime_tag (message);
if (!attach_mime)
{
xfree (ct);
xfree (proto);
return nullptr;
}
if (!strcmp (attach_mime, "multipart/signed"))
{
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
xfree (attach_mime);
}
}
else if (!opt.enable_smime)
; /* S/MIME not enabled; thus no further checks. */
else if (smtype)
{
log_debug ("%s:%s: smime-type is '%s'", SRCNAME, __func__, smtype);
if (!strcmp (ct, "application/pkcs7-mime")
|| !strcmp (ct, "application/x-pkcs7-mime"))
{
if (!strcmp (smtype, "signed-data"))
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
else if (!strcmp (smtype, "enveloped-data"))
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
}
}
else
{
/* No smime type. The filename parameter is often not
reliable, thus we better look into the message to see if
it is encrypted and assume an opaque signed one if this
is not the case. */
switch (is_really_cms_encrypted (message))
{
case 0:
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
break;
case 1:
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
break;
}
}
xfree (smtype);
xfree (proto);
xfree (ct);
}
else
{
log_debug ("%s:%s: message has no content type", SRCNAME, __func__);
/* CryptoEx (or the Toltec Connector) create messages without
the transport headers property and thus we don't know the
content type. We try to detect the message type anyway by
looking into the first and only attachments. */
switch (is_really_cms_encrypted (message))
{
case 0:
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
break;
case 1:
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
break;
default: /* Unknown. */
break;
}
}
/* If we did not found anything but let's change the class anyway. */
if (!newvalue && opt.enable_smime)
newvalue = xstrdup ("IPM.Note.GpgOL");
return newvalue;
}
/* Helper for mapi_change_message_class. Returns the new message
class as an allocated string.
This function is used for the message class
"IPM.Note.SMIME.MultipartSigned". This is an S/MIME message class
but smime support is not enabled. We need to check whether this is
actually a PGP/MIME message. */
static char *
change_message_class_ipm_note_smime_multipartsigned (LPMESSAGE message)
{
char *newvalue = NULL;
char *ct, *proto;
ct = mapi_get_message_content_type (message, &proto, NULL);
if (ct)
{
log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
if (proto
&& !strcmp (ct, "multipart/signed")
&& !strcmp (proto, "application/pgp-signature"))
{
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
}
else if (!strcmp (ct, "wks.confirmation.mail"))
{
newvalue = xstrdup ("IPM.Note.GpgOL.WKSConfirmation");
}
else if (ct && !strcmp (ct, "application/ms-tnef"))
{
/* So no PGP Inline. Lets look at the attachment. */
char *attach_mime = get_first_attach_mime_tag (message);
if (!attach_mime)
{
xfree (ct);
xfree (proto);
return nullptr;
}
if (!strcmp (attach_mime, "multipart/signed"))
{
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
xfree (attach_mime);
}
}
xfree (proto);
xfree (ct);
}
else
log_debug ("%s:%s: message has no content type", SRCNAME, __func__);
return newvalue;
}
/* Helper for mapi_change_message_class. Returns the new message
class as an allocated string.
This function is used for the message classes
"IPM.Note.Secure.CexSig" and "IPM.Note.Secure.Cexenc" (in the
latter case IS_CEXSIG is true). These are CryptoEx generated
signature or encryption messages. */
static char *
change_message_class_ipm_note_secure_cex (LPMESSAGE message, int is_cexenc)
{
char *newvalue = NULL;
char *ct, *smtype, *proto;
ct = mapi_get_message_content_type (message, &proto, &smtype);
if (ct)
{
log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
if (smtype)
log_debug ("%s:%s: smime-type is '%s'", SRCNAME, __func__, smtype);
if (proto)
log_debug ("%s:%s: protocol is '%s'", SRCNAME, __func__, proto);
if (smtype)
{
if (!strcmp (ct, "application/pkcs7-mime")
|| !strcmp (ct, "application/x-pkcs7-mime"))
{
if (!strcmp (smtype, "signed-data"))
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
else if (!strcmp (smtype, "enveloped-data"))
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
}
}
if (!newvalue && proto)
{
if (!strcmp (ct, "multipart/signed")
&& (!strcmp (proto, "application/pkcs7-signature")
|| !strcmp (proto, "application/x-pkcs7-signature")))
{
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
}
else if (!strcmp (ct, "multipart/signed")
&& (!strcmp (proto, "application/pgp-signature")))
{
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
}
}
if (!newvalue && (!strcmp (ct, "text/plain") ||
!strcmp (ct, "multipart/alternative") ||
!strcmp (ct, "multipart/mixed")))
{
newvalue = get_msgcls_from_pgp_lines (message);
}
if (!newvalue)
{
switch (is_really_cms_encrypted (message))
{
case 0:
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
break;
case 1:
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
break;
}
}
xfree (smtype);
xfree (proto);
xfree (ct);
}
else
{
log_debug ("%s:%s: message has no content type", SRCNAME, __func__);
if (is_cexenc)
{
switch (is_really_cms_encrypted (message))
{
case 0:
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
break;
case 1:
newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
break;
}
}
else
{
char *mimetag;
mimetag = get_first_attach_mime_tag (message);
if (mimetag && !strcmp (mimetag, "multipart/signed"))
newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
xfree (mimetag);
}
if (!newvalue)
{
newvalue = get_msgcls_from_pgp_lines (message);
}
}
if (!newvalue)
newvalue = xstrdup ("IPM.Note.GpgOL");
return newvalue;
}
static msgtype_t
string_to_type (const char *s)
{
if (!s || strlen (s) < 14)
{
return MSGTYPE_UNKNOWN;
}
if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.'))
{
s += 14;
if (!*s)
return MSGTYPE_GPGOL;
else if (!strcmp (s, ".MultipartSigned"))
return MSGTYPE_GPGOL_MULTIPART_SIGNED;
else if (!strcmp (s, ".MultipartEncrypted"))
return MSGTYPE_GPGOL_MULTIPART_ENCRYPTED;
else if (!strcmp (s, ".OpaqueSigned"))
return MSGTYPE_GPGOL_OPAQUE_SIGNED;
else if (!strcmp (s, ".OpaqueEncrypted"))
return MSGTYPE_GPGOL_OPAQUE_ENCRYPTED;
else if (!strcmp (s, ".ClearSigned"))
return MSGTYPE_GPGOL_CLEAR_SIGNED;
else if (!strcmp (s, ".PGPMessage"))
return MSGTYPE_GPGOL_PGP_MESSAGE;
else if (!strcmp (s, ".WKSConfirmation"))
return MSGTYPE_GPGOL_WKS_CONFIRMATION;
else
log_debug ("%s:%s: message class `%s' not supported",
SRCNAME, __func__, s-14);
}
else if (!strncmp (s, "IPM.Note.SMIME", 14) && (!s[14] || s[14] =='.'))
return MSGTYPE_SMIME;
return MSGTYPE_UNKNOWN;
}
/* This function checks whether MESSAGE requires processing by us and
adjusts the message class to our own. By passing true for
SYNC_OVERRIDE the actual MAPI message class will be updated to our
own message class overide. Return true if the message was
changed. */
int
mapi_change_message_class (LPMESSAGE message, int sync_override,
msgtype_t *r_type)
{
HRESULT hr;
ULONG tag;
SPropValue prop;
LPSPropValue propval = NULL;
char *newvalue = NULL;
int need_save = 0;
int have_override = 0;
if (!message)
return 0; /* No message: Nop. */
if (get_gpgolmsgclass_tag (message, &tag) )
return 0; /* Ooops. */
hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
if (FAILED (hr))
{
hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
if (FAILED (hr))
{
log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return 0;
}
}
else
{
have_override = 1;
log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
}
if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
{
const char *s = propval->Value.lpszA;
int cexenc = 0;
log_debug ("%s:%s: checking message class `%s'",
SRCNAME, __func__, s);
if (!strcmp (s, "IPM.Note"))
{
newvalue = change_message_class_ipm_note (message);
}
else if (opt.enable_smime && !strcmp (s, "IPM.Note.SMIME"))
{
newvalue = change_message_class_ipm_note_smime (message);
}
else if (opt.enable_smime
&& !strncmp (s, "IPM.Note.SMIME", 14) && (!s[14]||s[14] =='.'))
{
/* This is "IPM.Note.SMIME.foo" (where ".foo" is optional
but the previous condition has already taken care of
this). Note that we can't just insert a new part and
keep the SMIME; we need to change the SMIME part of the
class name so that Outlook does not process it as an
SMIME message. */
char *tmp = change_message_class_ipm_note_smime_multipartsigned
(message);
/* This case happens even for PGP/MIME mails but that is ok
as we later fiddle out the protocol. But we have to
check if this is a WKS Mail now so that we can do the
special handling for that. */
if (tmp && !strcmp (tmp, "IPM.Note.GpgOL.WKSConfirmation"))
{
newvalue = tmp;
}
else
{
xfree (tmp);
newvalue = (char*)xmalloc (strlen (s) + 1);
strcpy (stpcpy (newvalue, "IPM.Note.GpgOL"), s+14);
}
}
else if (!strcmp (s, "IPM.Note.SMIME.MultipartSigned"))
{
/* This is an S/MIME message class but smime support is not
enabled. We need to check whether this is actually a
PGP/MIME message. */
newvalue = change_message_class_ipm_note_smime_multipartsigned
(message);
}
else if (sync_override && have_override
&& !strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14]||s[14] =='.'))
{
/* In case the original message class is not yet an GpgOL
class we set it here. This is needed to convince Outlook
not to do any special processing for IPM.Note.SMIME etc. */
LPSPropValue propval2 = NULL;
hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A,
&propval2);
if (!SUCCEEDED (hr))
{
log_debug ("%s:%s: Failed to get PR_MESSAGE_CLASS_A property.",
SRCNAME, __func__);
}
else if (PROP_TYPE (propval2->ulPropTag) != PT_STRING8)
{
log_debug ("%s:%s: PR_MESSAGE_CLASS_A is not string.",
SRCNAME, __func__);
}
else if (!propval2->Value.lpszA)
{
log_debug ("%s:%s: PR_MESSAGE_CLASS_A is null.",
SRCNAME, __func__);
}
else if (!strcmp (propval2->Value.lpszA, s))
{
log_debug ("%s:%s: PR_MESSAGE_CLASS_A is already the same.",
SRCNAME, __func__);
}
else
{
newvalue = (char*)xstrdup (s);
}
MAPIFreeBuffer (propval2);
}
else if (opt.enable_smime
&& (!strcmp (s, "IPM.Note.Secure.CexSig")
|| (cexenc = !strcmp (s, "IPM.Note.Secure.CexEnc"))))
{
newvalue = change_message_class_ipm_note_secure_cex
(message, cexenc);
}
if (r_type && !newvalue)
{
*r_type = string_to_type (s);
}
}
if (!newvalue)
{
/* We use our Sig-Status property to mark messages which passed
this function. This helps us to avoid later tests. */
if (!mapi_has_sig_status (message))
{
mapi_set_sig_status (message, "#");
need_save = 1;
}
}
else
{
if (r_type)
{
*r_type = string_to_type (newvalue);
}
/* Save old message class if not yet done. (The second
condition is just a failsafe check). */
if (!get_gpgololdmsgclass_tag (message, &tag)
&& PROP_TYPE (propval->ulPropTag) == PT_STRING8)
{
LPSPropValue propval2 = NULL;
hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval2);
if (!FAILED (hr))
MAPIFreeBuffer (propval2);
else
{
/* No such property - save it. */
log_debug ("%s:%s: saving old message class\n",
SRCNAME, __func__);
prop.ulPropTag = tag;
prop.Value.lpszA = propval->Value.lpszA;
hr = message->SetProps (1, &prop, NULL);
if (hr)
{
log_error ("%s:%s: can't save old message class: hr=%#lx\n",
SRCNAME, __func__, hr);
MAPIFreeBuffer (propval);
return 0;
}
need_save = 1;
}
}
/* Change message class. */
log_debug ("%s:%s: setting message class to `%s'\n",
SRCNAME, __func__, newvalue);
prop.ulPropTag = PR_MESSAGE_CLASS_A;
prop.Value.lpszA = newvalue;
hr = message->SetProps (1, &prop, NULL);
xfree (newvalue);
if (hr)
{
log_error ("%s:%s: can't set message class: hr=%#lx\n",
SRCNAME, __func__, hr);
MAPIFreeBuffer (propval);
return 0;
}
need_save = 1;
}
MAPIFreeBuffer (propval);
if (need_save)
{
if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE))
return 0;
}
return 1;
}
/* Return the message class. This function will never return NULL so
it is mostly useful for debugging. Caller needs to release the
returned string. */
char *
mapi_get_message_class (LPMESSAGE message)
{
HRESULT hr;
LPSPropValue propval = NULL;
char *retstr;
if (!message)
return xstrdup ("[No message]");
hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
if (FAILED (hr))
{
log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return xstrdup (hr == MAPI_E_NOT_FOUND?
"[No message class property]":
"[Error getting message class property]");
}
if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
retstr = xstrdup (propval->Value.lpszA);
else
retstr = xstrdup ("[Invalid message class property]");
MAPIFreeBuffer (propval);
return retstr;
}
/* Return the old message class. This function returns NULL if no old
message class has been saved. Caller needs to release the returned
string. */
char *
mapi_get_old_message_class (LPMESSAGE message)
{
HRESULT hr;
ULONG tag;
LPSPropValue propval = NULL;
char *retstr;
if (!message)
return NULL;
if (get_gpgololdmsgclass_tag (message, &tag))
return NULL;
hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
if (FAILED (hr))
{
log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return NULL;
}
if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
retstr = xstrdup (propval->Value.lpszA);
else
retstr = NULL;
MAPIFreeBuffer (propval);
return retstr;
}
/* Return the sender of the message. According to the specs this is
an UTF-8 string; we rely on that the UI server handles
internationalized domain names. */
char *
mapi_get_sender (LPMESSAGE message)
{
HRESULT hr;
LPSPropValue propval = NULL;
char *buf;
char *p0, *p;
if (!message)
return NULL; /* No message: Nop. */
hr = HrGetOneProp ((LPMAPIPROP)message, PR_PRIMARY_SEND_ACCT, &propval);
if (FAILED (hr))
{
log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return NULL;
}
if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE)
{
log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
MAPIFreeBuffer (propval);
return NULL;
}
buf = wchar_to_utf8 (propval->Value.lpszW);
MAPIFreeBuffer (propval);
if (!buf)
{
log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
return NULL;
}
/* The PR_PRIMARY_SEND_ACCT property seems to be divided into fields
using Ctrl-A as delimiter. The first field looks like the ascii
formatted number of fields to follow, the second field like the
email account and the third seems to be a textual description of
that account. We return the second field. */
p = strchr (buf, '\x01');
if (!p)
{
log_error ("%s:%s: unknown format of the value `%s'\n",
- SRCNAME, __func__, buf);
+ SRCNAME, __func__, anonstr (buf));
xfree (buf);
return NULL;
}
for (p0=buf, p++; *p && *p != '\x01';)
*p0++ = *p++;
*p0 = 0;
/* When using an Exchange account this is an X.509 address and not
an SMTP address. We try to detect this here and extract only the
CN RDN. Note that there are two CNs. This is just a simple
approach and not a real parser. A better way to do this would be
to ask MAPI to resolve the X.500 name to an SMTP name. */
if (strstr (buf, "/o=") && strstr (buf, "/ou=") &&
(p = strstr (buf, "/cn=Recipients")) && (p = strstr (p+1, "/cn=")))
{
- log_debug ("%s:%s: orig address is `%s'\n", SRCNAME, __func__, buf);
+ log_debug ("%s:%s: orig address is `%s'\n", SRCNAME, __func__,
+ anonstr (buf));
memmove (buf, p+4, strlen (p+4)+1);
if (!strchr (buf, '@'))
{
/* Some Exchange accounts return only the accoutn name and
no rfc821 mail address. Kleopatra chokes on that, thus
we append a domain name. Thisis a bad hack. */
char *newbuf = (char *)xmalloc (strlen (buf) + 6 + 1);
strcpy (stpcpy (newbuf, buf), "@local");
xfree (buf);
buf = newbuf;
}
}
- log_debug ("%s:%s: address is `%s'\n", SRCNAME, __func__, buf);
+ log_debug ("%s:%s: address is `%s'\n", SRCNAME, __func__, anonstr (buf));
return buf;
}
static char *
resolve_ex_from_address (LPMESSAGE message)
{
HRESULT hr;
char *sender_entryid;
size_t entryidlen;
LPMAPISESSION session;
ULONG utype;
LPUNKNOWN user;
LPSPropValue propval = NULL;
char *buf;
if (g_ol_version_major < 14)
{
log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__);
return NULL;
}
sender_entryid = mapi_get_binary_prop (message, PR_SENDER_ENTRYID,
&entryidlen);
if (!sender_entryid)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
return NULL;
}
session = get_oom_mapi_session ();
if (!session)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
xfree (sender_entryid);
return NULL;
}
hr = session->OpenEntry (entryidlen, (LPENTRYID)sender_entryid,
&IID_IMailUser,
MAPI_BEST_ACCESS | MAPI_CACHE_ONLY,
&utype, (IUnknown**)&user);
if (FAILED (hr))
{
log_debug ("%s:%s: Failed to open cached entry. Fallback to uncached.",
SRCNAME, __func__);
hr = session->OpenEntry (entryidlen, (LPENTRYID)sender_entryid,
&IID_IMailUser,
MAPI_BEST_ACCESS,
&utype, (IUnknown**)&user);
}
gpgol_release (session);
if (FAILED (hr))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
return NULL;
}
hr = HrGetOneProp ((LPMAPIPROP)user, PR_SMTP_ADDRESS_W, &propval);
if (FAILED (hr))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
return NULL;
}
if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE)
{
log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
MAPIFreeBuffer (propval);
return NULL;
}
buf = wchar_to_utf8 (propval->Value.lpszW);
MAPIFreeBuffer (propval);
return buf;
}
/* Return the from address of the message as a malloced UTF-8 string.
Returns NULL if that address is not available. */
char *
mapi_get_from_address (LPMESSAGE message)
{
HRESULT hr;
LPSPropValue propval = NULL;
char *buf;
ULONG try_props[3] = {PidTagSenderSmtpAddress_W,
PR_SENT_REPRESENTING_SMTP_ADDRESS_W,
PR_SENDER_EMAIL_ADDRESS_W};
if (!message)
return xstrdup ("[no message]"); /* Ooops. */
for (int i = 0; i < 3; i++)
{
/* We try to get different properties first as they contain
the SMTP address of the sender. EMAIL address can be
some LDAP stuff for exchange. */
hr = HrGetOneProp ((LPMAPIPROP)message, try_props[i],
&propval);
if (!FAILED (hr))
{
break;
}
}
/* This is the last result that should always work but not necessarily
contain an SMTP Address. */
if (FAILED (hr))
{
log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return NULL;
}
if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE)
{
log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
MAPIFreeBuffer (propval);
return NULL;
}
buf = wchar_to_utf8 (propval->Value.lpszW);
MAPIFreeBuffer (propval);
if (!buf)
{
log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
return NULL;
}
if (strstr (buf, "/o="))
{
char *buf2;
/* If both SMTP Address properties are not set
we need to fallback to resolve the address
through the address book */
log_debug ("%s:%s: resolving exchange address.",
SRCNAME, __func__);
buf2 = resolve_ex_from_address (message);
if (buf2)
{
xfree (buf);
return buf2;
}
}
return buf;
}
/* Return the subject of the message as a malloced UTF-8 string.
Returns a replacement string if a subject is missing. */
char *
mapi_get_subject (LPMESSAGE message)
{
HRESULT hr;
LPSPropValue propval = NULL;
char *buf;
if (!message)
return xstrdup ("[no message]"); /* Ooops. */
hr = HrGetOneProp ((LPMAPIPROP)message, PR_SUBJECT_W, &propval);
if (FAILED (hr))
{
log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return xstrdup (_("[no subject]"));
}
if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE)
{
log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
MAPIFreeBuffer (propval);
return xstrdup (_("[no subject]"));
}
buf = wchar_to_utf8 (propval->Value.lpszW);
MAPIFreeBuffer (propval);
if (!buf)
{
log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
return xstrdup (_("[no subject]"));
}
return buf;
}
/* Return the message type. This function knows only about our own
message types. Returns MSGTYPE_UNKNOWN for any MESSAGE we have
no special support for. */
msgtype_t
mapi_get_message_type (LPMESSAGE message)
{
HRESULT hr;
ULONG tag;
LPSPropValue propval = NULL;
msgtype_t msgtype = MSGTYPE_UNKNOWN;
if (!message)
return msgtype;
if (get_gpgolmsgclass_tag (message, &tag) )
return msgtype; /* Ooops */
hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
if (FAILED (hr))
{
hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
if (FAILED (hr))
{
log_error ("%s:%s: HrGetOneProp(PR_MESSAGE_CLASS) failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return msgtype;
}
}
else
log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
{
msgtype = string_to_type (propval->Value.lpszA);
}
MAPIFreeBuffer (propval);
return msgtype;
}
/* This function is pretty useless because IConverterSession won't
take attachments into account. Need to write our own version. */
int
mapi_to_mime (LPMESSAGE message, const char *filename)
{
HRESULT hr;
LPCONVERTERSESSION session;
LPSTREAM stream;
hr = CoCreateInstance (CLSID_IConverterSession, NULL, CLSCTX_INPROC_SERVER,
IID_IConverterSession, (void **) &session);
if (FAILED (hr))
{
log_error ("%s:%s: can't create new IConverterSession object: hr=%#lx",
SRCNAME, __func__, hr);
return -1;
}
hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
(STGM_CREATE | STGM_READWRITE),
(char*)filename, NULL, &stream);
if (FAILED (hr))
{
log_error ("%s:%s: can't create file `%s': hr=%#lx\n",
SRCNAME, __func__, filename, hr);
hr = -1;
}
else
{
hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP);
if (FAILED (hr))
{
log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx",
SRCNAME, __func__, hr);
stream->Revert ();
hr = -1;
}
else
{
stream->Commit (0);
hr = 0;
}
gpgol_release (stream);
}
gpgol_release (session);
return hr;
}
/* Return a binary property in a malloced buffer with its length stored
at R_NBYTES. Returns NULL on error. */
char *
mapi_get_binary_prop (LPMESSAGE message, ULONG proptype, size_t *r_nbytes)
{
HRESULT hr;
LPSPropValue propval = NULL;
char *data;
*r_nbytes = 0;
hr = HrGetOneProp ((LPMAPIPROP)message, proptype, &propval);
if (FAILED (hr))
{
log_error ("%s:%s: error getting property %#lx: hr=%#lx",
SRCNAME, __func__, proptype, hr);
return NULL;
}
switch ( PROP_TYPE (propval->ulPropTag) )
{
case PT_BINARY:
/* This is a binary object but we know that it must be plain
ASCII due to the armored format. */
data = (char*)xmalloc (propval->Value.bin.cb + 1);
memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb);
data[propval->Value.bin.cb] = 0;
*r_nbytes = propval->Value.bin.cb;
break;
default:
log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
SRCNAME, __func__, proptype, propval->ulPropTag);
data = NULL;
break;
}
MAPIFreeBuffer (propval);
return data;
}
/* Return an integer property at R_VALUE. On error the function
returns -1 and sets R_VALUE to 0, on success 0 is returned. */
int
mapi_get_int_prop (LPMAPIPROP object, ULONG proptype, LONG *r_value)
{
int rc = -1;
HRESULT hr;
LPSPropValue propval = NULL;
*r_value = 0;
hr = HrGetOneProp (object, proptype, &propval);
if (FAILED (hr))
{
log_error ("%s:%s: error getting property %#lx: hr=%#lx",
SRCNAME, __func__, proptype, hr);
return -1;
}
switch ( PROP_TYPE (propval->ulPropTag) )
{
case PT_LONG:
*r_value = propval->Value.l;
rc = 0;
break;
default:
log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
SRCNAME, __func__, proptype, propval->ulPropTag);
break;
}
MAPIFreeBuffer (propval);
return rc;
}
/* Return the attachment method for attachment OBJ. In case of error
we return 0 which happens not to be defined. */
static int
get_attach_method (LPATTACH obj)
{
HRESULT hr;
LPSPropValue propval = NULL;
int method ;
hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
if (FAILED (hr))
{
log_error ("%s:%s: error getting attachment method: hr=%#lx",
SRCNAME, __func__, hr);
return 0;
}
/* We don't bother checking whether we really get a PT_LONG ulong
back; if not the system is seriously damaged and we can't do
further harm by returning a possible random value. */
method = propval->Value.l;
MAPIFreeBuffer (propval);
return method;
}
/* Return the filename from the attachment as a malloced string. The
encoding we return will be UTF-8, however the MAPI docs declare
that MAPI does only handle plain ANSI and thus we don't really care
later on. In fact we would need to convert the filename back to
wchar and use the Unicode versions of the file API. Returns NULL
on error or if no filename is available. */
static char *
get_attach_filename (LPATTACH obj)
{
HRESULT hr;
LPSPropValue propval;
char *name = NULL;
hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
if (FAILED(hr))
hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
if (FAILED(hr))
{
log_debug ("%s:%s: no filename property found", SRCNAME, __func__);
return NULL;
}
switch ( PROP_TYPE (propval->ulPropTag) )
{
case PT_UNICODE:
name = wchar_to_utf8 (propval->Value.lpszW);
if (!name)
log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
break;
case PT_STRING8:
name = xstrdup (propval->Value.lpszA);
break;
default:
log_debug ("%s:%s: proptag=%#lx not supported\n",
SRCNAME, __func__, propval->ulPropTag);
name = NULL;
break;
}
MAPIFreeBuffer (propval);
return name;
}
/* Return the content-id of the attachment OBJ or NULL if it does
not exists. Caller must free. */
static char *
get_attach_content_id (LPATTACH obj)
{
HRESULT hr;
LPSPropValue propval = NULL;
char *name;
hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_CONTENT_ID, &propval);
if (FAILED (hr))
{
if (hr != MAPI_E_NOT_FOUND)
log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
switch ( PROP_TYPE (propval->ulPropTag) )
{
case PT_UNICODE:
name = wchar_to_utf8 (propval->Value.lpszW);
if (!name)
log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
break;
case PT_STRING8:
name = xstrdup (propval->Value.lpszA);
break;
default:
log_debug ("%s:%s: proptag=%#lx not supported\n",
SRCNAME, __func__, propval->ulPropTag);
name = NULL;
break;
}
MAPIFreeBuffer (propval);
return name;
}
/* Return the content-type of the attachment OBJ or NULL if it does
not exists. Caller must free. */
static char *
get_attach_mime_tag (LPATTACH obj)
{
HRESULT hr;
LPSPropValue propval = NULL;
char *name;
hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval);
if (FAILED (hr))
{
if (hr != MAPI_E_NOT_FOUND)
log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
switch ( PROP_TYPE (propval->ulPropTag) )
{
case PT_UNICODE:
name = wchar_to_utf8 (propval->Value.lpszW);
if (!name)
log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
break;
case PT_STRING8:
name = xstrdup (propval->Value.lpszA);
break;
default:
log_debug ("%s:%s: proptag=%#lx not supported\n",
SRCNAME, __func__, propval->ulPropTag);
name = NULL;
break;
}
MAPIFreeBuffer (propval);
return name;
}
/* Return the GpgOL Attach Type for attachment OBJ. Tag needs to be
the tag of that property. */
attachtype_t
get_gpgolattachtype (LPATTACH obj, ULONG tag)
{
HRESULT hr;
LPSPropValue propval = NULL;
attachtype_t retval;
hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
if (FAILED (hr))
{
if (hr != MAPI_E_NOT_FOUND)
log_error ("%s:%s: error getting GpgOL Attach Type: hr=%#lx",
SRCNAME, __func__, hr);
return ATTACHTYPE_UNKNOWN;
}
retval = (attachtype_t)propval->Value.l;
MAPIFreeBuffer (propval);
return retval;
}
/* Gather information about attachments and return a new table of
attachments. Caller must release the returned table.s The routine
will return NULL in case of an error or if no attachments are
available. With FAST set only some information gets collected. */
mapi_attach_item_t *
mapi_create_attach_table (LPMESSAGE message, int fast)
{
HRESULT hr;
SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
LPMAPITABLE mapitable;
LPSRowSet mapirows;
mapi_attach_item_t *table;
unsigned int pos, n_attach;
ULONG moss_tag;
if (get_gpgolattachtype_tag (message, &moss_tag) )
return NULL;
/* Open the attachment table. */
hr = message->GetAttachmentTable (0, &mapitable);
memdbg_addRef (mapitable);
if (FAILED (hr))
{
log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
NULL, NULL, 0, &mapirows);
if (FAILED (hr))
{
log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
SRCNAME, __func__, hr);
gpgol_release (mapitable);
return NULL;
}
n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
log_debug ("%s:%s: message has %u attachments\n",
SRCNAME, __func__, n_attach);
if (!n_attach)
{
FreeProws (mapirows);
gpgol_release (mapitable);
return NULL;
}
/* Allocate our own table. */
table = (mapi_attach_item_t *)xcalloc (n_attach+1, sizeof *table);
for (pos=0; pos < n_attach; pos++)
{
LPATTACH att;
if (mapirows->aRow[pos].cValues < 1)
{
log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
table[pos].mapipos = -1;
continue;
}
if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
{
log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
table[pos].mapipos = -1;
continue;
}
table[pos].mapipos = mapirows->aRow[pos].lpProps[0].Value.l;
hr = message->OpenAttach (table[pos].mapipos, NULL,
MAPI_BEST_ACCESS, &att);
memdbg_addRef (att);
if (FAILED (hr))
{
log_error ("%s:%s: can't open attachment %d (%d): hr=%#lx",
SRCNAME, __func__, pos, table[pos].mapipos, hr);
table[pos].mapipos = -1;
continue;
}
table[pos].method = get_attach_method (att);
table[pos].filename = fast? NULL : get_attach_filename (att);
table[pos].content_type = fast? NULL : get_attach_mime_tag (att);
table[pos].content_id = fast? NULL : get_attach_content_id (att);
if (table[pos].content_type)
{
char *p = strchr (table[pos].content_type, ';');
if (p)
{
*p++ = 0;
trim_trailing_spaces (table[pos].content_type);
while (strchr (" \t\r\n", *p))
p++;
trim_trailing_spaces (p);
table[pos].content_type_parms = p;
}
}
table[pos].attach_type = get_gpgolattachtype (att, moss_tag);
gpgol_release (att);
}
table[0].private_mapitable = mapitable;
FreeProws (mapirows);
table[pos].end_of_table = 1;
mapitable = NULL;
if (fast)
{
log_debug ("%s:%s: attachment info: not shown due to fast flag\n",
SRCNAME, __func__);
}
else
{
log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__);
for (pos=0; !table[pos].end_of_table; pos++)
{
log_debug ("\t%d mt=%d fname=`%s' ct=`%s' ct_parms=`%s'\n",
table[pos].mapipos,
table[pos].attach_type,
- table[pos].filename, table[pos].content_type,
+ anonstr (table[pos].filename), table[pos].content_type,
table[pos].content_type_parms);
}
}
return table;
}
/* Release a table as created by mapi_create_attach_table. */
void
mapi_release_attach_table (mapi_attach_item_t *table)
{
unsigned int pos;
LPMAPITABLE mapitable;
if (!table)
return;
mapitable = (LPMAPITABLE)table[0].private_mapitable;
if (mapitable)
gpgol_release (mapitable);
for (pos=0; !table[pos].end_of_table; pos++)
{
xfree (table[pos].filename);
xfree (table[pos].content_type);
xfree (table[pos].content_id);
}
xfree (table);
}
/* Return an attachment as a new IStream object. Returns NULL on
failure. If R_ATTACH is not NULL the actual attachment will not be
released but stored at that address; the caller needs to release it
in this case. */
LPSTREAM
mapi_get_attach_as_stream (LPMESSAGE message, mapi_attach_item_t *item,
LPATTACH *r_attach)
{
HRESULT hr;
LPATTACH att;
LPSTREAM stream;
if (r_attach)
*r_attach = NULL;
if (!item || item->end_of_table || item->mapipos == -1)
return NULL;
hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
memdbg_addRef (att);
if (FAILED (hr))
{
log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
SRCNAME, __func__, item->mapipos, hr);
return NULL;
}
if (item->method != ATTACH_BY_VALUE)
{
log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
gpgol_release (att);
return NULL;
}
hr = gpgol_openProperty (att, PR_ATTACH_DATA_BIN, &IID_IStream,
0, 0, (LPUNKNOWN*) &stream);
if (FAILED (hr))
{
log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
SRCNAME, __func__, hr);
gpgol_release (att);
return NULL;
}
if (r_attach)
*r_attach = att;
else
gpgol_release (att);
return stream;
}
/* Return a malloced buffer with the content of the attachment. If
R_NBYTES is not NULL the number of bytes will get stored there.
ATT must have an attachment method of ATTACH_BY_VALUE. Returns
NULL on error. If UNPROTECT is set and the appropriate crypto
attribute is available, the function returns the unprotected
version of the atatchment. */
static char *
attach_to_buffer (LPATTACH att, size_t *r_nbytes)
{
HRESULT hr;
LPSTREAM stream;
STATSTG statInfo;
ULONG nread;
char *buffer;
hr = gpgol_openProperty (att, PR_ATTACH_DATA_BIN, &IID_IStream,
0, 0, (LPUNKNOWN*) &stream);
if (FAILED (hr))
{
log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
hr = stream->Stat (&statInfo, STATFLAG_NONAME);
if ( hr != S_OK )
{
log_error ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
gpgol_release (stream);
return NULL;
}
/* Allocate one byte more so that we can terminate the string. */
buffer = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 1);
hr = stream->Read (buffer, (size_t)statInfo.cbSize.QuadPart, &nread);
if ( hr != S_OK )
{
log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
xfree (buffer);
gpgol_release (stream);
return NULL;
}
if (nread != statInfo.cbSize.QuadPart)
{
log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
xfree (buffer);
buffer = NULL;
}
gpgol_release (stream);
/* Make sure that the buffer is a C string. */
if (buffer)
buffer[nread] = 0;
if (r_nbytes)
*r_nbytes = nread;
return buffer;
}
/* Return an attachment as a malloced buffer. The size of the buffer
will be stored at R_NBYTES. If unprotect is true, the atatchment
will be unprotected. Returns NULL on failure. */
char *
mapi_get_attach (LPMESSAGE message,
mapi_attach_item_t *item, size_t *r_nbytes)
{
HRESULT hr;
LPATTACH att;
char *buffer;
if (!item || item->end_of_table || item->mapipos == -1)
return NULL;
hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
memdbg_addRef (att);
if (FAILED (hr))
{
log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
SRCNAME, __func__, item->mapipos, hr);
return NULL;
}
if (item->method != ATTACH_BY_VALUE)
{
log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
gpgol_release (att);
return NULL;
}
buffer = attach_to_buffer (att, r_nbytes);
gpgol_release (att);
return buffer;
}
/* Mark this attachment as the original MOSS message. We set a custom
property as well as the hidden flag. */
int
mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item)
{
int retval = -1;
HRESULT hr;
LPATTACH att;
SPropValue prop;
if (!item || item->end_of_table || item->mapipos == -1)
return -1;
log_debug ("%s:%s: Marking %i as MOSS attachment",
SRCNAME, __func__, item->mapipos);
hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
memdbg_addRef (att);
if (FAILED (hr))
{
log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
SRCNAME, __func__, item->mapipos, hr);
return -1;
}
if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
goto leave;
prop.Value.l = ATTACHTYPE_MOSS;
hr = HrSetOneProp (att, &prop);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
SRCNAME, __func__, "GpgOL Attach Type", hr);
return false;
}
prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
prop.Value.b = TRUE;
hr = HrSetOneProp (att, &prop);
if (hr)
{
log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
hr = att->SaveChanges (KEEP_OPEN_READWRITE);
if (hr)
{
log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
retval = 0;
leave:
gpgol_release (att);
return retval;
}
/* If the hidden property has not been set on ATTACH, set it and save
the changes. */
int
mapi_set_attach_hidden (LPATTACH attach)
{
int retval = -1;
HRESULT hr;
LPSPropValue propval;
SPropValue prop;
hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
if (SUCCEEDED (hr)
&& PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN
&& propval->Value.b)
return 0;/* Already set to hidden. */
prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
prop.Value.b = TRUE;
hr = HrSetOneProp (attach, &prop);
if (hr)
{
log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
hr = attach->SaveChanges (KEEP_OPEN_READWRITE);
if (hr)
{
log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
retval = 0;
leave:
return retval;
}
/* Returns true if ATTACH has the hidden flag set to true. */
int
mapi_test_attach_hidden (LPATTACH attach)
{
HRESULT hr;
LPSPropValue propval = NULL;
int result = 0;
hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
if (FAILED (hr))
return result; /* No. */
if (PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN && propval->Value.b)
result = 1; /* Yes. */
MAPIFreeBuffer (propval);
return result;
}
/* Returns True if MESSAGE has the GpgOL Sig Status property. */
int
mapi_has_sig_status (LPMESSAGE msg)
{
HRESULT hr;
LPSPropValue propval = NULL;
ULONG tag;
int yes;
if (get_gpgolsigstatus_tag (msg, &tag) )
return 0; /* Error: Assume No. */
hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
if (FAILED (hr))
return 0; /* No. */
if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
yes = 1;
else
yes = 0;
MAPIFreeBuffer (propval);
return yes;
}
/* Returns True if MESSAGE has a GpgOL Sig Status property and that it
is not set to unchecked. */
int
mapi_test_sig_status (LPMESSAGE msg)
{
HRESULT hr;
LPSPropValue propval = NULL;
ULONG tag;
int yes;
if (get_gpgolsigstatus_tag (msg, &tag) )
return 0; /* Error: Assume No. */
hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
if (FAILED (hr))
return 0; /* No. */
/* We return False if we have an unknown signature status (?) or the
message has been sent by us and not yet checked (@). */
if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
yes = !(propval->Value.lpszA && (!strcmp (propval->Value.lpszA, "?")
|| !strcmp (propval->Value.lpszA, "@")));
else
yes = 0;
MAPIFreeBuffer (propval);
return yes;
}
/* Return the signature status as an allocated string. Will never
return NULL. */
char *
mapi_get_sig_status (LPMESSAGE msg)
{
HRESULT hr;
LPSPropValue propval = NULL;
ULONG tag;
char *retstr;
if (get_gpgolsigstatus_tag (msg, &tag) )
return xstrdup ("[Error getting tag for sig status]");
hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
if (FAILED (hr))
return xstrdup ("");
if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
retstr = xstrdup (propval->Value.lpszA);
else
retstr = xstrdup ("[Sig status has an invalid type]");
MAPIFreeBuffer (propval);
return retstr;
}
/* Set the signature status property to STATUS_STRING. There are a
few special values:
"#" The message is not of interest to us.
"@" The message has been created and signed or encrypted by us.
"?" The signature status has not been checked.
"!" The signature verified okay
"~" The signature was not fully verified.
"-" The signature is bad
Note that this function does not call SaveChanges. */
int
mapi_set_sig_status (LPMESSAGE message, const char *status_string)
{
HRESULT hr;
SPropValue prop;
if (get_gpgolsigstatus_tag (message, &prop.ulPropTag) )
return -1;
prop.Value.lpszA = xstrdup (status_string);
hr = HrSetOneProp (message, &prop);
xfree (prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
SRCNAME, __func__, "GpgOL Sig Status", hr);
return -1;
}
return 0;
}
/* When sending a message we need to fake the message class so that OL
processes it according to our needs. However, if we later try to
get the message class from the sent message, OL still has the SMIME
message class and tries to hide this by trying to decrypt the
message and return the message class from the plaintext. To
mitigate the problem we define our own msg class override
property. */
int
mapi_set_gpgol_msg_class (LPMESSAGE message, const char *name)
{
HRESULT hr;
SPropValue prop;
if (get_gpgolmsgclass_tag (message, &prop.ulPropTag) )
return -1;
prop.Value.lpszA = xstrdup (name);
hr = HrSetOneProp (message, &prop);
xfree (prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
SRCNAME, __func__, "GpgOL Msg Class", hr);
return -1;
}
return 0;
}
/* Return the charset as assigned by GpgOL to an attachment. This may
return NULL it is has not been assigned or is the standard
(UTF-8). */
char *
mapi_get_gpgol_charset (LPMESSAGE obj)
{
HRESULT hr;
LPSPropValue propval = NULL;
ULONG tag;
char *retstr;
if (get_gpgolcharset_tag (obj, &tag) )
return NULL; /* Error. */
hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
if (FAILED (hr))
return NULL;
if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
{
if (!strcmp (propval->Value.lpszA, "utf-8"))
retstr = NULL;
else
retstr = xstrdup (propval->Value.lpszA);
}
else
retstr = NULL;
MAPIFreeBuffer (propval);
return retstr;
}
/* Set the GpgOl charset property to an attachment.
Note that this function does not call SaveChanges. */
int
mapi_set_gpgol_charset (LPMESSAGE obj, const char *charset)
{
HRESULT hr;
SPropValue prop;
char *p;
/* Note that we lowercase the value and cut it to a max of 32
characters. The latter is required to make sure that
HrSetOneProp will always work. */
if (get_gpgolcharset_tag (obj, &prop.ulPropTag) )
return -1;
prop.Value.lpszA = xstrdup (charset);
for (p=prop.Value.lpszA; *p; p++)
*p = tolower (*(unsigned char*)p);
if (strlen (prop.Value.lpszA) > 32)
prop.Value.lpszA[32] = 0;
hr = HrSetOneProp ((LPMAPIPROP)obj, &prop);
xfree (prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
SRCNAME, __func__, "GpgOL Charset", hr);
return -1;
}
return 0;
}
/* Return GpgOL's draft info string as an allocated string. If no
draft info is available, NULL is returned. */
char *
mapi_get_gpgol_draft_info (LPMESSAGE msg)
{
HRESULT hr;
LPSPropValue propval = NULL;
ULONG tag;
char *retstr;
if (get_gpgoldraftinfo_tag (msg, &tag) )
return NULL;
hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
if (FAILED (hr))
return NULL;
if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
retstr = xstrdup (propval->Value.lpszA);
else
retstr = NULL;
MAPIFreeBuffer (propval);
return retstr;
}
/* Set GpgOL's draft info string to STRING. This string is defined as:
Character 1: 'E' = encrypt selected,
'e' = encrypt not selected.
'-' = don't care
Character 2: 'S' = sign selected,
's' = sign not selected.
'-' = don't care
Character 3: 'A' = Auto protocol
'P' = OpenPGP protocol
'X' = S/MIME protocol
'-' = don't care
If string is NULL, the property will get deleted.
Note that this function does not call SaveChanges. */
int
mapi_set_gpgol_draft_info (LPMESSAGE message, const char *string)
{
HRESULT hr;
SPropValue prop;
SPropTagArray proparray;
if (get_gpgoldraftinfo_tag (message, &prop.ulPropTag) )
return -1;
if (string)
{
prop.Value.lpszA = xstrdup (string);
hr = HrSetOneProp (message, &prop);
xfree (prop.Value.lpszA);
}
else
{
proparray.cValues = 1;
proparray.aulPropTag[0] = prop.ulPropTag;
hr = message->DeleteProps (&proparray, NULL);
}
if (hr)
{
log_error ("%s:%s: can't %s %s property: hr=%#lx\n",
SRCNAME, __func__, string?"set":"delete",
"GpgOL Draft Info", hr);
return -1;
}
return 0;
}
/* Return the MIME info as an allocated string. Will never return
NULL. */
char *
mapi_get_mime_info (LPMESSAGE msg)
{
HRESULT hr;
LPSPropValue propval = NULL;
ULONG tag;
char *retstr;
if (get_gpgolmimeinfo_tag (msg, &tag) )
return xstrdup ("[Error getting tag for MIME info]");
hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
if (FAILED (hr))
return xstrdup ("");
if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
retstr = xstrdup (propval->Value.lpszA);
else
retstr = xstrdup ("[MIME info has an invalid type]");
MAPIFreeBuffer (propval);
return retstr;
}
/* Helper around mapi_get_gpgol_draft_info to avoid
the string handling.
Return values are:
0 -> Do nothing
1 -> Encrypt
2 -> Sign
3 -> Encrypt & Sign*/
int
get_gpgol_draft_info_flags (LPMESSAGE message)
{
char *buf = mapi_get_gpgol_draft_info (message);
int ret = 0;
if (!buf)
{
return 0;
}
if (buf[0] == 'E')
{
ret |= 1;
}
if (buf[1] == 'S')
{
ret |= 2;
}
xfree (buf);
return ret;
}
/* Sets the draft info flags. Protocol is always Auto.
flags should be the same as defined by
get_gpgol_draft_info_flags
*/
int
set_gpgol_draft_info_flags (LPMESSAGE message, int flags)
{
char buf[4];
buf[3] = '\0';
buf[2] = 'A'; /* Protocol */
buf[1] = flags & 2 ? 'S' : 's';
buf[0] = flags & 1 ? 'E' : 'e';
return mapi_set_gpgol_draft_info (message, buf);
}
/* Helper for mapi_get_msg_content_type() */
static int
get_message_content_type_cb (void *dummy_arg,
rfc822parse_event_t event, rfc822parse_t msg)
{
(void)dummy_arg;
(void)msg;
if (event == RFC822PARSE_T2BODY)
return 42; /* Hack to stop the parsing after having read the
outer headers. */
return 0;
}
/* Return Content-Type of the current message. This one is taken
directly from the rfc822 header. If R_PROTOCOL is not NULL a
string with the protocol parameter will be stored at this address,
if no protocol is given NULL will be stored. If R_SMTYPE is not
NULL a string with the smime-type parameter will be stored there.
Caller must release all returned strings. */
char *
mapi_get_message_content_type (LPMESSAGE message,
char **r_protocol, char **r_smtype)
{
rfc822parse_t msg;
const char *header_lines, *s;
rfc822parse_field_t ctx;
size_t length;
char *retstr = NULL;
if (r_protocol)
*r_protocol = NULL;
if (r_smtype)
*r_smtype = NULL;
/* Read the headers into an rfc822 object. */
msg = rfc822parse_open (get_message_content_type_cb, NULL);
if (!msg)
{
log_error ("%s:%s: rfc822parse_open failed",
SRCNAME, __func__);
return NULL;
}
const std::string hdrStr = mapi_get_header (message);
if (hdrStr.empty())
{
log_error ("%s:%s: failed to get headers",
SRCNAME, __func__);
rfc822parse_close (msg);
return NULL;
}
header_lines = hdrStr.c_str();
while ((s = strchr (header_lines, '\n')))
{
length = (s - header_lines);
if (length && s[-1] == '\r')
length--;
if (!strncmp ("Wks-Phase: confirm", header_lines,
std::max (18, (int) length)))
{
log_debug ("%s:%s: detected wks confirmation mail",
SRCNAME, __func__);
retstr = xstrdup ("wks.confirmation.mail");
rfc822parse_close (msg);
return retstr;
}
rfc822parse_insert (msg, (const unsigned char*)header_lines, length);
header_lines = s+1;
}
/* Parse the content-type field. */
ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
if (ctx)
{
const char *s1, *s2;
s1 = rfc822parse_query_media_type (ctx, &s2);
if (s1)
{
retstr = (char*)xmalloc (strlen (s1) + 1 + strlen (s2) + 1);
strcpy (stpcpy (stpcpy (retstr, s1), "/"), s2);
if (r_protocol)
{
s = rfc822parse_query_parameter (ctx, "protocol", 0);
if (s)
*r_protocol = xstrdup (s);
}
if (r_smtype)
{
s = rfc822parse_query_parameter (ctx, "smime-type", 0);
if (s)
*r_smtype = xstrdup (s);
}
}
rfc822parse_release_field (ctx);
}
rfc822parse_close (msg);
return retstr;
}
/* Returns True if MESSAGE has a GpgOL Last Decrypted property with any value.
This indicates that there should be no PR_BODY tag. */
int
mapi_has_last_decrypted (LPMESSAGE message)
{
HRESULT hr;
LPSPropValue propval = NULL;
ULONG tag;
int yes = 0;
if (get_gpgollastdecrypted_tag (message, &tag) )
return 0; /* No. */
hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
if (FAILED (hr))
return 0; /* No. */
if (PROP_TYPE (propval->ulPropTag) == PT_BINARY)
yes = 1;
MAPIFreeBuffer (propval);
return yes;
}
/* Helper to check whether the file name of OBJ is "smime.p7m".
Returns on true if so. */
static int
has_smime_filename (LPATTACH obj)
{
HRESULT hr;
LPSPropValue propval;
int yes = 0;
hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
if (FAILED(hr))
{
hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
if (FAILED(hr))
return 0;
}
if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
{
if (!wcscmp (propval->Value.lpszW, L"smime.p7m"))
yes = 1;
}
else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
{
if (!strcmp (propval->Value.lpszA, "smime.p7m"))
yes = 1;
}
MAPIFreeBuffer (propval);
return yes;
}
/* Copy the MAPI body to a PGPBODY type attachment. */
int
mapi_body_to_attachment (LPMESSAGE message)
{
HRESULT hr;
LPSTREAM instream;
ULONG newpos;
LPATTACH newatt = NULL;
SPropValue prop;
LPSTREAM outstream = NULL;
LPUNKNOWN punk;
char body_filename[] = PGPBODYFILENAME;
instream = mapi_get_body_as_stream (message);
if (!instream)
return -1;
log_debug ("%s:%s: Creating MOSS body attachment",
SRCNAME, __func__);
hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
if (hr)
{
log_error ("%s:%s: can't create attachment: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
prop.ulPropTag = PR_ATTACH_METHOD;
prop.Value.ul = ATTACH_BY_VALUE;
hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
if (hr)
{
log_error ("%s:%s: can't set attach method: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
/* Mark that attachment so that we know why it has been created. */
if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
goto leave;
prop.Value.l = ATTACHTYPE_PGPBODY;
hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
SRCNAME, __func__, "GpgOL Attach Type", hr);
goto leave;
}
prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
prop.Value.b = TRUE;
hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
if (hr)
{
log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
prop.ulPropTag = PR_ATTACH_FILENAME_A;
prop.Value.lpszA = body_filename;
hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
if (hr)
{
log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
punk = (LPUNKNOWN)outstream;
hr = gpgol_openProperty (newatt, PR_ATTACH_DATA_BIN, &IID_IStream, 0,
MAPI_CREATE|MAPI_MODIFY, &punk);
if (FAILED (hr))
{
log_error ("%s:%s: can't create output stream: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
outstream = (LPSTREAM)punk;
/* Insert a blank line so that our mime parser skips over the mail
headers. */
hr = outstream->Write ("\r\n", 2, NULL);
if (hr)
{
log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr);
goto leave;
}
{
ULARGE_INTEGER cb;
cb.QuadPart = 0xffffffffffffffffll;
hr = instream->CopyTo (outstream, cb, NULL, NULL);
}
if (hr)
{
log_error ("%s:%s: can't copy streams: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
hr = outstream->Commit (0);
if (hr)
{
log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
SRCNAME, __func__, hr);
goto leave;
}
gpgol_release (outstream);
outstream = NULL;
hr = newatt->SaveChanges (0);
if (hr)
{
log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
SRCNAME, __func__, hr);
goto leave;
}
gpgol_release (newatt);
newatt = NULL;
hr = mapi_save_changes (message, KEEP_OPEN_READWRITE);
leave:
if (outstream)
{
outstream->Revert ();
gpgol_release (outstream);
}
if (newatt)
gpgol_release (newatt);
gpgol_release (instream);
return hr? -1:0;
}
int
mapi_mark_or_create_moss_attach (LPMESSAGE message, msgtype_t msgtype)
{
int i;
if (msgtype == MSGTYPE_UNKNOWN ||
msgtype == MSGTYPE_GPGOL)
{
return 0;
}
/* First check if we already have one marked. */
mapi_attach_item_t *table = mapi_create_attach_table (message, 0);
int part1 = 0,
part2 = 0;
for (i = 0; table && !table[i].end_of_table; i++)
{
if (table[i].attach_type == ATTACHTYPE_PGPBODY ||
table[i].attach_type == ATTACHTYPE_MOSS ||
table[i].attach_type == ATTACHTYPE_MOSSTEMPL)
{
if (!part1)
{
part1 = i + 1;
}
else if (!part2)
{
/* If we have two MOSS attachments we use
the second one. */
part2 = i + 1;
break;
}
}
}
if (part1 || part2)
{
/* Found existing moss attachment */
mapi_release_attach_table (table);
/* Remark to ensure that it is hidden. As our revert
code must unhide it so that it is not stored in winmail.dat
but used as the mosstmpl. */
mapi_attach_item_t *item = table - 1 + (part2 ? part2 : part1);
LPATTACH att;
if (message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att) != S_OK)
{
log_error ("%s:%s: can't open attachment at %d",
SRCNAME, __func__, item->mapipos);
return -1;
}
memdbg_addRef (att);
if (!mapi_test_attach_hidden (att))
{
mapi_set_attach_hidden (att);
}
gpgol_release (att);
if (part2)
return part2;
return part1;
}
if (msgtype == MSGTYPE_GPGOL_CLEAR_SIGNED ||
msgtype == MSGTYPE_GPGOL_PGP_MESSAGE)
{
/* Inline message we need to create body attachment so that we
are able to restore the content. */
if (mapi_body_to_attachment (message))
{
log_error ("%s:%s: Failed to create body attachment.",
SRCNAME, __func__);
return 0;
}
log_debug ("%s:%s: Created body attachment. Repeating lookup.",
SRCNAME, __func__);
/* The position of the MOSS attach might change depending on
the attachment count of the mail. So repeat the check to get
the right position. */
return mapi_mark_or_create_moss_attach (message, msgtype);
}
if (!table)
{
log_debug ("%s:%s: Neither pgp inline nor an attachment table.",
SRCNAME, __func__);
return 0;
}
/* MIME Mails check for S/MIME first. */
for (i = 0; !table[i].end_of_table; i++)
{
if (table[i].content_type
&& (!strcmp (table[i].content_type, "application/pkcs7-mime")
|| !strcmp (table[i].content_type,
"application/x-pkcs7-mime"))
&& table[i].filename
&& !strcmp (table[i].filename, "smime.p7m"))
break;
}
if (!table[i].end_of_table)
{
mapi_mark_moss_attach (message, table + i);
mapi_release_attach_table (table);
return i + 1;
}
/* PGP/MIME or S/MIME stuff. */
/* Multipart/encrypted message: We expect 2 attachments.
The first one with the version number and the second one
with the ciphertext. As we don't know wether we are
called the first time, we first try to find these
attachments by looking at all attachments. Only if this
fails we identify them by their order (i.e. the first 2
attachments) and mark them as part1 and part2. */
for (i = 0; !table[i].end_of_table; i++); /* Count entries */
if (i >= 2)
{
int part1_idx = -1,
part2_idx = -1;
/* At least 2 attachments but none are marked. Thus we
assume that this is the first time we see this
message and we will set the mark now if we see
appropriate content types. */
if (table[0].content_type
&& !strcmp (table[0].content_type,
"application/pgp-encrypted"))
part1_idx = 0;
if (table[1].content_type
&& !strcmp (table[1].content_type,
"application/octet-stream"))
part2_idx = 1;
if (part1_idx != -1 && part2_idx != -1)
{
mapi_mark_moss_attach (message, table+part1_idx);
mapi_mark_moss_attach (message, table+part2_idx);
mapi_release_attach_table (table);
return 2;
}
}
if (!table[0].end_of_table && table[1].end_of_table)
{
/* No MOSS flag found in the table but there is only one
attachment. Due to the message type we know that this is
the original MOSS message. We mark this attachment as
hidden, so that it won't get displayed. We further mark
it as our original MOSS attachment so that after parsing
we have a mean to find it again (see above). */
mapi_mark_moss_attach (message, table + 0);
mapi_release_attach_table (table);
return 1;
}
mapi_release_attach_table (table);
return 0; /* No original attachment - this should not happen. */
}
diff --git a/src/memdbg.cpp b/src/memdbg.cpp
index 02d8172..a720e4f 100644
--- a/src/memdbg.cpp
+++ b/src/memdbg.cpp
@@ -1,323 +1,323 @@
/* @file memdbg.cpp
* @brief Memory debugging helpers
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "memdbg.h"
#include "common_indep.h"
#include <gpg-error.h>
#include <unordered_map>
#include <string>
std::unordered_map <std::string, int> cppObjs;
std::unordered_map <void *, int> olObjs;
std::unordered_map <void *, std::string> olNames;
std::unordered_map <void *, std::string> allocs;
GPGRT_LOCK_DEFINE (memdbg_log);
-#define DBGGUARD if (!(opt.enable_debug & DBG_OOM_EXTRA)) return
+#define DBGGUARD if (!(opt.enable_debug & DBG_MEMORY)) return
#ifdef HAVE_W32_SYSTEM
# include "oomhelp.h"
#endif
/* Returns true on a name change */
static bool
register_name (void *obj, const char *nameSuggestion)
{
#ifdef HAVE_W32_SYSTEM
char *name = get_object_name ((LPUNKNOWN)obj);
bool suggestionUsed = false;
if (!name && nameSuggestion)
{
name = xstrdup (nameSuggestion);
suggestionUsed = true;
}
if (!name)
{
auto it = olNames.find (obj);
if (it != olNames.end())
{
if (it->second != "unknown")
{
- log_debug ("%s:%s Ptr %p name change from %s to unknown",
- SRCNAME, __func__, obj, it->second.c_str());
+ log_memory ("%s:%s Ptr %p name change from %s to unknown",
+ SRCNAME, __func__, obj, it->second.c_str());
it->second = "unknown";
return true;
}
}
return false;
}
std::string sName = name;
xfree (name);
auto it = olNames.find (obj);
if (it != olNames.end())
{
if (it->second != sName)
{
- log_debug ("%s:%s Ptr %p name change from %s to %s",
+ log_memory ("%s:%s Ptr %p name change from %s to %s",
SRCNAME, __func__, obj, it->second.c_str(),
sName.c_str());
it->second = sName;
return !suggestionUsed;
}
}
else
{
olNames.insert (std::make_pair (obj, sName));
}
#else
(void) obj;
#endif
return false;
}
void
_memdbg_addRef (void *obj, const char *nameSuggestion)
{
DBGGUARD;
if (!obj)
{
return;
}
gpgrt_lock_lock (&memdbg_log);
auto it = olObjs.find (obj);
if (it == olObjs.end())
{
it = olObjs.insert (std::make_pair (obj, 0)).first;
}
if (register_name (obj, nameSuggestion) && it->second)
{
log_error ("%s:%s Name change without null ref on %p!",
SRCNAME, __func__, obj);
}
it->second++;
gpgrt_lock_unlock (&memdbg_log);
}
void
memdbg_released (void *obj)
{
DBGGUARD;
if (!obj)
{
return;
}
gpgrt_lock_lock (&memdbg_log);
auto it = olObjs.find (obj);
if (it == olObjs.end())
{
log_error ("%s:%s Released %p without query if!!",
SRCNAME, __func__, obj);
gpgrt_lock_unlock (&memdbg_log);
return;
}
it->second--;
if (it->second < 0)
{
log_error ("%s:%s Released %p below zero",
SRCNAME, __func__, obj);
}
gpgrt_lock_unlock (&memdbg_log);
}
void
memdbg_ctor (const char *objName)
{
DBGGUARD;
if (!objName)
{
TRACEPOINT;
return;
}
gpgrt_lock_lock (&memdbg_log);
const std::string nameStr (objName);
auto it = cppObjs.find (nameStr);
if (it == cppObjs.end())
{
it = cppObjs.insert (std::make_pair (nameStr, 0)).first;
}
it->second++;
gpgrt_lock_unlock (&memdbg_log);
}
void
memdbg_dtor (const char *objName)
{
DBGGUARD;
if (!objName)
{
TRACEPOINT;
return;
}
const std::string nameStr (objName);
auto it = cppObjs.find (nameStr);
if (it == cppObjs.end())
{
log_error ("%s:%s Dtor of %s before ctor",
SRCNAME, __func__, nameStr.c_str());
gpgrt_lock_unlock (&memdbg_log);
return;
}
it->second--;
if (it->second < 0)
{
log_error ("%s:%s Dtor of %s more often then ctor",
SRCNAME, __func__, nameStr.c_str());
}
gpgrt_lock_unlock (&memdbg_log);
}
void
_memdbg_alloc (void *ptr, const char *srcname, const char *func, int line)
{
DBGGUARD;
if (!ptr)
{
TRACEPOINT;
return;
}
gpgrt_lock_lock (&memdbg_log);
const std::string identifier = std::string (srcname) + std::string (":") +
std::string (func) + std::string (":") +
std::to_string (line);
auto it = allocs.find (ptr);
if (it != allocs.end())
{
TRACEPOINT;
gpgrt_lock_unlock (&memdbg_log);
return;
}
allocs.insert (std::make_pair (ptr, identifier));
gpgrt_lock_unlock (&memdbg_log);
}
int
memdbg_free (void *ptr)
{
DBGGUARD false;
if (!ptr)
{
TRACEPOINT;
return false;
}
gpgrt_lock_lock (&memdbg_log);
auto it = allocs.find (ptr);
if (it == allocs.end())
{
log_error ("%s:%s Free unregistered: %p",
SRCNAME, __func__, ptr);
gpgrt_lock_unlock (&memdbg_log);
return false;
}
allocs.erase (it);
gpgrt_lock_unlock (&memdbg_log);
return true;
}
void
memdbg_dump ()
{
DBGGUARD;
gpgrt_lock_lock (&memdbg_log);
- log_debug(""
+ log_memory (""
"------------------------------MEMORY DUMP----------------------------------");
- log_debug("-- C++ Objects --");
+ log_memory("-- C++ Objects --");
for (const auto &pair: cppObjs)
{
- log_debug("%s\t: %i", pair.first.c_str(), pair.second);
+ log_memory("%s\t: %i", pair.first.c_str(), pair.second);
}
- log_debug("-- C++ End --");
- log_debug("-- OL Objects --");
+ log_memory("-- C++ End --");
+ log_memory("-- OL Objects --");
for (const auto &pair: olObjs)
{
if (!pair.second)
{
continue;
}
const auto it = olNames.find (pair.first);
if (it == olNames.end())
{
- log_debug("%p\t: %i", pair.first, pair.second);
+ log_memory("%p\t: %i", pair.first, pair.second);
}
else
{
- log_debug("%p:%s\t: %i", pair.first,
+ log_memory("%p:%s\t: %i", pair.first,
it->second.c_str (), pair.second);
}
}
- log_debug("-- OL End --");
- log_debug("-- Allocated Addresses --");
+ log_memory("-- OL End --");
+ log_memory("-- Allocated Addresses --");
for (const auto &pair: allocs)
{
- log_debug ("%s: %p", pair.second.c_str(), pair.first);
+ log_memory ("%s: %p", pair.second.c_str(), pair.first);
}
- log_debug("-- Allocated Addresses End --");
+ log_memory("-- Allocated Addresses End --");
- log_debug(""
+ log_memory(""
"------------------------------MEMORY END ----------------------------------");
gpgrt_lock_unlock (&memdbg_log);
}
diff --git a/src/oomhelp.cpp b/src/oomhelp.cpp
index 143f13f..f7d7bc5 100644
--- a/src/oomhelp.cpp
+++ b/src/oomhelp.cpp
@@ -1,2585 +1,2582 @@
/* 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <windows.h>
#include <olectl.h>
#include <string>
#include <rpc.h>
#include "common.h"
#include "oomhelp.h"
#include "gpgoladdin.h"
HRESULT
gpgol_queryInterface (LPUNKNOWN pObj, REFIID riid, LPVOID FAR *ppvObj)
{
HRESULT ret = pObj->QueryInterface (riid, ppvObj);
if ((opt.enable_debug & DBG_OOM_EXTRA) && *ppvObj)
{
memdbg_addRef (*ppvObj);
}
return ret;
}
HRESULT
gpgol_openProperty (LPMAPIPROP obj, ULONG ulPropTag, LPCIID lpiid,
ULONG ulInterfaceOptions, ULONG ulFlags,
LPUNKNOWN FAR * lppUnk)
{
HRESULT ret = obj->OpenProperty (ulPropTag, lpiid,
ulInterfaceOptions, ulFlags,
lppUnk);
- if ((opt.enable_debug & DBG_OOM_EXTRA) && *lppUnk)
+ 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)
{
HRESULT hr;
LPDISPATCH disp = NULL;
LPTYPEINFO tinfo = NULL;
BSTR bstrname;
char *name = NULL;
if (!obj)
goto leave;
/* We can't use gpgol_queryInterface here to avoid recursion */
obj->QueryInterface (IID_IDispatch, (void **)&disp);
if (!disp)
goto leave;
disp->GetTypeInfo (0, 0, &tinfo);
if (!tinfo)
{
log_debug ("%s:%s: no typeinfo found for object\n",
SRCNAME, __func__);
goto leave;
}
bstrname = NULL;
hr = tinfo->GetDocumentation (MEMBERID_NIL, &bstrname, 0, 0, 0);
if (hr || !bstrname)
log_debug ("%s:%s: GetDocumentation failed: hr=%#lx\n",
SRCNAME, __func__, hr);
if (bstrname)
{
name = wchar_to_utf8 (bstrname);
SysFreeString (bstrname);
}
leave:
if (tinfo)
tinfo->Release ();
if (disp)
disp->Release ();
return name;
}
/* Lookup the dispid of object PDISP for member NAME. Returns
DISPID_UNKNOWN on error. */
DISPID
lookup_oom_dispid (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
DISPID dispid;
wchar_t *wname;
if (!pDisp || !name)
return DISPID_UNKNOWN; /* Error: Invalid arg. */
wname = utf8_to_wchar (name);
if (!wname)
return DISPID_UNKNOWN;/* Error: Out of memory. */
hr = pDisp->GetIDsOfNames (IID_NULL, &wname, 1,
LOCALE_SYSTEM_DEFAULT, &dispid);
xfree (wname);
if (hr != S_OK || dispid == DISPID_UNKNOWN)
log_debug ("%s:%s: error looking up dispid(%s)=%d: hr=0x%x\n",
SRCNAME, __func__, name, (int)dispid, (unsigned int)hr);
if (hr != S_OK)
dispid = DISPID_UNKNOWN;
return dispid;
}
static void
init_excepinfo (EXCEPINFO *err)
{
if (!err)
{
return;
}
err->wCode = 0;
err->wReserved = 0;
err->bstrSource = nullptr;
err->bstrDescription = nullptr;
err->bstrHelpFile = nullptr;
err->dwHelpContext = 0;
err->pvReserved = nullptr;
err->pfnDeferredFillIn = nullptr;
err->scode = 0;
}
void
dump_excepinfo (EXCEPINFO err)
{
- log_debug ("%s:%s: Exception: \n"
+ 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)
{
HRESULT hr;
LPDISPATCH pObj = pStart;
LPDISPATCH pDisp = NULL;
log_oom ("%s:%s: looking for %p->`%s'",
SRCNAME, __func__, pStart, fullname);
while (pObj)
{
DISPPARAMS dispparams;
VARIANT aVariant[4];
VARIANT vtResult;
wchar_t *wname;
char name[128];
int n_parms = 0;
BSTR parmstr = NULL;
INT parmint = 0;
DISPID dispid;
char *p, *pend;
int dispmethod;
unsigned int argErr = 0;
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
if (pDisp)
{
gpgol_release (pDisp);
pDisp = NULL;
}
if (gpgol_queryInterface (pObj, IID_IDispatch, (LPVOID*)&pDisp) != S_OK)
{
log_error ("%s:%s Object does not support IDispatch",
SRCNAME, __func__);
gpgol_release (pObj);
return NULL;
}
/* Confirmed through testing that the retval needs a release */
if (pObj != pStart)
gpgol_release (pObj);
pObj = NULL;
if (!pDisp)
return NULL; /* The object has no IDispatch interface. */
if (!*fullname)
{
- if ((opt.enable_debug & DBG_OOM))
+ 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);
}
return pDisp; /* Ready. */
}
/* Break out the next name part. */
{
const char *dot;
size_t n;
dot = strchr (fullname, '.');
if (dot == fullname)
{
gpgol_release (pDisp);
return NULL; /* Empty name part: error. */
}
else if (dot)
n = dot - fullname;
else
n = strlen (fullname);
if (n >= sizeof name)
n = sizeof name - 1;
strncpy (name, fullname, n);
name[n] = 0;
if (dot)
fullname = dot + 1;
else
fullname += strlen (fullname);
}
if (!strncmp (name, "get_", 4) && name[4])
{
dispmethod = DISPATCH_PROPERTYGET;
memmove (name, name+4, strlen (name+4)+1);
}
else if ((p = strchr (name, '(')))
{
*p++ = 0;
pend = strchr (p, ')');
if (pend)
*pend = 0;
if (*p == ',' && p[1] != ',')
{
/* We assume this is "foo(,30007)". I.e. the frst arg
is not given and the second one is an integer. */
parmint = (int)strtol (p+1, NULL, 10);
n_parms = 4;
}
else
{
wname = utf8_to_wchar (p);
if (wname)
{
parmstr = SysAllocString (wname);
xfree (wname);
}
if (!parmstr)
{
gpgol_release (pDisp);
return NULL; /* Error: Out of memory. */
}
n_parms = 1;
}
dispmethod = DISPATCH_METHOD|DISPATCH_PROPERTYGET;
}
else
dispmethod = DISPATCH_METHOD;
/* Lookup the dispid. */
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
{
if (parmstr)
SysFreeString (parmstr);
gpgol_release (pDisp);
return NULL; /* Name not found. */
}
/* Invoke the method. */
dispparams.rgvarg = aVariant;
dispparams.cArgs = 0;
if (n_parms)
{
if (n_parms == 4)
{
dispparams.rgvarg[0].vt = VT_ERROR;
dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[1].vt = VT_ERROR;
dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[2].vt = VT_INT;
dispparams.rgvarg[2].intVal = parmint;
dispparams.rgvarg[3].vt = VT_ERROR;
dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND;
dispparams.cArgs = n_parms;
}
else if (n_parms == 1 && parmstr)
{
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = parmstr;
dispparams.cArgs++;
}
}
dispparams.cNamedArgs = 0;
VariantInit (&vtResult);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
dispmethod, &dispparams,
&vtResult, &execpinfo, &argErr);
if (parmstr)
SysFreeString (parmstr);
if (hr != S_OK || vtResult.vt != VT_DISPATCH)
{
log_debug ("%s:%s: failure: '%s' p=%p vt=%d hr=0x%x argErr=0x%x dispid=0x%x",
SRCNAME, __func__,
name, vtResult.pdispVal, vtResult.vt, (unsigned int)hr,
(unsigned int)argErr, (unsigned int)dispid);
dump_excepinfo (execpinfo);
VariantClear (&vtResult);
gpgol_release (pDisp);
return NULL; /* Invoke failed. */
}
pObj = vtResult.pdispVal;
memdbg_addRef (pObj);
}
gpgol_release (pDisp);
log_debug ("%s:%s: no object", SRCNAME, __func__);
return NULL;
}
/* Helper for put_oom_icon. */
static int
put_picture_or_mask (LPDISPATCH pDisp, int resource, int size, int is_mask)
{
HRESULT hr;
PICTDESC pdesc;
LPDISPATCH pPict;
DISPID dispid_put = DISPID_PROPERTYPUT;
UINT fuload;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[2];
/* When loading the mask we need to set the monochrome flag. We
better create a DIB section to avoid possible rendering
problems. */
fuload = LR_CREATEDIBSECTION | LR_SHARED;
if (is_mask)
fuload |= LR_MONOCHROME;
memset (&pdesc, 0, sizeof pdesc);
pdesc.cbSizeofstruct = sizeof pdesc;
pdesc.picType = PICTYPE_BITMAP;
pdesc.bmp.hbitmap = (HBITMAP) LoadImage (glob_hinst,
MAKEINTRESOURCE (resource),
IMAGE_BITMAP, size, size, fuload);
if (!pdesc.bmp.hbitmap)
{
log_error_w32 (-1, "%s:%s: LoadImage(%d) failed\n",
SRCNAME, __func__, resource);
return -1;
}
/* Wrap the image into an OLE object. */
hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp,
TRUE, (void **) &pPict);
if (hr != S_OK || !pPict)
{
log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
/* Store to the Picture or Mask property of the CommandBarButton. */
dispid = lookup_oom_dispid (pDisp, is_mask? "Mask":"Picture");
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_DISPATCH;
dispparams.rgvarg[0].pdispVal = pPict;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting icon failed: %#lx", SRCNAME, __func__, hr);
return -1;
}
return 0;
}
/* Update the icon of PDISP using the bitmap with RESOURCE ID. The
function adds the system pixel size to the resource id to compute
the actual icon size. The resource id of the mask is the N+1. */
int
put_oom_icon (LPDISPATCH pDisp, int resource_id, int size)
{
int rc;
/* This code is only relevant for Outlook < 2010.
Ideally it should grab the system pixel size and use an
icon of the appropiate size (e.g. 32 or 64px)
*/
rc = put_picture_or_mask (pDisp, resource_id, size, 0);
if (!rc)
rc = put_picture_or_mask (pDisp, resource_id + 1, size, 1);
return rc;
}
/* Set the boolean property NAME to VALUE. */
int
put_oom_bool (LPDISPATCH pDisp, const char *name, int value)
{
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
return -1;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_BOOL;
dispparams.rgvarg[0].boolVal = value? VARIANT_TRUE:VARIANT_FALSE;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting '%s' failed: %#lx",
SRCNAME, __func__, name, hr);
return -1;
}
return 0;
}
/* Set the property NAME to VALUE. */
int
put_oom_int (LPDISPATCH pDisp, const char *name, int value)
{
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
return -1;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = value;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting '%s' failed: %#lx",
SRCNAME, __func__, name, hr);
return -1;
}
return 0;
}
/* Set the property NAME to STRING. */
int
put_oom_string (LPDISPATCH pDisp, const char *name, const char *string)
{
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
BSTR bstring;
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
return -1;
{
wchar_t *tmp = utf8_to_wchar (string);
bstring = tmp? SysAllocString (tmp):NULL;
xfree (tmp);
if (!bstring)
{
log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__);
return -1;
}
}
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_BSTR;
dispparams.rgvarg[0].bstrVal = bstring;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUT, &dispparams,
NULL, &execpinfo, NULL);
SysFreeString (bstring);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting '%s' failed: %#lx",
SRCNAME, __func__, name, hr);
dump_excepinfo (execpinfo);
return -1;
}
return 0;
}
/* Set the property NAME to DISP. */
int
put_oom_disp (LPDISPATCH pDisp, const char *name, LPDISPATCH disp)
{
HRESULT hr;
DISPID dispid_put = DISPID_PROPERTYPUT;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[1];
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
dispid = lookup_oom_dispid (pDisp, name);
if (dispid == DISPID_UNKNOWN)
return -1;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_DISPATCH;
dispparams.rgvarg[0].pdispVal = disp;
dispparams.cArgs = 1;
dispparams.rgdispidNamedArgs = &dispid_put;
dispparams.cNamedArgs = 1;
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYPUTREF, &dispparams,
NULL, &execpinfo, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Putting '%s' failed: %#lx",
SRCNAME, __func__, name, hr);
dump_excepinfo (execpinfo);
return -1;
}
return 0;
}
/* Get the boolean property NAME of the object PDISP. Returns False if
not found or if it is not a boolean property. */
int
get_oom_bool (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
int result = 0;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
VARIANT rVariant;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK)
log_debug ("%s:%s: Property '%s' not found: %#lx",
SRCNAME, __func__, name, hr);
else if (rVariant.vt != VT_BOOL)
log_debug ("%s:%s: Property `%s' is not a boolean (vt=%d)",
SRCNAME, __func__, name, rVariant.vt);
else
result = !!rVariant.boolVal;
VariantClear (&rVariant);
}
return result;
}
/* Get the integer property NAME of the object PDISP. Returns 0 if
not found or if it is not an integer property. */
int
get_oom_int (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
int result = 0;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
VARIANT rVariant;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK)
log_debug ("%s:%s: Property '%s' not found: %#lx",
SRCNAME, __func__, name, hr);
else if (rVariant.vt != VT_INT && rVariant.vt != VT_I4)
log_debug ("%s:%s: Property `%s' is not an integer (vt=%d)",
SRCNAME, __func__, name, rVariant.vt);
else
result = rVariant.intVal;
VariantClear (&rVariant);
}
return result;
}
/* Get the string property NAME of the object PDISP. Returns NULL if
not found or if it is not a string property. */
char *
get_oom_string (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
char *result = NULL;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
VARIANT rVariant;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK)
log_debug ("%s:%s: Property '%s' not found: %#lx",
SRCNAME, __func__, name, hr);
else if (rVariant.vt != VT_BSTR)
log_debug ("%s:%s: Property `%s' is not a string (vt=%d)",
SRCNAME, __func__, name, rVariant.vt);
else if (rVariant.bstrVal)
result = wchar_to_utf8 (rVariant.bstrVal);
VariantClear (&rVariant);
}
return result;
}
/* Get the object property NAME of the object PDISP. Returns NULL if
not found or if it is not an object perty. */
LPUNKNOWN
get_oom_iunknown (LPDISPATCH pDisp, const char *name)
{
HRESULT hr;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
VARIANT rVariant;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_PROPERTYGET, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK)
log_debug ("%s:%s: Property '%s' not found: %#lx",
SRCNAME, __func__, name, hr);
else if (rVariant.vt != VT_UNKNOWN)
log_debug ("%s:%s: Property `%s' is not of class IUnknown (vt=%d)",
SRCNAME, __func__, name, rVariant.vt);
else
{
memdbg_addRef (rVariant.punkVal);
return rVariant.punkVal;
}
VariantClear (&rVariant);
}
return NULL;
}
/* Return the control object described by the tag property with value
TAG. The object POBJ must support the FindControl method. Returns
NULL if not found. */
LPDISPATCH
get_oom_control_bytag (LPDISPATCH pDisp, const char *tag)
{
HRESULT hr;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[4];
VARIANT rVariant;
BSTR bstring;
LPDISPATCH result = NULL;
dispid = lookup_oom_dispid (pDisp, "FindControl");
if (dispid == DISPID_UNKNOWN)
{
log_debug ("%s:%s: Object %p has no FindControl method",
SRCNAME, __func__, pDisp);
return NULL;
}
{
wchar_t *tmp = utf8_to_wchar (tag);
bstring = tmp? SysAllocString (tmp):NULL;
xfree (tmp);
if (!bstring)
{
log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__);
return NULL;
}
}
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_ERROR; /* Visible */
dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[1].vt = VT_BSTR; /* Tag */
dispparams.rgvarg[1].bstrVal = bstring;
dispparams.rgvarg[2].vt = VT_ERROR; /* Id */
dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[3].vt = VT_ERROR;/* Type */
dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND;
dispparams.cArgs = 4;
dispparams.cNamedArgs = 0;
VariantInit (&rVariant);
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&rVariant, NULL, NULL);
SysFreeString (bstring);
if (hr == S_OK && rVariant.vt == VT_DISPATCH && rVariant.pdispVal)
{
gpgol_queryInterface (rVariant.pdispVal, IID_IDispatch,
(LPVOID*)&result);
gpgol_release (rVariant.pdispVal);
if (!result)
log_debug ("%s:%s: Object with tag `%s' has no dispatch intf.",
SRCNAME, __func__, tag);
}
else
{
log_debug ("%s:%s: No object with tag `%s' found: vt=%d hr=%#lx",
SRCNAME, __func__, tag, rVariant.vt, hr);
VariantClear (&rVariant);
}
return result;
}
/* Add a new button to an object which supports the add method.
Returns the new object or NULL on error. */
LPDISPATCH
add_oom_button (LPDISPATCH pObj)
{
HRESULT hr;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[5];
VARIANT rVariant;
dispid = lookup_oom_dispid (pObj, "Add");
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */
dispparams.rgvarg[0].boolVal = VARIANT_TRUE;
dispparams.rgvarg[1].vt = VT_ERROR; /* Before */
dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[2].vt = VT_ERROR; /* Parameter */
dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[3].vt = VT_ERROR; /* Id */
dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND;
dispparams.rgvarg[4].vt = VT_INT; /* Type */
dispparams.rgvarg[4].intVal = MSOCONTROLBUTTON;
dispparams.cArgs = 5;
dispparams.cNamedArgs = 0;
VariantInit (&rVariant);
hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&rVariant, NULL, NULL);
if (hr != S_OK || rVariant.vt != VT_DISPATCH || !rVariant.pdispVal)
{
log_error ("%s:%s: Adding Control failed: %#lx - vt=%d",
SRCNAME, __func__, hr, rVariant.vt);
VariantClear (&rVariant);
return NULL;
}
return rVariant.pdispVal;
}
/* Add a new button to an object which supports the add method.
Returns the new object or NULL on error. */
void
del_oom_button (LPDISPATCH pObj)
{
HRESULT hr;
DISPID dispid;
DISPPARAMS dispparams;
VARIANT aVariant[5];
dispid = lookup_oom_dispid (pObj, "Delete");
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_BOOL; /* Temporary */
dispparams.rgvarg[0].boolVal = VARIANT_FALSE;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
log_error ("%s:%s: Deleting Control failed: %#lx",
SRCNAME, __func__, hr);
}
/* Gets the current contexts HWND. Returns NULL on error */
HWND
get_oom_context_window (LPDISPATCH context)
{
LPOLEWINDOW actExplorer;
HWND ret = NULL;
actExplorer = (LPOLEWINDOW) get_oom_object(context,
"Application.ActiveExplorer");
if (actExplorer)
actExplorer->GetWindow (&ret);
else
{
log_debug ("%s:%s: Could not find active window",
SRCNAME, __func__);
}
gpgol_release (actExplorer);
return ret;
}
int
put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value)
{
LPDISPATCH propertyAccessor;
VARIANT cVariant[2];
VARIANT rVariant;
DISPID dispid;
DISPPARAMS dispparams;
HRESULT hr;
EXCEPINFO execpinfo;
BSTR b_property;
wchar_t *w_property;
unsigned int argErr = 0;
init_excepinfo (&execpinfo);
log_oom ("%s:%s: Looking up property: %s;",
SRCNAME, __func__, dasl_id);
propertyAccessor = get_oom_object (pDisp, "PropertyAccessor");
if (!propertyAccessor)
{
log_error ("%s:%s: Failed to look up property accessor.",
SRCNAME, __func__);
return -1;
}
dispid = lookup_oom_dispid (propertyAccessor, "SetProperty");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find SetProperty DISPID",
SRCNAME, __func__);
return -1;
}
/* Prepare the parameter */
w_property = utf8_to_wchar (dasl_id);
b_property = SysAllocString (w_property);
xfree (w_property);
/* Variant 0 carries the data. */
VariantInit (&cVariant[0]);
if (VariantCopy (&cVariant[0], value))
{
log_error ("%s:%s: Falied to copy value.",
SRCNAME, __func__);
return -1;
}
/* Variant 1 is the DASL as found out by experiments. */
VariantInit (&cVariant[1]);
cVariant[1].vt = VT_BSTR;
cVariant[1].bstrVal = b_property;
dispparams.rgvarg = cVariant;
dispparams.cArgs = 2;
dispparams.cNamedArgs = 0;
VariantInit (&rVariant);
hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&rVariant, &execpinfo, &argErr);
VariantClear (&cVariant[0]);
VariantClear (&cVariant[1]);
gpgol_release (propertyAccessor);
if (hr != S_OK)
{
- log_debug ("%s:%s: error: invoking SetProperty p=%p vt=%d"
+ 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);
return -1;
}
VariantClear (&rVariant);
return 0;
}
int
put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value)
{
wchar_t *w_value = utf8_to_wchar (value);
BSTR b_value = SysAllocString(w_value);
xfree (w_value);
VARIANT var;
VariantInit (&var);
var.vt = VT_BSTR;
var.bstrVal = b_value;
int ret = put_pa_variant (pDisp, dasl_id, &var);
VariantClear (&var);
return ret;
}
int
put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value)
{
VARIANT var;
VariantInit (&var);
var.vt = VT_INT;
var.intVal = value;
int ret = put_pa_variant (pDisp, dasl_id, &var);
VariantClear (&var);
return ret;
}
/* Get a MAPI property through OOM using the PropertyAccessor
* interface and the DASL Uid. Returns -1 on error.
* Variant has to be cleared with VariantClear.
* rVariant must be a pointer to a Variant.
*/
int get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant)
{
LPDISPATCH propertyAccessor;
VARIANT cVariant[1];
DISPID dispid;
DISPPARAMS dispparams;
HRESULT hr;
EXCEPINFO execpinfo;
BSTR b_property;
wchar_t *w_property;
unsigned int argErr = 0;
init_excepinfo (&execpinfo);
log_oom ("%s:%s: Looking up property: %s;",
SRCNAME, __func__, dasl_id);
propertyAccessor = get_oom_object (pDisp, "PropertyAccessor");
if (!propertyAccessor)
{
log_error ("%s:%s: Failed to look up property accessor.",
SRCNAME, __func__);
return -1;
}
dispid = lookup_oom_dispid (propertyAccessor, "GetProperty");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find GetProperty DISPID",
SRCNAME, __func__);
return -1;
}
/* Prepare the parameter */
w_property = utf8_to_wchar (dasl_id);
b_property = SysAllocString (w_property);
xfree (w_property);
cVariant[0].vt = VT_BSTR;
cVariant[0].bstrVal = b_property;
dispparams.rgvarg = cVariant;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
VariantInit (rVariant);
hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
rVariant, &execpinfo, &argErr);
SysFreeString (b_property);
gpgol_release (propertyAccessor);
if (hr != S_OK && strcmp (GPGOL_UID_DASL, dasl_id))
{
/* It often happens that mails don't have a uid by us e.g. if
they are not crypto mails or just dont have one. This is
not an error. */
log_debug ("%s:%s: error: invoking GetProperty p=%p vt=%d"
" hr=0x%x argErr=0x%x",
SRCNAME, __func__,
rVariant->pdispVal, rVariant->vt, (unsigned int)hr,
(unsigned int)argErr);
dump_excepinfo (execpinfo);
VariantClear (rVariant);
return -1;
}
return 0;
}
/* Get a property string by using the PropertyAccessor of pDisp
* returns NULL on error or a newly allocated result. */
char *
get_pa_string (LPDISPATCH pDisp, const char *property)
{
VARIANT rVariant;
char *result = NULL;
if (get_pa_variant (pDisp, property, &rVariant))
{
return NULL;
}
if (rVariant.vt == VT_BSTR && rVariant.bstrVal)
{
result = wchar_to_utf8 (rVariant.bstrVal);
}
else if (rVariant.vt & VT_ARRAY && !(rVariant.vt & VT_BYREF))
{
LONG uBound, lBound;
VARTYPE vt;
char *data;
SafeArrayGetVartype(rVariant.parray, &vt);
if (SafeArrayGetUBound (rVariant.parray, 1, &uBound) != S_OK ||
SafeArrayGetLBound (rVariant.parray, 1, &lBound) != S_OK ||
vt != VT_UI1)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
VariantClear (&rVariant);
return NULL;
}
result = (char *)xmalloc (uBound - lBound + 1);
data = (char *) rVariant.parray->pvData;
memcpy (result, data + lBound, uBound - lBound);
result[uBound - lBound] = '\0';
}
else
{
log_debug ("%s:%s: Property `%s' is not a string (vt=%d)",
SRCNAME, __func__, property, rVariant.vt);
}
VariantClear (&rVariant);
return result;
}
int
get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt)
{
VARIANT rVariant;
if (get_pa_variant (pDisp, property, &rVariant))
{
return -1;
}
if (rVariant.vt != VT_I4)
{
log_debug ("%s:%s: Property `%s' is not a int (vt=%d)",
SRCNAME, __func__, property, rVariant.vt);
return -1;
}
*rInt = rVariant.lVal;
VariantClear (&rVariant);
return 0;
}
/* Helper for exchange address lookup. */
static char *
get_recipient_addr_entry_fallbacks_ex (LPDISPATCH addr_entry)
{
/* Maybe check for type here? We are pretty sure that we are exchange */
/* According to MSDN Message Boards the PR_EMS_AB_PROXY_ADDRESSES_DASL
is more avilable then the SMTP Address. */
char *ret = get_pa_string (addr_entry, PR_EMS_AB_PROXY_ADDRESSES_DASL);
if (ret)
{
log_debug ("%s:%s: Found recipient through AB_PROXY: %s",
- SRCNAME, __func__, ret);
+ SRCNAME, __func__, anonstr (ret));
char *smtpbegin = strstr(ret, "SMTP:");
if (smtpbegin == ret)
{
ret += 5;
}
return ret;
}
else
{
log_debug ("%s:%s: Failed AB_PROXY lookup.",
SRCNAME, __func__);
}
LPDISPATCH ex_user = get_oom_object (addr_entry, "GetExchangeUser");
if (!ex_user)
{
log_debug ("%s:%s: Failed to find ExchangeUser",
SRCNAME, __func__);
return nullptr;
}
ret = get_oom_string (ex_user, "PrimarySmtpAddress");
gpgol_release (ex_user);
if (ret)
{
log_debug ("%s:%s: Found recipient through exchange user primary smtp address: %s",
- SRCNAME, __func__, ret);
+ SRCNAME, __func__, anonstr (ret));
return ret;
}
return nullptr;
}
/* Helper for additional fallbacks in recipient lookup */
static char *
get_recipient_addr_fallbacks (LPDISPATCH recipient)
{
if (!recipient)
{
return nullptr;
}
LPDISPATCH addr_entry = get_oom_object (recipient, "AddressEntry");
if (!addr_entry)
{
log_debug ("%s:%s: Failed to find AddressEntry",
SRCNAME, __func__);
return nullptr;
}
char *ret = get_recipient_addr_entry_fallbacks_ex (addr_entry);
gpgol_release (addr_entry);
return ret;
}
/* Try to resolve a recipient group and add it to the recipients vector.
returns true on success.
*/
static bool
try_resolve_group (LPDISPATCH addrEntry,
std::vector<std::pair<std::string, shared_disp_t> > &ret)
{
/* Get the name for debugging */
std::string name;
char *cname = get_oom_string (addrEntry, "Name");
if (cname)
{
name = cname;
}
xfree (cname);
int type = get_oom_int (addrEntry, "AddressEntryUserType");
if (type != DISTRIBUTION_LIST_ADDRESS_ENTRY_TYPE)
{
log_data ("%s:%s: type of %s is %i",
- SRCNAME, __func__, name.c_str(), type);
+ SRCNAME, __func__, anonstr (name.c_str()), type);
return false;
}
LPDISPATCH members = get_oom_object (addrEntry, "Members");
addrEntry = nullptr;
if (!members)
{
TRACEPOINT;
return false;
}
int count = get_oom_int (members, "Count");
if (!count)
{
TRACEPOINT;
gpgol_release (members);
return false;
}
bool foundOne = false;
for (int i = 1; i <= count; i++)
{
auto item_str = std::string("Item(") + std::to_string (i) + ")";
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__,
- (opt.enable_debug & DBG_MIME_PARSER) ?
- entryName.c_str() : "omitted");
+ anonstr (entryName.c_str()));
if (try_resolve_group (entry.get(), ret))
{
foundOne = true;
continue;
}
}
std::pair<std::string, shared_disp_t> element;
element.second = entry;
/* Resolve directly ? */
char *addrtype = get_pa_string (entry.get(), PR_ADDRTYPE_DASL);
if (addrtype && !strcmp (addrtype, "SMTP"))
{
xfree (addrtype);
char *resolved = get_pa_string (entry.get(), PR_EMAIL_ADDRESS_DASL);
if (resolved)
{
element.first = resolved;
ret.push_back (element);
foundOne = true;
continue;
}
}
xfree (addrtype);
/* Resolve through Exchange API */
char *ex_resolved = get_recipient_addr_entry_fallbacks_ex (entry.get());
if (ex_resolved)
{
element.first = ex_resolved;
ret.push_back (element);
foundOne = true;
continue;
}
log_debug ("%s:%s: failed to resolve name %s",
SRCNAME, __func__,
- (opt.enable_debug & DBG_MIME_PARSER) ?
- entryName.c_str() : "omitted");
+ anonstr (entryName.c_str()));
}
gpgol_release (members);
if (!foundOne)
{
log_debug ("%s:%s: failed to resolve group %s",
SRCNAME, __func__,
- (opt.enable_debug & DBG_MIME_PARSER) ?
- name.c_str() : "omitted");
+ anonstr (name.c_str()));
}
return foundOne;
}
/* Get the recipient mbox addresses with the addrEntry
object corresponding to the resolved address. */
std::vector<std::pair<std::string, shared_disp_t> >
get_oom_recipients_with_addrEntry (LPDISPATCH recipients, bool *r_err)
{
int recipientsCnt = get_oom_int (recipients, "Count");
std::vector<std::pair<std::string, shared_disp_t> > ret;
int i;
if (!recipientsCnt)
{
return ret;
}
/* Get the recipients */
for (i = 1; i <= recipientsCnt; i++)
{
char buf[16];
LPDISPATCH recipient;
snprintf (buf, sizeof (buf), "Item(%i)", i);
recipient = get_oom_object (recipients, buf);
if (!recipient)
{
/* Should be impossible */
log_error ("%s:%s: could not find Item %i;",
SRCNAME, __func__, i);
if (r_err)
{
*r_err = true;
}
break;
}
auto addrEntry = MAKE_SHARED (get_oom_object (recipient, "AddressEntry"));
if (addrEntry && try_resolve_group (addrEntry.get (), ret))
{
log_debug ("%s:%s: Resolved recipient group",
SRCNAME, __func__);
gpgol_release (recipient);
continue;
}
std::pair<std::string, shared_disp_t> entry;
entry.second = addrEntry;
char *resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL);
if (resolved)
{
entry.first = resolved;
xfree (resolved);
gpgol_release (recipient);
ret.push_back (entry);
continue;
}
/* No PR_SMTP_ADDRESS first fallback */
resolved = get_recipient_addr_fallbacks (recipient);
if (resolved)
{
entry.first = resolved;
xfree (resolved);
gpgol_release (recipient);
ret.push_back (entry);
continue;
}
char *address = get_oom_string (recipient, "Address");
gpgol_release (recipient);
log_debug ("%s:%s: Failed to look up Address probably "
"EX addr is returned",
SRCNAME, __func__);
if (address)
{
entry.first = address;
ret.push_back (entry);
xfree (address);
}
else if (r_err)
{
*r_err = true;
}
}
return ret;
}
/* Gets the resolved smtp addresses of the recpients. */
std::vector<std::string>
get_oom_recipients (LPDISPATCH recipients, bool *r_err)
{
std::vector<std::string> ret;
for (const auto pair: get_oom_recipients_with_addrEntry (recipients, r_err))
{
ret.push_back (pair.first);
}
return ret;
}
/* Add an attachment to the outlook dispatcher disp
that has an Attachment property.
inFile is the path to the attachment. Name is the
name that should be used in outlook. */
int
add_oom_attachment (LPDISPATCH disp, const wchar_t* inFileW,
const wchar_t* displayName)
{
LPDISPATCH attachments = get_oom_object (disp, "Attachments");
DISPID dispid;
DISPPARAMS dispparams;
VARIANT vtResult;
VARIANT aVariant[4];
HRESULT hr;
BSTR inFileB = nullptr,
dispNameB = nullptr;
unsigned int argErr = 0;
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
dispid = lookup_oom_dispid (attachments, "Add");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find attachment dispatcher",
SRCNAME, __func__);
return -1;
}
if (inFileW)
{
inFileB = SysAllocString (inFileW);
}
if (displayName)
{
dispNameB = SysAllocString (displayName);
}
dispparams.rgvarg = aVariant;
/* Contrary to the documentation the Source is the last
parameter and not the first. Additionally DisplayName
is documented but gets ignored by Outlook since Outlook
2003 */
dispparams.rgvarg[0].vt = VT_BSTR; /* DisplayName */
dispparams.rgvarg[0].bstrVal = dispNameB;
dispparams.rgvarg[1].vt = VT_INT; /* Position */
dispparams.rgvarg[1].intVal = 1;
dispparams.rgvarg[2].vt = VT_INT; /* Type */
dispparams.rgvarg[2].intVal = 1;
dispparams.rgvarg[3].vt = VT_BSTR; /* Source */
dispparams.rgvarg[3].bstrVal = inFileB;
dispparams.cArgs = 4;
dispparams.cNamedArgs = 0;
VariantInit (&vtResult);
hr = attachments->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&vtResult, &execpinfo, &argErr);
if (hr != S_OK)
{
log_debug ("%s:%s: error: invoking Add p=%p vt=%d hr=0x%x argErr=0x%x",
SRCNAME, __func__,
vtResult.pdispVal, vtResult.vt, (unsigned int)hr,
(unsigned int)argErr);
dump_excepinfo (execpinfo);
}
if (inFileB)
SysFreeString (inFileB);
if (dispNameB)
SysFreeString (dispNameB);
VariantClear (&vtResult);
gpgol_release (attachments);
return hr == S_OK ? 0 : -1;
}
LPDISPATCH
get_object_by_id (LPDISPATCH pDisp, REFIID id)
{
LPDISPATCH disp = NULL;
if (!pDisp)
return NULL;
if (gpgol_queryInterface(pDisp, id, (void **)&disp) != S_OK)
return NULL;
return disp;
}
LPDISPATCH
get_strong_reference (LPDISPATCH mail)
{
VARIANT var;
VariantInit (&var);
DISPPARAMS args;
VARIANT argvars[2];
VariantInit (&argvars[0]);
VariantInit (&argvars[1]);
argvars[1].vt = VT_DISPATCH;
argvars[1].pdispVal = mail;
argvars[0].vt = VT_INT;
argvars[0].intVal = 1;
args.cArgs = 2;
args.cNamedArgs = 0;
args.rgvarg = argvars;
LPDISPATCH ret = NULL;
if (!invoke_oom_method_with_parms (
GpgolAddin::get_instance()->get_application(),
"GetObjectReference", &var, &args))
{
ret = var.pdispVal;
log_oom ("%s:%s: Got strong ref %p for %p",
SRCNAME, __func__, ret, mail);
memdbg_addRef (ret);
}
else
{
log_error ("%s:%s: Failed to get strong ref.",
SRCNAME, __func__);
}
VariantClear (&var);
return ret;
}
LPMESSAGE
get_oom_message (LPDISPATCH mailitem)
{
LPUNKNOWN mapi_obj = get_oom_iunknown (mailitem, "MapiObject");
if (!mapi_obj)
{
log_error ("%s:%s: Failed to obtain MAPI Message.",
SRCNAME, __func__);
return NULL;
}
return (LPMESSAGE) mapi_obj;
}
static LPMESSAGE
get_oom_base_message_from_mapi (LPDISPATCH mapi_message)
{
HRESULT hr;
LPDISPATCH secureItem = NULL;
LPMESSAGE message = NULL;
LPMAPISECUREMESSAGE secureMessage = NULL;
secureItem = get_object_by_id (mapi_message,
IID_IMAPISecureMessage);
if (!secureItem)
{
log_error ("%s:%s: Failed to obtain SecureItem.",
SRCNAME, __func__);
return NULL;
}
secureMessage = (LPMAPISECUREMESSAGE) secureItem;
/* The call to GetBaseMessage is pretty much a jump
in the dark. So it would not be surprising to get
crashes here in the future. */
log_oom("%s:%s: About to call GetBaseMessage.",
SRCNAME, __func__);
hr = secureMessage->GetBaseMessage (&message);
memdbg_addRef (message);
gpgol_release (secureMessage);
if (hr != S_OK)
{
log_error_w32 (hr, "Failed to GetBaseMessage.");
return NULL;
}
return message;
}
LPMESSAGE
get_oom_base_message (LPDISPATCH mailitem)
{
LPMESSAGE mapi_message = get_oom_message (mailitem);
LPMESSAGE ret = NULL;
if (!mapi_message)
{
log_error ("%s:%s: Failed to obtain mapi_message.",
SRCNAME, __func__);
return NULL;
}
ret = get_oom_base_message_from_mapi ((LPDISPATCH)mapi_message);
gpgol_release (mapi_message);
return ret;
}
static int
invoke_oom_method_with_parms_type (LPDISPATCH pDisp, const char *name,
VARIANT *rVariant, DISPPARAMS *params,
int type)
{
HRESULT hr;
DISPID dispid;
dispid = lookup_oom_dispid (pDisp, name);
if (dispid != DISPID_UNKNOWN)
{
EXCEPINFO execpinfo;
init_excepinfo (&execpinfo);
DISPPARAMS dispparams = {NULL, NULL, 0, 0};
hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
type, params ? params : &dispparams,
rVariant, &execpinfo, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Method '%s' invokation failed: %#lx",
SRCNAME, __func__, name, hr);
dump_excepinfo (execpinfo);
return -1;
}
}
return 0;
}
int
invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name,
VARIANT *rVariant, DISPPARAMS *params)
{
return invoke_oom_method_with_parms_type (pDisp, name, rVariant, params,
DISPATCH_METHOD);
}
int
invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant)
{
return invoke_oom_method_with_parms (pDisp, name, rVariant, NULL);
}
LPMAPISESSION
get_oom_mapi_session ()
{
LPDISPATCH application = GpgolAddin::get_instance ()->get_application ();
LPDISPATCH oom_session = NULL;
LPMAPISESSION session = NULL;
LPUNKNOWN mapiobj = NULL;
HRESULT hr;
if (!application)
{
log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__);
return NULL;
}
oom_session = get_oom_object (application, "Session");
if (!oom_session)
{
log_error ("%s:%s: session object not found", SRCNAME, __func__);
return NULL;
}
mapiobj = get_oom_iunknown (oom_session, "MAPIOBJECT");
gpgol_release (oom_session);
if (!mapiobj)
{
log_error ("%s:%s: error getting Session.MAPIOBJECT", SRCNAME, __func__);
return NULL;
}
session = NULL;
hr = gpgol_queryInterface (mapiobj, IID_IMAPISession, (void**)&session);
gpgol_release (mapiobj);
if (hr != S_OK || !session)
{
log_error ("%s:%s: error getting IMAPISession: hr=%#lx",
SRCNAME, __func__, hr);
return NULL;
}
return session;
}
static int
create_category (LPDISPATCH categories, const char *category, int color)
{
VARIANT cVariant[3];
VARIANT rVariant;
DISPID dispid;
DISPPARAMS dispparams;
HRESULT hr;
EXCEPINFO execpinfo;
BSTR b_name;
wchar_t *w_name;
unsigned int argErr = 0;
init_excepinfo (&execpinfo);
if (!categories || !category)
{
TRACEPOINT;
return 1;
}
dispid = lookup_oom_dispid (categories, "Add");
if (dispid == DISPID_UNKNOWN)
{
log_error ("%s:%s: could not find Add DISPID",
SRCNAME, __func__);
return -1;
}
/* Do the string dance */
w_name = utf8_to_wchar (category);
b_name = SysAllocString (w_name);
xfree (w_name);
/* Variants are in reverse order
ShortcutKey -> 0 / Int
Color -> 1 / Int
Name -> 2 / Bstr */
VariantInit (&cVariant[2]);
cVariant[2].vt = VT_BSTR;
cVariant[2].bstrVal = b_name;
VariantInit (&cVariant[1]);
cVariant[1].vt = VT_INT;
cVariant[1].intVal = color;
VariantInit (&cVariant[0]);
cVariant[0].vt = VT_INT;
cVariant[0].intVal = 0;
dispparams.cArgs = 3;
dispparams.cNamedArgs = 0;
dispparams.rgvarg = cVariant;
hr = categories->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
&rVariant, &execpinfo, &argErr);
SysFreeString (b_name);
VariantClear (&cVariant[0]);
VariantClear (&cVariant[1]);
VariantClear (&cVariant[2]);
if (hr != S_OK)
{
log_debug ("%s:%s: error: invoking Add p=%p vt=%d"
" hr=0x%x argErr=0x%x",
SRCNAME, __func__,
rVariant.pdispVal, rVariant.vt, (unsigned int)hr,
(unsigned int)argErr);
dump_excepinfo (execpinfo);
VariantClear (&rVariant);
return -1;
}
VariantClear (&rVariant);
- log_debug ("%s:%s: Created category '%s'",
+ log_oom ("%s:%s: Created category '%s'",
SRCNAME, __func__, category);
return 0;
}
void
ensure_category_exists (LPDISPATCH application, const char *category, int color)
{
if (!application || !category)
{
TRACEPOINT;
return;
}
- log_debug ("Ensure category exists called for %s, %i", category, color);
+ log_oom ("Ensure category exists called for %s, %i", category, color);
LPDISPATCH stores = get_oom_object (application, "Session.Stores");
if (!stores)
{
log_error ("%s:%s: No stores found.",
SRCNAME, __func__);
return;
}
auto store_count = get_oom_int (stores, "Count");
for (int n = 1; n <= store_count; n++)
{
const auto store_str = std::string("Item(") + std::to_string(n) + ")";
LPDISPATCH store = get_oom_object (stores, store_str.c_str());
if (!store)
{
TRACEPOINT;
continue;
}
LPDISPATCH categories = get_oom_object (store, "Categories");
gpgol_release (store);
if (!categories)
{
categories = get_oom_object (application, "Session.Categories");
if (!categories)
{
TRACEPOINT;
continue;
}
}
auto count = get_oom_int (categories, "Count");
bool found = false;
for (int i = 1; i <= count && !found; i++)
{
const auto item_str = std::string("Item(") + std::to_string(i) + ")";
LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str());
if (!category_obj)
{
TRACEPOINT;
gpgol_release (categories);
break;
}
char *name = get_oom_string (category_obj, "Name");
if (name && !strcmp (category, name))
{
- log_debug ("%s:%s: Found category '%s'",
- SRCNAME, __func__, name);
+ 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_debug ("%s:%s: Found category '%s'",
- SRCNAME, __func__, category);
+ log_oom ("%s:%s: Found category '%s'",
+ SRCNAME, __func__, category);
}
}
/* Otherwise we have to create the category */
gpgol_release (categories);
}
gpgol_release (stores);
}
int
add_category (LPDISPATCH mail, const char *category)
{
char *tmp = get_oom_string (mail, "Categories");
if (!tmp)
{
TRACEPOINT;
return 1;
}
if (strstr (tmp, category))
{
- log_debug ("%s:%s: category '%s' already added.",
- SRCNAME, __func__, category);
+ log_oom ("%s:%s: category '%s' already added.",
+ SRCNAME, __func__, category);
return 0;
}
std::string newstr (tmp);
xfree (tmp);
if (!newstr.empty ())
{
newstr += ", ";
}
newstr += category;
return put_oom_string (mail, "Categories", newstr.c_str ());
}
int
remove_category (LPDISPATCH mail, const char *category)
{
char *tmp = get_oom_string (mail, "Categories");
if (!tmp)
{
TRACEPOINT;
return 1;
}
std::string newstr (tmp);
xfree (tmp);
std::string cat (category);
size_t pos1 = newstr.find (cat);
size_t pos2 = newstr.find (std::string(", ") + cat);
if (pos1 == std::string::npos && pos2 == std::string::npos)
{
- log_debug ("%s:%s: category '%s' not found.",
- SRCNAME, __func__, category);
+ log_oom ("%s:%s: category '%s' not found.",
+ SRCNAME, __func__, category);
return 0;
}
size_t len = cat.size();
if (pos2)
{
len += 2;
}
newstr.erase (pos2 != std::string::npos ? pos2 : pos1, len);
- log_debug ("%s:%s: removing category '%s'",
- SRCNAME, __func__, category);
+ log_oom ("%s:%s: removing category '%s'",
+ SRCNAME, __func__, category);
return put_oom_string (mail, "Categories", newstr.c_str ());
}
static char *
generate_uid ()
{
UUID uuid;
UuidCreate (&uuid);
unsigned char *str;
UuidToStringA (&uuid, &str);
char *ret = xstrdup ((char*)str);
RpcStringFreeA (&str);
return ret;
}
char *
get_unique_id (LPDISPATCH mail, int create, const char *uuid)
{
if (!mail)
{
return NULL;
}
/* Get the User Properties. */
if (!create)
{
char *uid = get_pa_string (mail, GPGOL_UID_DASL);
if (!uid)
{
log_debug ("%s:%s: No uuid found in oom for '%p'",
SRCNAME, __func__, mail);
return NULL;
}
else
{
log_debug ("%s:%s: Found uid '%s' for '%p'",
SRCNAME, __func__, uid, mail);
return uid;
}
}
char *newuid;
if (!uuid)
{
newuid = generate_uid ();
}
else
{
newuid = xstrdup (uuid);
}
int ret = put_pa_string (mail, GPGOL_UID_DASL, newuid);
if (ret)
{
log_debug ("%s:%s: failed to set uid '%s' for '%p'",
SRCNAME, __func__, newuid, mail);
xfree (newuid);
return NULL;
}
log_debug ("%s:%s: '%p' has now the uid: '%s' ",
SRCNAME, __func__, mail, newuid);
return newuid;
}
HWND
get_active_hwnd ()
{
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH activeWindow = get_oom_object (app, "ActiveWindow");
if (!activeWindow)
{
activeWindow = get_oom_object (app, "ActiveInspector");
if (!activeWindow)
{
activeWindow = get_oom_object (app, "ActiveExplorer");
if (!activeWindow)
{
TRACEPOINT;
return nullptr;
}
}
}
/* Both explorer and inspector have this. */
char *caption = get_oom_string (activeWindow, "Caption");
gpgol_release (activeWindow);
if (!caption)
{
TRACEPOINT;
return nullptr;
}
/* Might not be completly true for multiple explorers
on the same folder but good enugh. */
HWND hwnd = FindWindowExA(NULL, NULL, "rctrl_renwnd32",
caption);
xfree (caption);
return hwnd;
}
LPDISPATCH
create_mail ()
{
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return nullptr;
}
VARIANT var;
VariantInit (&var);
VARIANT argvars[1];
DISPPARAMS args;
VariantInit (&argvars[0]);
argvars[0].vt = VT_I2;
argvars[0].intVal = 0;
args.cArgs = 1;
args.cNamedArgs = 0;
args.rgvarg = argvars;
LPDISPATCH ret = nullptr;
if (invoke_oom_method_with_parms (app, "CreateItem", &var, &args))
{
log_error ("%s:%s: Failed to create mailitem.",
SRCNAME, __func__);
return ret;
}
ret = var.pdispVal;
return ret;
}
LPDISPATCH
get_account_for_mail (const char *mbox)
{
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH accounts = get_oom_object (app, "Session.Accounts");
if (!accounts)
{
TRACEPOINT;
return nullptr;
}
int count = get_oom_int (accounts, "Count");
for (int i = 1; i <= count; i++)
{
std::string item = std::string ("Item(") + std::to_string (i) + ")";
LPDISPATCH account = get_oom_object (accounts, item.c_str ());
if (!account)
{
TRACEPOINT;
continue;
}
char *smtpAddr = get_oom_string (account, "SmtpAddress");
if (!smtpAddr)
{
gpgol_release (account);
TRACEPOINT;
continue;
}
if (!stricmp (mbox, smtpAddr))
{
gpgol_release (accounts);
xfree (smtpAddr);
return account;
}
gpgol_release (account);
xfree (smtpAddr);
}
gpgol_release (accounts);
log_error ("%s:%s: Failed to find account for '%s'.",
- SRCNAME, __func__, mbox);
+ SRCNAME, __func__, anonstr (mbox));
return nullptr;
}
char *
get_sender_SendUsingAccount (LPDISPATCH mailitem, bool *r_is_GSuite)
{
LPDISPATCH sender = get_oom_object (mailitem, "SendUsingAccount");
if (!sender)
{
return nullptr;
}
char *buf = get_oom_string (sender, "SmtpAddress");
char *dispName = get_oom_string (sender, "DisplayName");
gpgol_release (sender);
/* Check for G Suite account */
if (dispName && !strcmp ("G Suite", dispName) && r_is_GSuite)
{
*r_is_GSuite = true;
}
xfree (dispName);
if (buf && strlen (buf))
{
log_debug ("%s:%s: found sender", SRCNAME, __func__);
return buf;
}
xfree (buf);
return nullptr;
}
char *
get_sender_Sender (LPDISPATCH mailitem)
{
LPDISPATCH sender = get_oom_object (mailitem, "Sender");
if (!sender)
{
return nullptr;
}
char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL);
gpgol_release (sender);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 2", SRCNAME, __func__);
return buf;
}
xfree (buf);
/* We have a sender object but not yet an smtp address likely
exchange. Try some more propertys of the message. */
buf = get_pa_string (mailitem, PR_TAG_SENDER_SMTP_ADDRESS);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 3", SRCNAME, __func__);
return buf;
}
xfree (buf);
buf = get_pa_string (mailitem, PR_TAG_RECEIVED_REPRESENTING_SMTP_ADDRESS);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 4", SRCNAME, __func__);
return buf;
}
xfree (buf);
return nullptr;
}
char *
get_sender_CurrentUser (LPDISPATCH mailitem)
{
LPDISPATCH sender = get_oom_object (mailitem,
"Session.CurrentUser");
if (!sender)
{
return nullptr;
}
char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL);
gpgol_release (sender);
if (buf && strlen (buf))
{
log_debug ("%s:%s Sender fallback 5", SRCNAME, __func__);
return buf;
}
xfree (buf);
return nullptr;
}
char *
get_sender_SenderEMailAddress (LPDISPATCH mailitem)
{
char *type = get_oom_string (mailitem, "SenderEmailType");
if (type && !strcmp ("SMTP", type))
{
char *senderMail = get_oom_string (mailitem, "SenderEmailAddress");
if (senderMail)
{
log_debug ("%s:%s: Sender found", SRCNAME, __func__);
xfree (type);
return senderMail;
}
}
xfree (type);
return nullptr;
}
char *
get_sender_SentRepresentingAddress (LPDISPATCH mailitem)
{
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__, buf);
return buf;
}
xfree (buf);
return nullptr;
}
char *
get_inline_body ()
{
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer");
if (!explorer)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse");
gpgol_release (explorer);
if (!inlineResponse)
{
return nullptr;
}
char *body = get_oom_string (inlineResponse, "Body");
gpgol_release (inlineResponse);
return body;
}
int
get_ex_major_version_for_addr (const char *mbox)
{
LPDISPATCH account = get_account_for_mail (mbox);
if (!account)
{
TRACEPOINT;
return -1;
}
char *version_str = get_oom_string (account, "ExchangeMailboxServerVersion");
gpgol_release (account);
if (!version_str)
{
return -1;
}
long int version = strtol (version_str, nullptr, 10);
xfree (version_str);
return (int) version;
}
int
get_ol_ui_language ()
{
LPDISPATCH app = GpgolAddin::get_instance()->get_application();
if (!app)
{
TRACEPOINT;
return 0;
}
LPDISPATCH langSettings = get_oom_object (app, "LanguageSettings");
if (!langSettings)
{
TRACEPOINT;
return 0;
}
VARIANT var;
VariantInit (&var);
VARIANT aVariant[1];
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = 2;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
int ret = invoke_oom_method_with_parms_type (langSettings, "LanguageID", &var,
&dispparams,
DISPATCH_PROPERTYGET);
gpgol_release (langSettings);
if (ret)
{
TRACEPOINT;
return 0;
}
if (var.vt != VT_INT && var.vt != VT_I4)
{
TRACEPOINT;
return 0;
}
int result = var.intVal;
VariantClear (&var);
return result;
}
void
log_addins ()
{
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
return;
}
LPDISPATCH addins = get_oom_object (app, "COMAddins");
if (!addins)
{
TRACEPOINT;
return;
}
std::string activeAddins;
int count = get_oom_int (addins, "Count");
for (int i = 1; i <= count; i++)
{
std::string item = std::string ("Item(") + std::to_string (i) + ")";
LPDISPATCH addin = get_oom_object (addins, item.c_str ());
if (!addin)
{
TRACEPOINT;
continue;
}
bool connected = get_oom_bool (addin, "Connect");
if (!connected)
{
gpgol_release (addin);
continue;
}
char *progId = get_oom_string (addin, "ProgId");
gpgol_release (addin);
if (!progId)
{
TRACEPOINT;
continue;
}
activeAddins += std::string (progId) + "\n";
xfree (progId);
}
gpgol_release (addins);
log_debug ("%s:%s:Active Addins:\n%s", SRCNAME, __func__,
activeAddins.c_str ());
return;
}
bool
is_preview_pane_visible (LPDISPATCH explorer)
{
if (!explorer)
{
TRACEPOINT;
return false;
}
VARIANT var;
VariantInit (&var);
VARIANT argvars[1];
DISPPARAMS args;
VariantInit (&argvars[0]);
argvars[0].vt = VT_INT;
argvars[0].intVal = 3;
args.cArgs = 1;
args.cNamedArgs = 0;
args.rgvarg = argvars;
if (invoke_oom_method_with_parms (explorer, "IsPaneVisible", &var, &args))
{
log_error ("%s:%s: Failed to check visibilty.",
SRCNAME, __func__);
return false;
}
if (var.vt != VT_BOOL)
{
TRACEPOINT;
return false;
}
return !!var.boolVal;
}
static LPDISPATCH
add_user_prop (LPDISPATCH user_props, const char *name)
{
if (!user_props || !name)
{
TRACEPOINT;
return 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);
return nullptr;
}
if (var.vt != VT_DISPATCH)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH ret = var.pdispVal;
memdbg_addRef (ret);
return ret;
}
LPDISPATCH
find_user_prop (LPDISPATCH user_props, const char *name)
{
if (!user_props || !name)
{
TRACEPOINT;
return 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);
return nullptr;
}
if (var.vt != VT_DISPATCH)
{
TRACEPOINT;
return nullptr;
}
LPDISPATCH ret = var.pdispVal;
memdbg_addRef (ret);
return ret;
}
LPDISPATCH
find_or_add_text_prop (LPDISPATCH user_props, const char *name)
{
TRACEPOINT;
LPDISPATCH ret = find_user_prop (user_props, name);
TRACEPOINT;
if (ret)
{
return ret;
}
ret = add_user_prop (user_props, name);
TRACEPOINT;
return ret;
}
void
release_disp (LPDISPATCH obj)
{
gpgol_release (obj);
}
diff --git a/src/parsecontroller.cpp b/src/parsecontroller.cpp
index cc2ee33..cfef007 100644
--- a/src/parsecontroller.cpp
+++ b/src/parsecontroller.cpp
@@ -1,665 +1,671 @@
/* @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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "parsecontroller.h"
#include "attachment.h"
#include "mimedataprovider.h"
#include "keycache.h"
#include <gpgme++/context.h>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/key.h>
#include <sstream>
#ifdef HAVE_W32_SYSTEM
#include "common.h"
/* We use UTF-8 internally. */
#undef _
# define _(a) utf8_gettext (a)
#else
# define _(a) a
#endif
const char decrypt_template_html[] = {
"<html><head></head><body>"
"<table border=\"0\" width=\"100%%\" cellspacing=\"1\" cellpadding=\"1\" bgcolor=\"#0069cc\">"
"<tr>"
"<td bgcolor=\"#0080ff\">"
"<p><span style=\"font-weight:600; background-color:#0080ff;\"><center>%s %s</center><span></p></td></tr>"
"<tr>"
"<td bgcolor=\"#e0f0ff\">"
"<center>"
"<br/>%s"
"</td></tr>"
"</table></body></html>"};
const char decrypt_template[] = {"%s %s\n\n%s"};
using namespace GpgME;
static bool
expect_no_headers (msgtype_t type)
{
return type != MSGTYPE_GPGOL_MULTIPART_SIGNED;
}
static bool
expect_no_mime (msgtype_t type)
{
return type == MSGTYPE_GPGOL_PGP_MESSAGE ||
type == MSGTYPE_GPGOL_CLEAR_SIGNED;
}
#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)
{
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));
}
#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)
{
memdbg_ctor ("ParseController");
log_data ("%s:%s: Creating parser for stream: %p of type %i",
SRCNAME, __func__, instream, type);
}
ParseController::~ParseController()
{
log_debug ("%s:%s", SRCNAME, __func__);
memdbg_dtor ("ParseController");
delete m_inputprovider;
delete m_outputprovider;
}
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)
{
data.seek (0, SEEK_SET);
auto id = data.type();
data.seek (0, SEEK_SET);
return id == Data::CMSSigned || id == Data::CMSEncrypted;
}
static std::string
format_recipients(GpgME::DecryptionResult result, Protocol protocol)
{
std::string msg;
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("<br/>") + key.userIDs()[0].id() + " (0x" + recipient.keyID() + ")";
continue;
}
msg += std::string("<br/>") + _("Unknown Key:") + " 0x" + recipient.keyID();
}
return msg;
}
static std::string
format_error(GpgME::DecryptionResult result, Protocol protocol)
{
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__);
return "Failed to Format error.";
}
msg = buf;
memdbg_alloc (buf);
xfree (buf);
return msg;
}
void
ParseController::setSender(const std::string &sender)
{
m_sender = sender;
}
static bool
is_valid_chksum(const GpgME::Signature &sig)
{
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 );
return sum & valid_mask;
}
/* 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()
{
// Wrap the input stream in an attachment / GpgME Data
Protocol protocol;
bool decrypt, verify;
Data input (m_inputprovider);
auto inputType = input.type ();
if (inputType == Data::Type::PGPSigned)
{
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;
}
else
{
protocol = Protocol::OpenPGP;
}
auto ctx = std::unique_ptr<Context> (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);
return;
}
/* Maybe a different option for this ? */
if (opt.autoresolve)
{
ctx->setFlag("auto-key-retrieve", "1");
}
ctx->setArmor(true);
if (!m_sender.empty())
{
ctx->setSender(m_sender.c_str());
}
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" : m_sender.c_str(), inputType);
+ m_sender.empty() ? "none" : anonstr (m_sender.c_str()), inputType);
if (decrypt)
{
input.seek (0, SEEK_SET);
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)
{
/* 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;
}
else
{
verify = false;
}
if (m_decrypt_result.error () || m_decrypt_result.isNull () ||
m_decrypt_result.error ().isCanceled ())
{
m_error = format_error (m_decrypt_result, protocol);
}
}
if (verify)
{
const auto sig = m_inputprovider->signature();
input.seek (0, SEEK_SET);
if (sig)
{
sig->seek (0, SEEK_SET);
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);
output.seek (0, SEEK_SET);
size_t nread;
while ((nread = input.read (buf, 4096)) > 0)
{
output.write (buf, nread);
}
}
else
{
m_verify_result = ctx->verifyOpaqueSignature(input, output);
const auto sigs = m_verify_result.signatures();
bool allBad = sigs.size();
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> (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);
}
}
#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());
bool has_valid_encrypted_checksum = false;
/* Ensure that the Keys for the signatures are available
and if it has a valid encrypted checksum. */
bool ultimate_keys_queried = false;
for (const auto sig: m_verify_result.signatures())
{
has_valid_encrypted_checksum = is_valid_chksum (sig);
KeyCache::instance ()->update (sig.fingerprint (), protocol);
if (!ultimate_keys_queried &&
(sig.validity() == Signature::Validity::Full ||
sig.validity() == Signature::Validity::Ultimate))
{
/* Ensure that we have the keys with ultimate
trust cached for the ui. */
// TODO this is something for the keycache
get_ultimate_keys ();
ultimate_keys_queried = true;
}
}
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;
}
- if (opt.enable_debug)
+ if (opt.enable_debug & DBG_DATA)
{
std::stringstream ss;
ss << m_decrypt_result << '\n' << m_verify_result;
for (const auto sig: m_verify_result.signatures())
{
const auto key = sig.key();
if (key.isNull())
{
ss << '\n' << "Cached key:\n" << KeyCache::instance()->getByFpr(
sig.fingerprint(), false);
}
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 ();
}
return;
}
const std::string
ParseController::get_html_body () const
{
if (m_outputprovider)
{
return m_outputprovider->get_html_body ();
}
else
{
return std::string();
}
}
const std::string
ParseController::get_body () const
{
if (m_outputprovider)
{
return m_outputprovider->get_body ();
}
else
{
return std::string();
}
}
const std::string
ParseController::get_body_charset() const
{
if (m_outputprovider)
{
return m_outputprovider->get_body_charset();
}
else
{
return std::string();
}
}
const std::string
ParseController::get_html_charset() const
{
if (m_outputprovider)
{
return m_outputprovider->get_html_charset();
}
else
{
return std::string();
}
}
std::vector<std::shared_ptr<Attachment> >
ParseController::get_attachments() const
{
if (m_outputprovider)
{
return m_outputprovider->get_attachments();
}
else
{
return std::vector<std::shared_ptr<Attachment> >();
}
}
GPGRT_LOCK_DEFINE(keylist_lock);
/* static */
std::vector<Key>
ParseController::get_ultimate_keys()
{
static bool s_keys_listed;
static std::vector<Key> s_ultimate_keys;
gpgrt_lock_lock (&keylist_lock);
if (s_keys_listed)
{
gpgrt_lock_unlock (&keylist_lock);
return s_ultimate_keys;
}
log_debug ("%s:%s: Starting keylisting.",
SRCNAME, __func__);
auto ctx = std::unique_ptr<Context> (Context::createForProtocol (OpenPGP));
if (!ctx)
{
/* Maybe PGP broken and not S/MIME */
log_error ("%s:%s: broken installation no ctx.",
SRCNAME, __func__);
gpgrt_lock_unlock (&keylist_lock);
return s_ultimate_keys;
}
ctx->setKeyListMode (KeyListMode::Local);
Error err;
TRACEPOINT;
if ((err = ctx->startKeyListing ()))
{
log_error ("%s:%s: Failed to start keylisting err: %i: %s",
SRCNAME, __func__, err.code (), err.asString());
gpgrt_lock_unlock (&keylist_lock);
return s_ultimate_keys;
}
TRACEPOINT;
while (!err)
{
const auto key = ctx->nextKey(err);
if (err || key.isNull())
{
TRACEPOINT;
break;
}
if (key.isInvalid ())
{
log_debug ("%s:%s: skipping invalid key.",
SRCNAME, __func__);
continue;
}
for (const auto uid: key.userIDs())
{
if (uid.validity() == UserID::Validity::Ultimate &&
uid.id())
{
s_ultimate_keys.push_back (key);
log_debug ("%s:%s: Adding ultimate uid.",
SRCNAME, __func__);
log_data ("%s:%s: Added uid %s.",
- SRCNAME, __func__, uid.id());
+ SRCNAME, __func__, uid.id());
break;
}
}
}
TRACEPOINT;
log_debug ("%s:%s: keylisting done.",
SRCNAME, __func__);
s_keys_listed = true;
gpgrt_lock_unlock (&keylist_lock);
return s_ultimate_keys;
}
diff --git a/src/rfc2047parse.c b/src/rfc2047parse.c
index b49af1a..aef750e 100644
--- a/src/rfc2047parse.c
+++ b/src/rfc2047parse.c
@@ -1,671 +1,671 @@
/* @file rfc2047parse.c
* @brief Parsercode for rfc2047
*
* Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/* This code is heavily based (mostly verbatim copy with glib
* dependencies removed) on GMime rev 496313fb
* modified by aheinecke@intevation.de
*
* Copyright (C) 2000-2014 Jeffrey Stedfast
*
* This library 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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdbool.h>
#include "common_indep.h"
#include <ctype.h>
#ifdef HAVE_W32_SYSTEM
# include "mlang-charset.h"
#endif
#include "gmime-table-private.h"
/* mabye we need this at some point later? */
#define G_MIME_RFC2047_WORKAROUNDS 1
static unsigned char gmime_base64_rank[256] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255,
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255,
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
};
typedef struct _rfc2047_token {
struct _rfc2047_token *next;
char *charset;
const char *text;
size_t length;
char encoding;
char is_8bit;
} rfc2047_token;
static rfc2047_token *
rfc2047_token_new (const char *text, size_t len)
{
rfc2047_token *token;
token = xmalloc (sizeof (rfc2047_token));
memset (token, 0, sizeof (rfc2047_token));
token->length = len;
token->text = text;
return token;
}
static rfc2047_token *
rfc2047_token_new_encoded_word (const char *word, size_t len)
{
rfc2047_token *token;
const char *payload;
char *charset;
const char *inptr;
const char *tmpchar;
char *buf, *lang;
char encoding;
size_t n;
/* check that this could even be an encoded-word token */
if (len < 7 || strncmp (word, "=?", 2) != 0 || strncmp (word + len - 2, "?=", 2) != 0)
return NULL;
/* skip over '=?' */
inptr = word + 2;
tmpchar = inptr;
if (*tmpchar == '?' || *tmpchar == '*') {
/* this would result in an empty charset */
return NULL;
}
/* skip to the end of the charset */
if (!(inptr = memchr (inptr, '?', len - 2)) || inptr[2] != '?')
return NULL;
/* copy the charset into a buffer */
n = (size_t) (inptr - tmpchar);
buf = xmalloc (n + 1);
memcpy (buf, tmpchar, n);
buf[n] = '\0';
charset = buf;
/* rfc2231 updates rfc2047 encoded words...
* The ABNF given in RFC 2047 for encoded-words is:
* encoded-word := "=?" charset "?" encoding "?" encoded-text "?="
* This specification changes this ABNF to:
* encoded-word := "=?" charset ["*" language] "?" encoding "?" encoded-text "?="
*/
/* trim off the 'language' part if it's there... */
if ((lang = strchr (charset, '*')))
*lang = '\0';
/* skip over the '?' */
inptr++;
/* make sure the first char after the encoding is another '?' */
if (inptr[1] != '?')
return NULL;
switch (*inptr++) {
case 'B': case 'b':
encoding = 'B';
break;
case 'Q': case 'q':
encoding = 'Q';
break;
default:
return NULL;
}
/* the payload begins right after the '?' */
payload = inptr + 1;
/* find the end of the payload */
inptr = word + len - 2;
/* make sure that we don't have something like: =?iso-8859-1?Q?= */
if (payload > inptr)
return NULL;
token = rfc2047_token_new (payload, inptr - payload);
token->charset = charset;
token->encoding = encoding;
return token;
}
static void
rfc2047_token_free (rfc2047_token * tok)
{
if (!tok)
{
return;
}
xfree (tok->charset);
xfree (tok);
}
static rfc2047_token *
tokenize_rfc2047_phrase (const char *in, size_t *len)
{
bool enable_rfc2047_workarounds = G_MIME_RFC2047_WORKAROUNDS;
rfc2047_token list, *lwsp, *token, *tail;
register const char *inptr = in;
bool encoded = false;
const char *text, *word;
bool ascii;
size_t n;
tail = (rfc2047_token *) &list;
list.next = NULL;
lwsp = NULL;
while (*inptr != '\0') {
text = inptr;
while (is_lwsp (*inptr))
inptr++;
if (inptr > text)
lwsp = rfc2047_token_new (text, inptr - text);
else
lwsp = NULL;
word = inptr;
ascii = true;
if (is_atom (*inptr)) {
if (enable_rfc2047_workarounds) {
/* Make an extra effort to detect and
* separate encoded-word tokens that
* have been merged with other
* words. */
if (!strncmp (inptr, "=?", 2)) {
inptr += 2;
/* skip past the charset (if one is even declared, sigh) */
while (*inptr && *inptr != '?') {
ascii = ascii && is_ascii (*inptr);
inptr++;
}
/* sanity check encoding type */
if (inptr[0] != '?' || !strchr ("BbQq", inptr[1]) || inptr[2] != '?')
goto non_rfc2047;
inptr += 3;
/* find the end of the rfc2047 encoded word token */
while (*inptr && strncmp (inptr, "?=", 2) != 0) {
ascii = ascii && is_ascii (*inptr);
inptr++;
}
if (*inptr == '\0') {
/* didn't find an end marker... */
inptr = word + 2;
ascii = true;
goto non_rfc2047;
}
inptr += 2;
} else {
non_rfc2047:
/* stop if we encounter a possible rfc2047 encoded
* token even if it's inside another word, sigh. */
while (is_atom (*inptr) && strncmp (inptr, "=?", 2) != 0)
inptr++;
}
} else {
while (is_atom (*inptr))
inptr++;
}
n = (size_t) (inptr - word);
if ((token = rfc2047_token_new_encoded_word (word, n))) {
/* rfc2047 states that you must ignore all
* whitespace between encoded words */
if (!encoded && lwsp != NULL) {
tail->next = lwsp;
tail = lwsp;
} else if (lwsp != NULL) {
rfc2047_token_free (lwsp);
}
tail->next = token;
tail = token;
encoded = true;
} else {
/* append the lwsp and atom tokens */
if (lwsp != NULL) {
tail->next = lwsp;
tail = lwsp;
}
token = rfc2047_token_new (word, n);
token->is_8bit = ascii ? 0 : 1;
tail->next = token;
tail = token;
encoded = false;
}
} else {
/* append the lwsp token */
if (lwsp != NULL) {
tail->next = lwsp;
tail = lwsp;
}
ascii = true;
while (*inptr && !is_lwsp (*inptr) && !is_atom (*inptr)) {
ascii = ascii && is_ascii (*inptr);
inptr++;
}
n = (size_t) (inptr - word);
token = rfc2047_token_new (word, n);
token->is_8bit = ascii ? 0 : 1;
tail->next = token;
tail = token;
encoded = false;
}
}
*len = (size_t) (inptr - in);
return list.next;
}
static void
rfc2047_token_list_free (rfc2047_token * tokens)
{
rfc2047_token * cur = tokens;
while (cur)
{
rfc2047_token *next = cur->next;
rfc2047_token_free (cur);
cur = next;
}
}
/* this decodes rfc2047's version of quoted-printable */
static size_t
quoted_decode (const unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
{
register const unsigned char *inptr;
register unsigned char *outptr;
const unsigned char *inend;
unsigned char c, c1;
unsigned int saved;
int need;
if (len == 0)
return 0;
inend = in + len;
outptr = out;
inptr = in;
need = *state;
saved = *save;
if (need > 0) {
if (isxdigit ((int) *inptr)) {
if (need == 1) {
c = toupper ((int) (saved & 0xff));
c1 = toupper ((int) *inptr++);
saved = 0;
need = 0;
goto decode;
}
saved = 0;
need = 0;
goto equals;
}
/* last encoded-word ended in a malformed quoted-printable sequence */
*outptr++ = '=';
if (need == 1)
*outptr++ = (char) (saved & 0xff);
saved = 0;
need = 0;
}
while (inptr < inend) {
c = *inptr++;
if (c == '=') {
equals:
if (inend - inptr >= 2) {
if (isxdigit ((int) inptr[0]) && isxdigit ((int) inptr[1])) {
c = toupper (*inptr++);
c1 = toupper (*inptr++);
decode:
*outptr++ = (((c >= 'A' ? c - 'A' + 10 : c - '0') & 0x0f) << 4)
| ((c1 >= 'A' ? c1 - 'A' + 10 : c1 - '0') & 0x0f);
} else {
/* malformed quoted-printable sequence? */
*outptr++ = '=';
}
} else {
/* truncated payload, maybe it was split across encoded-words? */
if (inptr < inend) {
if (isxdigit ((int) *inptr)) {
saved = *inptr;
need = 1;
break;
} else {
/* malformed quoted-printable sequence? */
*outptr++ = '=';
}
} else {
saved = 0;
need = 2;
break;
}
}
} else if (c == '_') {
/* _'s are an rfc2047 shortcut for encoding spaces */
*outptr++ = ' ';
} else {
*outptr++ = c;
}
}
*state = need;
*save = saved;
return (size_t) (outptr - out);
}
/**
* g_mime_encoding_base64_decode_step:
* @inbuf: input buffer
* @inlen: input buffer length
* @outbuf: output buffer
* @state: holds the number of bits that are stored in @save
* @save: leftover bits that have not yet been decoded
*
* Decodes a chunk of base64 encoded data.
*
* Returns: the number of bytes decoded (which have been dumped in
* @outbuf).
**/
size_t
g_mime_encoding_base64_decode_step (const unsigned char *inbuf, size_t inlen, unsigned char *outbuf, int *state, unsigned int *save)
{
register const unsigned char *inptr;
register unsigned char *outptr;
const unsigned char *inend;
register unsigned int saved;
unsigned char c;
int npad, n, i;
inend = inbuf + inlen;
outptr = outbuf;
inptr = inbuf;
npad = (*state >> 8) & 0xff;
n = *state & 0xff;
saved = *save;
/* convert 4 base64 bytes to 3 normal bytes */
while (inptr < inend) {
c = gmime_base64_rank[*inptr++];
if (c != 0xff) {
saved = (saved << 6) | c;
n++;
if (n == 4) {
*outptr++ = saved >> 16;
*outptr++ = saved >> 8;
*outptr++ = saved;
n = 0;
if (npad > 0) {
outptr -= npad;
npad = 0;
}
}
}
}
/* quickly scan back for '=' on the end somewhere */
/* fortunately we can drop 1 output char for each trailing '=' (up to 2) */
for (i = 2; inptr > inbuf && i; ) {
inptr--;
if (gmime_base64_rank[*inptr] != 0xff) {
if (*inptr == '=' && outptr > outbuf) {
if (n == 0) {
/* we've got a complete quartet so it's
safe to drop an output character. */
outptr--;
} else if (npad < 2) {
/* keep a record of the number of ='s at
the end of the input stream, up to 2 */
npad++;
}
}
i--;
}
}
*state = (npad << 8) | n;
*save = n ? saved : 0;
return (outptr - outbuf);
}
static size_t
rfc2047_token_decode (rfc2047_token *token, unsigned char *outbuf, int *state, unsigned int *save)
{
const unsigned char *inbuf = (const unsigned char *) token->text;
size_t len = token->length;
if (token->encoding == 'B')
return g_mime_encoding_base64_decode_step (inbuf, len, outbuf, state, save);
else
return quoted_decode (inbuf, len, outbuf, state, save);
}
static char *
rfc2047_decode_tokens (rfc2047_token *tokens, size_t buflen)
{
rfc2047_token *token, *next;
size_t outlen, len, tmplen;
unsigned char *outptr;
const char *charset;
char *outbuf;
char *decoded;
char encoding;
unsigned int save;
int state;
char *str;
decoded = xmalloc (buflen + 1);
memset (decoded, 0, buflen + 1);
tmplen = 76;
outbuf = xmalloc (tmplen);
token = tokens;
while (token != NULL) {
next = token->next;
if (token->encoding) {
/* In order to work around broken mailers, we need to combine
* the raw decoded content of runs of identically encoded word
* tokens before converting into UTF-8. */
encoding = token->encoding;
charset = token->charset;
len = token->length;
state = 0;
save = 0;
/* find the end of the run (and measure the buffer length we'll need) */
while (next && next->encoding == encoding && !strcmp (next->charset, charset)) {
len += next->length;
next = next->next;
}
/* make sure our temporary output buffer is large enough... */
if (len > tmplen)
{
outbuf = xrealloc (outbuf, len + 1);
tmplen = len + 1;
}
/* base64 / quoted-printable decode each of the tokens... */
outptr = outbuf;
outlen = 0;
do {
/* Note: by not resetting state/save each loop, we effectively
* treat the payloads as one continuous block, thus allowing
* us to handle cases where a hex-encoded triplet of a
* quoted-printable encoded payload is split between 2 or more
* encoded-word tokens. */
len = rfc2047_token_decode (token, outptr, &state, &save);
token = token->next;
outptr += len;
outlen += len;
} while (token != next);
outptr = outbuf;
/* convert the raw decoded text into UTF-8 */
if (!strcasecmp (charset, "UTF-8")) {
strncat (decoded, (char *) outptr, outlen);
} else {
#ifdef HAVE_W32_SYSTEM
str = ansi_charset_to_utf8 (charset, outptr, outlen, 0);
#else
log_debug ("%s:%s: Conversion not available on non W32 systems",
SRCNAME, __func__);
str = strndup (outptr, outlen);
#endif
if (!str)
{
log_error ("%s:%s: Failed conversion from: %s for word: %s.",
- SRCNAME, __func__, charset, outptr);
+ SRCNAME, __func__, charset, anonstr (outptr));
}
else
{
strcat (decoded, str);
xfree (str);
}
}
} else {
strncat (decoded, token->text, token->length);
}
if (token && token->is_8bit)
{
/* We don't support this. */
log_error ("%s:%s: Unknown 8bit encoding detected.",
SRCNAME, __func__);
}
token = next;
}
xfree (outbuf);
return decoded;
}
/**
* g_mime_utils_header_decode_phrase:
* @phrase: header to decode
*
* Decodes an rfc2047 encoded 'phrase' header.
*
* Note: See g_mime_set_user_charsets() for details on how charset
* conversion is handled for unencoded 8bit text and/or wrongly
* specified rfc2047 encoded-word tokens.
*
* Returns: a newly allocated UTF-8 string representing the the decoded
* header.
**/
static char *
g_mime_utils_header_decode_phrase (const char *phrase)
{
rfc2047_token *tokens;
char *decoded;
size_t len;
tokens = tokenize_rfc2047_phrase (phrase, &len);
decoded = rfc2047_decode_tokens (tokens, len);
rfc2047_token_list_free (tokens);
return decoded;
}
/* Try to parse an rfc 2047 filename for attachment handling.
returns the parsed string. On errors the input string is just
copied with strdup */
char *
rfc2047_parse (const char *input)
{
char *decoded;
if (!input)
return xstrdup ("");
- log_debug ("%s:%s: Input: \"%s\"",
- SRCNAME, __func__, input);
+ log_data ("%s:%s: Input: \"%s\"",
+ SRCNAME, __func__, input);
decoded = g_mime_utils_header_decode_phrase (input);
- log_debug ("%s:%s: Decoded: \"%s\"",
- SRCNAME, __func__, decoded);
+ log_data ("%s:%s: Decoded: \"%s\"",
+ SRCNAME, __func__, decoded);
if (!decoded || !strlen (decoded))
{
xfree (decoded);
return xstrdup (input);
}
return decoded;
}
diff --git a/src/w32-gettext.h b/src/w32-gettext.h
index c450b31..9608dc0 100644
--- a/src/w32-gettext.h
+++ b/src/w32-gettext.h
@@ -1,96 +1,96 @@
/* w32-gettext.h - A simple gettext implementation for Windows targets.
Copyright (C) 2005 g10 Code GmbH
This file is part of libgpg-error.
libgpg-error 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.
libgpg-error 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 libgpg-error; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
#if ENABLE_NLS
#include <locale.h>
#if !defined LC_MESSAGES && !(defined __LOCALE_H || (defined _LOCALE_H && defined __sun))
# define LC_MESSAGES 1729
#endif
#ifdef __cplusplus
extern "C" {
#if 0
}
#endif
#endif
/* Specify that the DOMAINNAME message catalog will be found
in DIRNAME rather than in the system locale data base. */
char *bindtextdomain (const char *domainname, const char *dirname);
const char *gettext (const char *msgid);
const char *utf8_gettext (const char *msgid);
char *textdomain (const char *domainname);
char *dgettext (const char *domainname, const char *msgid);
/* Return the localname as used by gettext. The return value will
never be NULL. */
const char *gettext_localename (void);
/* A pseudo function call that serves as a marker for the automated
extraction of messages, but does not call gettext(). The run-time
translation is done at a different place in the code.
The argument, String, should be a literal string. Concatenated strings
and other string expressions won't work.
The macro's expansion is not parenthesized, so that it is suitable as
initializer for static 'char[]' or 'const char[]' variables. */
#define gettext_noop(String) String
#else /* ENABLE_NLS */
static inline const char *gettext_localename (void) { return ""; }
#endif /* !ENABLE_NLS */
/* Conversion function. */
char *_wchar_to_utf8 (const wchar_t *string);
wchar_t *_utf8_to_wchar (const char *string);
char *utf8_to_native (const char *string);
char *native_to_utf8 (const char *string);
#define utf8_to_wchar(VAR1) ({wchar_t *retval; \
retval = _utf8_to_wchar (VAR1); \
- if ((opt.enable_debug & DBG_OOM_EXTRA) && 0) \
+ if ((opt.enable_debug & DBG_SUPERTRACE)) \
{ \
log_debug ("%s:%s:%i wchar_t alloc %p:%S", \
SRCNAME, __func__, __LINE__, retval, retval); \
} \
retval;})
#define wchar_to_utf8(VAR1) ({char *retval; \
retval = _wchar_to_utf8 (VAR1); \
- if ((opt.enable_debug & DBG_OOM_EXTRA) && 0) \
+ if ((opt.enable_debug & DBG_SUPERTRACE)) \
{ \
log_debug ("%s:%s:%i char utf8 alloc %p:%s", \
SRCNAME, __func__, __LINE__, retval, retval); \
} \
retval;})
#ifdef __cplusplus
}
#include <string>
std::string wchar_to_utf8_string (const wchar_t *string);
#endif
diff --git a/src/wks-helper.cpp b/src/wks-helper.cpp
index 388454a..acf79e3 100644
--- a/src/wks-helper.cpp
+++ b/src/wks-helper.cpp
@@ -1,852 +1,848 @@
/* wks-helper.cpp - Web Key Services for GpgOL
* Copyright (C) 2018 Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "wks-helper.h"
#include "common.h"
#include "cpphelp.h"
#include "oomhelp.h"
#include "windowmessages.h"
#include "mail.h"
#include "mapihelp.h"
#include <map>
#include <sstream>
#include <unistd.h>
#include <stdlib.h>
#include <gpg-error.h>
#include <gpgme++/key.h>
#include <gpgme++/data.h>
#include <gpgme++/context.h>
#define CHECK_MIN_INTERVAL (60 * 60 * 24 * 7)
#define WKS_REG_KEY "webkey"
-#define DEBUG_WKS 1
-
#undef _
#define _(a) utf8_gettext (a)
static std::map <std::string, WKSHelper::WKSState> s_states;
static std::map <std::string, time_t> s_last_checked;
static std::map <std::string, std::pair <GpgME::Data *, Mail *> > s_confirmation_cache;
static WKSHelper* singleton = nullptr;
GPGRT_LOCK_DEFINE (wks_lock);
WKSHelper::WKSHelper()
{
load ();
}
WKSHelper::~WKSHelper ()
{
// Ensure that we are not destroyed while
// worker is running.
gpgrt_lock_lock (&wks_lock);
gpgrt_lock_unlock (&wks_lock);
}
const WKSHelper*
WKSHelper::instance ()
{
if (!singleton)
{
singleton = new WKSHelper ();
}
return singleton;
}
WKSHelper::WKSState
WKSHelper::get_state (const std::string &mbox) const
{
gpgrt_lock_lock (&wks_lock);
const auto it = s_states.find(mbox);
const auto dataEnd = s_states.end();
gpgrt_lock_unlock (&wks_lock);
if (it == dataEnd)
{
return NotChecked;
}
return it->second;
}
time_t
WKSHelper::get_check_time (const std::string &mbox) const
{
gpgrt_lock_lock (&wks_lock);
const auto it = s_last_checked.find(mbox);
const auto dataEnd = s_last_checked.end();
gpgrt_lock_unlock (&wks_lock);
if (it == dataEnd)
{
return 0;
}
return it->second;
}
std::pair <GpgME::Data *, Mail *>
WKSHelper::get_cached_confirmation (const std::string &mbox) const
{
gpgrt_lock_lock (&wks_lock);
const auto it = s_confirmation_cache.find(mbox);
const auto dataEnd = s_confirmation_cache.end();
if (it == dataEnd)
{
gpgrt_lock_unlock (&wks_lock);
return std::make_pair (nullptr, nullptr);
}
auto ret = it->second;
s_confirmation_cache.erase (it);
gpgrt_lock_unlock (&wks_lock);
return ret;
}
static std::string
get_wks_client_path ()
{
char *gpg4win_dir = get_gpg4win_dir ();
if (!gpg4win_dir)
{
TRACEPOINT;
return std::string ();
}
const auto ret = std::string (gpg4win_dir) +
"\\..\\GnuPG\\bin\\gpg-wks-client.exe";
xfree (gpg4win_dir);
if (!access (ret.c_str (), F_OK))
{
return ret;
}
log_debug ("%s:%s: Failed to find wks-client in '%s'",
SRCNAME, __func__, ret.c_str ());
return std::string ();
}
static bool
check_published (const std::string &mbox)
{
const auto wksPath = get_wks_client_path ();
if (wksPath.empty())
{
return 0;
}
std::vector<std::string> args;
args.push_back (wksPath);
args.push_back (std::string ("--status-fd"));
args.push_back (std::string ("1"));
args.push_back (std::string ("--check"));
args.push_back (mbox);
// Spawn the process
auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine);
if (!ctx)
{
TRACEPOINT;
return false;
}
GpgME::Data mystdin, mystdout, mystderr;
char **cargs = vector_to_cArray (args);
GpgME::Error err = ctx->spawn (cargs[0], const_cast <const char **> (cargs),
mystdin, mystdout, mystderr,
GpgME::Context::SpawnNone);
release_cArray (cargs);
if (err)
{
log_debug ("%s:%s: WKS client spawn code: %i asString: %s",
SRCNAME, __func__, err.code(), err.asString());
return false;
}
auto data = mystdout.toString ();
rtrim (data);
return data == "[GNUPG:] SUCCESS";
}
static DWORD WINAPI
do_check (LPVOID arg)
{
const auto wksPath = get_wks_client_path ();
if (wksPath.empty())
{
return 0;
}
std::vector<std::string> args;
const auto mbox = std::string ((char *) arg);
xfree (arg);
args.push_back (wksPath);
args.push_back (std::string ("--status-fd"));
args.push_back (std::string ("1"));
args.push_back (std::string ("--supported"));
args.push_back (mbox);
// Spawn the process
auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine);
if (!ctx)
{
TRACEPOINT;
return 0;
}
GpgME::Data mystdin, mystdout, mystderr;
char **cargs = vector_to_cArray (args);
GpgME::Error err = ctx->spawn (cargs[0], const_cast <const char **> (cargs),
mystdin, mystdout, mystderr,
GpgME::Context::SpawnNone);
release_cArray (cargs);
if (err)
{
log_debug ("%s:%s: WKS client spawn code: %i asString: %s",
SRCNAME, __func__, err.code(), err.asString());
return 0;
}
auto data = mystdout.toString ();
rtrim (data);
bool success = data == "[GNUPG:] SUCCESS";
// TODO Figure out NeedsPublish state.
auto state = success ? WKSHelper::NeedsPublish : WKSHelper::NotSupported;
bool isPublished = false;
if (success)
{
log_debug ("%s:%s: WKS client: '%s' is supported",
- SRCNAME, __func__, mbox.c_str ());
+ SRCNAME, __func__, anonstr (mbox.c_str ()));
isPublished = check_published (mbox);
}
if (isPublished)
{
log_debug ("%s:%s: WKS client: '%s' is published",
- SRCNAME, __func__, mbox.c_str ());
+ SRCNAME, __func__, anonstr (mbox.c_str ()));
state = WKSHelper::IsPublished;
}
WKSHelper::instance()->update_state (mbox, state, false);
WKSHelper::instance()->update_last_checked (mbox, time (0));
return 0;
}
void
WKSHelper::start_check (const std::string &mbox, bool forced) const
{
const auto state = get_state (mbox);
if (!forced && (state != NotChecked && state != NotSupported))
{
log_debug ("%s:%s: Check aborted because its neither "
"not supported nor not checked.",
SRCNAME, __func__);
return;
}
auto lastTime = get_check_time (mbox);
auto now = time (0);
if (!forced && (state == NotSupported && lastTime &&
difftime (now, lastTime) < CHECK_MIN_INTERVAL))
{
/* Data is new enough */
log_debug ("%s:%s: Check aborted because last checked is too recent.",
SRCNAME, __func__);
return;
}
if (mbox.empty())
{
log_debug ("%s:%s: start check called without mbox",
SRCNAME, __func__);
}
log_debug ("%s:%s: WKSHelper starting check",
SRCNAME, __func__);
/* Start the actual work that can be done in a background thread. */
CloseHandle (CreateThread (nullptr, 0, do_check, xstrdup (mbox.c_str ()), 0,
nullptr));
return;
}
void
WKSHelper::load () const
{
/* Map of mbox'es to states. states are <state>;<last_checked> */
const auto map = get_registry_subkeys (WKS_REG_KEY);
for (const auto &pair: map)
{
const auto mbox = pair.first;
const auto states = gpgol_split (pair.second, ';');
if (states.size() != 2)
{
log_error ("%s:%s: Invalid state '%s' for '%s'",
- SRCNAME, __func__, mbox.c_str (), pair.second.c_str ());
+ SRCNAME, __func__, anonstr (mbox.c_str ()),
+ anonstr (pair.second.c_str ()));
continue;
}
WKSState state = (WKSState) strtol (states[0].c_str (), nullptr, 10);
if (state == PublishInProgress)
{
/* Probably an error during the last publish. Let's start again. */
update_state (mbox, NotChecked, false);
continue;
}
time_t update_time = (time_t) strtol (states[1].c_str (), nullptr, 10);
update_state (mbox, state, false);
update_last_checked (mbox, update_time, false);
}
}
void
WKSHelper::save () const
{
gpgrt_lock_lock (&wks_lock);
for (const auto &pair: s_states)
{
auto state = std::to_string (pair.second) + ';';
const auto it = s_last_checked.find (pair.first);
if (it != s_last_checked.end ())
{
state += std::to_string (it->second);
}
else
{
state += '0';
}
if (store_extension_subkey_value (WKS_REG_KEY, pair.first.c_str (),
state.c_str ()))
{
log_error ("%s:%s: Failed to store state.",
SRCNAME, __func__);
}
}
gpgrt_lock_unlock (&wks_lock);
}
static DWORD WINAPI
do_notify (LPVOID arg)
{
/** Wait till a message was sent */
std::pair<char *, int> *args = (std::pair<char *, int> *) arg;
Sleep (args->second);
do_in_ui_thread (WKS_NOTIFY, args->first);
delete args;
return 0;
}
void
WKSHelper::allow_notify (int sleepTimeMS) const
{
gpgrt_lock_lock (&wks_lock);
for (auto &pair: s_states)
{
if (pair.second == ConfirmationSeen ||
pair.second == NeedsPublish)
{
auto *args = new std::pair<char *, int> (xstrdup (pair.first.c_str()),
sleepTimeMS);
CloseHandle (CreateThread (nullptr, 0, do_notify,
args, 0,
nullptr));
break;
}
}
gpgrt_lock_unlock (&wks_lock);
}
void
WKSHelper::notify (const char *cBox) const
{
std::string mbox = cBox;
const auto state = get_state (mbox);
if (state == NeedsPublish)
{
char *buf;
gpgrt_asprintf (&buf, _("A Pubkey directory is available for the address:\n\n"
"\t%s\n\n"
"Register your Pubkey in that directory to make\n"
"it easy for others to send you encrypted mail.\n\n"
"It's secure and free!\n\n"
"Register automatically?"), mbox.c_str ());
memdbg_alloc (buf);
if (gpgol_message_box (get_active_hwnd (),
buf,
_("GpgOL: Pubkey directory available!"), MB_YESNO) == IDYES)
{
start_publish (mbox);
}
else
{
update_state (mbox, PublishDenied);
}
xfree (buf);
return;
}
if (state == ConfirmationSeen)
{
handle_confirmation_notify (mbox);
return;
}
log_debug ("%s:%s: Unhandled notify state: %i for '%s'",
- SRCNAME, __func__, state, cBox);
+ SRCNAME, __func__, state, anonstr (cBox));
return;
}
void
WKSHelper::start_publish (const std::string &mbox) const
{
log_debug ("%s:%s: Start publish for '%s'",
SRCNAME, __func__, mbox.c_str ());
update_state (mbox, PublishInProgress);
const auto key = GpgME::Key::locate (mbox.c_str ());
if (key.isNull ())
{
MessageBox (get_active_hwnd (),
"WKS publish failed to find key for mail address.",
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
return;
}
const auto wksPath = get_wks_client_path ();
if (wksPath.empty())
{
TRACEPOINT;
return;
}
std::vector<std::string> args;
args.push_back (wksPath);
args.push_back (std::string ("--create"));
args.push_back (std::string (key.primaryFingerprint ()));
args.push_back (mbox);
// Spawn the process
auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine);
if (!ctx)
{
TRACEPOINT;
return;
}
GpgME::Data mystdin, mystdout, mystderr;
char **cargs = vector_to_cArray (args);
GpgME::Error err = ctx->spawn (cargs[0], const_cast <const char **> (cargs),
mystdin, mystdout, mystderr,
GpgME::Context::SpawnNone);
release_cArray (cargs);
if (err)
{
log_debug ("%s:%s: WKS client spawn code: %i asString: %s",
SRCNAME, __func__, err.code(), err.asString());
return;
}
const auto data = mystdout.toString ();
if (data.empty ())
{
gpgol_message_box (get_active_hwnd (),
mystderr.toString().c_str (),
_("GpgOL: Directory request failed"),
MB_OK);
return;
}
-#ifdef DEBUG_WKS
- log_debug ("%s:%s: WKS client: returned '%s'",
+ log_data ("%s:%s: WKS client: returned '%s'",
SRCNAME, __func__, data.c_str ());
-#endif
if (!send_mail (data))
{
gpgol_message_box (get_active_hwnd (),
_("You might receive a confirmation challenge from\n"
"your provider to finish the registration."),
_("GpgOL: Registration request sent!"), MB_OK);
}
update_state (mbox, RequestSent);
return;
}
void
WKSHelper::update_state (const std::string &mbox, WKSState state,
bool store) const
{
gpgrt_lock_lock (&wks_lock);
auto it = s_states.find(mbox);
if (it != s_states.end())
{
it->second = state;
}
else
{
s_states.insert (std::make_pair (mbox, state));
}
gpgrt_lock_unlock (&wks_lock);
if (store)
{
save ();
}
}
void
WKSHelper::update_last_checked (const std::string &mbox, time_t time,
bool store) const
{
gpgrt_lock_lock (&wks_lock);
auto it = s_last_checked.find(mbox);
if (it != s_last_checked.end())
{
it->second = time;
}
else
{
s_last_checked.insert (std::make_pair (mbox, time));
}
gpgrt_lock_unlock (&wks_lock);
if (store)
{
save ();
}
}
int
WKSHelper::send_mail (const std::string &mimeData) const
{
std::istringstream ss(mimeData);
std::string from;
std::string to;
std::string subject;
std::string withoutHeaders;
std::getline (ss, from);
std::getline (ss, to);
std::getline (ss, subject);
if (from.compare (0, 6, "From: ") || to.compare (0, 4, "To: "),
subject.compare (0, 9, "Subject: "))
{
log_error ("%s:%s: Invalid mime data..",
SRCNAME, __func__);
return -1;
}
std::getline (ss, withoutHeaders, '\0');
from.erase (0, 6);
to.erase (0, 4);
subject.erase (0, 9);
rtrim (from);
rtrim (to);
rtrim (subject);
LPDISPATCH mail = create_mail ();
if (!mail)
{
log_error ("%s:%s: Failed to create mail for request.",
SRCNAME, __func__);
return -1;
}
/* Now we have a problem. The created LPDISPATCH pointer has
a different value then the one with which we saw the ItemLoad
event. But we want to get the mail object. So,.. surpise
a Hack! :-) */
auto last_mail = Mail::getLastMail ();
if (!Mail::isValidPtr (last_mail))
{
log_error ("%s:%s: Invalid last mail %p.",
SRCNAME, __func__, last_mail);
return -1;
}
/* Adding to / Subject etc already leads to changes and events so
we set up the state before this. */
last_mail->setOverrideMIMEData (mimeData);
last_mail->setCryptState (Mail::NeedsSecondAfterWrite);
if (put_oom_string (mail, "Subject", subject.c_str ()))
{
TRACEPOINT;
gpgol_release (mail);
return -1;
}
if (put_oom_string (mail, "To", to.c_str ()))
{
TRACEPOINT;
gpgol_release (mail);
return -1;
}
LPDISPATCH account = get_account_for_mail (from.c_str ());
if (account)
{
log_debug ("%s:%s: Found account to change for '%s'.",
- SRCNAME, __func__, from.c_str ());
+ SRCNAME, __func__, anonstr (from.c_str ()));
put_oom_disp (mail, "SendUsingAccount", account);
}
gpgol_release (account);
if (invoke_oom_method (mail, "Save", nullptr))
{
// Should not happen.
log_error ("%s:%s: Failed to save mail.",
SRCNAME, __func__);
return -1;
}
if (invoke_oom_method (mail, "Send", nullptr))
{
log_error ("%s:%s: Failed to send mail.",
SRCNAME, __func__);
return -1;
}
log_debug ("%s:%s: Done send mail.",
SRCNAME, __func__);
return 0;
}
static void
copy_stream_to_data (LPSTREAM stream, GpgME::Data *data)
{
HRESULT hr;
char buf[4096];
ULONG bRead;
while ((hr = stream->Read (buf, 4096, &bRead)) == S_OK ||
hr == S_FALSE)
{
if (!bRead)
{
// EOF
return;
}
data->write (buf, (size_t) bRead);
}
}
void
WKSHelper::handle_confirmation_notify (const std::string &mbox) const
{
auto pair = get_cached_confirmation (mbox);
GpgME::Data *mimeData = pair.first;
Mail *mail = pair.second;
if (!mail && !mimeData)
{
log_debug ("%s:%s: Confirmation notify without cached data.",
SRCNAME, __func__);
/* This happens when we have seen a confirmation but have
* not confirmed it and the state was saved. So we go back
* to the confirmation sent state and wait until we see
* the confirmation the next time. */
update_state (mbox, ConfirmationSent);
return;
}
/* First ask the user if he wants to confirm */
if (gpgol_message_box (get_active_hwnd (),
_("Confirm registration?"),
_("GpgOL: Pubkey directory confirmation"), MB_YESNO) != IDYES)
{
log_debug ("%s:%s: User aborted confirmation.",
SRCNAME, __func__);
delete mimeData;
/* Next time we read the confirmation we ask again. */
update_state (mbox, RequestSent);
return;
}
/* Do the confirmation */
const auto wksPath = get_wks_client_path ();
if (wksPath.empty())
{
TRACEPOINT;
return;
}
std::vector<std::string> args;
args.push_back (wksPath);
args.push_back (std::string ("--receive"));
// Spawn the process
auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine);
if (!ctx)
{
TRACEPOINT;
return;
}
GpgME::Data mystdout, mystderr;
char **cargs = vector_to_cArray (args);
GpgME::Error err = ctx->spawn (cargs[0], const_cast <const char **> (cargs),
*mimeData, mystdout, mystderr,
GpgME::Context::SpawnNone);
release_cArray (cargs);
if (err)
{
log_debug ("%s:%s: WKS client spawn code: %i asString: %s",
SRCNAME, __func__, err.code(), err.asString());
return;
}
const auto data = mystdout.toString ();
if (data.empty ())
{
gpgol_message_box (get_active_hwnd (),
mystderr.toString().c_str (),
_("GpgOL: Confirmation failed"),
MB_OK);
return;
}
-#ifdef DEBUG_WKS
- log_debug ("%s:%s: WKS client: returned '%s'",
- SRCNAME, __func__, data.c_str ());
-#endif
+ log_data ("%s:%s: WKS client: returned '%s'",
+ SRCNAME, __func__, data.c_str ());
+
if (!send_mail (data))
{
gpgol_message_box (get_active_hwnd (),
_("Your Pubkey can soon be retrieved from your domain."),
_("GpgOL: Request confirmed!"), MB_OK);
}
if (mail && Mail::isValidPtr (mail))
{
invoke_oom_method (mail->item(), "Delete", nullptr);
}
update_state (mbox, ConfirmationSent);
}
void
WKSHelper::handle_confirmation_read (Mail *mail, LPSTREAM stream) const
{
/* We get the handle_confirmation in the Read event. To do sending
etc. we have to move out of that event. For this we prepare
the data for later usage. */
if (!mail || !stream)
{
TRACEPOINT;
return;
}
/* Get the recipient of the confirmation mail */
const auto recipients = mail->getRecipients_o ();
/* We assert that we have one recipient as the mail should have been
sent by the wks-server. */
if (recipients.size() != 1)
{
log_error ("%s:%s: invalid recipients",
SRCNAME, __func__);
gpgol_release (stream);
return;
}
std::string mbox = recipients[0];
/* Prepare stdin for the wks-client process */
/* First we need to write the headers */
LPMESSAGE message = get_oom_base_message (mail->item());
if (!message)
{
log_error ("%s:%s: Failed to obtain message.",
SRCNAME, __func__);
gpgol_release (stream);
return;
}
const auto headers = mapi_get_header (message);
gpgol_release (message);
GpgME::Data *mystdin = new GpgME::Data();
mystdin->write (headers.c_str (), headers.size ());
/* Then the MIME data */
copy_stream_to_data (stream, mystdin);
gpgol_release (stream);
/* Then lets make sure its flushy */
mystdin->write (nullptr, 0);
/* And reset it to start */
mystdin->seek (0, SEEK_SET);
gpgrt_lock_lock (&wks_lock);
s_confirmation_cache.insert (std::make_pair (mbox, std::make_pair (mystdin, mail)));
gpgrt_lock_unlock (&wks_lock);
update_state (mbox, ConfirmationSeen);
/* Send the window message for notify. */
allow_notify (5000);
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Dec 6, 11:56 PM (19 h, 12 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
29/93/74c55df89308da455126387c5678

Event Timeline