Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34240964
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
220 KB
Subscribers
None
View Options
diff --git a/src/debug.cpp b/src/debug.cpp
index c453b39..88e2923 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;
}
free (logfile);
if (!name || *name == '\"' || !*name)
logfile = NULL;
else
logfile = strdup (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);
+ gpgol_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);
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);
+ gpgol_unlock (&anon_str_lock);
return it->second.c_str();
}
diff --git a/src/debug.h b/src/debug.h
index 7b089ce..1d762b9 100644
--- a/src/debug.h
+++ b/src/debug.h
@@ -1,127 +1,149 @@
#ifndef DEBUG_H
#define DEBUG_H
/* debug.h - 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/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef __cplusplus
extern "C" {
#if 0
}
#endif
#endif
/* Bit values used for extra log file verbosity. Value 1 is reserved
to enable debug menu options.
Note that the high values here are used for compatibility with
old howtos of how to enable debug flags. Based on the old
very split up logging categories.
Categories are meant to be:
DBG -> Generally useful information.
DBG_MEMORY -> Very verbose tracing of Releases / Allocs / Refs.
DBG_OOM -> Outlook Object Model events tracing.
DBG_DATA -> Including potentially private data and mime parser logging.
Common values are:
32 -> Only memory debugging.
544 -> OOM and Memory.
800 -> Full debugging.
*/
#define DBG_OOM (1<<1) // 2
#define DBG_MEMORY (1<<2) // 4
#define DBG_TRACE (1<<3) // 8
#define DBG_DATA (1<<4) // 16
void log_debug (const char *fmt, ...) __attribute__ ((format (printf,1,2)));
void log_error (const char *fmt, ...) __attribute__ ((format (printf,1,2)));
void log_vdebug (const char *fmt, va_list a);
void log_debug_w32 (int w32err, const char *fmt,
...) __attribute__ ((format (printf,2,3)));
void log_error_w32 (int w32err, const char *fmt,
...) __attribute__ ((format (printf,2,3)));
void log_hexdump (const void *buf, size_t buflen, const char *fmt,
...) __attribute__ ((format (printf,3,4)));
const char *anonstr (const char *data);
#define log_oom(format, ...) if ((opt.enable_debug & DBG_OOM)) \
log_debug("DBG_OOM/" format, ##__VA_ARGS__)
#define log_data(format, ...) if ((opt.enable_debug & DBG_DATA)) \
log_debug("DBG_DATA/" format, ##__VA_ARGS__)
#define log_memory(format, ...) if ((opt.enable_debug & DBG_MEMORY)) \
log_debug("DBG_MEM/" format, ##__VA_ARGS__)
#define log_trace(format, ...) if ((opt.enable_debug & DBG_TRACE)) \
log_debug("TRACE/" format, ##__VA_ARGS__)
#define gpgol_release(X) \
{ \
if (X && opt.enable_debug & DBG_MEMORY) \
{ \
log_memory ("%s:%s:%i: Object: %p released ref: %lu \n", \
SRCNAME, __func__, __LINE__, X, X->Release()); \
memdbg_released (X); \
} \
else if (X) \
{ \
X->Release(); \
} \
}
+
+#define gpgol_lock(X) \
+{ \
+ if (opt.enable_debug & DBG_TRACE) \
+ { \
+ log_trace ("%s:%s:%i: lock %p lock", \
+ SRCNAME, __func__, __LINE__, X); \
+ } \
+ gpgrt_lock_lock(X); \
+}
+
+
+#define gpgol_unlock(X) \
+{ \
+ if (opt.enable_debug & DBG_TRACE) \
+ { \
+ log_trace ("%s:%s:%i: lock %p unlock.", \
+ SRCNAME, __func__, __LINE__, X); \
+ } \
+ gpgrt_lock_unlock(X); \
+}
+
const char *log_srcname (const char *s);
#define SRCNAME log_srcname (__FILE__)
#define STRANGEPOINT log_debug ("%s:%s:%d:_trace_", \
SRCNAME, __func__, __LINE__);
#define TRACEPOINT log_trace ("%s:%s:%d", \
SRCNAME, __func__, __LINE__);
#define TSTART log_trace ("%s:%s:%d enter", SRCNAME, __func__, __LINE__);
#define TRETURN log_trace ("%s:%s:%d: return", SRCNAME, __func__, \
__LINE__); \
return
#define TBREAK log_trace ("%s:%s:%d: break", SRCNAME, __func__, \
__LINE__); \
break
const char *get_log_file (void);
void set_log_file (const char *name);
#ifdef _WIN64
#define SIZE_T_FORMAT "%I64u"
#else
# ifdef HAVE_W32_SYSTEM
# define SIZE_T_FORMAT "%u"
# else
# define SIZE_T_FORMAT "%lu"
# endif
#endif
#ifdef __cplusplus
}
#endif
#endif // DEBUG_H
diff --git a/src/dispcache.cpp b/src/dispcache.cpp
index 7b1bbf3..e26669e 100644
--- a/src/dispcache.cpp
+++ b/src/dispcache.cpp
@@ -1,114 +1,114 @@
/* 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 "dispcache.h"
#include "gpg-error.h"
#include "common.h"
#include "gpgoladdin.h"
#include <unordered_map>
GPGRT_LOCK_DEFINE (cache_lock);
class DispCache::Private
{
public:
Private()
{
}
void addDisp (int id, LPDISPATCH obj)
{
if (!id || !obj)
{
TRACEPOINT;
return;
}
- gpgrt_lock_lock (&cache_lock);
+ gpgol_lock (&cache_lock);
auto it = m_cache.find (id);
if (it != m_cache.end ())
{
log_debug ("%s:%s Item \"%i\" already cached. Replacing it.",
SRCNAME, __func__, id);
gpgol_release (it->second);
it->second = obj;
- gpgrt_lock_unlock (&cache_lock);
+ gpgol_unlock (&cache_lock);
return;
}
m_cache.insert (std::make_pair (id, obj));
- gpgrt_lock_unlock (&cache_lock);
+ gpgol_unlock (&cache_lock);
return;
}
LPDISPATCH getDisp (int id)
{
if (!id)
{
TRACEPOINT;
return nullptr;
}
- gpgrt_lock_lock (&cache_lock);
+ gpgol_lock (&cache_lock);
const auto it = m_cache.find (id);
if (it != m_cache.end())
{
LPDISPATCH ret = it->second;
- gpgrt_lock_unlock (&cache_lock);
+ gpgol_unlock (&cache_lock);
return ret;
}
- gpgrt_lock_unlock (&cache_lock);
+ gpgol_unlock (&cache_lock);
return nullptr;
}
~Private ()
{
- gpgrt_lock_lock (&cache_lock);
+ gpgol_lock (&cache_lock);
for (const auto it: m_cache)
{
gpgol_release (it.second);
}
- gpgrt_lock_unlock (&cache_lock);
+ gpgol_unlock (&cache_lock);
}
private:
std::unordered_map<int, LPDISPATCH> m_cache;
};
DispCache::DispCache (): d (new Private)
{
}
void
DispCache::addDisp (int id, LPDISPATCH obj)
{
d->addDisp (id, obj);
}
LPDISPATCH
DispCache::getDisp (int id)
{
return d->getDisp (id);
}
DispCache *
DispCache::instance ()
{
return GpgolAddin::get_instance ()->get_dispcache ().get ();
}
diff --git a/src/explorer-events.cpp b/src/explorer-events.cpp
index 7ccedfd..6139460 100644
--- a/src/explorer-events.cpp
+++ b/src/explorer-events.cpp
@@ -1,231 +1,231 @@
/* explorer-events.cpp - Event handling for the application.
* 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/>.
*/
/* The event handler classes defined in this file follow the
general pattern that they implment the IDispatch interface
through the eventsink macros and handle event invocations
in their invoke methods.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "eventsink.h"
#include "ocidl.h"
#include "common.h"
#include "oomhelp.h"
#include "mail.h"
#include "gpgoladdin.h"
#include "windowmessages.h"
/* Explorer Events */
BEGIN_EVENT_SINK(ExplorerEvents, IDispatch)
EVENT_SINK_DEFAULT_CTOR(ExplorerEvents)
EVENT_SINK_DEFAULT_DTOR(ExplorerEvents)
typedef enum
{
Activate = 0xF001,
AttachmentSelectionChange = 0xFC79,
BeforeFolderSwitch = 0xF003,
BeforeItemCopy = 0xFA0E,
BeforeItemCut = 0xFA0F,
BeforeItemPaste = 0xFA10,
BeforeMaximize = 0xFA11,
BeforeMinimize = 0xFA12,
BeforeMove = 0xFA13,
BeforeSize = 0xFA14,
BeforeViewSwitch = 0xF005,
Close = 0xF008,
Deactivate = 0xF006,
DisplayModeChange = 0xFC98,
FolderSwitch = 0xF002,
InlineResponse = 0xFC92,
InlineResponseClose = 0xFC96,
SelectionChange = 0xF007,
ViewSwitch = 0xF004
} ExplorerEvent;
/*
We need to avoid UI invalidations as much as possible as invalidations
can trigger reloads of mails and at a bad time can crash us.
So we only invalidate the UI after we have handled the read event of
a mail and again after decrypt / verify.
The problem is that we also need to update the UI when mails are
unselected so we don't show "Secure" if nothing is selected.
On a switch from one Mail to another we see two selection changes.
One for the unselect the other for the select immediately after
each other.
When we just have an unselect we see only one selection change.
So after we detect the unselect we switch the state in our
explorerMap to unselect seen and start a WatchDog thread.
That thread sleeps for 500ms and then checks if the state
was switched to select seen in the meantime. If
not it triggers the UI Invalidation in the GUI thread.
*/
#include <map>
typedef enum
{
WatchDogActive = 0x01,
UnselectSeen = 0x02,
SelectSeen = 0x04,
} SelectionState;
std::map<LPDISPATCH, int> s_explorerMap;
gpgrt_lock_t explorer_map_lock = GPGRT_LOCK_INITIALIZER;
static bool
hasSelection (LPDISPATCH explorer)
{
LPDISPATCH selection = get_oom_object (explorer, "Selection");
if (!selection)
{
TRACEPOINT;
return false;
}
int count = get_oom_int (selection, "Count");
gpgol_release (selection);
if (count)
{
return true;
}
return false;
}
static DWORD WINAPI
start_watchdog (LPVOID arg)
{
LPDISPATCH explorer = (LPDISPATCH) arg;
Sleep (500);
- gpgrt_lock_lock (&explorer_map_lock);
+ gpgol_lock (&explorer_map_lock);
auto it = s_explorerMap.find (explorer);
if (it == s_explorerMap.end ())
{
log_error ("%s:%s: Watchdog for unknwon explorer %p",
SRCNAME, __func__, explorer);
- gpgrt_lock_unlock (&explorer_map_lock);
+ gpgol_unlock (&explorer_map_lock);
return 0;
}
if ((it->second & SelectSeen))
{
log_oom ("%s:%s: Cancel watchdog as we have seen a select %p",
SRCNAME, __func__, explorer);
it->second = SelectSeen;
}
else if ((it->second & UnselectSeen))
{
log_debug ("%s:%s: Deteced unselect invalidating UI.",
SRCNAME, __func__);
it->second = UnselectSeen;
- gpgrt_lock_unlock (&explorer_map_lock);
+ gpgol_unlock (&explorer_map_lock);
do_in_ui_thread (INVALIDATE_UI, nullptr);
return 0;
}
- gpgrt_lock_unlock (&explorer_map_lock);
+ gpgol_unlock (&explorer_map_lock);
return 0;
}
static void
changeSeen (LPDISPATCH explorer)
{
- gpgrt_lock_lock (&explorer_map_lock);
+ gpgol_lock (&explorer_map_lock);
auto it = s_explorerMap.find (explorer);
if (it == s_explorerMap.end ())
{
it = s_explorerMap.insert (std::make_pair (explorer, 0)).first;
}
auto state = it->second;
bool has_selection = hasSelection (explorer);
if (has_selection)
{
it->second = (state & WatchDogActive) + SelectSeen;
log_oom ("%s:%s: Seen select for %p",
SRCNAME, __func__, explorer);
}
else
{
if ((it->second & WatchDogActive))
{
log_oom ("%s:%s: Seen unselect for %p but watchdog exists.",
SRCNAME, __func__, explorer);
}
else
{
CloseHandle (CreateThread (NULL, 0, start_watchdog, (LPVOID) explorer,
0, NULL));
}
it->second = UnselectSeen + WatchDogActive;
}
- gpgrt_lock_unlock (&explorer_map_lock);
+ gpgol_unlock (&explorer_map_lock);
}
EVENT_SINK_INVOKE(ExplorerEvents)
{
USE_INVOKE_ARGS
switch(dispid)
{
case SelectionChange:
{
log_oom ("%s:%s: Selection change in explorer: %p",
SRCNAME, __func__, this);
changeSeen (m_object);
break;
}
case Close:
{
log_oom ("%s:%s: Deleting event handler: %p",
SRCNAME, __func__, this);
GpgolAddin::get_instance ()->unregisterExplorerSink (this);
- gpgrt_lock_lock (&explorer_map_lock);
+ gpgol_lock (&explorer_map_lock);
s_explorerMap.erase (m_object);
- gpgrt_lock_unlock (&explorer_map_lock);
+ gpgol_unlock (&explorer_map_lock);
delete this;
return S_OK;
}
default:
break;
#if 0
log_oom ("%s:%s: Unhandled Event: %lx \n",
SRCNAME, __func__, dispid);
#endif
}
return S_OK;
}
END_EVENT_SINK(ExplorerEvents, IID_ExplorerEvents)
diff --git a/src/keycache.cpp b/src/keycache.cpp
index 26a72ce..f0e635b 100644
--- a/src/keycache.cpp
+++ b/src/keycache.cpp
@@ -1,1175 +1,1175 @@
/* @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)
{
TSTART;
s_thread_cnt++;
Mail::lockDelete ();
if (Mail::isValidPtr (m_mail))
{
m_mail->incrementLocateCount ();
}
Mail::unlockDelete ();
TRETURN;
};
~LocateArgs()
{
TSTART;
s_thread_cnt--;
Mail::lockDelete ();
if (Mail::isValidPtr (m_mail))
{
m_mail->decrementLocateCount ();
}
Mail::unlockDelete ();
TRETURN;
}
std::string m_mbox;
Mail *m_mail;
};
} // namespace
typedef std::pair<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)
{
TSTART;
auto args = std::unique_ptr<update_arg_t> ((update_arg_t*) arg);
log_debug ("%s:%s updating: \"%s\" with protocol %s",
SRCNAME, __func__, anonstr (args->first.c_str ()),
to_cstr (args->second));
auto ctx = std::unique_ptr<GpgME::Context> (GpgME::Context::createForProtocol
(args->second));
if (!ctx)
{
TRACEPOINT;
KeyCache::instance ()->onUpdateJobDone (args->first.c_str(),
GpgME::Key ());
TRETURN 0;
}
ctx->setKeyListMode (GpgME::KeyListMode::Local |
GpgME::KeyListMode::Signatures |
GpgME::KeyListMode::Validate |
GpgME::KeyListMode::WithTofu);
GpgME::Error err;
const auto newKey = ctx->key (args->first.c_str (), err, false);
TRACEPOINT;
if (newKey.isNull())
{
log_debug ("%s:%s Failed to find key for %s",
SRCNAME, __func__, anonstr (args->first.c_str ()));
}
if (err)
{
log_debug ("%s:%s Failed to find key for %s err: %s",
SRCNAME, __func__, anonstr (args->first.c_str()),
err.asString ());
}
KeyCache::instance ()->onUpdateJobDone (args->first.c_str(),
newKey);
log_debug ("%s:%s Update job done",
SRCNAME, __func__);
TRETURN 0;
}
static DWORD WINAPI
do_import (LPVOID arg)
{
TSTART;
auto args = std::unique_ptr<import_arg_t> ((import_arg_t*) arg);
const std::string mbox = args->first->m_mbox;
log_debug ("%s:%s importing for: \"%s\" with data \n%s",
SRCNAME, __func__, anonstr (mbox.c_str ()),
anonstr (args->second.c_str ()));
auto ctx = std::unique_ptr<GpgME::Context> (GpgME::Context::createForProtocol
(GpgME::OpenPGP));
if (!ctx)
{
TRACEPOINT;
TRETURN 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__, anonstr (mbox.c_str ()));
TRETURN 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__, anonstr (fpr));
}
KeyCache::instance ()->onAddrBookImportJobDone (mbox, fingerprints);
log_debug ("%s:%s Import job done for: %s",
SRCNAME, __func__, anonstr (mbox.c_str ()));
TRETURN 0;
}
class KeyCache::Private
{
public:
Private()
{
}
void setPgpKey(const std::string &mbox, const GpgME::Key &key)
{
TSTART;
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
auto it = m_pgp_key_map.find (mbox);
if (it == m_pgp_key_map.end ())
{
m_pgp_key_map.insert (std::pair<std::string, GpgME::Key> (mbox, key));
}
else
{
it->second = key;
}
insertOrUpdateInFprMap (key);
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN;
}
void setSmimeKey(const std::string &mbox, const GpgME::Key &key)
{
TSTART;
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
auto it = m_smime_key_map.find (mbox);
if (it == m_smime_key_map.end ())
{
m_smime_key_map.insert (std::pair<std::string, GpgME::Key> (mbox, key));
}
else
{
it->second = key;
}
insertOrUpdateInFprMap (key);
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN;
}
void setPgpKeySecret(const std::string &mbox, const GpgME::Key &key)
{
TSTART;
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
auto it = m_pgp_skey_map.find (mbox);
if (it == m_pgp_skey_map.end ())
{
m_pgp_skey_map.insert (std::pair<std::string, GpgME::Key> (mbox, key));
}
else
{
it->second = key;
}
insertOrUpdateInFprMap (key);
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN;
}
void setSmimeKeySecret(const std::string &mbox, const GpgME::Key &key)
{
TSTART;
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
auto it = m_smime_skey_map.find (mbox);
if (it == m_smime_skey_map.end ())
{
m_smime_skey_map.insert (std::pair<std::string, GpgME::Key> (mbox, key));
}
else
{
it->second = key;
}
insertOrUpdateInFprMap (key);
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN;
}
std::vector<GpgME::Key> getPGPOverrides (const char *addr)
{
TSTART;
std::vector<GpgME::Key> ret;
if (!addr)
{
TRETURN ret;
}
auto mbox = GpgME::UserID::addrSpecFromString (addr);
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
const auto it = m_addr_book_overrides.find (mbox);
if (it == m_addr_book_overrides.end ())
{
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN 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__, anonstr (fpr.c_str()));
continue;
}
ret.push_back (key);
}
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN ret;
}
GpgME::Key getKey (const char *addr, GpgME::Protocol proto)
{
TSTART;
if (!addr)
{
TRETURN GpgME::Key();
}
auto mbox = GpgME::UserID::addrSpecFromString (addr);
if (proto == GpgME::OpenPGP)
{
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
const auto it = m_pgp_key_map.find (mbox);
if (it == m_pgp_key_map.end ())
{
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN GpgME::Key();
}
const auto ret = it->second;
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN ret;
}
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
const auto it = m_smime_key_map.find (mbox);
if (it == m_smime_key_map.end ())
{
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN GpgME::Key();
}
const auto ret = it->second;
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN ret;
}
GpgME::Key getSKey (const char *addr, GpgME::Protocol proto)
{
TSTART;
if (!addr)
{
TRETURN GpgME::Key();
}
auto mbox = GpgME::UserID::addrSpecFromString (addr);
if (proto == GpgME::OpenPGP)
{
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
const auto it = m_pgp_skey_map.find (mbox);
if (it == m_pgp_skey_map.end ())
{
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN GpgME::Key();
}
const auto ret = it->second;
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN ret;
}
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
const auto it = m_smime_skey_map.find (mbox);
if (it == m_smime_skey_map.end ())
{
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN GpgME::Key();
}
const auto ret = it->second;
- gpgrt_lock_unlock (&keycache_lock);
+ gpgol_unlock (&keycache_lock);
TRETURN ret;
}
GpgME::Key getSigningKey (const char *addr, GpgME::Protocol proto)
{
TSTART;
const auto key = getSKey (addr, proto);
if (key.isNull())
{
log_debug ("%s:%s: secret key for %s is null",
SRCNAME, __func__, anonstr (addr));
TRETURN key;
}
if (!key.canReallySign())
{
log_debug ("%s:%s: Discarding key for %s because it can't sign",
SRCNAME, __func__, anonstr (addr));
TRETURN GpgME::Key();
}
if (!key.hasSecret())
{
log_debug ("%s:%s: Discarding key for %s because it has no secret",
SRCNAME, __func__, anonstr (addr));
TRETURN GpgME::Key();
}
if (in_de_vs_mode () && !key.isDeVs())
{
log_debug ("%s:%s: signing key for %s is not deVS",
SRCNAME, __func__, anonstr (addr));
TRETURN GpgME::Key();
}
TRETURN key;
}
std::vector<GpgME::Key> getEncryptionKeys (const std::vector<std::string>
&recipients,
GpgME::Protocol proto)
{
TSTART;
std::vector<GpgME::Key> ret;
if (recipients.empty ())
{
TRACEPOINT;
TRETURN 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_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_debug ("%s:%s: No key for %s. no internal encryption",
SRCNAME, __func__, anonstr (recip.c_str ()));
TRETURN 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__, anonstr (recip.c_str ()));
TRETURN std::vector<GpgME::Key>();
}
if (in_de_vs_mode () && !key.isDeVs ())
{
log_data ("%s:%s: key for %s is not deVS",
SRCNAME, __func__, anonstr (recip.c_str ()));
TRETURN std::vector<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_debug ("%s:%s: UID for %s does not have at least marginal trust",
SRCNAME, __func__, anonstr (recip.c_str ()));
TRETURN std::vector<GpgME::Key>();
}
// Accepting key
ret.push_back (key);
}
TRETURN ret;
}
void insertOrUpdateInFprMap (const GpgME::Key &key)
{
TSTART;
if (key.isNull() || !key.primaryFingerprint())
{
TRACEPOINT;
TRETURN;
}
- gpgrt_lock_lock (&fpr_map_lock);
+ gpgol_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_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);
+ gpgol_unlock (&fpr_map_lock);
TRETURN;
}
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);
+ gpgol_unlock (&fpr_map_lock);
TRETURN;
}
GpgME::Key getFromMap (const char *fpr) const
{
TSTART;
if (!fpr)
{
TRACEPOINT;
TRETURN GpgME::Key();
}
- gpgrt_lock_lock (&fpr_map_lock);
+ gpgol_lock (&fpr_map_lock);
std::string primaryFpr;
const auto it = m_sub_fpr_map.find (fpr);
if (it != m_sub_fpr_map.end ())
{
log_debug ("%s:%s using \"%s\" for \"%s\"",
SRCNAME, __func__, anonstr (it->second.c_str()),
anonstr (fpr));
primaryFpr = it->second;
}
else
{
primaryFpr = fpr;
}
const auto keyIt = m_fpr_map.find (primaryFpr);
if (keyIt != m_fpr_map.end ())
{
const auto ret = keyIt->second;
- gpgrt_lock_unlock (&fpr_map_lock);
+ gpgol_unlock (&fpr_map_lock);
TRETURN ret;
}
- gpgrt_lock_unlock (&fpr_map_lock);
+ gpgol_unlock (&fpr_map_lock);
TRETURN GpgME::Key();
}
GpgME::Key getByFpr (const char *fpr, bool block) const
{
TSTART;
if (!fpr)
{
TRACEPOINT;
TRETURN GpgME::Key ();
}
TRACEPOINT;
const auto ret = getFromMap (fpr);
if (ret.isNull())
{
// If the key was not found we need to check if there is
// an update running.
if (block)
{
const std::string sFpr (fpr);
int i = 0;
- gpgrt_lock_lock (&update_lock);
+ gpgol_lock (&update_lock);
while (m_update_jobs.find(sFpr) != m_update_jobs.end ())
{
i++;
if (i % 100 == 0)
{
log_debug ("%s:%s Waiting on update for \"%s\"",
SRCNAME, __func__, anonstr (fpr));
}
- gpgrt_lock_unlock (&update_lock);
+ gpgol_unlock (&update_lock);
Sleep (10);
- gpgrt_lock_lock (&update_lock);
+ gpgol_lock (&update_lock);
if (i == 3000)
{
/* Just to be on the save side */
log_error ("%s:%s Waiting on update for \"%s\" "
"failed! Bug!",
SRCNAME, __func__, anonstr (fpr));
break;
}
}
- gpgrt_lock_unlock (&update_lock);
+ gpgol_unlock (&update_lock);
TRACEPOINT;
const auto ret2 = getFromMap (fpr);
if (ret2.isNull ())
{
log_debug ("%s:%s Cache miss after blocking check %s.",
SRCNAME, __func__, anonstr (fpr));
}
else
{
log_debug ("%s:%s Cache hit after wait for %s.",
SRCNAME, __func__, anonstr (fpr));
TRETURN ret2;
}
}
log_debug ("%s:%s Cache miss for %s.",
SRCNAME, __func__, anonstr (fpr));
TRETURN GpgME::Key();
}
log_debug ("%s:%s Cache hit for %s.",
SRCNAME, __func__, anonstr (fpr));
TRETURN ret;
}
void update (const char *fpr, GpgME::Protocol proto)
{
TSTART;
if (!fpr)
{
TRETURN;
}
const std::string sFpr (fpr);
- gpgrt_lock_lock (&update_lock);
+ gpgol_lock (&update_lock);
if (m_update_jobs.find(sFpr) != m_update_jobs.end ())
{
log_debug ("%s:%s Update for \"%s\" already in progress.",
SRCNAME, __func__, anonstr (fpr));
- gpgrt_lock_unlock (&update_lock);
+ gpgol_unlock (&update_lock);
}
m_update_jobs.insert (sFpr);
- gpgrt_lock_unlock (&update_lock);
+ gpgol_unlock (&update_lock);
update_arg_t * args = new update_arg_t;
args->first = sFpr;
args->second = proto;
CloseHandle (CreateThread (NULL, 0, do_update,
(LPVOID) args, 0,
NULL));
TRETURN;
}
void onUpdateJobDone (const char *fpr, const GpgME::Key &key)
{
TSTART;
if (!fpr)
{
TRETURN;
}
TRACEPOINT;
insertOrUpdateInFprMap (key);
- gpgrt_lock_lock (&update_lock);
+ gpgol_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__, anonstr (fpr));
- gpgrt_lock_unlock (&update_lock);
+ gpgol_unlock (&update_lock);
TRETURN;
}
m_update_jobs.erase (it);
- gpgrt_lock_unlock (&update_lock);
+ gpgol_unlock (&update_lock);
TRACEPOINT;
TRETURN;
}
void importFromAddrBook (const std::string &mbox, const char *data,
Mail *mail)
{
TSTART;
if (!data || mbox.empty() || !mail)
{
TRACEPOINT;
TRETURN;
}
- gpgrt_lock_lock (&import_lock);
+ gpgol_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__, anonstr (mbox.c_str ()));
- gpgrt_lock_unlock (&import_lock);
+ gpgol_unlock (&import_lock);
}
m_import_jobs.insert (mbox);
- gpgrt_lock_unlock (&import_lock);
+ gpgol_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));
TRETURN;
}
void onAddrBookImportJobDone (const std::string &mbox,
const std::vector<std::string> &result_fprs)
{
TSTART;
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_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);
+ gpgol_unlock (&keycache_lock);
+ gpgol_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__, anonstr (mbox.c_str ()));
- gpgrt_lock_unlock (&import_lock);
+ gpgol_unlock (&import_lock);
TRETURN;
}
m_import_jobs.erase (job_it);
- gpgrt_lock_unlock (&import_lock);
+ gpgol_unlock (&import_lock);
TRETURN;
}
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)
{
TSTART;
if (!arg)
{
TRETURN 0;
}
auto args = std::unique_ptr<LocateArgs> ((LocateArgs *) arg);
const auto addr = args->m_mbox;
log_debug ("%s:%s searching key for addr: \"%s\"",
SRCNAME, __func__, anonstr (addr.c_str()));
const auto k = GpgME::Key::locate (addr.c_str());
if (!k.isNull ())
{
log_debug ("%s:%s found key for addr: \"%s\":%s",
SRCNAME, __func__, anonstr (addr.c_str()),
anonstr (k.primaryFingerprint()));
KeyCache::instance ()->setPgpKey (addr, k);
}
if (opt.enable_smime)
{
auto ctx = std::unique_ptr<GpgME::Context> (
GpgME::Context::createForProtocol (GpgME::CMS));
if (!ctx)
{
TRACEPOINT;
TRETURN 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;
TRETURN 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_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_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__);
TRETURN 0;
}
static void
locate_secret (const char *addr, GpgME::Protocol proto)
{
TSTART;
auto ctx = std::unique_ptr<GpgME::Context> (
GpgME::Context::createForProtocol (proto));
if (!ctx)
{
TRACEPOINT;
TRETURN;
}
if (!addr)
{
TRACEPOINT;
TRETURN;
}
const auto mbox = GpgME::UserID::addrSpecFromString (addr);
if (mbox.empty())
{
log_debug ("%s:%s: Empty mbox for addr %s",
SRCNAME, __func__, anonstr (addr));
TRETURN;
}
// We need to validate here to fetch CRL's
ctx->setKeyListMode (GpgME::KeyListMode::Local |
GpgME::KeyListMode::Validate);
GpgME::Error e = ctx->startKeyListing (mbox.c_str(), true);
if (e)
{
TRACEPOINT;
TRETURN;
}
std::vector<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_DATA))
{
std::stringstream ss;
ss << key;
log_data ("%s:%s: Skipping invalid secret key %s",
SRCNAME, __func__, ss.str().c_str());
}
continue;
}
if (proto == GpgME::OpenPGP)
{
log_debug ("%s:%s found pgp skey for addr: \"%s\":%s",
SRCNAME, __func__, anonstr (mbox.c_str()),
anonstr (key.primaryFingerprint()));
KeyCache::instance()->setPgpKeySecret (mbox, key);
TRETURN;
}
if (proto == GpgME::CMS)
{
log_debug ("%s:%s found cms skey for addr: \"%s\":%s",
SRCNAME, __func__, anonstr (mbox.c_str ()),
anonstr (key.primaryFingerprint()));
KeyCache::instance()->setSmimeKeySecret (mbox, key);
TRETURN;
}
} while (!err);
TRETURN;
}
static DWORD WINAPI
do_locate_secret (LPVOID arg)
{
TSTART;
auto args = std::unique_ptr<LocateArgs> ((LocateArgs *) arg);
log_debug ("%s:%s searching secret key for addr: \"%s\"",
SRCNAME, __func__, anonstr (args->m_mbox.c_str ()));
locate_secret (args->m_mbox.c_str(), GpgME::OpenPGP);
if (opt.enable_smime)
{
locate_secret (args->m_mbox.c_str(), GpgME::CMS);
}
log_debug ("%s:%s locator sthread thread done",
SRCNAME, __func__);
TRETURN 0;
}
void
KeyCache::startLocate (const std::vector<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
{
TSTART;
if (!addr)
{
TRACEPOINT;
TRETURN;
}
std::string recp = GpgME::UserID::addrSpecFromString (addr);
if (recp.empty ())
{
TRETURN;
}
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
if (d->m_pgp_key_map.find (recp) == d->m_pgp_key_map.end ())
{
// It's enough to look at the PGP Key map. We marked
// searched keys there.
d->m_pgp_key_map.insert (std::pair<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);
+ gpgol_unlock (&keycache_lock);
TRETURN;
}
void
KeyCache::startLocateSecret (const char *addr, Mail *mail) const
{
TSTART;
if (!addr)
{
TRACEPOINT;
TRETURN;
}
std::string recp = GpgME::UserID::addrSpecFromString (addr);
if (recp.empty ())
{
TRETURN;
}
- gpgrt_lock_lock (&keycache_lock);
+ gpgol_lock (&keycache_lock);
if (d->m_pgp_skey_map.find (recp) == d->m_pgp_skey_map.end ())
{
// It's enough to look at the PGP Key map. We marked
// searched keys there.
d->m_pgp_skey_map.insert (std::pair<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);
+ gpgol_unlock (&keycache_lock);
TRETURN;
}
void
KeyCache::setSmimeKey(const std::string &mbox, const GpgME::Key &key)
{
d->setSmimeKey(mbox, key);
}
void
KeyCache::setPgpKey(const std::string &mbox, const GpgME::Key &key)
{
d->setPgpKey(mbox, key);
}
void
KeyCache::setSmimeKeySecret(const std::string &mbox, const GpgME::Key &key)
{
d->setSmimeKeySecret(mbox, key);
}
void
KeyCache::setPgpKeySecret(const std::string &mbox, const GpgME::Key &key)
{
d->setPgpKeySecret(mbox, key);
}
bool
KeyCache::isMailResolvable(Mail *mail)
{
TSTART;
/* Get the data from the mail. */
const auto sender = mail->getSender ();
auto recps = mail->getCachedRecipients ();
if (sender.empty() || recps.empty())
{
log_debug ("%s:%s: Mail has no sender or no recipients.",
SRCNAME, __func__);
TRETURN false;
}
std::vector<GpgME::Key> encKeys = getEncryptionKeys (recps,
GpgME::OpenPGP);
if (!encKeys.empty())
{
TRETURN true;
}
if (!opt.enable_smime)
{
TRETURN false;
}
/* Check S/MIME instead here we need to include the sender
as we can't just generate a key. */
recps.push_back (sender);
GpgME::Key sigKey= getSigningKey (sender.c_str(), GpgME::CMS);
encKeys = getEncryptionKeys (recps, GpgME::CMS);
TRETURN !encKeys.empty() && !sigKey.isNull();
}
void
KeyCache::update (const char *fpr, GpgME::Protocol proto)
{
d->update (fpr, proto);
}
GpgME::Key
KeyCache::getByFpr (const char *fpr, bool block) const
{
return d->getByFpr (fpr, block);
}
void
KeyCache::onUpdateJobDone (const char *fpr, const GpgME::Key &key)
{
return d->onUpdateJobDone (fpr, key);
}
void
KeyCache::importFromAddrBook (const std::string &mbox, const char *key_data,
Mail *mail) 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 f30804ed..bfff980 100644
--- a/src/mail.cpp
+++ b/src/mail.cpp
@@ -1,3733 +1,3733 @@
/* @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)
{
TSTART;
if (getMailForItem (mailitem))
{
log_error ("Mail object for item: %p already exists. Bug.",
mailitem);
TRETURN;
}
m_event_sink = install_MailItemEvents_sink (mailitem);
if (!m_event_sink)
{
/* Should not happen but in that case we don't add us to the list
and just release the Mail item. */
log_error ("%s:%s: Failed to install MailItemEvents sink.",
SRCNAME, __func__);
gpgol_release(mailitem);
TRETURN;
}
- gpgrt_lock_lock (&mail_map_lock);
+ gpgol_lock (&mail_map_lock);
s_mail_map.insert (std::pair<LPDISPATCH, Mail *> (mailitem, this));
- gpgrt_lock_unlock (&mail_map_lock);
+ gpgol_unlock (&mail_map_lock);
s_last_mail = this;
memdbg_ctor ("Mail");
TRETURN;
}
GPGRT_LOCK_DEFINE(dtor_lock);
// static
void
Mail::lockDelete ()
{
TSTART;
- gpgrt_lock_lock (&dtor_lock);
+ gpgol_lock (&dtor_lock);
TRETURN;
}
// static
void
Mail::unlockDelete ()
{
TSTART;
- gpgrt_lock_unlock (&dtor_lock);
+ gpgol_unlock (&dtor_lock);
TRETURN;
}
Mail::~Mail()
{
TSTART;
/* This should fix a race condition where the mail is
deleted before the parser is accessed in the decrypt
thread. The shared_ptr of the parser then ensures
that the parser is alive even if the mail is deleted
while parsing. */
- gpgrt_lock_lock (&dtor_lock);
+ gpgol_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);
+ gpgol_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);
+ gpgol_unlock (&mail_map_lock);
if (!m_uuid.empty())
{
- gpgrt_lock_lock (&uid_map_lock);
+ gpgol_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);
+ gpgol_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);
+ gpgol_unlock (&dtor_lock);
log_oom ("%s:%s: returning",
SRCNAME, __func__);
TRETURN;
}
//static
Mail *
Mail::getMailForItem (LPDISPATCH mailitem)
{
TSTART;
if (!mailitem)
{
TRETURN NULL;
}
std::map<LPDISPATCH, Mail *>::iterator it;
- gpgrt_lock_lock (&mail_map_lock);
+ gpgol_lock (&mail_map_lock);
it = s_mail_map.find(mailitem);
- gpgrt_lock_unlock (&mail_map_lock);
+ gpgol_unlock (&mail_map_lock);
if (it == s_mail_map.end())
{
TRETURN NULL;
}
TRETURN it->second;
}
//static
Mail *
Mail::getMailForUUID (const char *uuid)
{
TSTART;
if (!uuid)
{
TRETURN NULL;
}
- gpgrt_lock_lock (&uid_map_lock);
+ gpgol_lock (&uid_map_lock);
auto it = s_uid_map.find(std::string(uuid));
- gpgrt_lock_unlock (&uid_map_lock);
+ gpgol_unlock (&uid_map_lock);
if (it == s_uid_map.end())
{
TRETURN NULL;
}
TRETURN it->second;
}
//static
bool
Mail::isValidPtr (const Mail *mail)
{
TSTART;
- gpgrt_lock_lock (&mail_map_lock);
+ gpgol_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);
+ gpgol_unlock (&mail_map_lock);
TRETURN true;
}
++it;
}
- gpgrt_lock_unlock (&mail_map_lock);
+ gpgol_unlock (&mail_map_lock);
TRETURN false;
}
int
Mail::preProcessMessage_m ()
{
TSTART;
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
TRETURN 0;
}
log_oom ("%s:%s: GetBaseMessage OK for %p.",
SRCNAME, __func__, m_mailitem);
/* Change the message class here. It is important that
we change the message class in the before read event
regardless if it is already set to one of GpgOL's message
classes. Changing the message class (even if we set it
to the same value again that it already has) causes
Outlook to reconsider what it "knows" about a message
and reread data from the underlying base message. */
mapi_change_message_class (message, 1, &m_type);
if (m_type == MSGTYPE_UNKNOWN)
{
gpgol_release (message);
TRETURN 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);
TRETURN 0;
}
static LPDISPATCH
get_attachment_o (LPDISPATCH mailitem, int pos)
{
TSTART;
LPDISPATCH attachment;
LPDISPATCH attachments = get_oom_object (mailitem, "Attachments");
if (!attachments)
{
log_debug ("%s:%s: Failed to get attachments.",
SRCNAME, __func__);
TRETURN NULL;
}
std::string item_str;
int count = get_oom_int (attachments, "Count");
if (count < 1)
{
log_debug ("%s:%s: Invalid attachment count: %i.",
SRCNAME, __func__, count);
gpgol_release (attachments);
TRETURN NULL;
}
if (pos > 0)
{
item_str = std::string("Item(") + std::to_string(pos) + ")";
}
else
{
item_str = std::string("Item(") + std::to_string(count) + ")";
}
attachment = get_oom_object (attachments, item_str.c_str());
gpgol_release (attachments);
TRETURN attachment;
}
/** Helper to check that all attachments are hidden, to be
called before crypto. */
int
Mail::checkAttachments_o () const
{
TSTART;
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
log_debug ("%s:%s: Failed to get attachments.",
SRCNAME, __func__);
TRETURN 1;
}
int count = get_oom_int (attachments, "Count");
if (!count)
{
gpgol_release (attachments);
TRETURN 0;
}
std::string message;
if (isEncrypted () && isSigned ())
{
message += _("Not all attachments were encrypted or signed.\n"
"The unsigned / unencrypted attachments are:\n\n");
}
else if (isSigned ())
{
message += _("Not all attachments were signed.\n"
"The unsigned attachments are:\n\n");
}
else if (isEncrypted ())
{
message += _("Not all attachments were encrypted.\n"
"The unencrypted attachments are:\n\n");
}
else
{
gpgol_release (attachments);
TRETURN 0;
}
bool foundOne = false;
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);
}
TRETURN 0;
}
/** Get the cipherstream of the mailitem. */
static LPSTREAM
get_attachment_stream_o (LPDISPATCH mailitem, int pos)
{
TSTART;
if (!pos)
{
log_debug ("%s:%s: Called with zero pos.",
SRCNAME, __func__);
TRETURN NULL;
}
LPDISPATCH attachment = get_attachment_o (mailitem, pos);
LPSTREAM stream = NULL;
if (!attachment)
{
// For opened messages that have ms-tnef type we
// create the moss attachment but don't find it
// in the OOM. Try to find it through MAPI.
HRESULT hr;
log_debug ("%s:%s: Failed to find MOSS Attachment. "
"Fallback to MAPI.", SRCNAME, __func__);
LPMESSAGE message = get_oom_message (mailitem);
if (!message)
{
log_debug ("%s:%s: Failed to get MAPI Interface.",
SRCNAME, __func__);
TRETURN NULL;
}
hr = gpgol_openProperty (message, PR_BODY_A, &IID_IStream, 0, 0,
(LPUNKNOWN*)&stream);
gpgol_release (message);
if (hr)
{
log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
SRCNAME, __func__, hr);
TRETURN NULL;
}
TRETURN stream;
}
LPATTACH mapi_attachment = NULL;
mapi_attachment = (LPATTACH) get_oom_iunknown (attachment,
"MapiObject");
gpgol_release (attachment);
if (!mapi_attachment)
{
log_debug ("%s:%s: Failed to get MapiObject of attachment: %p",
SRCNAME, __func__, attachment);
TRETURN NULL;
}
if (FAILED (gpgol_openProperty (mapi_attachment, PR_ATTACH_DATA_BIN,
&IID_IStream, 0, MAPI_MODIFY,
(LPUNKNOWN*) &stream)))
{
log_debug ("%s:%s: Failed to open stream for mapi_attachment: %p",
SRCNAME, __func__, mapi_attachment);
gpgol_release (mapi_attachment);
}
gpgol_release (mapi_attachment);
TRETURN stream;
}
#if 0
This should work. But Outlook says no. See the comment in set_pa_variant
about this. I left the code here as an example how to work with
safearrays and how this probably should work.
static int
copy_data_property(LPDISPATCH target, std::shared_ptr<Attachment> attach)
{
TSTART;
VARIANT var;
VariantInit (&var);
/* Get the size */
off_t size = attach->get_data ().seek (0, SEEK_END);
attach->get_data ().seek (0, SEEK_SET);
if (!size)
{
TRACEPOINT;
TRETURN 1;
}
if (!get_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var))
{
log_debug("Have variant. type: %x", var.vt);
}
else
{
log_debug("failed to get variant.");
}
/* Set the type to an array of unsigned chars (OLE SAFEARRAY) */
var.vt = VT_ARRAY | VT_UI1;
/* Set up the bounds structure */
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].cElements = static_cast<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);
TRETURN 1;
}
void *buffer = NULL;
/* Get a safe pointer to the array */
if (SafeArrayAccessData(var.parray, &buffer) != S_OK)
{
TRACEPOINT;
VariantClear(&var);
TRETURN 1;
}
/* Copy data to it */
size_t nread = attach->get_data ().read (buffer, static_cast<size_t> (size));
if (nread != static_cast<size_t> (size))
{
TRACEPOINT;
VariantClear(&var);
TRETURN 1;
}
/*/ Unlock the variant data */
if (SafeArrayUnaccessData(var.parray) != S_OK)
{
TRACEPOINT;
VariantClear(&var);
TRETURN 1;
}
if (set_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var))
{
TRACEPOINT;
VariantClear(&var);
TRETURN 1;
}
VariantClear(&var);
TRETURN 0;
}
#endif
static int
copy_attachment_to_file (std::shared_ptr<Attachment> att, HANDLE hFile)
{
TSTART;
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__);
TRETURN 1;
}
if (nread != nwritten)
{
log_error ("%s:%s: Write truncated.",
SRCNAME, __func__);
TRETURN 1;
}
}
TRETURN 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)
{
TSTART;
/* Currently we only set content id */
if (attachment->get_content_id ().empty())
{
log_debug ("%s:%s: Content id not found.",
SRCNAME, __func__);
TRETURN 0;
}
LPDISPATCH attach = get_attachment_o (mail, -1);
if (!attach)
{
log_error ("%s:%s: No attachment.",
SRCNAME, __func__);
TRETURN 1;
}
const std::string cid = attachment->get_content_id ();
int ret = put_pa_string (attach,
PR_ATTACH_CONTENT_ID_DASL,
cid.c_str());
log_debug ("%s:%s: Set attachment content id to: '%s'",
SRCNAME, __func__, anonstr (cid.c_str()));
if (ret)
{
log_error ("%s:%s: Failed.", SRCNAME, __func__);
gpgol_release (attach);
}
#if 0
The following was an experiement to delete the ATTACH_FLAGS values
so that we are not hiding attachments.
LPATTACH mapi_attach = (LPATTACH) get_oom_iunknown (attach, "MAPIOBJECT");
if (mapi_attach)
{
SPropTagArray proparray;
HRESULT hr;
proparray.cValues = 1;
proparray.aulPropTag[0] = 0x37140003;
hr = mapi_attach->DeleteProps (&proparray, NULL);
if (hr)
{
log_error ("%s:%s: can't delete property attach flags: hr=%#lx\n",
SRCNAME, __func__, hr);
ret = -1;
}
gpgol_release (mapi_attach);
}
else
{
log_error ("%s:%s: Failed to get mapi attachment.",
SRCNAME, __func__);
}
#endif
gpgol_release (attach);
TRETURN ret;
}
/** Helper to update the attachments of a mail object in oom.
does not modify the underlying mapi structure. */
static int
add_attachments_o(LPDISPATCH mail,
std::vector<std::shared_ptr<Attachment> > attachments)
{
TSTART;
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__, anonstr (dispName.c_str()));
continue;
}
HANDLE hFile;
wchar_t* wchar_file = get_tmp_outfile (wchar_name,
&hFile);
if (!wchar_file)
{
log_error ("%s:%s: Failed to obtain a tmp filename for: %s",
SRCNAME, __func__, anonstr (dispName.c_str()));
err = 1;
}
if (!err && copy_attachment_to_file (att, hFile))
{
log_error ("%s:%s: Failed to copy attachment %s to temp file",
SRCNAME, __func__, anonstr (dispName.c_str()));
err = 1;
}
if (!err && add_oom_attachment (mail, wchar_file, wchar_name))
{
log_error ("%s:%s: Failed to add attachment: %s",
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__, anonstr (dispName.c_str()));
err = 1;
}
xfree (wchar_file);
xfree (wchar_name);
if (!err)
{
log_debug ("%s:%s: Added attachment '%s'",
SRCNAME, __func__, anonstr (dispName.c_str()));
err = fixup_last_attachment_o (mail, att);
}
if (err)
{
anyError = true;
}
}
TRETURN anyError;
}
GPGRT_LOCK_DEFINE(parser_lock);
static DWORD WINAPI
do_parsing (LPVOID arg)
{
TSTART;
- gpgrt_lock_lock (&dtor_lock);
+ gpgol_lock (&dtor_lock);
/* We lock with mail dtors so we can be sure the mail->parser
call is valid. */
Mail *mail = (Mail *)arg;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: canceling parsing for: %p already deleted",
SRCNAME, __func__, arg);
- gpgrt_lock_unlock (&dtor_lock);
+ gpgol_unlock (&dtor_lock);
TRETURN 0;
}
blockInv ();
/* This takes a shared ptr of parser. So the parser is
still valid when the mail is deleted. */
auto parser = mail->parser ();
- gpgrt_lock_unlock (&dtor_lock);
+ gpgol_unlock (&dtor_lock);
- gpgrt_lock_lock (&parser_lock);
+ gpgol_lock (&parser_lock);
/* We lock the parser here to avoid too many
decryption attempts if there are
multiple mailobjects which might have already
been deleted (e.g. by quick switches of the mailview.)
Let's rather be a bit slower.
*/
log_debug ("%s:%s: preparing the parser for: %p",
SRCNAME, __func__, arg);
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: cancel for: %p already deleted",
SRCNAME, __func__, arg);
- gpgrt_lock_unlock (&parser_lock);
+ gpgol_unlock (&parser_lock);
unblockInv();
TRETURN 0;
}
if (!parser)
{
log_error ("%s:%s: no parser found for mail: %p",
SRCNAME, __func__, arg);
- gpgrt_lock_unlock (&parser_lock);
+ gpgol_unlock (&parser_lock);
unblockInv();
TRETURN -1;
}
parser->parse();
if (!opt.sync_dec)
{
do_in_ui_thread (PARSING_DONE, arg);
}
- gpgrt_lock_unlock (&parser_lock);
+ gpgol_unlock (&parser_lock);
unblockInv();
TRETURN 0;
}
/* How encryption is done:
There are two modes of encryption. Synchronous and Async.
If async is used depends on the value of mail->async_crypt_disabled.
Synchronous crypto:
> Send Event < | State NoCryptMail
Needs Crypto ? (get_gpgol_draft_info_flags != 0)
-> No:
Pass send -> unencrypted mail.
-> Yes:
mail->update_oom_data
State = Mail::NeedsFirstAfterWrite
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)
{
TSTART;
- gpgrt_lock_lock (&dtor_lock);
+ gpgol_lock (&dtor_lock);
/* We lock with mail dtors so we can be sure the mail->parser
call is valid. */
Mail *mail = (Mail *)arg;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: canceling crypt for: %p already deleted",
SRCNAME, __func__, arg);
- gpgrt_lock_unlock (&dtor_lock);
+ gpgol_unlock (&dtor_lock);
TRETURN 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);
+ gpgol_unlock (&dtor_lock);
TRETURN -1;
}
/* This takes a shared ptr of crypter. So the crypter is
still valid when the mail is deleted. */
auto crypter = mail->cryper ();
- gpgrt_lock_unlock (&dtor_lock);
+ gpgol_unlock (&dtor_lock);
if (!crypter)
{
log_error ("%s:%s: no crypter found for mail: %p",
SRCNAME, __func__, arg);
- gpgrt_lock_unlock (&parser_lock);
+ gpgol_unlock (&parser_lock);
mail->setWindowEnabled_o (true);
TRETURN -1;
}
GpgME::Error err;
int rc = crypter->do_crypto(err);
- gpgrt_lock_lock (&dtor_lock);
+ gpgol_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);
+ gpgol_unlock (&dtor_lock);
TRETURN 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);
+ gpgol_unlock (&dtor_lock);
TRETURN rc;
}
if (!mail->isAsyncCryptDisabled ())
{
mail->setCryptState (Mail::NeedsUpdateInOOM);
- gpgrt_lock_unlock (&dtor_lock);
+ gpgol_unlock (&dtor_lock);
// This deletes the Mail in Outlook 2010
do_in_ui_thread (CRYPTO_DONE, arg);
log_debug ("%s:%s: UI thread finished for %p",
SRCNAME, __func__, arg);
}
else
{
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);
+ gpgol_unlock (&dtor_lock);
}
/* This works around a bug in pinentry that it might
bring the wrong window to front. So after encryption /
signing we bring outlook back to front.
See GnuPG-Bug-Id: T3732
*/
do_in_ui_thread_async (BRING_TO_FRONT, nullptr, 250);
log_debug ("%s:%s: crypto thread for %p finished",
SRCNAME, __func__, arg);
TRETURN 0;
}
bool
Mail::isCryptoMail () const
{
TSTART;
if (m_type == MSGTYPE_UNKNOWN || m_type == MSGTYPE_GPGOL ||
m_type == MSGTYPE_SMIME)
{
/* Not a message for us. */
TRETURN false;
}
TRETURN true;
}
int
Mail::decryptVerify_o ()
{
TSTART;
if (!isCryptoMail ())
{
log_debug ("%s:%s: Decrypt Verify for non crypto mail: %p.",
SRCNAME, __func__, m_mailitem);
TRETURN 0;
}
if (m_needs_wipe)
{
log_error ("%s:%s: Decrypt verify called for msg that needs wipe: %p",
SRCNAME, __func__, m_mailitem);
TRETURN 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__);
TRETURN 1;
}
if (opt.prefer_html)
{
char *tmp = get_oom_string (m_mailitem, "HTMLBody");
if (!tmp)
{
TRACEPOINT;
TRETURN 1;
}
m_orig_body = tmp;
xfree (tmp);
if (put_oom_string (m_mailitem, "HTMLBody", placeholder_buf))
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
}
else
{
char *tmp = get_oom_string (m_mailitem, "Body");
if (!tmp)
{
TRACEPOINT;
TRETURN 1;
}
m_orig_body = tmp;
xfree (tmp);
if (put_oom_string (m_mailitem, "Body", placeholder_buf))
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
}
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);
TRETURN 0;
}
if (!cipherstream)
{
log_debug ("%s:%s: Failed to get cipherstream.",
SRCNAME, __func__);
TRETURN 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);
if (!opt.sync_dec)
{
HANDLE parser_thread = CreateThread (NULL, 0, do_parsing, (LPVOID) this, 0,
NULL);
if (!parser_thread)
{
log_error ("%s:%s: Failed to create decrypt / verify thread.",
SRCNAME, __func__);
}
CloseHandle (parser_thread);
TRETURN 0;
}
else
{
/* Parse synchronously */
do_parsing ((LPVOID) this);
parsing_done ();
TRETURN 0;
}
}
void find_and_replace(std::string& source, const std::string &find,
const std::string &replace)
{
TSTART;
for(std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos;)
{
source.replace(i, find.length(), replace);
i += replace.length();
}
TRETURN;
}
void
Mail::updateBody_o ()
{
TSTART;
if (!m_parser)
{
TRACEPOINT;
TRETURN;
}
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 ());
}
}
TRETURN;
}
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__);
}
}
TRETURN;
}
// No need to carry body anymore
m_orig_body = std::string();
auto html = m_parser->get_html_body ();
auto body = m_parser->get_body ();
/** Outlook does not show newlines if \r\r\n is a newline. We replace
these as apparently some other buggy MUA sends this. */
find_and_replace (html, "\r\r\n", "\r\n");
if (opt.prefer_html && !html.empty())
{
if (!m_block_html)
{
const auto charset = m_parser->get_html_charset();
int codepage = 0;
if (charset.empty())
{
codepage = get_oom_int (m_mailitem, "InternetCodepage");
log_debug ("%s:%s: Did not find html charset."
" Using internet Codepage %i.",
SRCNAME, __func__, codepage);
}
char *converted = ansi_charset_to_utf8 (charset.c_str(), html.c_str(),
html.size(), codepage);
TRACEPOINT;
int ret = put_oom_string (m_mailitem, "HTMLBody", converted ?
converted : "");
TRACEPOINT;
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
}
TRETURN;
}
else if (!body.empty())
{
/* We had a multipart/alternative mail but html should be
blocked. So we prefer the text/plain part and warn
once about this so that we hopefully don't get too
many bugreports about this. */
if (!opt.smime_html_warn_shown)
{
std::string caption = _("GpgOL") + std::string (": ") +
std::string (_("HTML display disabled."));
std::string buf = _("HTML content in unsigned S/MIME mails "
"is insecure.");
buf += "\n";
buf += _("GpgOL will only show such mails as text.");
buf += "\n\n";
buf += _("This message is shown only once.");
gpgol_message_box (getWindow (), buf.c_str(), caption.c_str(),
MB_OK);
opt.smime_html_warn_shown = true;
write_options ();
}
}
}
if (body.empty () && m_block_html && !html.empty())
{
#if 0
Sadly the following code still offers to load external references
it might also be too dangerous if Outlook somehow autoloads the
references as soon as the Body is put into HTML
// Fallback to show HTML as plaintext if HTML display
// is blocked.
log_error ("%s:%s: No text body. Putting HTML into plaintext.",
SRCNAME, __func__);
char *converted = ansi_charset_to_utf8 (m_parser->get_html_charset().c_str(),
html.c_str(), html.size());
int ret = put_oom_string (m_mailitem, "HTMLBody", converted ? converted : "");
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify html body of item.",
SRCNAME, __func__);
body = html;
}
else
{
char *plainBody = get_oom_string (m_mailitem, "Body");
if (!plainBody)
{
log_error ("%s:%s: Failed to obtain converted plain body.",
SRCNAME, __func__);
body = html;
}
else
{
ret = put_oom_string (m_mailitem, "HTMLBody", plainBody);
xfree (plainBody);
if (ret)
{
log_error ("%s:%s: Failed to put plain into html body of item.",
SRCNAME, __func__);
body = html;
}
else
{
TRETURN;
}
}
}
#endif
body = html;
std::string caption = _("GpgOL") + std::string (": ") +
std::string (_("HTML display disabled."));
std::string buf = _("HTML content in unsigned S/MIME mails "
"is insecure.");
buf += "\n";
buf += _("GpgOL will only show such mails as text.");
buf += "\n\n";
buf += _("Please ask the sender to sign the message or\n"
"to send it with a plain text alternative.");
gpgol_message_box (getWindow (), buf.c_str(), caption.c_str(),
MB_OK);
}
find_and_replace (body, "\r\r\n", "\r\n");
const auto plain_charset = m_parser->get_body_charset();
int codepage = 0;
if (plain_charset.empty())
{
codepage = get_oom_int (m_mailitem, "InternetCodepage");
log_debug ("%s:%s: Did not find body charset. "
"Using internet Codepage %i.",
SRCNAME, __func__, codepage);
}
char *converted = ansi_charset_to_utf8 (plain_charset.c_str(),
body.c_str(), body.size(),
codepage);
TRACEPOINT;
int ret = put_oom_string (m_mailitem, "Body", converted ? converted : "");
TRACEPOINT;
xfree (converted);
if (ret)
{
log_error ("%s:%s: Failed to modify body of item.",
SRCNAME, __func__);
}
TRETURN;
}
static int parsed_count;
void
Mail::parsing_done()
{
TSTART;
TRACEPOINT;
log_oom ("Mail %p Parsing done for parser num %i: %p",
this, parsed_count++, m_parser.get());
if (!m_parser)
{
/* This should not happen but it happens when outlook
sends multiple ItemLoad events for the same Mail
Object. In that case it could happen that one
parser was already done while a second is now
returning for the wrong mail (as it's looked up
by uuid.)
We have a check in get_uuid that the uuid was
not in the map before (and the parser is replaced).
So this really really should not happen. We
handle it anyway as we crash otherwise.
It should not happen because the parser is only
created in decrypt_verify which is called in the
read event. And even in there we check if the parser
was set.
*/
log_error ("%s:%s: No parser obj. For mail: %p",
SRCNAME, __func__, this);
TRETURN;
}
/* Store the results. */
m_decrypt_result = m_parser->decrypt_result ();
m_verify_result = m_parser->verify_result ();
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;
TRETURN;
}
int
Mail::encryptSignStart_o ()
{
TSTART;
if (m_crypt_state != NeedsActualCrypt)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
TRETURN -1;
}
int flags = 0;
if (!needs_crypto_m ())
{
TRETURN 0;
}
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get base message.",
SRCNAME, __func__);
TRETURN -1;
}
flags = get_gpgol_draft_info_flags (message);
gpgol_release (message);
const auto window = get_active_hwnd ();
if (m_is_gsuite)
{
auto att_table = mapi_create_attach_table (message, 0);
int n_att_usable = count_usable_attachments (att_table);
mapi_release_attach_table (att_table);
/* Check for attachments if we have some abort. */
if (n_att_usable)
{
wchar_t *w_title = utf8_to_wchar (_(
"GpgOL: Oops, G Suite Sync account detected"));
wchar_t *msg = utf8_to_wchar (
_("G Suite Sync breaks outgoing crypto mails "
"with attachments.\nUsing crypto and attachments "
"with G Suite Sync is not supported.\n\n"
"See: https://dev.gnupg.org/T3545 for details."));
MessageBoxW (window,
msg,
w_title,
MB_ICONINFORMATION|MB_OK);
xfree (msg);
xfree (w_title);
TRETURN -1;
}
}
m_do_inline = m_is_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);
TRETURN -1;
}
if (!m_async_crypt_disabled)
{
CloseHandle(CreateThread (NULL, 0, do_crypt,
(LPVOID) this, 0,
NULL));
}
else
{
do_crypt (this);
}
TRETURN 0;
}
int
Mail::needs_crypto_m () const
{
TSTART;
LPMESSAGE message = get_oom_message (m_mailitem);
int ret;
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
TRETURN false;
}
ret = get_gpgol_draft_info_flags (message);
gpgol_release(message);
TRETURN ret;
}
int
Mail::wipe_o (bool force)
{
TSTART;
if (!m_needs_wipe && !force)
{
TRETURN 0;
}
log_debug ("%s:%s: Removing plaintext from mailitem: %p.",
SRCNAME, __func__, m_mailitem);
if (put_oom_string (m_mailitem, "HTMLBody",
""))
{
if (put_oom_string (m_mailitem, "Body",
""))
{
log_debug ("%s:%s: Failed to wipe mailitem: %p.",
SRCNAME, __func__, m_mailitem);
TRETURN -1;
}
TRETURN -1;
}
else
{
put_oom_string (m_mailitem, "Body", "");
}
m_needs_wipe = false;
TRETURN 0;
}
int
Mail::updateOOMData_o ()
{
TSTART;
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__);
TRETURN -1;
}
m_sender = buf;
xfree (buf);
TRETURN 0;
}
std::string
Mail::getSender_o ()
{
TSTART;
if (m_sender.empty())
updateOOMData_o ();
TRETURN m_sender;
}
std::string
Mail::getSender () const
{
TSTART;
TRETURN m_sender;
}
int
Mail::closeAllMails_o ()
{
TSTART;
int err = 0;
/* Detach Folder sinks */
for (auto fit = s_folder_events_map.begin(); fit != s_folder_events_map.end(); ++fit)
{
detach_FolderEvents_sink (fit->second);
gpgol_release (fit->second);
}
s_folder_events_map.clear();
std::map<LPDISPATCH, Mail *>::iterator it;
TRACEPOINT;
- gpgrt_lock_lock (&mail_map_lock);
+ gpgol_lock (&mail_map_lock);
std::map<LPDISPATCH, Mail *> mail_map_copy = s_mail_map;
- gpgrt_lock_unlock (&mail_map_lock);
+ gpgol_unlock (&mail_map_lock);
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
/* XXX For non racy code the is_valid_ptr check should not
be necessary but we crashed sometimes closing a destroyed
mail. */
if (!isValidPtr (it->second))
{
log_debug ("%s:%s: Already deleted mail for %p",
SRCNAME, __func__, it->first);
continue;
}
if (!it->second->isCryptoMail ())
{
continue;
}
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++;
}
}
}
TRETURN err;
}
int
Mail::revertAllMails_o ()
{
TSTART;
int err = 0;
std::map<LPDISPATCH, Mail *>::iterator it;
- gpgrt_lock_lock (&mail_map_lock);
+ gpgol_lock (&mail_map_lock);
auto mail_map_copy = s_mail_map;
- gpgrt_lock_unlock (&mail_map_lock);
+ gpgol_unlock (&mail_map_lock);
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
if (it->second->revert_o ())
{
log_error ("Failed to revert mail: %p ", it->first);
err++;
continue;
}
it->second->setNeedsSave (true);
if (!invoke_oom_method (it->first, "Save", NULL))
{
log_error ("Failed to save reverted mail: %p ", it->second);
err++;
continue;
}
}
TRETURN err;
}
int
Mail::wipeAllMails_o ()
{
TSTART;
int err = 0;
std::map<LPDISPATCH, Mail *>::iterator it;
- gpgrt_lock_lock (&mail_map_lock);
+ gpgol_lock (&mail_map_lock);
auto mail_map_copy = s_mail_map;
- gpgrt_lock_unlock (&mail_map_lock);
+ gpgol_unlock (&mail_map_lock);
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
if (it->second->wipe_o ())
{
log_error ("Failed to wipe mail: %p ", it->first);
err++;
}
}
TRETURN err;
}
int
Mail::revert_o ()
{
TSTART;
int err = 0;
if (!m_processed)
{
TRETURN 0;
}
m_disable_att_remove_warning = true;
err = gpgol_mailitem_revert (m_mailitem);
if (err == -1)
{
log_error ("%s:%s: Message revert failed falling back to wipe.",
SRCNAME, __func__);
TRETURN wipe_o ();
}
/* We need to reprocess the mail next time around. */
m_processed = false;
m_needs_wipe = false;
m_disable_att_remove_warning = false;
TRETURN 0;
}
bool
Mail::isSMIME_m ()
{
TSTART;
msgtype_t msgtype;
LPMESSAGE message;
if (m_is_smime_checked)
{
TRETURN m_is_smime;
}
message = get_oom_message (m_mailitem);
if (!message)
{
log_error ("%s:%s: No message?",
SRCNAME, __func__);
TRETURN false;
}
msgtype = mapi_get_message_type (message);
m_is_smime = msgtype == MSGTYPE_GPGOL_OPAQUE_ENCRYPTED ||
msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED;
/* 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;
TRETURN m_is_smime;
}
static std::string
get_string_o (LPDISPATCH item, const char *str)
{
TSTART;
char *buf = get_oom_string (item, str);
if (!buf)
{
TRETURN std::string();
}
std::string ret = buf;
xfree (buf);
TRETURN ret;
}
std::string
Mail::getSubject_o () const
{
TSTART;
TRETURN get_string_o (m_mailitem, "Subject");
}
std::string
Mail::getBody_o () const
{
TSTART;
TRETURN get_string_o (m_mailitem, "Body");
}
std::vector<std::string>
Mail::getRecipients_o () const
{
TSTART;
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__);
}
TRETURN ret;
}
int
Mail::closeInspector_o (Mail *mail)
{
TSTART;
LPDISPATCH inspector = get_oom_object (mail->item(), "GetInspector");
HRESULT hr;
DISPID dispid;
if (!inspector)
{
log_debug ("%s:%s: No inspector.",
SRCNAME, __func__);
TRETURN -1;
}
dispid = lookup_oom_dispid (inspector, "Close");
if (dispid != DISPID_UNKNOWN)
{
VARIANT aVariant[1];
DISPPARAMS dispparams;
dispparams.rgvarg = aVariant;
dispparams.rgvarg[0].vt = VT_INT;
dispparams.rgvarg[0].intVal = 1;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
hr = inspector->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams,
NULL, NULL, NULL);
if (hr != S_OK)
{
log_debug ("%s:%s: Failed to close inspector: %#lx",
SRCNAME, __func__, hr);
gpgol_release (inspector);
TRETURN -1;
}
}
gpgol_release (inspector);
TRETURN 0;
}
/* static */
int
Mail::close (Mail *mail)
{
TSTART;
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__);
TRETURN rc;
}
void
Mail::setCloseTriggered (bool value)
{
TSTART;
m_close_triggered = value;
TRETURN;
}
bool
Mail::getCloseTriggered () const
{
TSTART;
TRETURN m_close_triggered;
}
static const UserID
get_uid_for_sender (const Key &k, const char *sender)
{
TSTART;
UserID ret;
if (!sender)
{
TRETURN ret;
}
if (!k.numUserIDs())
{
log_debug ("%s:%s: Key without uids",
SRCNAME, __func__);
TRETURN ret;
}
for (const auto uid: k.userIDs())
{
if (!uid.email() || !*(uid.email()))
{
/* This happens for S/MIME a lot */
log_debug ("%s:%s: skipping uid without email.",
SRCNAME, __func__);
continue;
}
auto normalized_uid = uid.addrSpec();
auto normalized_sender = UserID::addrSpecFromString(sender);
if (normalized_sender.empty() || normalized_uid.empty())
{
log_error ("%s:%s: normalizing '%s' or '%s' failed.",
SRCNAME, __func__, anonstr (uid.email()),
anonstr (sender));
continue;
}
if (normalized_sender == normalized_uid)
{
ret = uid;
}
}
TRETURN ret;
}
void
Mail::updateSigstate ()
{
TSTART;
std::string sender = getSender ();
if (sender.empty())
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
TRETURN;
}
if (m_verify_result.isNull())
{
log_debug ("%s:%s: No verify result.",
SRCNAME, __func__);
TRETURN;
}
if (m_verify_result.error())
{
log_debug ("%s:%s: verify error.",
SRCNAME, __func__);
TRETURN;
}
for (const auto sig: m_verify_result.signatures())
{
m_is_signed = true;
const auto key = KeyCache::instance ()->getByFpr (sig.fingerprint(),
true);
m_uid = get_uid_for_sender (key, sender.c_str());
if (m_uid.isNull() && !m_sent_on_behalf.empty ())
{
m_uid = get_uid_for_sender (key, m_sent_on_behalf.c_str ());
if (!m_uid.isNull())
{
log_debug ("%s:%s: Using sent on behalf '%s' instead of '%s'",
SRCNAME, __func__, anonstr (m_sent_on_behalf.c_str()),
anonstr (sender.c_str ()));
}
}
if ((sig.summary() & Signature::Summary::Valid) &&
m_uid.origin() == GpgME::Key::OriginWKD &&
(sig.validity() == Signature::Validity::Unknown ||
sig.validity() == Signature::Validity::Marginal))
{
// WKD is a shortcut to Level 2 trust.
log_debug ("%s:%s: Unknown or marginal from WKD -> Level 2",
SRCNAME, __func__);
}
else if (m_uid.isNull() || (sig.validity() != Signature::Validity::Marginal &&
sig.validity() != Signature::Validity::Full &&
sig.validity() != Signature::Validity::Ultimate))
{
/* For our category we only care about trusted sigs. And
the UID needs to match.*/
continue;
}
else if (sig.validity() == Signature::Validity::Marginal)
{
const auto tofu = m_uid.tofuInfo();
if (!tofu.isNull() &&
(tofu.validity() != TofuInfo::Validity::BasicHistory &&
tofu.validity() != TofuInfo::Validity::LargeHistory))
{
/* Marginal is only good enough without tofu.
Otherwise we wait for basic trust. */
log_debug ("%s:%s: Discarding marginal signature."
"With too little history.",
SRCNAME, __func__);
continue;
}
}
log_debug ("%s:%s: Classified sender as verified uid validity: %i origin: %i",
SRCNAME, __func__, m_uid.validity(), m_uid.origin());
m_sig = sig;
m_is_valid = true;
TRETURN;
}
log_debug ("%s:%s: No signature with enough trust. Using first",
SRCNAME, __func__);
m_sig = m_verify_result.signature(0);
TRETURN;
}
bool
Mail::isValidSig () const
{
TSTART;
TRETURN m_is_valid;
}
void
Mail::removeCategories_o ()
{
TSTART;
const char *decCategory = _("GpgOL: Encrypted Message");
const char *verifyCategory = _("GpgOL: Trusted Sender Address");
remove_category (m_mailitem, decCategory);
remove_category (m_mailitem, verifyCategory);
TRETURN;
}
/* Now for some tasty hack: Outlook sometimes does
not show the new categories properly but instead
does some weird scrollbar thing. This can be
avoided by resizing the message a bit. But somehow
this only needs to be done once.
Weird isn't it? But as this workaround worked let's
do it programatically. Fun. Wan't some tomato sauce
with this hack? */
static void
resize_active_window ()
{
TSTART;
HWND wnd = get_active_hwnd ();
static std::vector<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. */
TRETURN;
}
if (!wnd)
{
TRACEPOINT;
TRETURN;
}
RECT oldpos;
if (!GetWindowRect (wnd, &oldpos))
{
TRACEPOINT;
TRETURN;
}
if (!SetWindowPos (wnd, nullptr,
(int)oldpos.left,
(int)oldpos.top,
/* Anything smaller then 19 was ignored when the window was
* maximized on Windows 10 at least with a 1980*1024
* resolution. So I assume it's at least 1 percent.
* This is all hackish and ugly but should work for 90%...
* hopefully.
*/
(int)oldpos.right - oldpos.left - 20,
(int)oldpos.bottom - oldpos.top, 0))
{
TRACEPOINT;
TRETURN;
}
if (!SetWindowPos (wnd, nullptr,
(int)oldpos.left,
(int)oldpos.top,
(int)oldpos.right - oldpos.left,
(int)oldpos.bottom - oldpos.top, 0))
{
TRACEPOINT;
TRETURN;
}
resized_windows.push_back(wnd);
TRETURN;
}
void
Mail::updateCategories_o ()
{
TSTART;
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();
TRETURN;
}
bool
Mail::isSigned () const
{
TSTART;
TRETURN m_verify_result.numSignatures() > 0;
}
bool
Mail::isEncrypted () const
{
TSTART;
TRETURN !m_decrypt_result.isNull();
}
int
Mail::setUUID_o ()
{
TSTART;
char *uuid;
if (!m_uuid.empty())
{
/* This codepath is reached by decrypt again after a
close with discard changes. The close discarded
the uuid on the OOM object so we have to set
it again. */
log_debug ("%s:%s: Resetting uuid for %p to %s",
SRCNAME, __func__, this,
m_uuid.c_str());
uuid = get_unique_id (m_mailitem, 1, m_uuid.c_str());
}
else
{
uuid = get_unique_id (m_mailitem, 1, nullptr);
log_debug ("%s:%s: uuid for %p set to %s",
SRCNAME, __func__, this, uuid);
}
if (!uuid)
{
log_debug ("%s:%s: Failed to get/set uuid for %p",
SRCNAME, __func__, m_mailitem);
TRETURN -1;
}
if (m_uuid.empty())
{
m_uuid = uuid;
Mail *other = getMailForUUID (uuid);
if (other)
{
/* According to documentation this should not
happen as this means that multiple ItemLoad
events occured for the same mailobject without
unload / destruction of the mail.
But it happens. If you invalidate the UI
in the selection change event Outlook loads a
new mailobject for the mail. Might happen in
other surprising cases. We replace in that
case as experiments have shown that the last
mailobject is the one that is visible.
Still troubling state so we log this as an error.
*/
log_error ("%s:%s: There is another mail for %p "
"with uuid: %s replacing it.",
SRCNAME, __func__, m_mailitem, uuid);
delete other;
}
- gpgrt_lock_lock (&uid_map_lock);
+ gpgol_lock (&uid_map_lock);
s_uid_map.insert (std::pair<std::string, Mail *> (m_uuid, this));
- gpgrt_lock_unlock (&uid_map_lock);
+ gpgol_unlock (&uid_map_lock);
log_debug ("%s:%s: uuid for %p is now %s",
SRCNAME, __func__, this,
m_uuid.c_str());
}
xfree (uuid);
TRETURN 0;
}
/* TRETURNs 2 if the userid is ultimately trusted.
TRETURNs 1 if the userid is fully trusted but has
a signature by a key for which we have a secret
and which is ultimately trusted. (Direct trust)
0 otherwise */
static int
level_4_check (const UserID &uid)
{
TSTART;
if (uid.isNull())
{
TRETURN 0;
}
if (uid.validity () == UserID::Validity::Ultimate)
{
TRETURN 2;
}
if (uid.validity () == UserID::Validity::Full)
{
const auto ultimate_keys = 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__, anonstr (signer_uid_str),
anonstr (sig_uid_str),
anonstr (secKeyID));
TRETURN 1;
}
}
}
}
}
TRETURN 0;
}
std::string
Mail::getCryptoSummary () const
{
TSTART;
const int level = get_signature_level ();
bool enc = isEncrypted ();
if (level == 4 && enc)
{
TRETURN _("Security Level 4");
}
if (level == 4)
{
TRETURN _("Trust Level 4");
}
if (level == 3 && enc)
{
TRETURN _("Security Level 3");
}
if (level == 3)
{
TRETURN _("Trust Level 3");
}
if (level == 2 && enc)
{
TRETURN _("Security Level 2");
}
if (level == 2)
{
TRETURN _("Trust Level 2");
}
if (enc)
{
TRETURN _("Encrypted");
}
if (isSigned ())
{
/* Even if it is signed, if it is not validly
signed it's still completly insecure as anyone
could have signed this. So we avoid the label
"signed" here as this word already implies some
security. */
TRETURN _("Insecure");
}
TRETURN _("Insecure");
}
std::string
Mail::getCryptoOneLine () const
{
TSTART;
bool sig = isSigned ();
bool enc = isEncrypted ();
if (sig || enc)
{
if (sig && enc)
{
TRETURN _("Signed and encrypted message");
}
else if (sig)
{
TRETURN _("Signed message");
}
else if (enc)
{
TRETURN _("Encrypted message");
}
}
TRETURN _("Insecure message");
}
std::string
Mail::getCryptoDetails_o ()
{
TSTART;
std::string message;
/* No signature with keys but error */
if (!isEncrypted () && !isSigned () && m_verify_result.error())
{
message = _("You cannot be sure who sent, "
"modified and read the message in transit.");
message += "\n\n";
message += _("The message was signed but the verification failed with:");
message += "\n";
message += m_verify_result.error().asString();
TRETURN message;
}
/* No crypo, what are we doing here? */
if (!isEncrypted () && !isSigned ())
{
TRETURN _("You cannot be sure who sent, "
"modified and read the message in transit.");
}
/* Handle encrypt only */
if (isEncrypted () && !isSigned ())
{
if (in_de_vs_mode ())
{
if (m_sig.isDeVs())
{
message += _("The encryption was VS-NfD-compliant.");
}
else
{
message += _("The encryption was not VS-NfD-compliant.");
}
}
message += "\n\n";
message += _("You cannot be sure who sent the message because "
"it is not signed.");
TRETURN message;
}
bool keyFound = true;
const auto sigKey = KeyCache::instance ()->getByFpr (m_sig.fingerprint (),
true);
bool isOpenPGP = sigKey.isNull() ? !isSMIME_m () :
sigKey.protocol() == Protocol::OpenPGP;
char *buf;
bool hasConflict = false;
int level = get_signature_level ();
log_debug ("%s:%s: Formatting sig. Validity: %x Summary: %x Level: %i",
SRCNAME, __func__, m_sig.validity(), m_sig.summary(),
level);
if (level == 4)
{
/* level 4 check for direct trust */
int four_check = level_4_check (m_uid);
if (four_check == 2 && sigKey.hasSecret ())
{
message = _("You signed this message.");
}
else if (four_check == 1)
{
message = _("The senders identity was certified by yourself.");
}
else if (four_check == 2)
{
message = _("The sender is allowed to certify identities for you.");
}
else
{
log_error ("%s:%s:%i BUG: Invalid sigstate.",
SRCNAME, __func__, __LINE__);
TRETURN message;
}
}
else if (level == 3 && isOpenPGP)
{
/* Level three is only reachable through web of trust and no
direct signature. */
message = _("The senders identity was certified by several trusted people.");
}
else if (level == 3 && !isOpenPGP)
{
/* Level three is the only level for trusted S/MIME keys. */
gpgrt_asprintf (&buf, _("The senders identity is certified by the trusted issuer:\n'%s'\n"),
sigKey.issuerName());
memdbg_alloc (buf);
message = buf;
xfree (buf);
}
else if (level == 2 && m_uid.origin () == GpgME::Key::OriginWKD)
{
message = _("The mail provider of the recipient served this key.");
}
else if (level == 2 && m_uid.tofuInfo ().isNull ())
{
/* Marginal trust through pgp only */
message = _("Some trusted people "
"have certified the senders identity.");
}
else if (level == 2)
{
unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(),
m_uid.tofuInfo().encrFirst());
char *time = format_date_from_gpgme (first_contact);
/* i18n note signcount is always pulral because with signcount 1 we
* would not be in this branch. */
gpgrt_asprintf (&buf, _("The senders address is trusted, because "
"you have established a communication history "
"with this address starting on %s.\n"
"You encrypted %i and verified %i messages since."),
time, m_uid.tofuInfo().encrCount(),
m_uid.tofuInfo().signCount ());
memdbg_alloc (buf);
xfree (time);
message = buf;
xfree (buf);
}
else if (level == 1)
{
/* This could be marginal trust through pgp, or tofu with little
history. */
if (m_uid.tofuInfo ().signCount() == 1)
{
message += _("The senders signature was verified for the first time.");
}
else if (m_uid.tofuInfo ().validity() == TofuInfo::Validity::LittleHistory)
{
unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(),
m_uid.tofuInfo().encrFirst());
char *time = format_date_from_gpgme (first_contact);
gpgrt_asprintf (&buf, _("The senders address is not trustworthy yet because "
"you only verified %i messages and encrypted %i messages to "
"it since %s."),
m_uid.tofuInfo().signCount (),
m_uid.tofuInfo().encrCount (), time);
memdbg_alloc (buf);
xfree (time);
message = buf;
xfree (buf);
}
}
else
{
/* Now we are in level 0, this could be a technical problem, no key
or just unkown. */
message = isEncrypted () ? _("But the sender address is not trustworthy because:") :
_("The sender address is not trustworthy because:");
message += "\n";
keyFound = !(m_sig.summary() & Signature::Summary::KeyMissing);
bool general_problem = true;
/* First the general stuff. */
if (m_sig.summary() & Signature::Summary::Red)
{
message += _("The signature is invalid: \n");
}
else if (m_sig.summary() & Signature::Summary::SysError ||
m_verify_result.numSignatures() < 1)
{
message += _("There was an error verifying the signature.\n");
const auto err = m_sig.status ();
if (err)
{
message += err.asString () + std::string ("\n");
}
}
else if (m_sig.summary() & Signature::Summary::SigExpired)
{
message += _("The signature is expired.\n");
}
else
{
message += isOpenPGP ? _("The used key") : _("The used certificate");
message += " ";
general_problem = false;
}
/* Now the key problems */
if ((m_sig.summary() & Signature::Summary::KeyMissing))
{
message += _("is not available.");
}
else if ((m_sig.summary() & Signature::Summary::KeyRevoked))
{
message += _("is revoked.");
}
else if ((m_sig.summary() & Signature::Summary::KeyExpired))
{
message += _("is expired.");
}
else if ((m_sig.summary() & Signature::Summary::BadPolicy))
{
message += _("is not meant for signing.");
}
else if ((m_sig.summary() & Signature::Summary::CrlMissing))
{
message += _("could not be checked for revocation.");
}
else if ((m_sig.summary() & Signature::Summary::CrlTooOld))
{
message += _("could not be checked for revocation.");
}
else if ((m_sig.summary() & Signature::Summary::TofuConflict) ||
m_uid.tofuInfo().validity() == TofuInfo::Conflict)
{
message += _("is not the same as the key that was used "
"for this address in the past.");
hasConflict = true;
}
else if (m_uid.isNull())
{
gpgrt_asprintf (&buf, _("does not claim the address: \"%s\"."),
getSender_o ().c_str());
memdbg_alloc (buf);
message += buf;
xfree (buf);
}
else if (((m_sig.validity() & Signature::Validity::Undefined) ||
(m_sig.validity() & Signature::Validity::Unknown) ||
(m_sig.summary() == Signature::Summary::None) ||
(m_sig.validity() == 0))&& !general_problem)
{
/* Bit of a catch all for weird results. */
if (isOpenPGP)
{
message += _("is not certified by any trustworthy key.");
}
else
{
message += _("is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown.");
}
}
else if (m_uid.isRevoked())
{
message += _("The sender marked this address as revoked.");
}
else if ((m_sig.validity() & Signature::Validity::Never))
{
message += _("is marked as not trustworthy.");
}
}
message += "\n\n";
if (in_de_vs_mode ())
{
if (isSigned ())
{
if (m_sig.isDeVs ())
{
message += _("The signature is VS-NfD-compliant.");
}
else
{
message += _("The signature is not VS-NfD-compliant.");
}
message += "\n";
}
if (isEncrypted ())
{
if (m_decrypt_result.isDeVs ())
{
message += _("The encryption is VS-NfD-compliant.");
}
else
{
message += _("The encryption is not VS-NfD-compliant.");
}
message += "\n\n";
}
else
{
message += "\n";
}
}
if (hasConflict)
{
message += _("Click here to change the key used for this address.");
}
else if (keyFound)
{
message += isOpenPGP ? _("Click here for details about the key.") :
_("Click here for details about the certificate.");
}
else
{
message += isOpenPGP ? _("Click here to search the key on the configured keyserver.") :
_("Click here to search the certificate on the configured X509 keyserver.");
}
TRETURN message;
}
int
Mail::get_signature_level () const
{
TSTART;
if (!m_is_signed)
{
TRETURN 0;
}
if (m_uid.isNull ())
{
/* No m_uid matches our sender. */
TRETURN 0;
}
if (m_is_valid && (m_uid.validity () == UserID::Validity::Ultimate ||
(m_uid.validity () == UserID::Validity::Full &&
level_4_check (m_uid))) && (!in_de_vs_mode () || m_sig.isDeVs()))
{
TRETURN 4;
}
if (m_is_valid && m_uid.validity () == UserID::Validity::Full &&
(!in_de_vs_mode () || m_sig.isDeVs()))
{
TRETURN 3;
}
if (m_is_valid)
{
TRETURN 2;
}
if (m_sig.validity() == Signature::Validity::Marginal)
{
TRETURN 1;
}
if (m_sig.summary() & Signature::Summary::TofuConflict ||
m_uid.tofuInfo().validity() == TofuInfo::Conflict)
{
TRETURN 0;
}
TRETURN 0;
}
int
Mail::getCryptoIconID () const
{
TSTART;
int level = get_signature_level ();
int offset = isEncrypted () ? ENCRYPT_ICON_OFFSET : 0;
TRETURN IDI_LEVEL_0 + level + offset;
}
const char*
Mail::getSigFpr () const
{
TSTART;
if (!m_is_signed || m_sig.isNull())
{
TRETURN nullptr;
}
TRETURN m_sig.fingerprint();
}
/** Try to locate the keys for all recipients */
void
Mail::locateKeys_o ()
{
TSTART;
if (m_locate_in_progress)
{
/** XXX
The strangest thing seems to happen here:
In get_recipients the lookup for "AddressEntry" on
an unresolved address might cause network traffic.
So Outlook somehow "detaches" this call and keeps
processing window messages while the call is running.
So our do_delayed_locate might trigger a second locate.
If we access the OOM in this call while we access the
same object in the blocked "detached" call we crash.
(T3931)
After the window message is handled outlook retunrs
in the original lookup.
A better fix here might be a non recursive lock
of the OOM. But I expect that if we lock the handling
of the Windowmessage we might deadlock.
*/
log_debug ("%s:%s: Locate for %p already in progress.",
SRCNAME, __func__, this);
TRETURN;
}
m_locate_in_progress = true;
Addressbook::check_o (this);
if (opt.autoresolve)
{
// First update oom data to have recipients and sender updated.
updateOOMData_o ();
KeyCache::instance()->startLocateSecret (getSender_o ().c_str (), this);
KeyCache::instance()->startLocate (getSender_o ().c_str (), this);
KeyCache::instance()->startLocate (getCachedRecipients (), this);
}
autosecureCheck ();
m_locate_in_progress = false;
TRETURN;
}
bool
Mail::isHTMLAlternative () const
{
TSTART;
TRETURN m_is_html_alternative;
}
char *
Mail::takeCachedHTMLBody ()
{
TSTART;
char *ret = m_cached_html_body;
m_cached_html_body = nullptr;
TRETURN ret;
}
char *
Mail::takeCachedPlainBody ()
{
TSTART;
char *ret = m_cached_plain_body;
m_cached_plain_body = nullptr;
TRETURN ret;
}
int
Mail::getCryptoFlags () const
{
TSTART;
TRETURN m_crypto_flags;
}
void
Mail::setNeedsEncrypt (bool value)
{
TSTART;
m_needs_encrypt = value;
TRETURN;
}
bool
Mail::getNeedsEncrypt () const
{
TSTART;
TRETURN m_needs_encrypt;
}
std::vector<std::string>
Mail::getCachedRecipients ()
{
TSTART;
TRETURN m_cached_recipients;
}
void
Mail::appendToInlineBody (const std::string &data)
{
TSTART;
m_inline_body += data;
TRETURN;
}
int
Mail::inlineBodyToBody_o ()
{
TSTART;
if (!m_crypter)
{
log_error ("%s:%s: No crypter.",
SRCNAME, __func__);
TRETURN -1;
}
const auto body = m_crypter->get_inline_data ();
if (body.empty())
{
TRETURN 0;
}
/* For inline we always work with UTF-8 */
put_oom_int (m_mailitem, "InternetCodepage", 65001);
int ret = put_oom_string (m_mailitem, "Body",
body.c_str ());
TRETURN ret;
}
void
Mail::updateCryptMAPI_m ()
{
TSTART;
log_debug ("%s:%s: Update crypt mapi",
SRCNAME, __func__);
if (m_crypt_state != NeedsUpdateInMAPI)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
TRETURN;
}
if (!m_crypter)
{
if (!m_mime_data.empty())
{
log_debug ("%s:%s: Have override mime data creating dummy crypter",
SRCNAME, __func__);
m_crypter = std::shared_ptr <CryptController> (new CryptController (this, false,
false,
GpgME::UnknownProtocol));
}
else
{
log_error ("%s:%s: No crypter.",
SRCNAME, __func__);
m_crypt_state = NoCryptMail;
TRETURN;
}
}
if (m_crypter->update_mail_mapi ())
{
log_error ("%s:%s: Failed to update MAPI after crypt",
SRCNAME, __func__);
m_crypt_state = NoCryptMail;
}
else
{
m_crypt_state = WantsSendMIME;
}
/** If sync we need the crypter in update_crypt_oom */
if (!isAsyncCryptDisabled ())
{
// We don't need the crypter anymore.
resetCrypter ();
}
TRETURN;
}
/** Checks in OOM if the body is either
empty or contains the -----BEGIN tag.
pair.first -> true if body starts with -----BEGIN
pair.second -> true if body is empty. */
static std::pair<bool, bool>
has_crypt_or_empty_body_oom (Mail *mail)
{
TSTART;
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;
TRETURN ret;
}
if (!body.size())
{
ret.second = true;
}
else
{
log_data ("%s:%s: Body found in %p : \"%s\"",
SRCNAME, __func__, mail, body.c_str ());
}
TRETURN ret;
}
void
Mail::updateCryptOOM_o ()
{
TSTART;
log_debug ("%s:%s: Update crypt oom for %p",
SRCNAME, __func__, this);
if (m_crypt_state != NeedsUpdateInOOM)
{
log_debug ("%s:%s: invalid state %i",
SRCNAME, __func__, m_crypt_state);
resetCrypter ();
TRETURN;
}
if (getDoPGPInline ())
{
if (inlineBodyToBody_o ())
{
log_error ("%s:%s: Inline body to body failed %p.",
SRCNAME, __func__, this);
gpgol_bug (get_active_hwnd(), ERR_INLINE_BODY_TO_BODY);
m_crypt_state = NoCryptMail;
TRETURN;
}
}
if (m_crypter->get_protocol () == GpgME::CMS && m_crypter->is_encrypter ())
{
/* We put the PIDNameContentType headers here for exchange
because this is the only way we found to inject the
smime-type. */
if (put_pa_string (m_mailitem,
PR_PIDNameContentType_DASL,
"application/pkcs7-mime;smime-type=\"enveloped-data\";name=smime.p7m"))
{
log_debug ("%s:%s: Failed to put PIDNameContentType for %p.",
SRCNAME, __func__, this);
}
}
/** When doing async update_crypt_mapi follows and needs
the crypter. */
if (isAsyncCryptDisabled ())
{
resetCrypter ();
}
const auto pair = has_crypt_or_empty_body_oom (this);
if (pair.first)
{
log_debug ("%s:%s: Looks like inline body. You can pass %p.",
SRCNAME, __func__, this);
m_crypt_state = WantsSendInline;
TRETURN;
}
// 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;
TRETURN;
}
m_crypt_state = NeedsSecondAfterWrite;
TRETURN;
}
void
Mail::setWindowEnabled_o (bool value)
{
TSTART;
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);
TRETURN;
}
bool
Mail::check_inline_response ()
{
TSTART;
/* Async sending is known to cause instabilities. So we keep
a hidden option to disable it. */
if (opt.sync_enc)
{
m_async_crypt_disabled = true;
TRETURN m_async_crypt_disabled;
}
m_async_crypt_disabled = false;
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (attachments)
{
/* This is horrible. But. For some kinds of attachments (we
got reports about Office attachments the write in the
send event triggered by our crypto done code fails with
an exception. There does not appear to be a detectable
pattern when this happens.
As we can't be sure and do not know for which attachments
this really happens we do not use async crypt for any
mails with attachments. :-/
Better be save (not crash) instead of nice (async).
TODO: Figure this out.
The log goes like this. We pass the send event. That triggers
a write, which we pass. And then that fails. So it looks like
moving to Outbox fails. Because we can save as much as we
like before that.
Using the IMessage::SubmitMessage MAPI interface works, but
as it is unstable in our current implementation we do not
want to use it.
mailitem-events.cpp:Invoke: Passing send event for mime-encrypted message 12B7C6E0.
application-events.cpp:Invoke: Unhandled Event: f002
mailitem-events.cpp:Invoke: Write : 0ED4D058
mailitem-events.cpp:Invoke: Passing write event.
oomhelp.cpp:invoke_oom_method_with_parms_type: Method 'Send' invokation failed: 0x80020009
oomhelp.cpp:dump_excepinfo: Exception:
wCode: 0x1000
wReserved: 0x0
source: Microsoft Outlook
desc: The operation failed. The messaging interfaces have returned an unknown error. If the problem persists, restart Outlook.
help: null
helpCtx: 0x0
deferredFill: 00000000
scode: 0x80040119
*/
int count = get_oom_int (attachments, "Count");
gpgol_release (attachments);
if (count)
{
m_async_crypt_disabled = true;
log_debug ("%s:%s: Detected attachments. "
"Disabling async crypt due to T4131.",
SRCNAME, __func__);
TRETURN m_async_crypt_disabled;
}
}
LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
if (!app)
{
TRACEPOINT;
TRETURN false;
}
LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer");
if (!explorer)
{
TRACEPOINT;
TRETURN false;
}
LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse");
gpgol_release (explorer);
if (!inlineResponse)
{
TRETURN false;
}
// We have inline response
// Check if we are it. It's a bit naive but meh. Worst case
// is that we think inline response too often and do sync
// crypt where we could do async crypt.
char * inlineSubject = get_oom_string (inlineResponse, "Subject");
gpgol_release (inlineResponse);
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);
TRETURN m_async_crypt_disabled;
}
// static
Mail *
Mail::getLastMail ()
{
TSTART;
if (!s_last_mail || !isValidPtr (s_last_mail))
{
s_last_mail = nullptr;
}
TRETURN s_last_mail;
}
// static
void
Mail::clearLastMail ()
{
TSTART;
s_last_mail = nullptr;
TRETURN;
}
// static
void
Mail::locateAllCryptoRecipients_o ()
{
TSTART;
- gpgrt_lock_lock (&mail_map_lock);
+ gpgol_lock (&mail_map_lock);
std::map<LPDISPATCH, Mail *>::iterator it;
auto mail_map_copy = s_mail_map;
- gpgrt_lock_unlock (&mail_map_lock);
+ gpgol_unlock (&mail_map_lock);
for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it)
{
if (it->second->needs_crypto_m ())
{
it->second->locateKeys_o ();
}
}
TRETURN;
}
int
Mail::removeAllAttachments_o ()
{
TSTART;
int ret = 0;
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
TRACEPOINT;
TRETURN 0;
}
int count = get_oom_int (attachments, "Count");
LPDISPATCH to_delete[count];
/* Populate the array so that we don't get in an index mess */
for (int i = 1; i <= count; i++)
{
auto item_str = std::string("Item(") + std::to_string (i) + ")";
to_delete[i-1] = get_oom_object (attachments, item_str.c_str());
}
gpgol_release (attachments);
/* Now delete all attachments */
for (int i = 0; i < count; i++)
{
LPDISPATCH attachment = to_delete[i];
if (!attachment)
{
log_error ("%s:%s: No such attachment %i",
SRCNAME, __func__, i);
ret = -1;
}
/* Delete the attachments that are marked to delete */
if (invoke_oom_method (attachment, "Delete", NULL))
{
log_error ("%s:%s: Deleting attachment %i",
SRCNAME, __func__, i);
ret = -1;
}
gpgol_release (attachment);
}
TRETURN ret;
}
int
Mail::removeOurAttachments_o ()
{
TSTART;
LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
if (!attachments)
{
TRACEPOINT;
TRETURN 0;
}
int count = get_oom_int (attachments, "Count");
LPDISPATCH to_delete[count];
int del_cnt = 0;
for (int i = 1; i <= count; i++)
{
auto item_str = std::string("Item(") + std::to_string (i) + ")";
LPDISPATCH attachment = get_oom_object (attachments, item_str.c_str());
if (!attachment)
{
TRACEPOINT;
continue;
}
attachtype_t att_type;
if (get_pa_int (attachment, GPGOL_ATTACHTYPE_DASL, (int*) &att_type))
{
/* Not our attachment. */
gpgol_release (attachment);
continue;
}
if (att_type == ATTACHTYPE_PGPBODY || att_type == ATTACHTYPE_MOSS ||
att_type == ATTACHTYPE_MOSSTEMPL)
{
/* One of ours to delete. */
to_delete[del_cnt++] = attachment;
/* Dont' release yet */
continue;
}
gpgol_release (attachment);
}
gpgol_release (attachments);
int ret = 0;
for (int i = 0; i < del_cnt; i++)
{
LPDISPATCH attachment = to_delete[i];
/* Delete the attachments that are marked to delete */
if (invoke_oom_method (attachment, "Delete", NULL))
{
log_error ("%s:%s: Error: deleting attachment %i",
SRCNAME, __func__, i);
ret = -1;
}
gpgol_release (attachment);
}
TRETURN ret;
}
/* We are very verbose because if we fail it might mean
that we have leaked plaintext -> critical. */
bool
Mail::hasCryptedOrEmptyBody_o ()
{
TSTART;
const auto pair = has_crypt_or_empty_body_oom (this);
if (pair.first /* encrypted marker */)
{
log_debug ("%s:%s: Crypt Marker detected in OOM body. Return true %p.",
SRCNAME, __func__, this);
TRETURN true;
}
if (!pair.second)
{
log_debug ("%s:%s: Unexpected content detected. Return false %p.",
SRCNAME, __func__, this);
TRETURN false;
}
// Pair second == true (is empty) can happen on OOM error.
LPMESSAGE message = get_oom_base_message (m_mailitem);
if (!message && pair.second)
{
if (message)
{
gpgol_release (message);
}
TRETURN true;
}
size_t r_nbytes = 0;
char *mapi_body = mapi_get_body (message, &r_nbytes);
gpgol_release (message);
if (!mapi_body || !r_nbytes)
{
// Body or bytes are null. we are empty.
xfree (mapi_body);
log_debug ("%s:%s: MAPI error or empty message. Return true. %p.",
SRCNAME, __func__, this);
TRETURN true;
}
if (r_nbytes > 10 && !strncmp (mapi_body, "-----BEGIN", 10))
{
// Body is crypt.
log_debug ("%s:%s: MAPI Crypt marker detected. Return true. %p.",
SRCNAME, __func__, this);
xfree (mapi_body);
TRETURN true;
}
xfree (mapi_body);
log_debug ("%s:%s: Found mapi body. Return false. %p.",
SRCNAME, __func__, this);
TRETURN false;
}
std::string
Mail::getVerificationResultDump ()
{
TSTART;
std::stringstream ss;
ss << m_verify_result;
TRETURN ss.str();
}
void
Mail::setBlockStatus_m ()
{
TSTART;
SPropValue prop;
LPMESSAGE message = get_oom_base_message (m_mailitem);
prop.ulPropTag = PR_BLOCK_STATUS;
prop.Value.l = 1;
HRESULT hr = message->SetProps (1, &prop, NULL);
if (hr)
{
log_error ("%s:%s: can't set block value: hr=%#lx\n",
SRCNAME, __func__, hr);
}
gpgol_release (message);
TRETURN;
}
void
Mail::setBlockHTML (bool value)
{
TSTART;
m_block_html = value;
TRETURN;
}
void
Mail::incrementLocateCount ()
{
TSTART;
m_locate_count++;
TRETURN;
}
void
Mail::decrementLocateCount ()
{
TSTART;
m_locate_count--;
if (m_locate_count < 0)
{
log_error ("%s:%s: locate count mismatch.",
SRCNAME, __func__);
m_locate_count = 0;
}
if (!m_locate_count)
{
autosecureCheck ();
}
TRETURN;
}
void
Mail::autosecureCheck ()
{
TSTART;
if (!opt.autosecure || !opt.autoresolve || m_manual_crypto_opts ||
m_locate_count)
{
TRETURN;
}
bool ret = KeyCache::instance()->isMailResolvable (this);
log_debug ("%s:%s: status %i",
SRCNAME, __func__, ret);
/* As we are safe to call at any time, because we need
* to be triggered by the locator threads finishing we
* need to actually set the draft info flags in
* the ui thread. */
do_in_ui_thread_async (ret ? DO_AUTO_SECURE : DONT_AUTO_SECURE,
this);
TRETURN;
}
void
Mail::setDoAutosecure_m (bool value)
{
TSTART;
TRACEPOINT;
LPMESSAGE msg = get_oom_base_message (m_mailitem);
if (!msg)
{
TRACEPOINT;
TRETURN;
}
/* We need to set a uuid so that autosecure can
be disabled manually */
setUUID_o ();
int old_flags = get_gpgol_draft_info_flags (msg);
if (old_flags && m_first_autosecure_check &&
/* Someone with always sign and autosecure active
* will want to get autoencryption. */
!(old_flags == 2 && opt.sign_default))
{
/* They were set explicily before us. This can be
* because they were a draft (which is bad) or
* because they are a reply/forward to a crypto mail
* or because there are conflicting settings. */
log_debug ("%s:%s: Mail %p had already flags set.",
SRCNAME, __func__, m_mailitem);
m_first_autosecure_check = false;
m_manual_crypto_opts = true;
gpgol_release (msg);
TRETURN;
}
m_first_autosecure_check = false;
set_gpgol_draft_info_flags (msg, value ? 3 : opt.sign_default ? 2 : 0);
gpgol_release (msg);
gpgoladdin_invalidate_ui();
TRETURN;
}
void
Mail::installFolderEventHandler_o()
{
TSTART;
TRACEPOINT;
LPDISPATCH folder = get_oom_object (m_mailitem, "Parent");
if (!folder)
{
TRACEPOINT;
TRETURN;
}
char *objName = get_object_name (folder);
if (!objName || strcmp (objName, "MAPIFolder"))
{
log_debug ("%s:%s: Mail %p parent is not a mapi folder.",
SRCNAME, __func__, m_mailitem);
xfree (objName);
gpgol_release (folder);
TRETURN;
}
xfree (objName);
char *path = get_oom_string (folder, "FullFolderPath");
if (!path)
{
TRACEPOINT;
path = get_oom_string (folder, "FolderPath");
}
if (!path)
{
log_error ("%s:%s: Mail %p parent has no folder path.",
SRCNAME, __func__, m_mailitem);
gpgol_release (folder);
TRETURN;
}
std::string strPath (path);
xfree (path);
if (s_folder_events_map.find (strPath) == s_folder_events_map.end())
{
log_debug ("%s:%s: Install folder events watcher for %s.",
SRCNAME, __func__, anonstr (strPath.c_str()));
const auto sink = install_FolderEvents_sink (folder);
s_folder_events_map.insert (std::make_pair (strPath, sink));
}
/* Folder already registered */
gpgol_release (folder);
TRETURN;
}
void
Mail::refCurrentItem()
{
TSTART;
if (m_currentItemRef)
{
log_debug ("%s:%s: Current item multi ref. Bug?",
SRCNAME, __func__);
TRETURN;
}
/* This prevents a crash in Outlook 2013 when sending a mail as it
* would unload too early.
*
* As it didn't crash when the mail was opened in Outlook Spy this
* mimics that the mail is inspected somewhere else. */
m_currentItemRef = get_oom_object (m_mailitem, "GetInspector.CurrentItem");
TRETURN;
}
void
Mail::releaseCurrentItem()
{
TSTART;
if (!m_currentItemRef)
{
TRETURN;
}
log_oom ("%s:%s: releasing CurrentItem ref %p",
SRCNAME, __func__, m_currentItemRef);
LPDISPATCH tmp = m_currentItemRef;
m_currentItemRef = nullptr;
/* This can cause our destruction */
gpgol_release (tmp);
TRETURN;
}
diff --git a/src/parsecontroller.cpp b/src/parsecontroller.cpp
index b6428bc..84a7592 100644
--- a/src/parsecontroller.cpp
+++ b/src/parsecontroller.cpp
@@ -1,692 +1,692 @@
/* @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)
{
TSTART;
TRETURN type != MSGTYPE_GPGOL_MULTIPART_SIGNED;
}
static bool
expect_no_mime (msgtype_t type)
{
TSTART;
TRETURN type == MSGTYPE_GPGOL_PGP_MESSAGE ||
type == MSGTYPE_GPGOL_CLEAR_SIGNED;
}
#ifdef 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)
{
TSTART;
memdbg_ctor ("ParseController");
log_data ("%s:%s: Creating parser for stream: %p of type %i"
" expect no headers: %i expect no mime: %i",
SRCNAME, __func__, instream, type,
expect_no_headers (type), expect_no_mime (type));
TRETURN;
}
#endif
ParseController::ParseController(FILE *instream, msgtype_t type):
m_inputprovider (new MimeDataProvider(instream,
expect_no_headers(type))),
m_outputprovider (new MimeDataProvider(expect_no_mime(type))),
m_type (type),
m_block_html (false)
{
TSTART;
memdbg_ctor ("ParseController");
log_data ("%s:%s: Creating parser for stream: %p of type %i",
SRCNAME, __func__, instream, type);
TRETURN;
}
ParseController::~ParseController()
{
TSTART;
log_debug ("%s:%s", SRCNAME, __func__);
memdbg_dtor ("ParseController");
delete m_inputprovider;
delete m_outputprovider;
TRETURN;
}
static void
operation_for_type(msgtype_t type, bool *decrypt,
bool *verify)
{
*decrypt = false;
*verify = false;
switch (type)
{
case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
case MSGTYPE_GPGOL_PGP_MESSAGE:
*decrypt = true;
break;
case MSGTYPE_GPGOL_MULTIPART_SIGNED:
case MSGTYPE_GPGOL_CLEAR_SIGNED:
*verify = true;
break;
case MSGTYPE_GPGOL_OPAQUE_SIGNED:
*verify = true;
break;
case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
*decrypt = true;
break;
default:
log_error ("%s:%s: Unknown data type: %i",
SRCNAME, __func__, type);
}
}
static bool
is_smime (Data &data)
{
TSTART;
data.seek (0, SEEK_SET);
auto id = data.type();
data.seek (0, SEEK_SET);
TRETURN id == Data::CMSSigned || id == Data::CMSEncrypted;
}
static std::string
format_recipients(GpgME::DecryptionResult result, Protocol protocol)
{
TSTART;
std::string msg;
for (const auto recipient: result.recipients())
{
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();
}
TRETURN msg;
}
static std::string
format_error(GpgME::DecryptionResult result, Protocol protocol)
{
TSTART;
char *buf;
bool no_sec = false;
std::string msg;
if (result.error ().isCanceled () ||
result.error ().code () == GPG_ERR_NO_SECKEY)
{
msg = _("Decryption canceled or timed out.");
}
if (result.error ().code () == GPG_ERR_DECRYPT_FAILED ||
result.error ().code () == GPG_ERR_NO_SECKEY)
{
no_sec = true;
for (const auto &recipient: result.recipients ()) {
no_sec &= (recipient.status ().code () == GPG_ERR_NO_SECKEY);
}
}
if (no_sec)
{
msg = _("No secret key found to decrypt the message. "
"It is encrypted to the following keys:");
msg += format_recipients (result, protocol);
}
else
{
msg = _("Could not decrypt the data: ");
if (result.isNull ())
{
msg += _("Failed to parse the mail.");
}
else if (result.isLegacyCipherNoMDC())
{
msg += _("Data is not integrity protected. "
"Decrypting it could be a security problem. (no MDC)");
}
else
{
msg += result.error().asString();
}
}
if (gpgrt_asprintf (&buf, opt.prefer_html ? decrypt_template_html :
decrypt_template,
protocol == OpenPGP ? "OpenPGP" : "S/MIME",
_("Encrypted message (decryption not possible)"),
msg.c_str()) == -1)
{
log_error ("%s:%s:Failed to Format error.",
SRCNAME, __func__);
TRETURN "Failed to Format error.";
}
msg = buf;
memdbg_alloc (buf);
xfree (buf);
TRETURN msg;
}
void
ParseController::setSender(const std::string &sender)
{
TSTART;
m_sender = sender;
TRETURN;
}
static bool
is_valid_chksum(const GpgME::Signature &sig)
{
TSTART;
const auto sum = sig.summary();
static unsigned int valid_mask = (unsigned int) (
GpgME::Signature::Valid |
GpgME::Signature::Green |
GpgME::Signature::KeyRevoked |
GpgME::Signature::KeyExpired |
GpgME::Signature::SigExpired |
GpgME::Signature::CrlMissing |
GpgME::Signature::CrlTooOld |
GpgME::Signature::TofuConflict );
TRETURN 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()
{
TSTART;
// 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);
TRETURN;
}
/* Maybe a different option for this ? */
if (opt.autoretrieve)
{
ctx->setFlag("auto-key-retrieve", "1");
}
ctx->setArmor(true);
if (!m_sender.empty())
{
ctx->setSender(m_sender.c_str());
}
Data output (m_outputprovider);
log_debug ("%s:%s:%p decrypt: %i verify: %i with protocol: %s sender: %s type: %i",
SRCNAME, __func__, this,
decrypt, verify,
protocol == OpenPGP ? "OpenPGP" :
protocol == CMS ? "CMS" : "Unknown",
m_sender.empty() ? "none" : anonstr (m_sender.c_str()), inputType);
if (decrypt)
{
input.seek (0, SEEK_SET);
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 & 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 ();
}
TRETURN;
}
const std::string
ParseController::get_html_body () const
{
TSTART;
if (m_outputprovider)
{
TRETURN m_outputprovider->get_html_body ();
}
else
{
TRETURN std::string();
}
}
const std::string
ParseController::get_body () const
{
TSTART;
if (m_outputprovider)
{
TRETURN m_outputprovider->get_body ();
}
else
{
TRETURN std::string();
}
}
const std::string
ParseController::get_body_charset() const
{
TSTART;
if (m_outputprovider)
{
TRETURN m_outputprovider->get_body_charset();
}
else
{
TRETURN std::string();
}
}
const std::string
ParseController::get_html_charset() const
{
TSTART;
if (m_outputprovider)
{
TRETURN m_outputprovider->get_html_charset();
}
else
{
TRETURN std::string();
}
}
std::vector<std::shared_ptr<Attachment> >
ParseController::get_attachments() const
{
TSTART;
if (m_outputprovider)
{
TRETURN m_outputprovider->get_attachments();
}
else
{
TRETURN std::vector<std::shared_ptr<Attachment> >();
}
}
GPGRT_LOCK_DEFINE(keylist_lock);
/* static */
std::vector<Key>
ParseController::get_ultimate_keys()
{
TSTART;
static bool s_keys_listed;
static std::vector<Key> s_ultimate_keys;
- gpgrt_lock_lock (&keylist_lock);
+ gpgol_lock (&keylist_lock);
if (s_keys_listed)
{
- gpgrt_lock_unlock (&keylist_lock);
+ gpgol_unlock (&keylist_lock);
TRETURN 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);
+ gpgol_unlock (&keylist_lock);
TRETURN 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);
+ gpgol_unlock (&keylist_lock);
TRETURN 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());
break;
}
}
}
TRACEPOINT;
log_debug ("%s:%s: keylisting done.",
SRCNAME, __func__);
s_keys_listed = true;
- gpgrt_lock_unlock (&keylist_lock);
+ gpgol_unlock (&keylist_lock);
TRETURN s_ultimate_keys;
}
diff --git a/src/windowmessages.cpp b/src/windowmessages.cpp
index 0b233e9..e269d0a 100644
--- a/src/windowmessages.cpp
+++ b/src/windowmessages.cpp
@@ -1,570 +1,570 @@
/* @file windowmessages.h
* @brief Helper class to work with the windowmessage handler thread.
*
* Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include "windowmessages.h"
#include "common.h"
#include "oomhelp.h"
#include "mail.h"
#include "gpgoladdin.h"
#include "wks-helper.h"
#include "addressbook.h"
#include <stdio.h>
#define RESPONDER_CLASS_NAME "GpgOLResponder"
/* Singleton window */
static HWND g_responder_window = NULL;
static int invalidation_blocked = 0;
LONG_PTR WINAPI
gpgol_window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// log_debug ("WMG: %x", (unsigned int) message);
if (message == WM_USER + 42)
{
TSTART;
wm_ctx_t *ctx = (wm_ctx_t *) lParam;
log_debug ("%s:%s: Recieved user msg: %i",
SRCNAME, __func__, ctx->wmsg_type);
switch (ctx->wmsg_type)
{
case (PARSING_DONE):
{
auto mail = (Mail*) ctx->data;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: Parsing done for mail which is gone.",
SRCNAME, __func__);
TBREAK;
}
mail->parsing_done();
TBREAK;
}
case (RECIPIENT_ADDED):
{
auto mail = (Mail*) ctx->data;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: Recipient add for mail which is gone.",
SRCNAME, __func__);
TBREAK;
}
mail->locateKeys_o ();
TBREAK;
}
case (REVERT_MAIL):
{
auto mail = (Mail*) ctx->data;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: Revert mail for mail which is gone.",
SRCNAME, __func__);
TBREAK;
}
mail->setNeedsSave (true);
/* Some magic here. Accessing any existing inline body cements
it. Otherwise updating the body through the revert also changes
the body of a inline mail. */
char *inlineBody = get_inline_body ();
xfree (inlineBody);
// Does the revert.
log_debug ("%s:%s: Revert mail. Invoking save.",
SRCNAME, __func__);
invoke_oom_method (mail->item (), "Save", NULL);
log_debug ("%s:%s: Revert mail. Save done. Updating body..",
SRCNAME, __func__);
mail->updateBody_o ();
log_debug ("%s:%s: Revert mail done.",
SRCNAME, __func__);
TBREAK;
}
case (INVALIDATE_UI):
{
if (!invalidation_blocked)
{
log_debug ("%s:%s: Invalidating UI",
SRCNAME, __func__);
gpgoladdin_invalidate_ui();
log_debug ("%s:%s: Invalidation done",
SRCNAME, __func__);
}
else
{
log_debug ("%s:%s: Received invalidation msg while blocked."
" Ignoring it",
SRCNAME, __func__);
}
TBREAK;
}
case (INVALIDATE_LAST_MAIL):
{
log_debug ("%s:%s: clearing last mail",
SRCNAME, __func__);
Mail::clearLastMail ();
TBREAK;
}
case (CLOSE):
{
auto mail = (Mail*) ctx->data;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: Close for mail which is gone.",
SRCNAME, __func__);
TBREAK;
}
mail->refCurrentItem();
Mail::closeInspector_o (mail);
TRACEPOINT;
Mail::close (mail);
log_debug ("%s:%s: Close finished.",
SRCNAME, __func__);
mail->releaseCurrentItem();
TBREAK;
}
case (CRYPTO_DONE):
{
auto mail = (Mail*) ctx->data;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: Crypto done for mail which is gone.",
SRCNAME, __func__);
TBREAK;
}
// modify the mail.
if (mail->cryptState () == Mail::NeedsUpdateInOOM)
{
// Save the Mail
log_debug ("%s:%s: Crypto done for %p updating oom.",
SRCNAME, __func__, mail);
mail->updateCryptOOM_o ();
}
if (mail->cryptState () == Mail::NeedsSecondAfterWrite)
{
invoke_oom_method (mail->item (), "Save", NULL);
log_debug ("%s:%s: Second save done for %p Invoking second send.",
SRCNAME, __func__, mail);
}
// Finaly this should pass.
if (invoke_oom_method (mail->item (), "Send", NULL))
{
log_error ("%s:%s: Send failed for %p. "
"Trying SubmitMessage instead.\n"
"This will likely crash.",
SRCNAME, __func__, mail);
/* What we do here is similar to the T3656 workaround
in mailitem-events.cpp. In our tests this works but
is unstable. So we only use it as a very very last
resort. */
auto mail_message = get_oom_base_message (mail->item());
if (!mail_message)
{
gpgol_bug (mail->getWindow (),
ERR_GET_BASE_MSG_FAILED);
TBREAK;
}
// It's important we use the _base_ message here.
mapi_save_changes (mail_message, FORCE_SAVE);
HRESULT hr = mail_message->SubmitMessage(0);
gpgol_release (mail_message);
if (hr == S_OK)
{
do_in_ui_thread_async (CLOSE, (LPVOID) mail);
}
else
{
log_error ("%s:%s: SubmitMessage Failed hr=0x%lx.",
SRCNAME, __func__, hr);
gpgol_bug (mail->getWindow (),
ERR_SEND_FALLBACK_FAILED);
}
}
else
{
mail->releaseCurrentItem ();
}
log_debug ("%s:%s: Send for %p completed.",
SRCNAME, __func__, mail);
TBREAK;
}
case (BRING_TO_FRONT):
{
HWND wnd = get_active_hwnd ();
if (wnd)
{
log_debug ("%s:%s: Bringing window %p to front.",
SRCNAME, __func__, wnd);
bring_to_front (wnd);
}
else
{
log_debug ("%s:%s: No active window found for bring to front.",
SRCNAME, __func__);
}
TBREAK;
}
case (WKS_NOTIFY):
{
WKSHelper::instance ()->notify ((const char *) ctx->data);
xfree (ctx->data);
TBREAK;
}
case (CLEAR_REPLY_FORWARD):
{
auto mail = (Mail*) ctx->data;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: Clear reply forward for mail which is gone.",
SRCNAME, __func__);
TBREAK;
}
mail->wipe_o (true);
mail->removeAllAttachments_o ();
TBREAK;
}
case (DO_AUTO_SECURE):
{
auto mail = (Mail*) ctx->data;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
SRCNAME, __func__);
TBREAK;
}
mail->setDoAutosecure_m (true);
TBREAK;
}
case (DONT_AUTO_SECURE):
{
auto mail = (Mail*) ctx->data;
if (!Mail::isValidPtr (mail))
{
log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
SRCNAME, __func__);
TBREAK;
}
mail->setDoAutosecure_m (false);
TBREAK;
}
case (CONFIG_KEY_DONE):
{
log_debug ("%s:%s: Key configuration done.",
SRCNAME, __func__);
Addressbook::update_key_o (ctx->data);
TBREAK;
}
default:
log_debug ("%s:%s: Unknown msg %x",
SRCNAME, __func__, ctx->wmsg_type);
}
TRETURN 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
HWND
create_responder_window ()
{
TSTART;
size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
char cls_name[cls_name_len];
if (g_responder_window)
{
TRETURN g_responder_window;
}
/* Create Window wants a mutable string as the first parameter */
snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
WNDCLASS windowClass;
windowClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
windowClass.lpfnWndProc = gpgol_window_proc;
windowClass.cbClsExtra = 0;
windowClass.cbWndExtra = 0;
windowClass.hInstance = (HINSTANCE) GetModuleHandle(NULL);
windowClass.hIcon = 0;
windowClass.hCursor = 0;
windowClass.hbrBackground = 0;
windowClass.lpszMenuName = 0;
windowClass.lpszClassName = cls_name;
RegisterClass(&windowClass);
g_responder_window = CreateWindow (cls_name, RESPONDER_CLASS_NAME, 0, 0, 0,
0, 0, 0, (HMENU) 0,
(HINSTANCE) GetModuleHandle(NULL), 0);
TRETURN g_responder_window;
}
static int
send_msg_to_ui_thread (wm_ctx_t *ctx)
{
TSTART;
size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
char cls_name[cls_name_len];
snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
HWND responder = FindWindow (cls_name, RESPONDER_CLASS_NAME);
if (!responder)
{
log_error ("%s:%s: Failed to find responder window.",
SRCNAME, __func__);
TRETURN -1;
}
SendMessage (responder, WM_USER + 42, 0, (LPARAM) ctx);
TRETURN 0;
}
int
do_in_ui_thread (gpgol_wmsg_type type, void *data)
{
TSTART;
wm_ctx_t ctx = {NULL, UNKNOWN, 0, 0};
ctx.wmsg_type = type;
ctx.data = data;
log_debug ("%s:%s: Sending message of type %i",
SRCNAME, __func__, type);
if (send_msg_to_ui_thread (&ctx))
{
TRETURN -1;
}
TRETURN ctx.err;
}
static DWORD WINAPI
do_async (LPVOID arg)
{
TSTART;
wm_ctx_t *ctx = (wm_ctx_t*) arg;
log_debug ("%s:%s: Do async with type %i after %i ms",
SRCNAME, __func__, ctx ? ctx->wmsg_type : -1,
ctx->delay);
if (ctx->delay)
{
Sleep (ctx->delay);
}
send_msg_to_ui_thread (ctx);
xfree (ctx);
TRETURN 0;
}
void
do_in_ui_thread_async (gpgol_wmsg_type type, void *data, int delay)
{
TSTART;
wm_ctx_t *ctx = (wm_ctx_t *) xcalloc (1, sizeof (wm_ctx_t));
ctx->wmsg_type = type;
ctx->data = data;
ctx->delay = delay;
CloseHandle (CreateThread (NULL, 0, do_async, (LPVOID) ctx, 0, NULL));
TRETURN;
}
LRESULT CALLBACK
gpgol_hook(int code, WPARAM wParam, LPARAM lParam)
{
/* Once we are in the close events we don't have enough
control to revert all our changes so we have to do it
with this nice little hack by catching the WM_CLOSE message
before it reaches outlook. */
LPCWPSTRUCT cwp = (LPCWPSTRUCT) lParam;
/* What we do here is that we catch all WM_CLOSE messages that
get to Outlook. Then we check if the last open Explorer
is the target of the close. In set case we start our shutdown
routine before we pass the WM_CLOSE to outlook */
switch (cwp->message)
{
case WM_CLOSE:
{
HWND lastChild = NULL;
log_debug ("%s:%s: Got WM_CLOSE",
SRCNAME, __func__);
if (!GpgolAddin::get_instance() || !GpgolAddin::get_instance ()->get_application())
{
TRACEPOINT;
TBREAK;
}
LPDISPATCH explorers = get_oom_object (GpgolAddin::get_instance ()->get_application(),
"Explorers");
if (!explorers)
{
log_error ("%s:%s: No explorers object",
SRCNAME, __func__);
TBREAK;
}
int count = get_oom_int (explorers, "Count");
if (count != 1)
{
log_debug ("%s:%s: More then one explorer. Not shutting down.",
SRCNAME, __func__);
gpgol_release (explorers);
TBREAK;
}
LPDISPATCH explorer = get_oom_object (explorers, "Item(1)");
gpgol_release (explorers);
if (!explorer)
{
TRACEPOINT;
TBREAK;
}
/* Casting to LPOLEWINDOW and calling GetWindow
succeeded in Outlook 2016 but always TRETURNed
the number 1. So we need this hack. */
char *caption = get_oom_string (explorer, "Caption");
gpgol_release (explorer);
if (!caption)
{
log_debug ("%s:%s: No caption.",
SRCNAME, __func__);
TBREAK;
}
/* rctrl_renwnd32 is the window class of outlook. */
HWND hwnd = FindWindowExA(NULL, lastChild, "rctrl_renwnd32",
caption);
xfree (caption);
lastChild = hwnd;
if (hwnd == cwp->hwnd)
{
log_debug ("%s:%s: WM_CLOSE windowmessage for explorer. "
"Shutting down.",
SRCNAME, __func__);
GpgolAddin::get_instance ()->shutdown();
TBREAK;
}
TBREAK;
}
case WM_SYSCOMMAND:
/*
This comes to often and when we are closed from the icon
we also get WM_CLOSE
if (cwp->wParam == SC_CLOSE)
{
log_debug ("%s:%s: SC_CLOSE syscommand. Closing all mails.",
SRCNAME, __func__);
GpgolAddin::get_instance ()->shutdown();
} */
break;
default:
// log_debug ("WM: %x", (unsigned int) cwp->message);
break;
}
return CallNextHookEx (NULL, code, wParam, lParam);
}
/* Create the message hook for outlook's windowmessages
we are especially interested in WM_QUIT to do cleanups
and prevent the "Item has changed" question. */
HHOOK
create_message_hook()
{
TSTART;
TRETURN SetWindowsHookEx (WH_CALLWNDPROC,
gpgol_hook,
NULL,
GetCurrentThreadId());
}
GPGRT_LOCK_DEFINE (invalidate_lock);
static bool invalidation_in_progress;
DWORD WINAPI
delayed_invalidate_ui (LPVOID minsleep)
{
TSTART;
if (invalidation_in_progress)
{
log_debug ("%s:%s: Invalidation canceled as it is in progress.",
SRCNAME, __func__);
TRETURN 0;
}
TRACEPOINT;
invalidation_in_progress = true;
- gpgrt_lock_lock(&invalidate_lock);
+ gpgol_lock(&invalidate_lock);
int sleep_ms = (intptr_t)minsleep;
Sleep (sleep_ms);
int i = 0;
while (invalidation_blocked)
{
i++;
if (i % 10 == 0)
{
log_debug ("%s:%s: Waiting for invalidation.",
SRCNAME, __func__);
}
Sleep (100);
/* Do we need an abort statement here? */
}
do_in_ui_thread (INVALIDATE_UI, nullptr);
TRACEPOINT;
invalidation_in_progress = false;
- gpgrt_lock_unlock(&invalidate_lock);
+ gpgol_unlock(&invalidate_lock);
TRETURN 0;
}
DWORD WINAPI
close_mail (LPVOID mail)
{
TSTART;
do_in_ui_thread (CLOSE, mail);
TRETURN 0;
}
void
blockInv()
{
TSTART;
invalidation_blocked++;
log_oom ("%s:%s: Invalidation block count %i",
SRCNAME, __func__, invalidation_blocked);
TRETURN;
}
void
unblockInv()
{
TSTART;
invalidation_blocked--;
log_oom ("%s:%s: Invalidation block count %i",
SRCNAME, __func__, invalidation_blocked);
if (invalidation_blocked < 0)
{
log_error ("%s:%s: Invalidation block mismatch",
SRCNAME, __func__);
invalidation_blocked = 0;
}
TRETURN;
}
diff --git a/src/wks-helper.cpp b/src/wks-helper.cpp
index acf79e3..96cc23e 100644
--- a/src/wks-helper.cpp
+++ b/src/wks-helper.cpp
@@ -1,848 +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"
#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);
+ gpgol_lock (&wks_lock);
+ gpgol_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);
+ gpgol_lock (&wks_lock);
const auto it = s_states.find(mbox);
const auto dataEnd = s_states.end();
- gpgrt_lock_unlock (&wks_lock);
+ gpgol_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);
+ gpgol_lock (&wks_lock);
const auto it = s_last_checked.find(mbox);
const auto dataEnd = s_last_checked.end();
- gpgrt_lock_unlock (&wks_lock);
+ gpgol_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);
+ gpgol_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);
+ gpgol_unlock (&wks_lock);
return std::make_pair (nullptr, nullptr);
}
auto ret = it->second;
s_confirmation_cache.erase (it);
- gpgrt_lock_unlock (&wks_lock);
+ gpgol_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__, anonstr (mbox.c_str ()));
isPublished = check_published (mbox);
}
if (isPublished)
{
log_debug ("%s:%s: WKS client: '%s' is published",
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__, 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);
+ gpgol_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);
+ gpgol_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);
+ gpgol_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);
+ gpgol_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, 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;
}
log_data ("%s:%s: WKS client: returned '%s'",
SRCNAME, __func__, data.c_str ());
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);
+ gpgol_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);
+ gpgol_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);
+ gpgol_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);
+ gpgol_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__, 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;
}
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);
+ gpgol_lock (&wks_lock);
s_confirmation_cache.insert (std::make_pair (mbox, std::make_pair (mystdin, mail)));
- gpgrt_lock_unlock (&wks_lock);
+ gpgol_unlock (&wks_lock);
update_state (mbox, ConfirmationSeen);
/* Send the window message for notify. */
allow_notify (5000);
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Dec 23, 3:20 PM (20 h, 49 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
32/3a/1a21fdf7ab86f00485046727128b
Attached To
rO GpgOL
Event Timeline
Log In to Comment