diff --git a/src/Makefile.am b/src/Makefile.am
index a529f8b..2e6f529 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,119 +1,117 @@
# Makefile.am - makefile for GPGol
# Copyright (C) 2005 g10 Code GmbH
# Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
# Software engineering by Intevation GmbH
# Copyright (C) 2018 Intevation GmbH
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
## Process this file with automake to produce Makefile.in
SUBDIRS = icons
bin_PROGRAMS = gpgol
EXTRA_DIST = \
versioninfo.rc.in mapi32.def Outlook.gpl \
dialogs.rc
EXEEXT = .dll
-AM_CFLAGS = $(GPGME_CFLAGS) $(LIBASSUAN_CFLAGS) -shared
+AM_CFLAGS = $(GPGME_CFLAGS) -shared
AM_CXXFLAGS = $(GPGME_CFLAGS) $(LIBASSUAN_CFLAGS) -shared -std=c++11
AM_CXXFLAGS += $(GPGMEPP_CXXFLAGS) -D_FILE_OFFSET_BITS=64
gpgol_SOURCES = \
addin-options.cpp addin-options.h \
application-events.cpp \
attachment.h attachment.cpp \
common.h common.c \
common_indep.h common_indep.c \
config-dialog.c \
cpphelp.cpp cpphelp.h \
cryptcontroller.cpp cryptcontroller.h \
dialogs.h \
- engine-assuan.c engine-assuan.h \
- engine.c engine.h \
eventsink.h \
eventsinks.h \
exechelp.c exechelp.h \
explorer-events.cpp \
explorers-events.cpp \
filetype.c filetype.h \
gmime-table-private.h \
gpgoladdin.cpp gpgoladdin.h \
gpgol.def \
gpgol-ids.h \
keycache.cpp keycache.h \
mail.h mail.cpp \
mailitem-events.cpp \
main.c \
mapihelp.cpp mapihelp.h \
mimedataprovider.cpp mimedataprovider.h \
mimemaker.cpp mimemaker.h \
mlang-charset.cpp mlang-charset.h \
mymapi.h \
mymapitags.h \
olflange.cpp olflange.h \
oomhelp.cpp oomhelp.h \
overlay.cpp overlay.h \
parsecontroller.cpp parsecontroller.h \
parsetlv.h parsetlv.c \
resource.rc \
revert.cpp revert.h \
rfc2047parse.h rfc2047parse.c \
rfc822parse.c rfc822parse.h \
ribbon-callbacks.cpp ribbon-callbacks.h \
w32-gettext.c w32-gettext.h \
windowmessages.h windowmessages.cpp \
wks-helper.cpp wks-helper.h \
xmalloc.h
#treeview_SOURCES = treeview.c
# W32API 3.2 comes with an unusable libmapi32.a. We build our own
# version. Note the omission of -k (--kill-at) from the DLLTOOL
# command line. We also create our own virtual copies to the _static_
# versions of GPGME and gpg-error, because we want to link to them
# statically, and not dynamically (otherwise Outlook would not find
# them).
gpgol_DEPENDENCIES = libmapi32.a libgpg-error.a libgpgme.a libassuan.a libgpgmepp.a
if BUILD_W64
DLLTOOLFLAGS64=--as-flags=--64 -m i386:x86-64
endif
libmapi32.a: mapi32.def
$(DLLTOOL) $(DLLTOOLFLAGS64) --output-lib $@ --def $<
libgpg-error.a:
ln -s $$($(GPG_ERROR_CONFIG) --prefix)/lib/libgpg-error.a .
libgpgme.a:
ln -s $$($(GPGME_CONFIG) --prefix)/lib/libgpgme.a .
libassuan.a:
ln -s $$($(LIBASSUAN_CONFIG) --prefix)/lib/libassuan.a .
libgpgmepp.a:
ln -s $$($(GPGME_CONFIG) --prefix)/lib/libgpgmepp.a .
clean-local:
rm -f libmapi32.a libgpg-error.a libgpgme.a libassuan.a libgpgmepp.a
gpgol_LDFLAGS = -static-libgcc -static-libstdc++
gpgol_LDADD = $(srcdir)/gpgol.def \
-L . -lgpgmepp -lgpgme -lassuan -lgpg-error \
-lmapi32 -lshell32 -lgdi32 -lcomdlg32 \
-lole32 -loleaut32 -lws2_32 -ladvapi32 \
-luuid -lgdiplus -lrpcrt4
resource.o: resource.rc versioninfo.rc dialogs.rc dialogs.h
.rc.o:
$(WINDRES) -I $(srcdir) -I . -I .. `test -f '$<' || echo '$(srcdir)/'`$< $@
diff --git a/src/addin-options.cpp b/src/addin-options.cpp
index 8384efc..160a34a 100644
--- a/src/addin-options.cpp
+++ b/src/addin-options.cpp
@@ -1,207 +1,206 @@
/* addin-options.cpp - Options for the Ol >= 2010 Addin
* Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include
#include "dialogs.h"
#include "common.h"
-#include "engine.h"
#include
#include
#include
/* To avoid writing a dialog template for each language we use gettext
for the labels and hope that there is enough space in the dialog to
fit the longest translation.. */
static void
set_labels (HWND dlg)
{
static struct { int itemid; const char *label; } labels[] = {
{ IDC_G_GENERAL, N_("General")},
{ IDC_ENABLE_SMIME, N_("Enable the S/MIME support")},
{ IDC_G_SEND, N_("Message sending")},
{ IDC_ENCRYPT_DEFAULT, N_("&Encrypt new messages by default")},
{ IDC_SIGN_DEFAULT, N_("&Sign new messages by default")},
{ IDC_INLINE_PGP, N_("&Send OpenPGP mails without "
"attachments as PGP/Inline")},
{ IDC_REPLYCRYPT, N_("S&elect crypto settings automatically "
"for reply and forward")},
{ IDC_AUTORRESOLVE, N_("&Resolve recipient keys automatically")},
{ IDC_GPG_OPTIONS, N_("Debug...")},
{ IDC_GPG_CONF, N_("Configure GnuPG")},
{ IDC_VERSION_INFO, N_("Version ")VERSION},
{ 0, NULL}
};
int i;
for (i=0; labels[i].itemid; i++)
SetDlgItemText (dlg, labels[i].itemid, _(labels[i].label));
}
static void
launch_kleo_config (HWND hDlg)
{
char *uiserver = get_uiserver_name ();
bool showError = false;
if (uiserver)
{
std::string path (uiserver);
xfree (uiserver);
if (path.find("kleopatra.exe") != std::string::npos)
{
size_t dpos;
if ((dpos = path.find(" --daemon")) != std::string::npos)
{
path.erase(dpos, strlen(" --daemon"));
}
auto ctx = GpgME::Context::createForEngine(GpgME::SpawnEngine);
if (!ctx)
{
log_error ("%s:%s: No spawn engine.",
SRCNAME, __func__);
}
std::string parentWid = std::to_string ((int) (intptr_t) hDlg);
const char *argv[] = {path.c_str(),
"--config",
"--parent-windowid",
parentWid.c_str(),
NULL };
log_debug ("%s:%s: Starting %s %s %s",
SRCNAME, __func__, path.c_str(), argv[1], argv[2]);
GpgME::Data d(GpgME::Data::null);
ctx->spawnAsync(path.c_str(), argv, d, d,
d, (GpgME::Context::SpawnFlags) (
GpgME::Context::SpawnAllowSetFg |
GpgME::Context::SpawnShowWindow));
}
else
{
showError = true;
}
}
else
{
showError = true;
}
if (showError)
{
MessageBox (NULL,
_("Could not find Kleopatra.\n"
"Please reinstall Gpg4win with the Kleopatra component enabled."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
}
}
static INT_PTR CALLBACK
options_window_proc (HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
(void)lParam;
switch (uMsg)
{
case WM_INITDIALOG:
{
SendDlgItemMessage (hDlg, IDC_ENABLE_SMIME, BM_SETCHECK,
!!opt.enable_smime, 0L);
SendDlgItemMessage (hDlg, IDC_ENCRYPT_DEFAULT, BM_SETCHECK,
!!opt.encrypt_default, 0L);
SendDlgItemMessage (hDlg, IDC_SIGN_DEFAULT, BM_SETCHECK,
!!opt.sign_default, 0L);
SendDlgItemMessage (hDlg, IDC_INLINE_PGP, BM_SETCHECK,
!!opt.inline_pgp, 0L);
SendDlgItemMessage (hDlg, IDC_REPLYCRYPT, BM_SETCHECK,
!!opt.reply_crypt, 0L);
SendDlgItemMessage (hDlg, IDC_AUTORRESOLVE, BM_SETCHECK,
!!opt.autoresolve, 0L);
set_labels (hDlg);
ShowWindow (GetDlgItem (hDlg, IDC_GPG_OPTIONS),
opt.enable_debug ? SW_SHOW : SW_HIDE);
}
return 1;
case WM_LBUTTONDOWN:
{
return 1;
}
case WM_COMMAND:
switch (LOWORD (wParam))
{
case IDOK:
{
opt.enable_smime = !!SendDlgItemMessage
(hDlg, IDC_ENABLE_SMIME, BM_GETCHECK, 0, 0L);
opt.encrypt_default = !!SendDlgItemMessage
(hDlg, IDC_ENCRYPT_DEFAULT, BM_GETCHECK, 0, 0L);
opt.sign_default = !!SendDlgItemMessage
(hDlg, IDC_SIGN_DEFAULT, BM_GETCHECK, 0, 0L);
opt.inline_pgp = !!SendDlgItemMessage
(hDlg, IDC_INLINE_PGP, BM_GETCHECK, 0, 0L);
opt.reply_crypt = !!SendDlgItemMessage
(hDlg, IDC_REPLYCRYPT, BM_GETCHECK, 0, 0L);
opt.autoresolve = !!SendDlgItemMessage
(hDlg, IDC_AUTORRESOLVE, BM_GETCHECK, 0, 0L);
write_options ();
EndDialog (hDlg, TRUE);
break;
}
case IDC_GPG_CONF:
launch_kleo_config (hDlg);
break;
case IDC_GPG_OPTIONS:
config_dialog_box (hDlg);
break;
}
case WM_SYSCOMMAND:
switch (LOWORD (wParam))
{
case SC_CLOSE:
EndDialog (hDlg, TRUE);
}
break;
}
return 0;
}
void
options_dialog_box (HWND parent)
{
int resid;
resid = IDD_ADDIN_OPTIONS;
if (!parent)
parent = GetDesktopWindow ();
DialogBoxParam (glob_hinst, MAKEINTRESOURCE (resid), parent,
options_window_proc, 0);
}
diff --git a/src/engine-assuan.c b/src/engine-assuan.c
deleted file mode 100644
index 8907c18..0000000
--- a/src/engine-assuan.c
+++ /dev/null
@@ -1,2245 +0,0 @@
-/* engine-assuan.c - Crypto engine using an Assuan server
- * Copyright (C) 2007, 2008, 2009, 2010 g10 Code GmbH
- *
- * This file is part of GpgOL.
- *
- * GpgOL is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 2.1
- * of the License, or (at your option) any later version.
- *
- * GpgOL is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, see .
- */
-
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include "common.h"
-#include "engine.h"
-#include "engine-assuan.h"
-#include "exechelp.h"
-
-/* Debug macros. */
-#define debug_ioworker (opt.enable_debug & DBG_IOWORKER)
-#define debug_ioworker_extra (opt.enable_debug & DBG_IOWORKER_EXTRA)
-
-
-/* How many times we will try to connect to a server after we have
- started him. */
-#define FIREUP_RETRIES 10
-
-
-/* This is the buffer object used for the asynchronous reading of the
- status channel. */
-struct status_buffer_s
-{
- int eof;
- int linelen; /* Used length of LINE. */
- char line[ASSUAN_LINELENGTH];
-};
-typedef struct status_buffer_s *status_buffer_t;
-
-
-/* We operate in an asynchronous mode and thus need to run code for
- final cleanup. Thus all functions need to implement a closure
- function and setup an closure_data_t object. */
-struct closure_data_s;
-typedef struct closure_data_s *closure_data_t;
-struct closure_data_s
-{
- void (*closure)(closure_data_t);
- gpg_error_t final_err; /* Final error code. */
- engine_filter_t filter;
- assuan_context_t assctx;
- ULONG cmdid;
- assuan_fd_t status_read_fd;
- struct gpgme_data_cbs status_cbs;
- gpgme_data_t status_data;
- status_buffer_t status_buffer; /* Allocated on demand. */
- int status_ready;
- gpgme_data_t sigdata; /* Used by verify_closure. */
- gpg_error_t last_err;
-};
-
-
-/* The object used by our I/O worker thread. */
-struct work_item_s;
-typedef struct work_item_s *work_item_t;
-struct work_item_s
-{
- work_item_t next;
- int used; /* If not set this object may be reused. */
- int waiting; /* Helper for async_worker_thread. */
-
- const char *name; /* Description used for debugging. */
- ULONG cmdid; /* Used to group work items of one command. */
- closure_data_t cld;/* NULL or the closure. */
- int wait_on_success; /* This work item needs to be ready before
- invoking a closure for this command. */
- gpgme_data_t data; /* The data object we write to or read from. */
- int writing; /* If true we are going to write to HD. */
- HANDLE hd; /* The handle we read from or write to. */
- int inactive; /* If set, the handle is not yet active. */
- int io_pending; /* I/O is still pending. The value is the number
- of bytes to be written or the size of the
- buffer given to ReadFile. */
- int got_ready; /* Operation finished. */
- int delayed_ready; /* Ready but delayed due to a missing prerequesite. */
- int got_error; /* An error as been encountered. */
- int aborting; /* Set to true after a CancelIO has been issued. */
- void (*finalize)(work_item_t); /* Function called immediately before
- the item is removed from the
- queue. */
- OVERLAPPED ov; /* The overlapped info structure. */
- char buffer[8192]; /* The buffer used by ReadFile or WriteFile. */
-};
-
-
-/* A helper context used to convey information from op_assuan_encrypt
- to op_assuan_encrypt_bottom. */
-struct engine_assuan_encstate_s
-{
- engine_filter_t filter;
- const char *protocol_name;
- HANDLE inpipe[2];
- HANDLE outpipe[2];
- closure_data_t cld;
- assuan_context_t ctx;
- ULONG cmdid;
-};
-
-
-/* The queue of all outstandig I/O operations. Protected by the
- work_queue_lock. */
-static work_item_t work_queue;
-
-/* The big lock used to protect the work queue. */
-static CRITICAL_SECTION work_queue_lock;
-
-/* An auto-reset event which will be signaled to get the
- async_worker_thread out of its WFMO and to inspect the work
- queue. */
-static HANDLE work_queue_event;
-
-
-/*-- prototypes --*/
-static DWORD WINAPI async_worker_thread (void *dummy);
-
-static unsigned int
-handle_to_int (HANDLE handle)
-{
- /* According to MSDN
- https://msdn.microsoft.com/en-us/library/
- windows/desktop/aa384203%28v=vs.85%29.aspx:
-
- 64-bit versions of Windows use 32-bit handles for
- interoperability. When sharing a handle between 32-bit
- and 64-bit applications, only the lower 32 bits are significant,
- so it is safe to truncate the handle (when passing it from 64-bit
- to 32-bit) or sign-extend the handle (when passing it from 32-bit
- to 64-bit). Handles that can be shared include handles to user
- objects such as windows (HWND), handles to GDI objects such as pens
- and brushes (HBRUSH and HPEN), and handles to named objects such
- as mutexes, semaphores, and file handles.
-
- So this hack is safe.
- */
-#ifndef __clang__
-#pragma GCC diagnostic ignored "-Wpointer-to-int-cast"
-#endif
- return (unsigned int) handle;
-#ifndef __clang__
-#pragma GCC diagnostic pop
-#endif
-}
-
-
-/* Return the next command id. Command Ids are used to group
- resources of one command. */
-static ULONG
-create_command_id (void)
-{
- static ULONG command_id;
- ULONG cmdid;
-
- while (!(cmdid = InterlockedIncrement (&command_id)))
- ;
- return cmdid;
-}
-
-
-static void
-close_pipe (HANDLE apipe[2])
-{
- int i;
-
- for (i=0; i < 2; i++)
- if (apipe[i] != INVALID_HANDLE_VALUE)
- {
- CloseHandle (apipe[i]);
- apipe[i] = INVALID_HANDLE_VALUE;
- }
-}
-
-
-/* Duplicate HANDLE into the server's process and close HANDLE. Note
- that HANDLE is closed even if the function fails. Returns the
- duplicated handle on success or INVALID_HANDLE_VALUE on error. */
-static HANDLE
-dup_to_server (HANDLE handle, pid_t serverpid)
-{
- HANDLE prochandle, newhandle;
-
- prochandle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, serverpid);
- if (!prochandle)
- {
- log_error_w32 (-1, "%s:%s: OpenProcess(%lu) failed",
- SRCNAME, __func__, (unsigned long)serverpid);
- CloseHandle (handle);
- return INVALID_HANDLE_VALUE;
- }
-
- if (!DuplicateHandle (GetCurrentProcess(), handle,
- prochandle, &newhandle, 0,
- TRUE, DUPLICATE_SAME_ACCESS ))
- {
- log_error_w32 (-1, "%s:%s: DuplicateHandle to pid %lu failed",
- SRCNAME, __func__, (unsigned long)serverpid);
- CloseHandle (prochandle);
- CloseHandle (handle);
- return INVALID_HANDLE_VALUE;
- }
- CloseHandle (prochandle);
- CloseHandle (handle);
- return newhandle;
-}
-
-
-/* Create pipe with one end being inheritable and prepared for
- overlapped I/O.
-
- FILEDES[0] := read handle.
- FILEDES[1] := write handle.
-
- SERVERPID is the PID of the server. FOR_WRITE is seen out of our
- perspective; if it is set, the read handle is created in the server
- process and the write handle is overlapped. If it is not set the
- write handle is created in the server process and the read handle
- is overlapped.
-*/
-static gpg_error_t
-create_io_pipe (HANDLE filedes[2], pid_t serverpid, int for_write)
-{
- static ULONG pipenumber;
- ULONG pipeno;
- char pipename[100];
- HANDLE r, w;
- SECURITY_ATTRIBUTES sec_attr;
-
- memset (&sec_attr, 0, sizeof sec_attr );
- sec_attr.nLength = sizeof sec_attr;
-
- /* CreatePipe is in reality implemented using a Named Pipe. We do
- it the same but use a name which is in our name space. We allow
- only one instance, use the standard timeout of 120 seconds and
- buffers of 4k. */
- pipeno = InterlockedIncrement (&pipenumber);
- snprintf (pipename, sizeof pipename, "\\\\.\\pipe\\GpgOL_anon.%08x.%08x",
- (unsigned int)GetCurrentProcessId(), (unsigned int)pipeno);
- sec_attr.bInheritHandle = /*for_write? TRUE :*/FALSE;
- r = CreateNamedPipe (pipename, (PIPE_ACCESS_INBOUND
- | (for_write? 0:FILE_FLAG_OVERLAPPED)),
- PIPE_TYPE_BYTE | PIPE_WAIT,
- 1, 4096, 4096, 120000, &sec_attr);
- if (r == INVALID_HANDLE_VALUE)
- {
- log_error_w32 (-1, "%s:%s: CreateNamedPipe failed for `%s'",
- SRCNAME, __func__, pipename);
- return gpg_error (GPG_ERR_GENERAL);
- }
- if (for_write)
- {
- r = dup_to_server (r, serverpid);
- if (r == INVALID_HANDLE_VALUE)
- {
- log_error_w32 (-1, "%s:%s: dup_for_server(r) failed for `%s'",
- SRCNAME, __func__, pipename);
- return gpg_error (GPG_ERR_GENERAL);
- }
- }
-
- /* Now open the other side of the named pipe. Because we have not
- called ConnectNamedPipe another process should not be able to
- open the pipe in the meantime. This is an educated guess by
- looking at REACTOS and WINE - they implement an anonymous pipe
- this way. */
- sec_attr.bInheritHandle = /*for_write?*/ FALSE /*: TRUE*/;
- w = CreateFile (pipename, GENERIC_WRITE, 0, &sec_attr,
- OPEN_EXISTING, (FILE_ATTRIBUTE_NORMAL
- | (for_write? FILE_FLAG_OVERLAPPED:0)),
- NULL);
- if (w == INVALID_HANDLE_VALUE)
- {
- log_error_w32 (-1, "%s:%s: CreateFile failed for `%s'",
- SRCNAME, __func__, pipename);
- CloseHandle (r);
- return gpg_error (GPG_ERR_GENERAL);
- }
- if (!for_write)
- {
- w = dup_to_server (w, serverpid);
- if (w == INVALID_HANDLE_VALUE)
- {
- log_error_w32 (-1, "%s:%s: dup_for_server(w) failed for `%s'",
- SRCNAME, __func__, pipename);
- CloseHandle (r);
- return gpg_error (GPG_ERR_GENERAL);
- }
- }
-
- filedes[0] = r;
- filedes[1] = w;
- if (debug_ioworker)
- log_debug ("%s:%s: new pipe created: r=%p%s w=%p%s", SRCNAME, __func__,
- r, for_write? " (server)":"",
- w, !for_write?" (server)":"");
- return 0;
-}
-
-
-/* Return the socket name of the UI Server. */
-static const char *
-get_socket_name (void)
-{
- static char *name;
-
- if (!name)
- {
- const char *dir = default_homedir ();
- name = xmalloc (strlen (dir) + 11 + 1);
- strcpy (stpcpy (name, dir), "\\S.uiserver");
- }
-
- return name;
-}
-
-
-static gpg_error_t
-send_one_option (assuan_context_t ctx, const char *name, const char *value)
-{
- gpg_error_t err;
- char buffer[1024];
-
- if (!value || !*value)
- err = 0; /* Avoid sending empty strings. */
- else
- {
- snprintf (buffer, sizeof buffer, "OPTION %s=%s", name, value);
- err = assuan_transact (ctx, buffer, NULL, NULL, NULL, NULL, NULL, NULL);
- }
-
- return err;
-}
-
-
-static gpg_error_t
-getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
-{
- pid_t *pid = opaque;
- char pidbuf[50];
-
- /* There is only the pid in the server's response. */
- if (length >= sizeof pidbuf)
- length = sizeof pidbuf -1;
- if (length)
- {
- strncpy (pidbuf, buffer, length);
- pidbuf[length] = 0;
- *pid = (pid_t)strtoul (pidbuf, NULL, 10);
- }
- return 0;
-}
-
-
-/* Send options to the UI server and return the server's PID. */
-static gpg_error_t
-send_options (assuan_context_t ctx, void *hwnd, pid_t *r_pid)
-{
- gpg_error_t err = 0;
- char numbuf[50];
-
- *r_pid = (pid_t)(-1);
- log_debug ("%s:%s: transacting",
- SRCNAME, __func__);
- err = assuan_transact (ctx, "GETINFO pid", getinfo_pid_cb, r_pid,
- NULL, NULL, NULL, NULL);
- log_debug ("%s:%s: transaction done",
- SRCNAME, __func__);
- if (!err && *r_pid == (pid_t)(-1))
- {
- log_debug ("%s:%s: server did not return a PID", SRCNAME, __func__);
- err = gpg_error (GPG_ERR_ASSUAN_SERVER_FAULT);
- }
-
- if (*r_pid != (pid_t)(-1) && !AllowSetForegroundWindow (*r_pid))
- log_error_w32 (-1, "AllowSetForegroundWindow("SIZE_T_FORMAT") failed",
- *r_pid);
-
- if (!err && hwnd)
- {
- log_debug ("%s:%s: Sending Window ID",
- SRCNAME, __func__);
- snprintf (numbuf, sizeof numbuf, "%x", handle_to_int (hwnd));
- err = send_one_option (ctx, "window-id", numbuf);
- }
-
- log_debug ("%s:%s: Returning.",
- SRCNAME, __func__);
- return err;
-}
-
-
-/* Connect to the UI server and setup the connection. */
-static gpg_error_t
-connect_uiserver (assuan_context_t *r_ctx, pid_t *r_pid, ULONG *r_cmdid,
- void *hwnd)
-{
- gpg_error_t rc;
- const char *socket_name = NULL;
- lock_spawn_t lock;
-
- *r_ctx = NULL;
- *r_pid = (pid_t)(-1);
- *r_cmdid = 0;
-
- log_debug ("%s:%s: Connecting",
- SRCNAME, __func__);
- socket_name = get_socket_name();
- if (!socket_name || !*socket_name)
- {
- log_error ("%s:%s: Invalid socket name",
- SRCNAME, __func__);
- return gpg_error (GPG_ERR_INV_ARG);
- }
-
- rc = assuan_new (r_ctx);
- if (rc)
- {
- log_error ("%s:%s: Could not allocate context",
- SRCNAME, __func__);
- return rc;
- }
-
- log_debug ("%s:%s: initial try",
- SRCNAME, __func__);
- rc = assuan_socket_connect (*r_ctx, socket_name, -1, 0);
- if (rc)
- {
- int count;
-
- log_debug ("%s:%s: UI server not running at: \"%s\", starting it",
- SRCNAME, __func__, socket_name);
-
- /* Now try to connect again with the spawn lock taken. */
- if (!(rc = gpgol_lock_spawning (&lock))
- && assuan_socket_connect (*r_ctx, socket_name, -1, 0))
- {
- char *uiserver = get_uiserver_name ();
- if (!uiserver)
- {
- log_error ("%s:%s: UI server not installed",
- SRCNAME, __func__);
- assuan_release (*r_ctx);
- *r_ctx = NULL;
- return GPG_ERR_GENERAL;
- }
- rc = gpgol_spawn_detached (uiserver);
- xfree (uiserver);
- if (!rc)
- {
- /* Give it a bit of time to start up and try a couple of
- times. */
- for (count = 0; count < 10; count++)
- {
- Sleep (1000);
- log_debug ("%s:%s: Connect try %i to \"%s\"",
- SRCNAME, __func__, count, socket_name);
- rc = assuan_socket_connect (*r_ctx, socket_name, -1, 0);
- if (!rc)
- {
- break;
- log_debug ("Connected");
- }
- }
- }
-
- }
- gpgol_unlock_spawning (&lock);
- log_debug ("%s:%s: Spawn unlocked.",
- SRCNAME, __func__);
- }
- else
- {
- log_debug ("%s:%s: initial try succeded",
- SRCNAME, __func__);
- }
-
- if (rc)
- {
- log_error ("%s:%s: UI Server failed to start",
- SRCNAME, __func__);
- assuan_release (*r_ctx);
- *r_ctx = NULL;
- return rc;
- }
-#if 0
- // Something for later
- if (debug_flags & DEBUG_ASSUAN)
- assuan_set_log_stream (*ctx, debug_file);
-#endif
- log_debug ("%s:%s: About to send options",
- SRCNAME, __func__);
- rc = send_options (*r_ctx, hwnd, r_pid);
- log_debug ("%s:%s: Options sent to uiserver",
- SRCNAME, __func__);
- if (rc)
- {
- assuan_release (*r_ctx);
- *r_ctx = NULL;
- return rc;
- }
- *r_cmdid = create_command_id ();
-
- return 0;
-}
-
-
-/* Send the optional session information. */
-static void
-send_session_info (assuan_context_t ctx, engine_filter_t filter)
-{
- char line[1020];
- unsigned int number = engine_private_get_session_number (filter);
- const char *title = engine_private_get_session_title (filter);
-
- if (title && *title)
- snprintf (line, sizeof line, "SESSION %u %s", number, title);
- else
- snprintf (line, sizeof line, "SESSION %u", number);
- assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
-}
-
-
-
-static void
-cleanup (void)
-{
- /* Fixme: We should stop the worker thread. */
-}
-
-
-/* Cleanup static resources. */
-void
-op_assuan_deinit (void)
-{
- cleanup ();
-}
-
-
-/* Initialize this system. */
-int
-op_assuan_init (void)
-{
- static int init_done;
-
- if (init_done)
- return 0;
-
- /* Fire up the pipe worker thread. */
- {
- HANDLE th;
- DWORD mytid, tid;
-
- InitializeCriticalSection (&work_queue_lock);
- work_queue_event = CreateEvent (NULL, FALSE, FALSE, NULL);
- if (!work_queue_event)
- {
- log_error_w32 (-1, "%s:%s: CreateEvent failed", SRCNAME, __func__);
- return gpg_error (GPG_ERR_GENERAL);
- }
- mytid = GetCurrentThreadId ();
- th = CreateThread (NULL, 256*1024, async_worker_thread, &mytid,
- 0, &tid);
- if (th == INVALID_HANDLE_VALUE)
- log_error ("failed to launch the async_worker_thread");
- else
- CloseHandle (th);
- }
-
- init_done = 1;
- return 0;
-}
-
-
-/* Dummy window procedure. */
-static LRESULT CALLBACK
-attach_thread_input_wndw_proc (HWND hwnd, UINT msg,
- WPARAM wparam, LPARAM lparam)
-{
- return DefWindowProc (hwnd, msg, wparam, lparam);
-}
-
-
-/* Our helper thread needs to attach its input events to the main
- message queue. To do this we need to create a the message queue
- first by creating an hidden window within this thread. */
-static void
-attach_thread_input (DWORD other_tid)
-{
- WNDCLASS wndwclass = {0, attach_thread_input_wndw_proc, 0, 0, glob_hinst,
- 0, 0, 0, 0, "gpgol-assuan-engine"};
- HWND hwnd;
-
- /* First create a window to make sure that a message queue exists
- for this thread. */
- if (!RegisterClass (&wndwclass))
- {
- log_error_w32 (-1, "%s:%s: error registering window class",
- SRCNAME, __func__);
- return;
- }
- hwnd = CreateWindow ("gpgol-assuan-engine", "gpgol-assuan-engine",
- 0, 0, 0, 0, 0, NULL, NULL, glob_hinst, NULL);
- if (!hwnd)
- {
- log_error_w32 (-1, "%s:%s: error creating main window",
- SRCNAME, __func__);
- return;
- }
-
- /* Now attach it to the main thread. */
- if (!AttachThreadInput (GetCurrentThreadId (), other_tid, TRUE))
- log_error_w32 (-1, "%s:%s: AttachThreadInput failed",
- SRCNAME, __func__);
- log_debug ("%s:%s: attached thread %lu to %lu", SRCNAME, __func__,
- GetCurrentThreadId (), other_tid);
-}
-
-
-
-/* Helper to write to the callback. */
-static void
-write_to_callback (work_item_t item, DWORD nbytes)
-{
- int nwritten;
-
- if (!nbytes)
- {
- /* (With overlapped, EOF is not indicated by NBYTES==0.) */
- log_error ("%s:%s: [%s:%p] short read (0 bytes)",
- SRCNAME, __func__, item->name, item->hd);
- }
- else
- {
- assert (nbytes > 0);
- nwritten = gpgme_data_write (item->data, item->buffer, nbytes);
- if (nwritten < 0)
- {
- log_error ("%s:%s: [%s:%p] error writing to callback: %s",
- SRCNAME, __func__, item->name, item->hd,strerror (errno));
- item->got_error = 1;
- }
- else if (nwritten < nbytes)
- {
- log_error ("%s:%s: [%s:%p] short write to callback (%d of %lu)",
- SRCNAME, __func__, item->name, item->hd, nwritten,nbytes);
- item->got_error = 1;
- }
- else
- {
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] wrote %d bytes to callback",
- SRCNAME, __func__, item->name, item->hd, nwritten);
- }
- }
-}
-
-/* Helper for async_worker_thread: Read data from the server and pass
- it on to the caller. Returns true if the item's handle needs to be
- put on the wait list. This is called with the worker mutex
- hold. */
-static int
-worker_start_read (work_item_t item)
-{
- DWORD nbytes;
- int retval = 0;
-
- /* Read from the handle and write to the callback. The gpgme
- callback is expected to never block. */
- if (ReadFile (item->hd, item->buffer, sizeof item->buffer,
- &nbytes, &item->ov) )
- {
- write_to_callback (item, nbytes);
- retval = 1;
- }
- else
- {
- int syserr = GetLastError ();
-
- if (syserr == ERROR_IO_PENDING)
- {
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] io(read) pending",
- SRCNAME, __func__, item->name, item->hd);
- item->io_pending = sizeof item->buffer;
- retval = 1;
- }
- else if (syserr == ERROR_HANDLE_EOF || syserr == ERROR_BROKEN_PIPE)
- {
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] EOF%s seen",
- SRCNAME, __func__, item->name, item->hd,
- syserr == ERROR_BROKEN_PIPE? " (broken pipe)":"");
- item->got_ready = 1;
- }
- else
- {
- log_error_w32 (syserr, "%s:%s: [%s:%p] read error",
- SRCNAME, __func__, item->name, item->hd);
- item->got_error = 1;
- }
- }
-
- return retval;
-}
-
-/* Result checking helper for async_worker_thread: Take data from the
- server and pass it on to the caller. This is called with the
- worker mutex hold. */
-static void
-worker_check_read (work_item_t item, DWORD nbytes)
-{
- write_to_callback (item, nbytes);
-}
-
-
-
-/* Helper for async_worker_thread. Returns true if the item's handle
- needs to be put on the wait list. This is called with the worker
- mutex hold. */
-static int
-worker_start_write (work_item_t item)
-{
- int nread;
- DWORD nbytes;
- int retval = 0;
-
- assert (!item->io_pending);
-
- /* Read from the callback and the write to the handle. The gpgme
- callback is expected to never block. */
- nread = gpgme_data_read (item->data, item->buffer, sizeof item->buffer);
- if (nread < 0)
- {
- if (errno == EAGAIN)
- {
- /* EAGAIN from the callback. That means that data is
- currently not available. */
- if (debug_ioworker_extra)
- log_debug ("%s:%s: [%s:%p] EAGAIN received from callback",
- SRCNAME, __func__, item->name, item->hd);
- retval = 1;
- }
- else
- {
- log_error ("%s:%s: [%s:%p] error reading from callback: %s",
- SRCNAME, __func__, item->name, item->hd,strerror (errno));
- item->got_error = 1;
- }
- }
- else if (!nread)
- {
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] EOF received from callback",
- SRCNAME, __func__, item->name, item->hd);
- item->got_ready = 1;
- }
- else
- {
- if (WriteFile (item->hd, item->buffer, nread, &nbytes, &item->ov))
- {
- if (nbytes < nread)
- {
- log_error ("%s:%s: [%s:%p] short write (%lu of %d)",
- SRCNAME, __func__, item->name,item->hd,nbytes, nread);
- item->got_error = 1;
- }
- else
- {
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] wrote %lu bytes",
- SRCNAME, __func__, item->name, item->hd, nbytes);
- }
- retval = 1; /* Keep on waiting for space in the pipe. */
- }
- else
- {
- int syserr = GetLastError ();
-
- if (syserr == ERROR_IO_PENDING)
- {
- /* This is the common case. Start the async I/O. */
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] io(write) pending (%d bytes)",
- SRCNAME, __func__, item->name, item->hd, nread);
- item->io_pending = nread;
- retval = 1; /* Need to wait for the I/O to complete. */
- }
- else
- {
- log_error_w32 (syserr, "%s:%s: [%s:%p] write error",
- SRCNAME, __func__, item->name, item->hd);
- item->got_error = 1;
- }
- }
- }
-
- return retval;
-}
-
-
-/* Result checking helper for async_worker_thread. This is called with
- the worker mutex hold. */
-static void
-worker_check_write (work_item_t item, DWORD nbytes)
-{
- if (nbytes < item->io_pending)
- {
- log_error ("%s:%s: [%s:%p] short write (%lu of %d)",
- SRCNAME,__func__, item->name, item->hd, nbytes,
- item->io_pending);
- item->got_error = 1;
- }
- else
- {
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] write finished (%lu bytes)",
- SRCNAME, __func__, item->name, item->hd, nbytes);
- }
-}
-
-
-
-/* The worker thread which feeds the pipes. */
-static DWORD WINAPI
-async_worker_thread (void *dummy)
-{
- work_item_t item;
- int n;
- DWORD nbytes;
- HANDLE hdarray[MAXIMUM_WAIT_OBJECTS];
- int count, addit, any_ready, hdarraylen;
- /* Due to problems opening stuff with Internet exploder, Word or
- Wordview, we can't use MsgWaitForMultipleObjects and the event
- loops. For test purposes a compatibiliy option allows to revert
- to the old behaviour. */
- int msgwait = opt.compat.use_mwfmo;
- DWORD orig_thread = *(DWORD*)dummy;
-
-
- if (msgwait)
- attach_thread_input ( orig_thread );
-
- for (;;)
- {
- /*
- Step 1: Walk our queue and fire up async I/O requests.
- */
- if (debug_ioworker_extra)
- log_debug ("%s:%s: step 1 - scanning work queue", SRCNAME, __func__);
- EnterCriticalSection (&work_queue_lock);
-
- /* We always need to wait on the the work queue event. */
- hdarraylen = 0;
- hdarray[hdarraylen++] = work_queue_event;
-
- count = 0;
- any_ready = 0;
- for (item = work_queue; item; item = item->next)
- {
- item->waiting = 0;
- if (!item->used)
- continue;
- assert (item->hd != INVALID_HANDLE_VALUE);
- count++;
- if (item->got_error)
- {
- if (!item->delayed_ready)
- any_ready = 1;
- continue;
- }
- assert (item->data);
- if (hdarraylen == DIM (hdarray))
- {
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] wait array full - ignored for now",
- SRCNAME, __func__, item->name, item->hd);
- continue;
- }
-
- /* Decide whether we need to wait for this item. This is
- the case if the previous WriteFile or ReadFile indicated
- that I/O is pending or if the worker_start_foo function
- indicated that we should wait. Put handles we want to
- wait upon into HDARRAY. */
- if (item->inactive)
- addit = 0;
- else if (item->io_pending)
- addit = 1;
- else if (item->writing)
- addit = worker_start_write (item);
- else
- addit = worker_start_read (item);
-
- if (addit)
- {
- hdarray[hdarraylen++] = item->hd;
- item->waiting = 1; /* Only required for debugging. */
- }
-
- /* Set a flag if this work item is ready or got an error. */
- if (!item->delayed_ready && (item->got_error || item->got_ready))
- any_ready = 1;
- }
- LeaveCriticalSection (&work_queue_lock);
-
- /*
- Step 2: Wait for events or handle activitity.
- */
- if (any_ready)
- {
- /* There is at least one work item which is ready or got an
- error. Skip the wait step so that we process it
- immediately. */
- if (debug_ioworker_extra)
- log_debug ("%s:%s: step 2 - %d items in queue; skipping wait",
- SRCNAME, __func__, count);
- }
- else
- {
- if (debug_ioworker_extra)
- {
- log_debug ("%s:%s: step 2 - "
- "%d items in queue; waiting for %d items:",
- SRCNAME, __func__, count, hdarraylen-1);
- for (item = work_queue; item; item = item->next)
- {
- if (item->waiting)
- log_debug ("%s:%s: [%s:%p]",
- SRCNAME, __func__, item->name, item->hd);
- }
- }
-
- /* First process any window messages of this thread. Do
- this before wating so that the message queue is cleared
- before waiting and we don't get stucked due to messages
- not removed. We need to process the message queue also
- after the wait because we will only get to here if there
- is actual ui-server work to be done but some messages
- might still be in the queue. */
- if (msgwait)
- {
- MSG msg;
-
- while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
- {
- TranslateMessage (&msg);
- DispatchMessage (&msg);
- }
- n = MsgWaitForMultipleObjects (hdarraylen, hdarray, FALSE,
- INFINITE, QS_ALLEVENTS);
- }
- else
- {
- n = WaitForMultipleObjects (hdarraylen, hdarray, FALSE,
- INFINITE);
- }
- if (n == WAIT_FAILED)
- {
- /* The WFMO failed. This is an error; to help debugging
- we now print information about all the handles we
- wanted to wait upon. */
- int i;
- DWORD hdinfo;
-
- log_error_w32 (-1, "%s:%s: WFMO failed", SRCNAME, __func__);
- for (i=0; i < hdarraylen; i++)
- {
- hdinfo = 0;
- if (!GetHandleInformation (hdarray[i], &hdinfo))
- log_debug_w32 (-1, "%s:%s: WFMO GetHandleInfo(%p) failed",
- SRCNAME, __func__, hdarray[i]);
- else
- log_debug ("%s:%s: WFMO GetHandleInfo(%p)=0x%lu",
- SRCNAME, __func__, hdarray[i],
- (unsigned long)hdinfo);
- }
- Sleep (1000);
- }
- else if (n >= 0 && n < hdarraylen)
- {
- if (debug_ioworker_extra)
- log_debug ("%s:%s: WFMO succeeded (res=%d, hd=%p)",
- SRCNAME, __func__, n, hdarray[n]);
- }
- else if (n == hdarraylen)
- {
- if (debug_ioworker_extra)
- log_debug ("%s:%s: WFMO succeeded (res=%d, msgevent)",
- SRCNAME, __func__, n);
- }
- else
- {
- log_error ("%s:%s: WFMO returned: %d", SRCNAME, __func__, n);
- Sleep (1000);
- }
-
- if (msgwait)
- {
- MSG msg;
-
- while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
- {
- TranslateMessage (&msg);
- DispatchMessage (&msg);
- }
- }
- }
-
- /*
- Step 3: Handle I/O completion status.
- */
- EnterCriticalSection (&work_queue_lock);
- if (debug_ioworker_extra)
- log_debug ("%s:%s: step 3 - checking completion states",
- SRCNAME, __func__);
- for (item = work_queue; item; item = item->next)
- {
- if (!item->io_pending || item->inactive)
- {
- /* No I/O is pending for that item, thus there is no
- need to check a completion status. */
- }
- else if (GetOverlappedResult (item->hd, &item->ov, &nbytes, FALSE))
- {
- /* An overlapped I/O result is available. Check that
- the returned number of bytes are plausible and clear
- the I/O pending flag of this work item. For a read
- item worker_check_read forwards the received data to
- the caller. */
- if (item->writing)
- worker_check_write (item, nbytes);
- else
- worker_check_read (item, nbytes);
- item->io_pending = 0;
- }
- else
- {
- /* Some kind of error occured: Set appropriate
- flags. */
- int syserr = GetLastError ();
- if (syserr == ERROR_IO_INCOMPLETE)
- {
- /* This is a common case, the I/O has not yet
- completed for this work item. No need for any
- action. */
- }
- else if (!item->writing && (syserr == ERROR_HANDLE_EOF
- || syserr == ERROR_BROKEN_PIPE) )
- {
- /* Got EOF. */
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] EOF%s received",
- SRCNAME, __func__, item->name, item->hd,
- syserr==ERROR_BROKEN_PIPE?" (broken pipe)":"");
- item->io_pending = 0;
- item->got_ready = 1;
- }
- else
- {
- /* Something went wrong. We better cancel the I/O. */
- log_error_w32 (syserr,
- "%s:%s: [%s:%p] GetOverlappedResult failed",
- SRCNAME, __func__, item->name, item->hd);
- item->io_pending = 0;
- item->got_error = 1;
- if (!item->aborting)
- {
- item->aborting = 1;
- if (!CancelIo (item->hd))
- log_error_w32 (-1, "%s:%s: [%s:%p] CancelIo failed",
- SRCNAME,__func__, item->name, item->hd);
- }
- else
- item->got_ready = 1;
- }
- }
- }
- LeaveCriticalSection (&work_queue_lock);
-
- /*
- Step 4: Act on the flags set in step 3.
- */
-
- /* Give the system a chance to process cancel I/O etc. */
- SwitchToThread ();
-
- EnterCriticalSection (&work_queue_lock);
- if (debug_ioworker_extra)
- log_debug ("%s:%s: step 4 - cleaning up work queue",
- SRCNAME, __func__);
- for (item = work_queue; item; item = item->next)
- {
- if (item->used && (item->got_ready || item->got_error))
- {
- /* This is a work item either flagged as ready or as in
- error state. */
-
- if (item->cld)
- {
- /* Perform the closure. */
- work_item_t itm2;
-
- /* If the Assuan state did not return an ERR but the
- I/O machinery set this work item int the error
- state, we set the final error to reflect this. */
- if (!item->cld->final_err && item->got_error)
- item->cld->final_err = gpg_error (GPG_ERR_EIO);
-
- /* Check whether there are other work items in this
- group we need to wait for before invoking the
- closure. */
- for (itm2=work_queue; itm2; itm2 = itm2->next)
- if (itm2->used && itm2 != item
- && itm2->cmdid == item->cmdid
- && itm2->wait_on_success
- && !(itm2->got_ready || itm2->got_error))
- break;
- if (itm2)
- {
- /* We need to delay the closure until all work
- items we depend on are ready. */
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] delaying closure "
- "due to [%s:%p]", SRCNAME, __func__,
- item->name, item->hd,
- itm2->name, itm2->hd);
- item->delayed_ready = 1;
- if (item->cld->final_err)
- {
- /* However, if we received an error we better
- do not assume that the server has properly
- closed all I/O channels. Send a cancel
- to the work item we are waiting for. */
- if (!itm2->aborting)
- {
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] calling CancelIO",
- SRCNAME, __func__,
- itm2->name, itm2->hd);
- itm2->aborting = 1;
- if (!CancelIo (itm2->hd))
- log_error_w32 (-1, "%s:%s: [%s:%p] "
- "CancelIo failed",
- SRCNAME,__func__,
- itm2->name, itm2->hd);
- }
- else
- {
- /* Ooops second time here: Clear the
- wait flag so that we won't get to
- here anymore. */
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] clearing "
- "wait on success flag",
- SRCNAME, __func__,
- itm2->name, itm2->hd);
- itm2->wait_on_success = 0;
- }
- }
- goto step4_cont;
- }
-
- item->delayed_ready = 0;
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] invoking closure",
- SRCNAME,__func__, item->name, item->hd);
-
- item->cld->closure (item->cld);
- xfree (item->cld);
- item->cld = NULL;
- } /* End closure processing. */
-
- item->got_ready = 0;
- item->finalize (item);
- item->used = 0;
- step4_cont:
- ;
- }
- }
-
- LeaveCriticalSection (&work_queue_lock);
- }
- return 0;
-}
-
-
-void
-engine_assuan_cancel (void *cancel_data)
-{
- (void)cancel_data;
- /* FIXME */
-}
-
-
-
-
-/* Standard finalize handler. Called right before the item is removed
- from the queue. Called while the work_queue_lock is hold. */
-static void
-finalize_handler (work_item_t item)
-{
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] closing handle",
- SRCNAME, __func__, item->name, item->hd);
- CloseHandle (item->hd);
- item->hd = INVALID_HANDLE_VALUE;
-}
-
-/* A finalize handler which does not close the handle. */
-static void
-noclose_finalize_handler (work_item_t item)
-{
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] called",
- SRCNAME, __func__, item->name, item->hd);
- item->hd = INVALID_HANDLE_VALUE;
-}
-
-
-/* Add a data callback and a handle to the work queue. This should
- only be called once per handle. Caller gives up ownership of
- CLD. */
-static void
-enqueue_callback (const char *name, assuan_context_t ctx,
- gpgme_data_t data, HANDLE hd,
- int for_write, void (*fin_handler)(work_item_t),
- ULONG cmdid, closure_data_t cld,
- int wait_on_success, int inactive)
-{
- work_item_t item;
- int created = 0;
-
- (void)ctx;
-
- EnterCriticalSection (&work_queue_lock);
- for (item = work_queue; item; item = item->next)
- if (!item->used)
- break;
- if (!item)
- {
- item = xmalloc (sizeof *item);
- item->next = work_queue;
- work_queue = item;
- created = 1;
- }
- item->used = 1;
- item->name = name;
- item->cmdid = cmdid;
- item->cld = cld;
- item->wait_on_success = wait_on_success;
- item->data = data;
- item->writing = for_write;
- item->hd = hd;
- item->inactive = inactive;
- item->io_pending = 0;
- item->got_ready = 0;
- item->delayed_ready = 0;
- item->got_error = 0;
- item->aborting = 0;
- item->finalize = fin_handler;
- memset (&item->ov, 0, sizeof item->ov);
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] created%s",
- SRCNAME, __func__, item->name, item->hd,
- created?"":" (reusing)");
- LeaveCriticalSection (&work_queue_lock);
-}
-
-
-/* Set all items of CMDID into the active state. */
-static void
-set_items_active (ULONG cmdid)
-{
- work_item_t item;
-
- EnterCriticalSection (&work_queue_lock);
- for (item = work_queue; item; item = item->next)
- if (item->used && item->cmdid == cmdid)
- item->inactive = 0;
- LeaveCriticalSection (&work_queue_lock);
-}
-
-
-static void
-destroy_command (ULONG cmdid, int force)
-{
- work_item_t item;
-
- EnterCriticalSection (&work_queue_lock);
- for (item = work_queue; item; item = item->next)
- if (item->used && item->cmdid == cmdid
- && (!item->wait_on_success || force))
- {
- if (debug_ioworker)
- log_debug ("%s:%s: [%s:%p] cmdid=%lu registered for destroy",
- SRCNAME, __func__, item->name, item->hd, item->cmdid);
- /* First send an I/O cancel in case the last
- GetOverlappedResult returned only a partial result. This
- works because we are always running within the
- async_worker_thread. */
-/* if (!CancelIo (item->hd)) */
-/* log_error_w32 (-1, "%s:%s: [%s:%p] CancelIo failed", */
-/* SRCNAME, __func__, item->name, item->hd); */
- item->got_ready = 1;
- }
- LeaveCriticalSection (&work_queue_lock);
-}
-
-
-/* Process a status line. */
-static int
-status_handler (closure_data_t cld, const char *line)
-{
- gpg_error_t err;
- int retval = 0;
-
- if (debug_ioworker)
- log_debug ("%s:%s: cld %p, line `%s'", SRCNAME, __func__, cld, line);
-
- if (*line == '#' || !*line)
- ;
- else if (line[0] == 'D' && line[1] == ' ')
- {
- line += 2;
- }
- else if (line[0] == 'S' && (!line[1] || line[1] == ' '))
- {
- for (line += 1; *line == ' '; line++)
- ;
- }
- else if (line[0] == 'O' && line[1] == 'K' && (!line[2] || line[2] == ' '))
- {
- for (line += 2; *line == ' '; line++)
- ;
- cld->final_err = 0;
- retval = 1;
- }
- else if (!strncmp (line, "ERR", 3) && (!line[3] || line[3] == ' '))
- {
- for (line += 3; *line == ' '; line++)
- ;
- err = strtoul (line, NULL, 10);
- if (!err)
- err = gpg_error (GPG_ERR_ASS_INV_RESPONSE);
- cld->final_err = err;
- retval = 1;
- }
- else if (!strncmp (line, "INQUIRE", 7) && (!line[7] || line[7] == ' '))
- {
- for (line += 7; *line == ' '; line++)
- ;
- /* We have no inquire handler thus get out of it immediately. */
- err = assuan_write_line (cld->assctx, "END");
- if (err)
- cld->last_err = err;
- }
- else if (!strncmp (line, "END", 3) && (!line[3] || line[3] == ' '))
- {
- for (line += 3; *line == ' '; line++)
- ;
- }
- else
- retval = -1; /* Invalid response. */
-
- return retval;
-}
-
-
-/* This write callback is used by GPGME to push data to our status
- line handler. The function should return the number of bytes
- written, and -1 on error. If an error occurs, ERRNO should be set
- to describe the type of the error. */
-static ssize_t
-status_in_cb (void *opaque, const void *buffer, size_t size)
-{
- size_t orig_size = size;
- closure_data_t cld = opaque;
- status_buffer_t sb;
- size_t nleft, nbytes;
- char *p;
-
- assert (cld);
- if (!size)
- return 0;
-
- if (!(sb=cld->status_buffer))
- {
- cld->status_buffer = sb = xmalloc (sizeof *cld->status_buffer);
- sb->eof = 0;
- sb->linelen = 0;
- }
-
- do
- {
- assert (sb->linelen < ASSUAN_LINELENGTH);
- nleft = ASSUAN_LINELENGTH - sb->linelen;
- nbytes = size < nleft? size : nleft;
- memcpy (sb->line+sb->linelen, buffer, nbytes);
- sb->linelen += nbytes;
- size -= nbytes;
- while ((p = memchr (sb->line, '\n', sb->linelen)) && !cld->status_ready)
- {
- *p = 0;
- if (p > sb->line && p[-1] == '\r')
- p[-1] = 0;
- switch (status_handler (cld, sb->line))
- {
- case 0:
- break;
- case 1: /* Ready. */
- cld->status_ready = 1;
- destroy_command (cld->cmdid, 0);
- break;
- default:
- log_error ("%s:%s: invalid line from server", SRCNAME, __func__);
- errno = EINVAL;
- return -1;
- }
- sb->linelen -= (p+1 - sb->line);
- memmove (sb->line, p+1, sb->linelen);
- }
- if (sb->linelen >= ASSUAN_LINELENGTH)
- {
- log_error ("%s:%s: line from server too long", SRCNAME, __func__);
- errno = ERANGE;
- return -1;
- }
- }
- while (size);
-
- return orig_size;
-}
-
-
-
-/* Start an asynchronous command. Caller gives up ownership of
- CLD. */
-static gpg_error_t
-start_command (assuan_context_t ctx, closure_data_t cld,
- ULONG cmdid, const char *line)
-{
- gpg_error_t err;
- assuan_fd_t fds[5];
- int nfds;
-
- if (debug_ioworker)
- log_debug ("%s:%s: sending `%s'", SRCNAME, __func__, line);
-
- /* Get the fd used by assuan for status channel reads. This is the
- first fd returned by assuan_get_active_fds for read fds. */
- nfds = assuan_get_active_fds (ctx, 0, fds, DIM (fds));
- if (nfds < 1)
- return gpg_error (GPG_ERR_GENERAL); /* Ooops. */
-
- cld->cmdid = cmdid;
- cld->status_cbs.write = (gpgme_data_write_cb_t) status_in_cb;
- cld->assctx = ctx;
- /* Fixme: We might want to have reference counting for CLD to cope
- with the problem that the gpgme data object uses CLD which might
- get invalidated at any time. */
- err = gpgme_data_new_from_cbs (&cld->status_data, &cld->status_cbs, cld);
- if (err)
- {
- xfree (cld);
- return err;
- }
-
- set_items_active (cmdid);
- enqueue_callback ("status", ctx, cld->status_data, fds[0], 0,
- noclose_finalize_handler, cmdid, cld, 0, 0);
- cld = NULL; /* Now belongs to the status work item. */
-
- /* Process the work queue. */
- if (!SetEvent (work_queue_event))
- log_error_w32 (-1, "%s:%s: SetEvent failed", SRCNAME, __func__);
- /* Send the command. */
- return assuan_write_line (ctx, line);
-}
-
-
-static const char *
-get_protocol_name (protocol_t protocol)
-{
- switch (protocol)
- {
- case PROTOCOL_OPENPGP: return "OpenPGP"; break;
- case PROTOCOL_SMIME: return "CMS"; break;
- default: return NULL;
- }
-}
-
-
-/* Callback used to get the protocool status line form a PREP_ENCRYPT
- or SENDER command. */
-static gpg_error_t
-prep_foo_status_cb (void *opaque, const char *line)
-{
- protocol_t *protocol = opaque;
-
- if (!strncmp (line, "PROTOCOL", 8) && (line[8]==' ' || !line[8]))
- {
- for (line += 8; *line == ' '; line++)
- ;
- if (!strncmp (line, "OpenPGP", 7) && (line[7]==' '||!line[7]))
- *protocol = PROTOCOL_OPENPGP;
- else if (!strncmp (line, "CMS", 3) && (line[3]==' '||!line[3]))
- *protocol = PROTOCOL_SMIME;
- }
- return 0;
-}
-
-
-
-
-/* Note that this closure is called in the context of the
- async_worker_thread. */
-static void
-encrypt_closure (closure_data_t cld)
-{
- engine_private_finished (cld->filter, cld->final_err);
-}
-
-
-/* Encrypt the data from INDATA to the OUTDATA object for all
- recpients given in the NULL terminated array RECIPIENTS. This
- function terminates with success and then expects the caller to
- wait for the result of the encryption using engine_wait. FILTER is
- used for asynchronous commnication with the engine module. HWND is
- the window handle of the current window and used to maintain the
- correct relationship between a popups and the active window. If
- this function returns success, the data objects may only be
- destroyed after an engine_wait or engine_cancel. On success the
- function returns a poiunter to the encryption state and thus
- requires that op_assuan_encrypt_bottom will be run later.
- SENDER is the sender's mailbox or NULL; this information may be
- used by the UI-server for role selection. */
-int
-op_assuan_encrypt (protocol_t protocol,
- gpgme_data_t indata, gpgme_data_t outdata,
- engine_filter_t filter, void *hwnd,
- unsigned int flags,
- const char *sender, char **recipients,
- protocol_t *r_used_protocol,
- struct engine_assuan_encstate_s **r_encstate)
-{
- gpg_error_t err;
- closure_data_t cld;
- assuan_context_t ctx;
- char line[1024];
- HANDLE inpipe[2], outpipe[2];
- ULONG cmdid;
- pid_t pid;
- int i;
- char *p;
- int detect_protocol;
- const char *protocol_name;
- struct engine_assuan_encstate_s *encstate;
-
- *r_encstate = NULL;
-
- detect_protocol = !(protocol_name = get_protocol_name (protocol));
-
- TRACEPOINT;
- err = connect_uiserver (&ctx, &pid, &cmdid, hwnd);
- TRACEPOINT;
- if (err)
- return err;
-
- TRACEPOINT;
- if ((err = create_io_pipe (inpipe, pid, 1)))
- return err;
- if ((err = create_io_pipe (outpipe, pid, 0)))
- {
- close_pipe (inpipe);
- return err;
- }
- TRACEPOINT;
-
- cld = xcalloc (1, sizeof *cld);
- cld->closure = encrypt_closure;
- cld->filter = filter;
-
- TRACEPOINT;
- err = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- send_session_info (ctx, filter);
- TRACEPOINT;
-
- /* If a sender has been supplied, tell the server about it. We
- don't care about error because servers may not implement SENDER
- in an encryption context. */
- if (sender && *sender)
- {
- snprintf (line, sizeof line, "SENDER --info -- %s", sender);
- assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- }
-
- /* Send the recipients to the server. */
- for (i=0; recipients && recipients[i]; i++)
- {
- snprintf (line, sizeof line, "RECIPIENT %s", recipients[i]);
- for (p=line; *p; p++)
- if (*p == '\n' || *p =='\r' )
- *p = ' ';
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- }
- TRACEPOINT;
-
- /* If the protocol has not been given, let the UI server tell us the
- protocol to use. If we know that we will also sign the message,
- send the prep_encrypt anyway to tell the server about a
- forthcoming sign command. */
- if (detect_protocol)
- {
- protocol = PROTOCOL_UNKNOWN;
- TRACEPOINT;
- err = assuan_transact (ctx,
- (flags & ENGINE_FLAG_SIGN_FOLLOWS)
- ? "PREP_ENCRYPT --expect-sign": "PREP_ENCRYPT",
- NULL, NULL, NULL, NULL,
- prep_foo_status_cb, &protocol);
- TRACEPOINT;
- if (err)
- {
- if (gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
- err = gpg_error (GPG_ERR_INV_VALUE);
- goto leave;
- }
- if ( !(protocol_name = get_protocol_name (protocol)) )
- {
- err = gpg_error (GPG_ERR_INV_VALUE);
- goto leave;
- }
- }
- else
- {
- if ( !protocol_name )
- {
- err = gpg_error (GPG_ERR_INV_VALUE);
- goto leave;
- }
-
- snprintf (line, sizeof line, (flags & ENGINE_FLAG_SIGN_FOLLOWS)
- ? "PREP_ENCRYPT --protocol=%s --expect-sign"
- : "PREP_ENCRYPT --protocol=%s",
- protocol_name);
- TRACEPOINT;
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- TRACEPOINT;
- if (err)
- {
- if (gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
- err = gpg_error (GPG_ERR_INV_VALUE);
- goto leave;
- }
- }
-
- *r_used_protocol = protocol;
-
- /* Note: We don't use real descriptor passing but a hack: We
- duplicate the handle into the server process and the server then
- uses this handle. Eventually we should put this code into
- assuan_sendfd. */
- snprintf (line, sizeof line, "INPUT FD=%d", handle_to_int (inpipe[0]));
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- if (flags & ENGINE_FLAG_BINARY_OUTPUT)
- snprintf (line, sizeof line, "OUTPUT FD=%d --binary",
- handle_to_int (outpipe[1]));
- else
- snprintf (line, sizeof line, "OUTPUT FD=%d", handle_to_int (outpipe[1]));
- TRACEPOINT;
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- TRACEPOINT;
- if (err)
- goto leave;
-
- /* The work items are created as inactive, so that the worker thread
- ignores them. They are set to active by with the start_command
- function called by op_assuan_encrypt_bottom. */
- enqueue_callback (" input", ctx, indata, inpipe[1], 1, finalize_handler,
- cmdid, NULL, 0, 1);
- enqueue_callback ("output", ctx, outdata, outpipe[0], 0, finalize_handler,
- cmdid, NULL, 1 /* Wait on success */, 1);
- TRACEPOINT;
-
- encstate = xcalloc (1, sizeof *encstate);
- encstate->filter = filter;
- encstate->protocol_name = protocol_name;
- encstate->inpipe[0] = inpipe[0];
- encstate->inpipe[1] = inpipe[1];
- encstate->outpipe[0] = outpipe[0];
- encstate->outpipe[1] = outpipe[1];
- encstate->cld = cld;
- encstate->ctx = ctx;
- encstate->cmdid = cmdid;
- *r_encstate = encstate;
- return 0;
-
- leave:
- if (err)
- {
- /* Fixme: Cancel stuff in the work_queue. */
- close_pipe (inpipe);
- close_pipe (outpipe);
- xfree (cld);
- assuan_release (ctx);
- }
- else
- engine_private_set_cancel (filter, ctx);
- return err;
-}
-
-/* Continue and actually start the encryption or cancel it with CANCEL
- set to TRUE. The fucntion takes ownvership of ENCSTATE. */
-int
-op_assuan_encrypt_bottom (struct engine_assuan_encstate_s *encstate,
- int cancel)
-{
- char line[1024];
- gpg_error_t err;
-
- if (!encstate)
- return 0;
- if (cancel)
- err = gpg_error (GPG_ERR_CANCELED);
- else
- {
- snprintf (line, sizeof line, "ENCRYPT --protocol=%s",
- encstate->protocol_name);
- TRACEPOINT;
- err = start_command (encstate->ctx, encstate->cld,
- encstate->cmdid, line);
- TRACEPOINT;
- encstate->cld = NULL; /* Now owned by start_command. */
- }
-
- if (err)
- {
- xfree (encstate->cld);
- encstate->cld = NULL;
- engine_private_set_cancel (encstate->filter, NULL);
- close_pipe (encstate->inpipe);
- close_pipe (encstate->outpipe);
- if (cancel)
- destroy_command (encstate->cmdid, 1);
- assuan_release (encstate->ctx);
- encstate->ctx = NULL;
- }
- else
- engine_private_set_cancel (encstate->filter, encstate->ctx);
- xfree (encstate);
- TRACEPOINT;
- return err;
-}
-
-
-
-
-/* Note that this closure is called in the context of the
- async_worker_thread. */
-static void
-sign_closure (closure_data_t cld)
-{
- engine_private_finished (cld->filter, cld->final_err);
-}
-
-
-/* Created a detached signature for INDATA and write it to OUTDATA.
- On termination of the signing command engine_private_finished() is
- called with FILTER as the first argument. SENDER is the sender's
- mail address (a mailbox). The used protocol will be stored at
- R_USED_PROTOCOL on return. */
-int
-op_assuan_sign (protocol_t protocol,
- gpgme_data_t indata, gpgme_data_t outdata,
- engine_filter_t filter, void *hwnd,
- const char *sender, protocol_t *r_used_protocol,
- int flags)
-{
- gpg_error_t err;
- closure_data_t cld;
- assuan_context_t ctx;
- char line[1024];
- HANDLE inpipe[2], outpipe[2];
- ULONG cmdid;
- pid_t pid;
- int detect_protocol;
- const char *protocol_name;
- protocol_t suggested_protocol;
-
- detect_protocol = !(protocol_name = get_protocol_name (protocol));
-
- err = connect_uiserver (&ctx, &pid, &cmdid, hwnd);
- if (err)
- return err;
-
- if ((err = create_io_pipe (inpipe, pid, 1)))
- return err;
- if ((err = create_io_pipe (outpipe, pid, 0)))
- {
- close_pipe (inpipe);
- return err;
- }
-
- cld = xcalloc (1, sizeof *cld);
- cld->closure = sign_closure;
- cld->filter = filter;
-
- err = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
-
- send_session_info (ctx, filter);
-
- /* We always send the SENDER command because it allows us to figure
- out the protocol to use. In case the UI server fails to send the
- protocol we fall back to OpenPGP. The --protocol option is used
- to given the server a hint on what protocol we would prefer. */
- suggested_protocol = PROTOCOL_UNKNOWN;
- if (!sender)
- sender = "";
- snprintf (line, sizeof line, "SENDER%s%s -- %s",
- protocol_name? " --protocol=":"",
- protocol_name? protocol_name:"",
- sender? sender:"");
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL,
- prep_foo_status_cb, &suggested_protocol);
- if (err)
- {
- if (gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
- err = gpg_error (GPG_ERR_INV_VALUE);
- goto leave;
- }
- if (detect_protocol)
- {
- log_debug ("%s:%s: suggested protocol is %d",
- SRCNAME, __func__, suggested_protocol);
- protocol = (suggested_protocol == PROTOCOL_UNKNOWN?
- PROTOCOL_OPENPGP : suggested_protocol);
- if ( !(protocol_name = get_protocol_name (protocol)) )
- {
- err = gpg_error (GPG_ERR_INV_VALUE);
- goto leave;
- }
- }
- *r_used_protocol = protocol;
- log_debug ("%s:%s: using protocol %s", SRCNAME, __func__, protocol_name);
-
- snprintf (line, sizeof line, "INPUT FD=%d", handle_to_int (inpipe[0]));
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- snprintf (line, sizeof line, "OUTPUT FD=%d", handle_to_int (outpipe[1]));
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
-
- enqueue_callback (" input", ctx, indata, inpipe[1], 1, finalize_handler,
- cmdid, NULL, 0, 0);
- enqueue_callback ("output", ctx, outdata, outpipe[0], 0, finalize_handler,
- cmdid, NULL, 1 /* Wait on success */, 0);
-
- if (flags & ENGINE_FLAG_DETACHED)
- snprintf (line, sizeof line, "SIGN --protocol=%s --detached",
- protocol_name);
- else
- snprintf (line, sizeof line, "SIGN --protocol=%s", protocol_name);
- err = start_command (ctx, cld, cmdid, line);
- cld = NULL; /* Now owned by start_command. */
- if (err)
- goto leave;
-
-
- leave:
- if (err)
- {
- /* Fixme: Cancel stuff in the work_queue. */
- close_pipe (inpipe);
- close_pipe (outpipe);
- xfree (cld);
- assuan_release (ctx);
- }
- else
- engine_private_set_cancel (filter, ctx);
- return err;
-}
-
-
-
-
-/* Note that this closure is called in the context of the
- async_worker_thread. */
-static void
-decrypt_closure (closure_data_t cld)
-{
- engine_private_finished (cld->filter, cld->final_err);
-}
-
-
-/* Decrypt data from INDATA to OUTDATE. If WITH_VERIFY is set, the
- signature of a PGP/MIME combined message is also verified the same
- way as with op_assuan_verify. */
-int
-op_assuan_decrypt (protocol_t protocol,
- gpgme_data_t indata, gpgme_data_t outdata,
- engine_filter_t filter, void *hwnd,
- int with_verify, const char *from_address)
-{
- gpg_error_t err;
- closure_data_t cld;
- assuan_context_t ctx;
- char line[1024];
- HANDLE inpipe[2], outpipe[2];
- ULONG cmdid;
- pid_t pid;
- const char *protocol_name;
-
- if (!(protocol_name = get_protocol_name (protocol)))
- return gpg_error(GPG_ERR_INV_VALUE);
-
- err = connect_uiserver (&ctx, &pid, &cmdid, hwnd);
- if (err)
- return err;
-
- if ((err = create_io_pipe (inpipe, pid, 1)))
- return err;
- if ((err = create_io_pipe (outpipe, pid, 0)))
- {
- close_pipe (inpipe);
- return err;
- }
-
- cld = xcalloc (1, sizeof *cld);
- cld->closure = decrypt_closure;
- cld->filter = filter;
-
- err = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
-
- send_session_info (ctx, filter);
- if (with_verify && from_address)
- {
- snprintf (line, sizeof line, "SENDER --info -- %s", from_address);
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- }
-
- snprintf (line, sizeof line, "INPUT FD=%d", handle_to_int (inpipe[0]));
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- snprintf (line, sizeof line, "OUTPUT FD=%d", handle_to_int (outpipe[1]));
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
-
- enqueue_callback (" input", ctx, indata, inpipe[1], 1, finalize_handler,
- cmdid, NULL, 0, 0);
- enqueue_callback ("output", ctx, outdata, outpipe[0], 0, finalize_handler,
- cmdid, NULL, 1 /* Wait on success */, 0);
-
- snprintf (line, sizeof line, "DECRYPT --protocol=%s%s",
- protocol_name, with_verify? "":" --no-verify");
- err = start_command (ctx, cld, cmdid, line);
- cld = NULL; /* Now owned by start_command. */
- if (err)
- goto leave;
-
-
- leave:
- if (err)
- {
- /* Fixme: Cancel stuff in the work_queue. */
- close_pipe (inpipe);
- close_pipe (outpipe);
- xfree (cld);
- assuan_release (ctx);
- }
- else
- engine_private_set_cancel (filter, ctx);
- return err;
-}
-
-
-
-/* Note that this closure is called in the context of the
- async_worker_thread. */
-static void
-verify_closure (closure_data_t cld)
-{
- gpgme_data_release (cld->sigdata);
- cld->sigdata = NULL;
- engine_private_finished (cld->filter, cld->final_err);
-}
-
-
-/* With MSGDATA, SIGNATURE and SIGLEN given:
-
- Verify a detached message where the data is in the gpgme object
- MSGDATA and the signature given as the string SIGNATURE.
-
- With MSGDATA and OUTDATA given:
-
- Verify an opaque signature from MSGDATA and write the decoded
- plaintext to OUTDATA.
-
-*/
-int
-op_assuan_verify (gpgme_protocol_t protocol,
- gpgme_data_t msgdata, const char *signature, size_t sig_len,
- gpgme_data_t outdata,
- engine_filter_t filter, void *hwnd, const char *from_address)
-{
- gpg_error_t err;
- closure_data_t cld = NULL;
- assuan_context_t ctx;
- char line[1024];
- HANDLE msgpipe[2], sigpipe[2], outpipe[2];
- ULONG cmdid;
- pid_t pid;
- gpgme_data_t sigdata = NULL;
- const char *protocol_name;
- int opaque_mode;
-
- msgpipe[0] = INVALID_HANDLE_VALUE;
- msgpipe[1] = INVALID_HANDLE_VALUE;
- sigpipe[0] = INVALID_HANDLE_VALUE;
- sigpipe[1] = INVALID_HANDLE_VALUE;
- outpipe[0] = INVALID_HANDLE_VALUE;
- outpipe[1] = INVALID_HANDLE_VALUE;
-
- if (!(protocol_name = get_protocol_name (protocol)))
- return gpg_error(GPG_ERR_INV_VALUE);
-
- if (signature && sig_len && !outdata)
- opaque_mode = 0;
- else if (!signature && !sig_len && outdata)
- opaque_mode = 1;
- else
- return gpg_error(GPG_ERR_INV_VALUE);
-
- if (!opaque_mode)
- {
- err = gpgme_data_new_from_mem (&sigdata, signature, sig_len, 0);
- if (err)
- goto leave;
- }
-
- err = connect_uiserver (&ctx, &pid, &cmdid, hwnd);
- if (err)
- goto leave;
-
- if (!opaque_mode)
- {
- if ((err = create_io_pipe (msgpipe, pid, 1)))
- goto leave;
- if ((err = create_io_pipe (sigpipe, pid, 1)))
- goto leave;
- }
- else
- {
- if ((err = create_io_pipe (msgpipe, pid, 1)))
- goto leave;
- if ((err = create_io_pipe (outpipe, pid, 0)))
- goto leave;
- }
-
- cld = xcalloc (1, sizeof *cld);
- cld->closure = verify_closure;
- cld->filter = filter;
- cld->sigdata = sigdata;
-
- err = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
-
- send_session_info (ctx, filter);
- if (from_address)
- {
- snprintf (line, sizeof line, "SENDER --info -- %s", from_address);
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- }
-
- if (!opaque_mode)
- {
- snprintf (line, sizeof line, "MESSAGE FD=%d",
- handle_to_int (msgpipe[0]));
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- snprintf (line, sizeof line, "INPUT FD=%d", handle_to_int (sigpipe[0]));
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- enqueue_callback (" msg", ctx, msgdata, msgpipe[1], 1,
- finalize_handler, cmdid, NULL, 0, 0);
- enqueue_callback (" sig", ctx, sigdata, sigpipe[1], 1,
- finalize_handler, cmdid, NULL, 0, 0);
- }
- else
- {
- snprintf (line, sizeof line, "INPUT FD=%d", handle_to_int (msgpipe[0]));
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- snprintf (line, sizeof line, "OUTPUT FD=%d", handle_to_int (outpipe[1]));
- err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- goto leave;
- enqueue_callback (" msg", ctx, msgdata, msgpipe[1], 1,
- finalize_handler, cmdid, NULL, 0, 0);
- enqueue_callback (" out", ctx, outdata, outpipe[0], 0,
- finalize_handler, cmdid, NULL, 1, 0);
- }
-
- snprintf (line, sizeof line, "VERIFY --protocol=%s", protocol_name);
- err = start_command (ctx, cld, cmdid, line);
- cld = NULL; /* Now owned by start_command. */
- sigdata = NULL; /* Ditto. */
- if (err)
- goto leave;
-
-
- leave:
- if (err)
- {
- /* Fixme: Cancel stuff in the work_queue. */
- close_pipe (msgpipe);
- close_pipe (sigpipe);
- close_pipe (outpipe);
- gpgme_data_release (sigdata);
- xfree (cld);
- assuan_release (ctx);
- }
- else
- engine_private_set_cancel (filter, ctx);
- return err;
-}
-
-
-
-/* Ask the server to fire up the key manager. */
-int
-op_assuan_start_keymanager (void *hwnd)
-{
- gpg_error_t err;
- assuan_context_t ctx;
- ULONG cmdid;
- pid_t pid;
-
- err = connect_uiserver (&ctx, &pid, &cmdid, hwnd);
- if (!err)
- {
- err = assuan_transact (ctx, "START_KEYMANAGER",
- NULL, NULL, NULL, NULL, NULL, NULL);
- assuan_release (ctx);
- }
- return err;
-}
-
-
-
-/* Ask the server to fire up the config dialog. */
-int
-op_assuan_start_confdialog (void *hwnd)
-{
- gpg_error_t err;
- assuan_context_t ctx;
- ULONG cmdid;
- pid_t pid;
-
- err = connect_uiserver (&ctx, &pid, &cmdid, hwnd);
- if (!err)
- {
- err = assuan_transact (ctx, "START_CONFDIALOG",
- NULL, NULL, NULL, NULL, NULL, NULL);
- assuan_release (ctx);
- }
- return err;
-}
-
-/* Send a decrypt files command to the server.
- Filenames should be a null terminated array of char*
-*/
-int
-op_assuan_start_decrypt_files (void *hwnd, char **filenames)
-{
- gpg_error_t err;
- assuan_context_t ctx;
- ULONG cmdid;
- pid_t pid;
- char line[1024];
-
- err = connect_uiserver (&ctx, &pid, &cmdid, hwnd);
- if (!err)
- {
- while (*filenames != NULL)
- {
- snprintf(line, sizeof(line), "FILE %s",
- percent_escape(*filenames, NULL));
- err = assuan_transact (ctx, line,
- NULL, NULL, NULL, NULL, NULL, NULL);
- if (err)
- return err;
- filenames++;
- }
- err = assuan_transact (ctx, "DECRYPT_FILES --nohup",
- NULL, NULL, NULL, NULL, NULL, NULL);
- assuan_release (ctx);
- }
- return err;
-}
diff --git a/src/engine-assuan.h b/src/engine-assuan.h
deleted file mode 100644
index 6785385..0000000
--- a/src/engine-assuan.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/* engine-assuan.h - Assuan server based crypto engine
- * Copyright (C) 2007, 2008 g10 Code GmbH
- *
- * This file is part of GpgOL.
- *
- * GpgOL is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 2.1
- * of the License, or (at your option) any later version.
- *
- * GpgOL is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, see .
- */
-
-#ifndef GPGOL_ENGINE_ASSUAN_H
-#define GPGOL_ENGINE_ASSUAN_H
-
-#ifdef __cplusplus
-extern "C" {
-#if 0
-}
-#endif
-#endif
-
-#include /* We need it for gpgme_data_t. */
-
-#include "engine.h"
-
-struct engine_assuan_encstate_s;
-
-int op_assuan_init (void);
-void op_assuan_deinit (void);
-void engine_assuan_cancel (void *cancel_data);
-
-int op_assuan_encrypt (protocol_t protocol,
- gpgme_data_t indata, gpgme_data_t outdata,
- engine_filter_t notify_data, void *hwnd,
- unsigned int flags,
- const char *sender, char **recipients,
- protocol_t *r_used_protocol,
- struct engine_assuan_encstate_s **r_encstate);
-int op_assuan_encrypt_bottom (struct engine_assuan_encstate_s *encstate,
- int cancel);
-int op_assuan_sign (protocol_t protocol,
- gpgme_data_t indata, gpgme_data_t outdata,
- engine_filter_t filter, void *hwnd,
- const char *sender, protocol_t *r_used_protocol,
- int flags);
-int op_assuan_decrypt (protocol_t protocol,
- gpgme_data_t indata, gpgme_data_t outdata,
- engine_filter_t filter, void *hwnd,
- int with_verify, const char *from_address);
-int op_assuan_verify (gpgme_protocol_t protocol,
- gpgme_data_t data, const char *signature, size_t sig_len,
- gpgme_data_t outdata,
- engine_filter_t filter, void *hwnd,
- const char *from_address);
-
-int op_assuan_start_keymanager (void *hwnd);
-
-int op_assuan_start_confdialog (void *hwnd);
-
-int op_assuan_start_decrypt_files (void *hwnd, char **filenames);
-
-
-#ifdef __cplusplus
-}
-#endif
-#endif /*GPGOL_ENGINE_ASSUAN_H*/
diff --git a/src/engine.c b/src/engine.c
deleted file mode 100644
index 0a111fd..0000000
--- a/src/engine.c
+++ /dev/null
@@ -1,949 +0,0 @@
-/* engine.c - Crypto engine dispatcher
- * Copyright (C) 2007, 2008 g10 Code GmbH
- *
- * This file is part of GpgOL.
- *
- * GpgOL is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 2.1
- * of the License, or (at your option) any later version.
- *
- * GpgOL is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, see .
- */
-
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#define WIN32_LEAN_AND_MEAN
-#include
-
-#include "common.h"
-#include "engine.h"
-#include "engine-assuan.h"
-
-
-#define FILTER_BUFFER_SIZE (128*1024)
-
-
-#define debug_filter (opt.enable_debug & DBG_FILTER)
-#define debug_filter_extra (opt.enable_debug & DBG_FILTER_EXTRA)
-
-/* Definition of the key object. */
-struct engine_keyinfo_s
-{
- struct {
- gpgme_key_t key;
- } gpgme;
-
-};
-
-
-/* Definition of the filter object. This object shall only be
- accessed by one thread. */
-struct engine_filter_s
-{
-
- struct {
- CRITICAL_SECTION lock; /* The lock for the this object. */
- HANDLE condvar; /* Manual reset event signaled if LENGTH > 0. */
- int nonblock; /* Put gpgme data cb in non blocking mode. */
- size_t length; /* Number of bytes in BUFFER waiting to be
- send down the pipe. */
- char buffer[FILTER_BUFFER_SIZE];
- int got_eof; /* End of file has been indicated. */
- /* These objects are only in this structure because we
- use this structure's lock to protect them. */
- int ready; /* Set to true if the gpgme process has finished. */
- HANDLE ready_event; /* And the corresponding event. */
- gpg_error_t status; /* Status of the gpgme process. */
- } in;
-
- struct {
- CRITICAL_SECTION lock; /* The lock for the this object. */
- HANDLE condvar; /* Manual reset event signaled if LENGTH == 0. */
- int nonblock; /* Put gpgme data cb in non blocking mode. */
- size_t length; /* Number of bytes in BUFFER waiting to be
- send back to the caller. */
- char buffer[FILTER_BUFFER_SIZE];
- } out;
-
- /* Flag to push an extra LF out. */
- int add_extra_lf;
-
- /* The data sink as set by engine_create_filter. */
- int (*outfnc) (void *, const void *, size_t);
- void *outfncdata;
-
- /* Objects to be released by engine_wait/cancel. */
- struct gpgme_data_cbs cb_inbound; /* Callback structure for gpgme. */
- struct gpgme_data_cbs cb_outbound; /* Ditto. */
- gpgme_data_t indata; /* Input data. */
- gpgme_data_t outdata; /* Output data. */
- void *cancel_data; /* Used by engine_cancel. */
-
- /* A pointer used convey information from engine_encrypt_prepare to
- engine_encrypt_start. */
- struct engine_assuan_encstate_s *encstate;
-
- /* Optional data to be passed to the backend to help the server
- display a useful title and to associate operations with one OL
- task. */
- unsigned int session_number;
- char *session_title; /* (malloced) */
- char *sender_address; /* (malloced) */
-};
-
-
-static void
-take_in_lock (engine_filter_t filter, const char *func)
-{
- EnterCriticalSection (&filter->in.lock);
- if (debug_filter_extra)
- log_debug ("%s:%s: in.lock taken\n", SRCNAME, func);
-}
-
-static void
-release_in_lock (engine_filter_t filter, const char *func)
-{
- LeaveCriticalSection (&filter->in.lock);
- if (debug_filter_extra)
- log_debug ("%s:%s: in.lock released\n", SRCNAME, func);
-}
-
-static void
-take_out_lock (engine_filter_t filter, const char *func)
-{
- EnterCriticalSection (&filter->out.lock);
- if (debug_filter_extra)
- log_debug ("%s:%s: out.lock taken\n", SRCNAME, func);
-}
-
-static void
-release_out_lock (engine_filter_t filter, const char *func)
-{
- LeaveCriticalSection (&filter->out.lock);
- if (debug_filter_extra)
- log_debug ("%s:%s: out.lock released\n", SRCNAME, func);
-}
-
-
-
-
-
-/* Create a new filter object. */
-static engine_filter_t
-create_filter (void)
-{
- engine_filter_t filter;
-
- filter = xcalloc (1, sizeof *filter);
-
- InitializeCriticalSection (&filter->in.lock);
- filter->in.condvar = CreateEvent (NULL, TRUE, 0, NULL);
- if (!filter->in.condvar)
- log_error_w32 (-1, "%s:%s: create in.condvar failed", SRCNAME, __func__);
-
- InitializeCriticalSection (&filter->out.lock);
- filter->out.condvar = CreateEvent (NULL, TRUE, 0, NULL);
- if (!filter->out.condvar)
- log_error_w32 (-1, "%s:%s: create out.condvar failed", SRCNAME, __func__);
-
- /* Create an automatic event (it only used one time so the type is
- actually not important). */
- filter->in.ready_event = CreateEvent (NULL, 0, 0, NULL);
- if (!filter->in.ready_event)
- log_error_w32 (-1, "%s:%s: CreateEvent failed", SRCNAME, __func__);
-
- /* If we are using the assuan engine we need to make the gpgme read
- callback non blocking. */
- filter->in.nonblock = 1;
-
- return filter;
-}
-
-
-static void
-release_filter (engine_filter_t filter)
-{
- if (filter)
- {
- if (filter->in.condvar)
- CloseHandle (filter->in.condvar);
- if (filter->out.condvar)
- CloseHandle (filter->out.condvar);
- if (filter->in.ready_event)
- CloseHandle (filter->in.ready_event);
- gpgme_data_release (filter->indata);
- gpgme_data_release (filter->outdata);
- xfree (filter->session_title);
- xfree (filter->sender_address);
- xfree (filter);
- }
-}
-
-
-/* This read callback is used by GPGME to read data from a filter
- object. The function should return the number of bytes read, 0 on
- EOF, and -1 on error. If an error occurs, ERRNO should be set to
- describe the type of the error. */
-static ssize_t
-filter_gpgme_read_cb (void *handle, void *buffer, size_t size)
-{
- engine_filter_t filter = handle;
- int nbytes;
-
- if (!filter || !buffer || !size)
- {
- errno = EINVAL;
- return (ssize_t)(-1);
- }
-
- if (debug_filter)
- log_debug ("%s:%s: filter %p: enter\n", SRCNAME, __func__, filter );
- take_in_lock (filter, __func__);
- while (!filter->in.length)
- {
- if (filter->in.got_eof || filter->in.ready)
- {
- release_in_lock (filter, __func__);
- if (debug_filter)
- log_debug ("%s:%s: filter %p: returning EOF\n",
- SRCNAME, __func__, filter );
- return 0; /* Return EOF. */
- }
- release_in_lock (filter, __func__);
- if (filter->in.nonblock)
- {
- errno = EAGAIN;
- if (debug_filter_extra)
- log_debug ("%s:%s: filter %p: leave; result=EAGAIN\n",
- SRCNAME, __func__, filter);
- return -1;
- }
- if (debug_filter)
- log_debug ("%s:%s: filter %p: waiting for in.condvar\n",
- SRCNAME, __func__, filter);
- WaitForSingleObject (filter->in.condvar, 500);
- take_in_lock (filter, __func__);
- if (debug_filter)
- log_debug ("%s:%s: filter %p: continuing\n",
- SRCNAME, __func__, filter);
- }
-
- if (debug_filter)
- log_debug ("%s:%s: filter %p: requested read size=%d (in.length=%d)\n",
- SRCNAME, __func__, filter, (int)size, (int)filter->in.length);
- nbytes = size < filter->in.length ? size : filter->in.length;
- memcpy (buffer, filter->in.buffer, nbytes);
- if (filter->in.length > nbytes)
- memmove (filter->in.buffer, filter->in.buffer + nbytes,
- filter->in.length - nbytes);
- filter->in.length -= nbytes;
- release_in_lock (filter, __func__);
-
- if (debug_filter)
- log_debug ("%s:%s: filter %p: leave; result=%d\n",
- SRCNAME, __func__, filter, (int)nbytes);
-
- return nbytes;
-}
-
-
-/* This write callback is used by GPGME to write data to the filter
- object. The function should return the number of bytes written,
- and -1 on error. If an error occurs, ERRNO should be set to
- describe the type of the error. */
-static ssize_t
-filter_gpgme_write_cb (void *handle, const void *buffer, size_t size)
-{
- engine_filter_t filter = handle;
- int nbytes;
-
- if (!filter || !buffer || !size)
- {
- errno = EINVAL;
- return (ssize_t)(-1);
- }
-
- if (debug_filter)
- log_debug ("%s:%s: enter\n", SRCNAME, __func__);
- take_out_lock (filter, __func__);
- while (filter->out.length)
- {
- release_out_lock (filter, __func__);
- if (filter->out.nonblock)
- {
- errno = EAGAIN;
- if (debug_filter_extra)
- log_debug ("%s:%s: leave; result=EAGAIN\n", SRCNAME, __func__);
- return -1;
- }
- if (debug_filter)
- log_debug ("%s:%s: waiting for out.condvar\n", SRCNAME, __func__);
- WaitForSingleObject (filter->out.condvar, 500);
- take_out_lock (filter, __func__);
- if (debug_filter)
- log_debug ("%s:%s: continuing\n", SRCNAME, __func__);
- }
-
- if (debug_filter)
- log_debug ("%s:%s: requested write size=%d\n",
- SRCNAME, __func__, (int)size);
- nbytes = size < FILTER_BUFFER_SIZE ? size : FILTER_BUFFER_SIZE;
- memcpy (filter->out.buffer, buffer, nbytes);
- filter->out.length = nbytes;
- release_out_lock (filter, __func__);
-
- if (debug_filter)
- log_debug ("%s:%s: leave; result=%d\n", SRCNAME, __func__, (int)nbytes);
- return nbytes;
-}
-
-
-/* Store a cancel parameter into FILTER. Only use by the engine backends. */
-void
-engine_private_set_cancel (engine_filter_t filter, void *cancel_data)
-{
- filter->cancel_data = cancel_data;
-}
-
-
-/* This function is called by the gpgme backend to notify a filter
- object about the final status of an operation. It may only be
- called by the engine-gpgme.c module. */
-void
-engine_private_finished (engine_filter_t filter, gpg_error_t status)
-{
- if (!filter)
- {
- log_debug ("%s:%s: called without argument\n", SRCNAME, __func__);
- return;
- }
- if (debug_filter)
- log_debug ("%s:%s: filter %p: process terminated: %s <%s>\n",
- SRCNAME, __func__, filter,
- gpg_strerror (status), gpg_strsource (status));
-
- take_in_lock (filter, __func__);
- filter->in.ready = 1;
- filter->in.status = status;
- filter->cancel_data = NULL;
- if (!SetEvent (filter->in.ready_event))
- log_error_w32 (-1, "%s:%s: SetEvent failed", SRCNAME, __func__);
- release_in_lock (filter, __func__);
- if (debug_filter)
- log_debug ("%s:%s: leaving\n", SRCNAME, __func__);
-}
-
-
-unsigned int
-engine_private_get_session_number (engine_filter_t filter)
-{
- return filter? filter->session_number : 0;
-}
-
-const char *
-engine_private_get_session_title (engine_filter_t filter)
-{
- return filter? filter->session_title : NULL;
-}
-
-
-
-/* Initialize the engine dispatcher. */
-int
-engine_init (void)
-{
- gpg_error_t err;
-
- do
- err = op_assuan_init ();
- while (err
- && MessageBox (NULL,
- _("The user interface server is not available "
- "or could not be started in time. You may "
- "want to try again."),
- _("GpgOL"),
- MB_ICONQUESTION|MB_RETRYCANCEL) == IDRETRY);
- return err;
-}
-
-
-/* Shutdown the engine dispatcher. */
-void
-engine_deinit (void)
-{
- op_assuan_deinit ();
-}
-
-/* Helper function to return a new session number. */
-unsigned int
-engine_new_session_number (void)
-{
- static ULONG number;
- ULONG n;
-
- /* The protocol says the session number is a 32 bit value, thus we
- make that sure even in the case that ULONG is changed to a 64 bit
- type (Not an issue under Windows, though). */
- while (!((n = InterlockedIncrement (&number))&0xffffffff) )
- ;
- return n;
-}
-
-
-
-/* Filter the INDATA of length INDATA and write the output using
- OUTFNC. OUTFNCDATA is passed as first argument to OUTFNC, followed
- by the data to be written and its length. FILTER is an object
- returned for example by engine_encrypt_start. The function returns
- 0 on success or an error code on error.
-
- Passing INDATA as NULL and INDATALEN as 0, the filter will be
- flushed, that is all remaining stuff will be written to OUTFNC.
- This indicates EOF and the filter won't accept anymore input. */
-int
-engine_filter (engine_filter_t filter, const void *indata, size_t indatalen)
-{
- gpg_error_t err;
- int nbytes;
-
- if (debug_filter)
- log_debug ("%s:%s: enter; filter=%p\n", SRCNAME, __func__, filter);
- /* Our implementation is for now straightforward without any
- additional buffer filling etc. */
- if (!filter || !filter->outfnc)
- return gpg_error (GPG_ERR_INV_VALUE);
- if (filter->in.length > FILTER_BUFFER_SIZE
- || filter->out.length > FILTER_BUFFER_SIZE)
- return gpg_error (GPG_ERR_BUG);
- if (filter->in.got_eof)
- return gpg_error (GPG_ERR_CONFLICT); /* EOF has already been indicated. */
-
- if (debug_filter)
- log_debug ("%s:%s: indata=%p indatalen=%d outfnc=%p\n",
- SRCNAME, __func__, indata, (int)indatalen, filter->outfnc);
- for (;;)
- {
- int any, anyany;
-
- /* If there is something to write out, do this now to make space
- for more data. */
- any = anyany = 0;
- take_out_lock (filter, __func__);
- while (filter->out.length)
- {
- if (debug_filter)
- log_debug ("%s:%s: pushing "SIZE_T_FORMAT" bytes to the outfnc\n",
- SRCNAME, __func__, filter->out.length);
- nbytes = filter->outfnc (filter->outfncdata,
- filter->out.buffer, filter->out.length);
- if (nbytes == -1)
- {
- if (debug_filter)
- log_debug ("%s:%s: error writing data\n", SRCNAME, __func__);
- release_out_lock (filter, __func__);
- return gpg_error (GPG_ERR_EIO);
- }
- assert (nbytes <= filter->out.length && nbytes >= 0);
- if (nbytes < filter->out.length)
- memmove (filter->out.buffer, filter->out.buffer + nbytes,
- filter->out.length - nbytes);
- filter->out.length -= nbytes;
- any = anyany = 1;
- }
- if (any && !PulseEvent (filter->out.condvar))
- log_error_w32 (-1, "%s:%s: PulseEvent(%p)[out] failed",
- SRCNAME, __func__, filter->out.condvar);
- release_out_lock (filter, __func__);
-
-
- any = 0;
- take_in_lock (filter, __func__);
- if (!indata && !indatalen)
- {
- filter->in.got_eof = 1;
- /* Flush requested. Tell the output function to also flush. */
- nbytes = filter->outfnc (filter->outfncdata, NULL, 0);
- if (nbytes == -1)
- {
- log_debug ("%s:%s: error flushing data\n", SRCNAME, __func__);
- err = gpg_error (GPG_ERR_EIO);
- }
- else
- err = 0;
- release_in_lock (filter, __func__);
- return err;
- }
-
- /* Fill the input buffer, relinquish control to the callback
- processor and loop until all input data has been
- processed. */
- if (indatalen && filter->in.length < FILTER_BUFFER_SIZE )
- {
- size_t tmplen;
-
- tmplen = FILTER_BUFFER_SIZE - filter->in.length;
- tmplen = (indatalen > tmplen? tmplen : indatalen);
-
- memcpy (filter->in.buffer+filter->in.length, indata, tmplen);
- filter->in.length += tmplen;
- indata += tmplen;
- indatalen -= tmplen;
- any = 1;
- }
- if (any && !PulseEvent (filter->in.condvar))
- log_error_w32 (-1, "%s:%s: PulseEvent(%p)[in] failed",
- SRCNAME, __func__, filter->in.condvar);
- /* Terminate the loop if the filter queue is empty OR the filter
- is ready and there is nothing left for output. */
- if (!filter->in.length || (filter->in.ready && !filter->out.length))
- {
- release_in_lock (filter, __func__);
- err = filter->in.status;
- break; /* the loop. */
- }
- release_in_lock (filter, __func__);
-
- /* Also terminate the loop if we have no more data. This is to
- allow this threrad to gather more data and to fill up the
- buffer while I/O is still going on in the other thread. */
- if (!indatalen)
- {
- err = 0;
- break; /* the loop. */
- }
-
- /* In case we did not do anything, we wait for a short while to
- relieve the CPU from spinning. This usually happens while
- the UI-server is waiting for a user response and thus a short
- delay does not harm. */
- if (!anyany)
- SleepEx (10, TRUE);
- }
-
- if (debug_filter)
- log_debug ("%s:%s: leave; err=%d\n", SRCNAME, __func__, err);
- return err;
-}
-
-
-/* Dummy data sink used if caller does not need an output
- function. */
-static int
-dummy_outfnc (void *opaque, const void *data, size_t datalen)
-{
- (void)opaque;
- (void)data;
- return (int)datalen;
-}
-
-
-/* Create a new filter object which uses OUTFNC as its data sink. If
- OUTFNC is called with NULL/0 for the data to be written, the
- function should do a flush. OUTFNC is expected to return the
- number of bytes actually written or -1 on error. It may return 0
- to indicate that no data has been written and the caller shall try
- again. OUTFNC and OUTFNCDATA are internally used by the engine
- even after the call to this function. There lifetime only ends
- after an engine_wait or engine_cancel. */
-int
-engine_create_filter (engine_filter_t *r_filter,
- int (*outfnc) (void *, const void *, size_t),
- void *outfncdata)
-{
- gpg_error_t err;
- engine_filter_t filter;
-
- filter = create_filter ();
- filter->cb_inbound.read = (gpgme_data_read_cb_t) filter_gpgme_read_cb;
- filter->cb_outbound.write = (gpgme_data_write_cb_t) filter_gpgme_write_cb;
- filter->outfnc = outfnc? outfnc : dummy_outfnc;
- filter->outfncdata = outfncdata;
-
- err = gpgme_data_new_from_cbs (&filter->indata,
- &filter->cb_inbound, filter);
- if (err)
- goto failure;
-
- err = gpgme_data_new_from_cbs (&filter->outdata,
- &filter->cb_outbound, filter);
- if (err)
- goto failure;
-
- if (debug_filter)
- log_debug ("%s:%s: filter %p: created\n",
- SRCNAME, __func__, filter );
- *r_filter = filter;
- return 0;
-
- failure:
- release_filter (filter);
- return err;
-}
-
-
-/* Set the FILTER in a mode which pushes an extra linefeed out. */
-void
-engine_request_extra_lf (engine_filter_t filter)
-{
- filter->add_extra_lf = 1;
-}
-
-
-/* Set the session number for FILTER to NUMBER. */
-void
-engine_set_session_number (engine_filter_t filter, unsigned int value)
-{
- if (filter)
- filter->session_number = value;
-}
-
-/* Set the session title for FILTER to TITLE. */
-void
-engine_set_session_title (engine_filter_t filter, const char *title)
-{
- if (filter)
- {
- xfree (filter->session_title);
- filter->session_title = title? xstrdup (title) : NULL;
- }
-}
-
-/* Set the sender address for FILTER to ADDR. Setting the sender
- address to NULL is allowed. This sender address may be used by the
- server to check whether the used key matches the sender address
- while verifying a message. Note that this sender address is not
- used for selecting a key during encryption or verification. */
-void
-engine_set_sender_address (engine_filter_t filter, const char *addr)
-{
- if (filter)
- {
- xfree (filter->sender_address);
- filter->sender_address = addr? xstrdup (addr) : NULL;
- }
-}
-
-
-/* Wait for FILTER to finish. Returns 0 on success. FILTER is not
- valid after the function has returned success. */
-int
-engine_wait (engine_filter_t filter)
-{
- gpg_error_t err;
- int more;
- int nbytes;
-
- if (!filter || !filter->outfnc)
- return gpg_error (GPG_ERR_INV_VALUE);
-
- /* If we are here, engine_filter is not anymore called but there is
- likely stuff in the output buffer which needs to be written
- out. */
- /* Argh, Busy waiting. As soon as we change fromCritical Sections
- to a kernel based objects we should use WaitOnMultipleObjects to
- wait for the out.lock as well as for the ready_event. */
- do
- {
- more = 0;
- take_out_lock (filter, __func__);
- if (filter->out.length)
- {
- nbytes = filter->outfnc (filter->outfncdata,
- filter->out.buffer, filter->out.length);
- if (nbytes < 0)
- {
- log_error ("%s:%s: error writing data\n", SRCNAME, __func__);
- release_out_lock (filter, __func__);
- return gpg_error (GPG_ERR_EIO);
- }
-
- assert (nbytes <= filter->out.length && nbytes >= 0);
- if (nbytes < filter->out.length)
- memmove (filter->out.buffer, filter->out.buffer + nbytes,
- filter->out.length - nbytes);
- filter->out.length -= nbytes;
- if (filter->out.length)
- {
- if (debug_filter_extra)
- log_debug ("%s:%s: still "SIZE_T_FORMAT" pending bytes for outfnc\n",
- SRCNAME, __func__, filter->out.length);
- more = 1;
- }
- }
- if (!PulseEvent (filter->out.condvar))
- log_error_w32 (-1, "%s:%s: PulseEvent(out) failed", SRCNAME, __func__);
- release_out_lock (filter, __func__);
- take_in_lock (filter, __func__);
- if (!filter->in.ready)
- more = 1;
- release_in_lock (filter, __func__);
- if (more)
- SwitchToThread ();
- /* Let's not burn at 100% CPU please */
- Sleep (100);
- }
- while (more);
-
- /* If requested write an extra LF, so that the MIME parser sees a
- complete line. */
- if (filter->add_extra_lf)
- {
- int extra_written = 0;
- do
- {
- more = 0;
- take_out_lock (filter, __func__);
- if (!extra_written)
- {
- nbytes = filter->outfnc (filter->outfncdata, "\n", 1);
- if (nbytes < 0)
- {
- log_error ("%s:%s: error writing extra lf\n",
- SRCNAME, __func__);
- release_out_lock (filter, __func__);
- return gpg_error (GPG_ERR_EIO);
- }
- if (!nbytes)
- {
- if (debug_filter_extra)
- log_debug ("%s:%s: extra lf still pending for outfnc\n",
- SRCNAME, __func__);
- more = 1;
- }
- else
- extra_written = 1;
- }
- if (!PulseEvent (filter->out.condvar))
- log_error_w32 (-1, "%s:%s: PulseEvent(out) failed",
- SRCNAME, __func__);
- release_out_lock (filter, __func__);
- take_in_lock (filter, __func__);
- if (!filter->in.ready)
- more = 1;
- release_in_lock (filter, __func__);
- if (more)
- SwitchToThread ();
- }
- while (more);
- }
-
- if (WaitForSingleObject (filter->in.ready_event, INFINITE) != WAIT_OBJECT_0)
- {
- log_error_w32 (-1, "%s:%s: WFSO failed", SRCNAME, __func__);
- return gpg_error (GPG_ERR_GENERAL);
- }
- err = filter->in.status;
- log_debug ("%s:%s: filter %p ready: %s", SRCNAME, __func__,
- filter, gpg_strerror (err));
-
- if (!err)
- release_filter (filter);
- return err;
-}
-
-
-/* Cancel FILTER. */
-void
-engine_cancel (engine_filter_t filter)
-{
- void *cancel_data;
-
- if (!filter)
- return;
-
- /* First we need to cancel a possible prepared encrypt operation. */
- engine_encrypt_start (filter, 1);
-
- take_in_lock (filter, __func__);
- cancel_data = filter->cancel_data;
- filter->cancel_data = NULL;
- filter->in.ready = 1;
- release_in_lock (filter, __func__);
- if (cancel_data)
- {
- log_debug ("%s:%s: filter %p: sending cancel command to backend",
- SRCNAME, __func__, filter);
- engine_assuan_cancel (cancel_data);
- if (WaitForSingleObject (filter->in.ready_event, INFINITE)
- != WAIT_OBJECT_0)
- log_error_w32 (-1, "%s:%s: filter %p: WFSO failed",
- SRCNAME, __func__, filter);
- else
- log_debug ("%s:%s: filter %p: backend has been canceled",
- SRCNAME, __func__, filter);
- }
- log_debug ("%s:%s: filter %p: canceled", SRCNAME, __func__, filter);
- release_filter (filter);
-}
-
-
-
-/* Start an encryption operation to all RECIPIENTS using PROTOCOL
- RECIPIENTS is a NULL terminated array of rfc2822 addresses. FILTER
- is an object created by engine_create_filter. The caller needs to
- call engine_wait to finish the operation. A filter object may not
- be reused after having been used through this function. However,
- the lifetime of the filter object lasts until the final engine_wait
- or engine_cancel. On return the protocol to be used is stored at
- R_PROTOCOL. This is a two part fucntion. engine_encrypt_prepare
- needs to be called first followed by engine_encrypt_start. The
- latter command has just one argument CANCEL which can be set to
- true to cancel the prepared command.
-
- FLAGS modifies the operation. Valid flags are:
-
- ENGINE_FLAG_SIGN_FOLLOWS
- Expect that we are part of a sign+encrypt operaion. This
- allows the UI-server to presetn a better dialog.
-
- SENDER is the sender's mailbox or NULL; this information may be
- used by the UI-server for role selection.
- */
-int
-engine_encrypt_prepare (engine_filter_t filter, HWND hwnd,
- protocol_t req_protocol, unsigned int flags,
- const char *sender, char **recipients,
- protocol_t *r_protocol)
-{
- gpg_error_t err;
- protocol_t used_protocol;
-
- *r_protocol = req_protocol;
- err = op_assuan_encrypt (req_protocol, filter->indata, filter->outdata,
- filter, hwnd, flags, sender, recipients,
- &used_protocol, &filter->encstate);
- if (!err)
- *r_protocol = used_protocol;
-
- return err;
-}
-
-/* See engine_encrypt_prepare. */
-int
-engine_encrypt_start (engine_filter_t filter, int cancel)
-{
- gpg_error_t err;
-
- err = op_assuan_encrypt_bottom (filter->encstate, cancel);
- filter->encstate = NULL;
-
- return err;
-}
-
-
-/* Start an detached signing operation. FILTER is an object created
- by engine_create_filter. The caller needs to call engine_wait to
- finish the operation. A filter object may not be reused after
- having been used through this function. However, the lifetime of
- the filter object lasts until the final engine_wait or
- engine_cancel. SENDER is the sender's mailbox or NULL. On return
- the protocol to be used is stored at R_PROTOCOL. */
-int
-engine_sign_start (engine_filter_t filter, HWND hwnd, protocol_t protocol,
- const char *sender, protocol_t *r_protocol)
-{
- gpg_error_t err;
- protocol_t used_protocol;
-
- err = op_assuan_sign (protocol, filter->indata, filter->outdata,
- filter, hwnd, sender, &used_protocol,
- ENGINE_FLAG_DETACHED);
- if (!err)
- *r_protocol = used_protocol;
-
- return err;
-}
-
-/* Start an inline signing operation. Same as engine_sign_start but
- without the detatched option. */
-int
-engine_sign_opaque_start (engine_filter_t filter, HWND hwnd,
- protocol_t protocol, const char *sender,
- protocol_t *r_protocol)
-{
- gpg_error_t err;
- protocol_t used_protocol;
-
- err = op_assuan_sign (protocol, filter->indata, filter->outdata,
- filter, hwnd, sender, &used_protocol,
- 0);
- if (!err)
- *r_protocol = used_protocol;
-
- return err;
-}
-
-
-/* Start an decrypt operation. FILTER is an object created by
- engine_create_filter. The caller needs to call engine_wait to
- finish the operation. A filter object may not be reused after
- having been used through this function. However, the lifetime of
- the filter object lasts until the final engine_wait or
- engine_cancel. FROM_ADDRESS may be passed to allow the backend
- matching the sender's address with the one in the certificate (in
- case the decrypted message ncludes a signed message). */
-int
-engine_decrypt_start (engine_filter_t filter, HWND hwnd, protocol_t protocol,
- int with_verify, const char *from_address)
-{
- gpg_error_t err;
-
- err = op_assuan_decrypt (protocol, filter->indata, filter->outdata,
- filter, hwnd, with_verify, from_address);
- return err;
-}
-
-
-/* Start a verify operation. FILTER is an object created by
- engine_create_filter; an output function is not required. SIGNATURE
- is the detached signature or NULL if FILTER delivers an opaque
- signature. The caller needs to call engine_wait to finish the
- operation. A filter object may not be reused after having been
- used through this function. However, the lifetime of the filter
- object lasts until the final engine_wait or engine_cancel.
- FROM_ADDRESS may be passed to allow the backend matching the
- sender's address with the one in the certificate. */
-int
-engine_verify_start (engine_filter_t filter, HWND hwnd, const char *signature,
- size_t sig_len, protocol_t protocol,
- const char *from_address)
-{
- gpg_error_t err;
-
- if (!signature)
- err = op_assuan_verify (protocol, filter->indata, NULL, 0,
- filter->outdata, filter, hwnd, from_address);
- else
- err = op_assuan_verify (protocol, filter->indata, signature, sig_len,
- NULL, filter, hwnd, from_address);
-
- return err;
-}
-
-
-/* Fire up the key manager. Returns 0 on success. */
-int
-engine_start_keymanager (HWND hwnd)
-{
- return op_assuan_start_keymanager (hwnd);
-}
-
-/* Fire up the config dialog. Returns 0 on success. */
-int
-engine_start_confdialog (HWND hwnd)
-{
- return op_assuan_start_confdialog (hwnd);
-}
diff --git a/src/engine.h b/src/engine.h
deleted file mode 100644
index 49ab9a8..0000000
--- a/src/engine.h
+++ /dev/null
@@ -1,105 +0,0 @@
-/* engine.h - Crypto engine dispatcher
- * Copyright (C) 2007 g10 Code GmbH
- *
- * This file is part of GpgOL.
- *
- * GpgOL is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * as published by the Free Software Foundation; either version 2.1
- * of the License, or (at your option) any later version.
- *
- * GpgOL is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, see .
- */
-
-#ifndef GPGOL_ENGINE_H
-#define GPGOL_ENGINE_H 1
-
-#ifdef __cplusplus
-extern "C" {
-#if 0
-}
-#endif
-#endif
-
-typedef enum
- {
- OP_SIG_NORMAL = 0,
- OP_SIG_DETACH = 1,
- OP_SIG_CLEAR = 2
- }
-engine_sigtype_t;
-
-
-/* The key info object. */
-struct engine_keyinfo_s;
-typedef struct engine_keyinfo_s *engine_keyinfo_t;
-
-/* The filter object. */
-struct engine_filter_s;
-typedef struct engine_filter_s *engine_filter_t;
-
-/* Flag values used by the engine functions. */
-#define ENGINE_FLAG_SIGN_FOLLOWS 1 /* Expect a sign+encrypt operation. */
-#define ENGINE_FLAG_BINARY_OUTPUT 2 /* Create binary output */
-#define ENGINE_FLAG_DETACHED 4 /* Create detached signature */
-
-
-
-/*-- engine.c -- */
-int engine_init (void);
-void engine_deinit (void);
-unsigned int engine_new_session_number (void);
-
-void engine_private_set_cancel (engine_filter_t filter, void *cancel_data);
-void engine_private_finished (engine_filter_t filter, gpg_error_t status);
-
-unsigned int engine_private_get_session_number (engine_filter_t filter);
-const char *engine_private_get_session_title (engine_filter_t filter);
-
-int engine_filter (engine_filter_t filter,
- const void *indata, size_t indatalen);
-int engine_create_filter (engine_filter_t *r_filter,
- int (*outfnc) (void *, const void *, size_t),
- void *outfncdata);
-void engine_request_extra_lf (engine_filter_t filter);
-void engine_set_session_number (engine_filter_t filter, unsigned int value);
-void engine_set_session_title (engine_filter_t filter, const char *title);
-void engine_set_sender_address (engine_filter_t filter, const char *addr);
-
-int engine_wait (engine_filter_t filter);
-void engine_cancel (engine_filter_t filter);
-
-int engine_encrypt_prepare (engine_filter_t filter, HWND hwnd,
- protocol_t req_protocol, unsigned int flags,
- const char *sender, char **recipients,
- protocol_t *r_protocol);
-int engine_encrypt_start (engine_filter_t filter, int cancel);
-int engine_sign_start (engine_filter_t filter, HWND hwnd, protocol_t protocol,
- const char *sender, protocol_t *r_protocol);
-int engine_sign_opaque_start (engine_filter_t filter, HWND hwnd,
- protocol_t protocol, const char *sender,
- protocol_t *r_protocol);
-
-int engine_decrypt_start (engine_filter_t filter, HWND hwnd,
- protocol_t protocol, int with_verify,
- const char *from_address);
-int engine_verify_start (engine_filter_t filter, HWND hwnd,
- const char *signature, size_t sig_len,
- protocol_t protocol,
- const char *from_address);
-
-int engine_start_keymanager (HWND hwnd);
-int engine_start_confdialog (HWND hwnd);
-
-
-
-#ifdef __cplusplus
-}
-#endif
-#endif /*GPGOL_ENGINE_H*/
diff --git a/src/gpgoladdin.cpp b/src/gpgoladdin.cpp
index 0cadbc8..38ed039 100644
--- a/src/gpgoladdin.cpp
+++ b/src/gpgoladdin.cpp
@@ -1,1129 +1,1111 @@
/* gpgoladdin.cpp - Connect GpgOL to Outlook as an addin
* Copyright (C) 2013 Intevation GmbH
* 2015 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include "common.h"
#include "gpgoladdin.h"
#include "mymapi.h"
#include "mymapitags.h"
-#include "engine.h"
-#include "engine-assuan.h"
#include "mapihelp.h"
#include "oomhelp.h"
#include "olflange.h"
#include "gpgol-ids.h"
#include "ribbon-callbacks.h"
#include "eventsinks.h"
#include "eventsink.h"
#include "windowmessages.h"
#include "mail.h"
#include "addin-options.h"
#include
#include
#define ICON_SIZE_LARGE 32
#define ICON_SIZE_NORMAL 16
/* We use UTF-8 internally. */
#undef _
#define _(a) utf8_gettext (a)
ULONG addinLocks = 0;
bool can_unload = false;
static std::list g_ribbon_uis;
static GpgolAddin * addin_instance = NULL;
/* This is the main entry point for the addin
Outlook uses this function to query for an Object implementing
the IClassFactory interface.
*/
STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, LPVOID* ppvObj)
{
if (!ppvObj)
return E_POINTER;
*ppvObj = NULL;
if (rclsid != CLSID_GPGOL)
return CLASS_E_CLASSNOTAVAILABLE;
/* Let the factory give the requested interface. */
GpgolAddinFactory* factory = new GpgolAddinFactory();
if (!factory)
return E_OUTOFMEMORY;
HRESULT hr = factory->QueryInterface (riid, ppvObj);
if(FAILED(hr))
{
*ppvObj = NULL;
delete factory;
}
return hr;
}
STDAPI DllCanUnloadNow()
{
/* This is called regularly to check if memory can be freed
by unloading the dll. The following unload will not call
any addin methods like disconnect etc. It will just
unload the Library. Any callbacks will become invalid.
So we _only_ say it's ok to unload if we were disconnected.
For the epic story behind the next line see GnuPG-Bug-Id 1837 */
return can_unload ? S_OK : S_FALSE;
}
/* Class factory */
STDMETHODIMP GpgolAddinFactory::QueryInterface (REFIID riid, LPVOID* ppvObj)
{
HRESULT hr = S_OK;
*ppvObj = NULL;
if ((IID_IUnknown == riid) || (IID_IClassFactory == riid))
*ppvObj = static_cast(this);
else
{
hr = E_NOINTERFACE;
LPOLESTR sRiid = NULL;
StringFromIID (riid, &sRiid);
/* Should not happen */
log_debug ("GpgolAddinFactory queried for unknown interface: %S \n", sRiid);
}
if (*ppvObj)
((LPUNKNOWN)*ppvObj)->AddRef();
return hr;
}
/* This actually creates the instance of our COM object */
STDMETHODIMP GpgolAddinFactory::CreateInstance (LPUNKNOWN punk, REFIID riid,
LPVOID* ppvObj)
{
(void)punk;
*ppvObj = NULL;
GpgolAddin* obj = GpgolAddin::get_instance();
if (NULL == obj)
return E_OUTOFMEMORY;
HRESULT hr = obj->QueryInterface (riid, ppvObj);
if (FAILED(hr))
{
LPOLESTR sRiid = NULL;
StringFromIID (riid, &sRiid);
fprintf(stderr, "failed to create instance for: %S", sRiid);
}
return hr;
}
/* GpgolAddin definition */
/* Constructor of GpgolAddin
Initializes members and creates the interface objects for the new
context. Does the DLL initialization if it has not been done
before.
The ref count is set by the factory after creation.
*/
GpgolAddin::GpgolAddin (void) : m_lRef(0),
m_application(nullptr),
m_addin(nullptr),
m_applicationEventSink(nullptr),
m_explorersEventSink(nullptr),
m_disabled(false),
m_hook(nullptr)
{
read_options ();
/* RibbonExtender is it's own object to avoid the pitfalls of
multiple inheritance
*/
m_ribbonExtender = new GpgolRibbonExtender();
}
GpgolAddin::~GpgolAddin (void)
{
if (m_disabled)
{
return;
}
log_debug ("%s:%s: Releasing Application Event Sink;",
SRCNAME, __func__);
gpgol_release (m_explorersEventSink);
gpgol_release (m_applicationEventSink);
- engine_deinit ();
write_options ();
UnhookWindowsHookEx (m_hook);
addin_instance = NULL;
log_debug ("%s:%s: Object deleted\n", SRCNAME, __func__);
}
STDMETHODIMP
GpgolAddin::QueryInterface (REFIID riid, LPVOID* ppvObj)
{
HRESULT hr = S_OK;
*ppvObj = NULL;
if (m_disabled)
return E_NOINTERFACE;
if ((riid == IID_IUnknown) || (riid == IID_IDTExtensibility2) ||
(riid == IID_IDispatch))
{
*ppvObj = (LPUNKNOWN) this;
}
else if (riid == IID_IRibbonExtensibility)
{
return m_ribbonExtender->QueryInterface (riid, ppvObj);
}
else
{
hr = E_NOINTERFACE;
#if 0
LPOLESTR sRiid = NULL;
StringFromIID(riid, &sRiid);
log_debug ("%s:%s: queried for unimplmented interface: %S",
SRCNAME, __func__, sRiid);
#endif
}
if (*ppvObj)
((LPUNKNOWN)*ppvObj)->AddRef();
return hr;
}
static void
addGpgOLToReg (const std::string &path)
{
HKEY h;
int err = RegOpenKeyEx (HKEY_CURRENT_USER, path.c_str(), 0,
KEY_ALL_ACCESS, &h);
if (err != ERROR_SUCCESS)
{
log_debug ("%s:%s: no DoNotDisableAddinList entry '%s' creating it",
SRCNAME, __func__, path.c_str ());
err = RegCreateKeyEx (HKEY_CURRENT_USER, path.c_str (), 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
&h, NULL);
}
if (err != ERROR_SUCCESS)
{
log_error ("%s:%s: failed to create key.",
SRCNAME, __func__);
return;
}
DWORD type;
err = RegQueryValueEx (h, GPGOL_PROGID, NULL, &type, NULL, NULL);
if (err == ERROR_SUCCESS)
{
log_debug ("%s:%s: Found gpgol reg key. Leaving it unchanged.",
SRCNAME, __func__);
RegCloseKey (h);
return;
}
// No key exists. Create one.
DWORD dwTemp = 1;
err = RegSetValueEx (h, GPGOL_PROGID, 0, REG_DWORD, (BYTE*)&dwTemp, 4);
RegCloseKey (h);
if (err != ERROR_SUCCESS)
{
log_error ("%s:%s: failed to set registry value.",
SRCNAME, __func__);
}
else
{
log_debug ("%s:%s: added gpgol to %s",
SRCNAME, __func__, path.c_str ());
}
}
/* This is a bit evil as we basically disable outlooks resiliency
for us. But users are still able to manually disable the addon
or change the donotdisable setting to zero and we won't change
it.
It has been much requested by users that we do this automatically.
*/
static void
setupDoNotDisable ()
{
std::string path = "Software\\Microsoft\\Office\\";
path += std::to_string (g_ol_version_major);
path += ".0\\Outlook\\Resiliency\\DoNotDisableAddinList";
addGpgOLToReg (path);
path = "Software\\Microsoft\\Office\\";
path += std::to_string (g_ol_version_major);
path += ".0\\Outlook\\Resiliency\\AddinList";
addGpgOLToReg (path);
}
STDMETHODIMP
GpgolAddin::OnConnection (LPDISPATCH Application, ext_ConnectMode ConnectMode,
LPDISPATCH AddInInst, SAFEARRAY ** custom)
{
(void)custom;
char* version;
log_debug ("%s:%s: this is GpgOL %s\n",
SRCNAME, __func__, PACKAGE_VERSION);
can_unload = false;
m_application = Application;
m_application->AddRef();
m_addin = AddInInst;
version = get_oom_string (Application, "Version");
log_debug ("%s:%s: using GPGME %s\n",
SRCNAME, __func__, gpgme_check_version (NULL));
log_debug ("%s:%s: in Outlook %s\n",
SRCNAME, __func__, version);
g_ol_version_major = atoi (version);
if (!version || !strlen (version) ||
(strncmp (version, "14", 2) &&
strncmp (version, "15", 2) &&
strncmp (version, "16", 2)))
{
m_disabled = true;
log_debug ("%s:%s: Disabled addin for unsupported version.",
SRCNAME, __func__);
xfree (version);
return S_OK;
}
- engine_init ();
setupDoNotDisable ();
if (ConnectMode != ext_cm_Startup)
{
OnStartupComplete (custom);
}
return S_OK;
}
STDMETHODIMP
GpgolAddin::OnDisconnection (ext_DisconnectMode RemoveMode,
SAFEARRAY** custom)
{
(void)custom;
(void)RemoveMode;
log_debug ("%s:%s: cleaning up GpgolAddin object;",
SRCNAME, __func__);
/* Doing the wipe in the dtor is too late. Outlook
does not allow us any OOM calls then and only returns
"Unexpected error" in that case. Weird. */
if (Mail::close_all_mails ())
{
MessageBox (NULL,
"Failed to remove plaintext from at least one message.\n\n"
"Until GpgOL is activated again it is possible that the "
"plaintext of messages decrypted in this Session is saved "
"or transfered back to your mailserver.",
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
}
write_options();
can_unload = true;
return S_OK;
}
STDMETHODIMP
GpgolAddin::OnAddInsUpdate (SAFEARRAY** custom)
{
(void)custom;
return S_OK;
}
static void
check_html_preferred()
{
/* Check if HTML Mail should be enabled. */
HKEY h;
std::string path = "Software\\Microsoft\\Office\\";
path += std::to_string (g_ol_version_major);
path += ".0\\Outlook\\Options\\Mail";
opt.prefer_html = 1;
int err = RegOpenKeyEx (HKEY_CURRENT_USER, path.c_str() , 0, KEY_READ, &h);
if (err != ERROR_SUCCESS)
{
log_debug ("%s:%s: no mail options under %s",
SRCNAME, __func__, path.c_str());
return;
}
else
{
DWORD type;
err = RegQueryValueEx (h, "ReadAsPlain", NULL, &type, NULL, NULL);
if (err != ERROR_SUCCESS || type != REG_DWORD)
{
log_debug ("%s:%s: No type or key for ReadAsPlain",
SRCNAME, __func__);
return;
}
else
{
DWORD data;
DWORD size = sizeof (DWORD);
err = RegQueryValueEx (h, "ReadAsPlain", NULL, NULL, (LPBYTE)&data,
&size);
if (err != ERROR_SUCCESS)
{
log_debug ("%s:%s: Failed to find out ReadAsPlain",
SRCNAME, __func__);
return;
}
opt.prefer_html = data ? 0 : 1;
return;
}
}
}
static LPDISPATCH
install_explorer_sinks (LPDISPATCH application)
{
LPDISPATCH explorers = get_oom_object (application, "Explorers");
if (!explorers)
{
log_error ("%s:%s: No explorers object",
SRCNAME, __func__);
return nullptr;
}
int count = get_oom_int (explorers, "Count");
for (int i = 1; i <= count; i++)
{
std::string item = "Item(";
item += std::to_string (i) + ")";
LPDISPATCH explorer = get_oom_object (explorers, item.c_str());
if (!explorer)
{
log_error ("%s:%s: failed to get explorer %i",
SRCNAME, __func__, i);
continue;
}
/* Explorers delete themself in the close event of the explorer. */
LPDISPATCH sink = install_ExplorerEvents_sink (explorer);
if (!sink)
{
log_error ("%s:%s: failed to create eventsink for explorer %i",
SRCNAME, __func__, i);
}
else
{
log_oom_extra ("%s:%s: created sink %p for explorer %i",
SRCNAME, __func__, sink, i);
}
add_explorer (explorer);
gpgol_release (explorer);
}
/* Now install the event sink to handle new explorers */
return install_ExplorersEvents_sink (explorers);
}
STDMETHODIMP
GpgolAddin::OnStartupComplete (SAFEARRAY** custom)
{
(void)custom;
TRACEPOINT;
if (!create_responder_window())
{
log_error ("%s:%s: Failed to create the responder window;",
SRCNAME, __func__);
}
if (!m_application)
{
/* Should not happen as OnConnection should be called before */
log_error ("%s:%s: no application set;",
SRCNAME, __func__);
return E_NOINTERFACE;
}
if (!(m_hook = create_message_hook ()))
{
log_error ("%s:%s: Failed to create messagehook. ",
SRCNAME, __func__);
}
/* Set up categories */
const char *decCategory = _("GpgOL: Encrypted Message");
const char *verifyCategory = _("GpgOL: Trusted Sender Address");
ensure_category_exists (m_application, decCategory, 8);
ensure_category_exists (m_application, verifyCategory, 5);
install_forms ();
m_applicationEventSink = install_ApplicationEvents_sink (m_application);
m_explorersEventSink = install_explorer_sinks (m_application);
check_html_preferred ();
return S_OK;
}
STDMETHODIMP
GpgolAddin::OnBeginShutdown (SAFEARRAY * * custom)
{
(void)custom;
TRACEPOINT;
return S_OK;
}
STDMETHODIMP
GpgolAddin::GetTypeInfoCount (UINT *r_count)
{
*r_count = 0;
TRACEPOINT; /* Should not happen */
return S_OK;
}
STDMETHODIMP
GpgolAddin::GetTypeInfo (UINT iTypeInfo, LCID lcid,
LPTYPEINFO *r_typeinfo)
{
(void)iTypeInfo;
(void)lcid;
(void)r_typeinfo;
TRACEPOINT; /* Should not happen */
return S_OK;
}
STDMETHODIMP
GpgolAddin::GetIDsOfNames (REFIID riid, LPOLESTR *rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
(void)riid;
(void)rgszNames;
(void)cNames;
(void)lcid;
(void)rgDispId;
TRACEPOINT; /* Should not happen */
return E_NOINTERFACE;
}
STDMETHODIMP
GpgolAddin::Invoke (DISPID dispid, REFIID riid, LCID lcid,
WORD flags, DISPPARAMS *parms, VARIANT *result,
EXCEPINFO *exepinfo, UINT *argerr)
{
USE_INVOKE_ARGS
TRACEPOINT; /* Should not happen */
return DISP_E_MEMBERNOTFOUND;
}
/* Definition of GpgolRibbonExtender */
GpgolRibbonExtender::GpgolRibbonExtender (void) : m_lRef(0)
{
}
GpgolRibbonExtender::~GpgolRibbonExtender (void)
{
log_debug ("%s:%s: cleaning up GpgolRibbonExtender object;",
SRCNAME, __func__);
log_debug ("%s:%s: Object deleted\n", SRCNAME, __func__);
}
STDMETHODIMP
GpgolRibbonExtender::QueryInterface(REFIID riid, LPVOID* ppvObj)
{
HRESULT hr = S_OK;
*ppvObj = NULL;
if ((riid == IID_IUnknown) || (riid == IID_IRibbonExtensibility) ||
(riid == IID_IDispatch))
{
*ppvObj = (LPUNKNOWN) this;
}
else
{
LPOLESTR sRiid = NULL;
StringFromIID (riid, &sRiid);
log_debug ("%s:%s: queried for unknown interface: %S",
SRCNAME, __func__, sRiid);
}
if (*ppvObj)
((LPUNKNOWN)*ppvObj)->AddRef();
return hr;
}
STDMETHODIMP
GpgolRibbonExtender::GetTypeInfoCount (UINT *r_count)
{
*r_count = 0;
TRACEPOINT; /* Should not happen */
return S_OK;
}
STDMETHODIMP
GpgolRibbonExtender::GetTypeInfo (UINT iTypeInfo, LCID lcid,
LPTYPEINFO *r_typeinfo)
{
(void)iTypeInfo;
(void)lcid;
(void)r_typeinfo;
TRACEPOINT; /* Should not happen */
return S_OK;
}
/* Good documentation of what this function is supposed to do can
be found at: http://msdn.microsoft.com/en-us/library/cc237568.aspx
There is also a very good blog explaining how Ribbon Extensibility
is supposed to work.
http://blogs.msdn.com/b/andreww/archive/2007/03/09/
why-is-it-so-hard-to-shim-iribbonextensibility.aspx
*/
#define ID_MAPPER(name,id) \
if (!wcscmp (rgszNames[i], name)) \
{ \
found = true; \
rgDispId[i] = id; \
break; \
} \
STDMETHODIMP
GpgolRibbonExtender::GetIDsOfNames (REFIID riid, LPOLESTR *rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
(void)riid;
(void)lcid;
bool found = false;
if (!rgszNames || !cNames || !rgDispId)
{
return E_POINTER;
}
for (unsigned int i = 0; i < cNames; i++)
{
log_debug ("%s:%s: GetIDsOfNames for: %S",
SRCNAME, __func__, rgszNames[i]);
/* How this is supposed to work with cNames > 1 is unknown,
but we can just say that we won't support callbacks with
different parameters and just match the name (the first element)
and we give it one of our own dispIds's that are later handled in
the invoke part */
- ID_MAPPER (L"attachmentDecryptCallback", ID_CMD_DECRYPT)
ID_MAPPER (L"btnDecrypt", ID_BTN_DECRYPT)
ID_MAPPER (L"btnDecryptLarge", ID_BTN_DECRYPT_LARGE)
ID_MAPPER (L"btnEncrypt", ID_BTN_ENCRYPT)
ID_MAPPER (L"btnEncryptLarge", ID_BTN_ENCRYPT_LARGE)
ID_MAPPER (L"btnEncryptSmall", IDI_ENCRYPT_20_PNG)
ID_MAPPER (L"btnSignSmall", IDI_SIGN_20_PNG)
ID_MAPPER (L"btnSignEncryptLarge", IDI_SIGN_ENCRYPT_40_PNG)
ID_MAPPER (L"btnEncryptFileLarge", ID_BTN_ENCSIGN_LARGE)
ID_MAPPER (L"btnSignLarge", ID_BTN_SIGN_LARGE)
ID_MAPPER (L"btnVerifyLarge", ID_BTN_VERIFY_LARGE)
ID_MAPPER (L"btnSigstateLarge", ID_BTN_SIGSTATE_LARGE)
/* MIME support: */
ID_MAPPER (L"encryptMime", ID_CMD_MIME_ENCRYPT)
ID_MAPPER (L"encryptMimeEx", ID_CMD_MIME_ENCRYPT_EX)
ID_MAPPER (L"signMime", ID_CMD_MIME_SIGN)
ID_MAPPER (L"signMimeEx", ID_CMD_MIME_SIGN_EX)
ID_MAPPER (L"encryptSignMime", ID_CMD_SIGN_ENCRYPT_MIME)
ID_MAPPER (L"encryptSignMimeEx", ID_CMD_SIGN_ENCRYPT_MIME_EX)
ID_MAPPER (L"getEncryptPressed", ID_GET_ENCRYPT_PRESSED)
ID_MAPPER (L"getEncryptPressedEx", ID_GET_ENCRYPT_PRESSED_EX)
ID_MAPPER (L"getSignPressed", ID_GET_SIGN_PRESSED)
ID_MAPPER (L"getSignPressedEx", ID_GET_SIGN_PRESSED_EX)
ID_MAPPER (L"getSignEncryptPressed", ID_GET_SIGN_ENCRYPT_PRESSED)
ID_MAPPER (L"getSignEncryptPressedEx", ID_GET_SIGN_ENCRYPT_PRESSED_EX)
ID_MAPPER (L"ribbonLoaded", ID_ON_LOAD)
ID_MAPPER (L"openOptions", ID_CMD_OPEN_OPTIONS)
ID_MAPPER (L"getSigLabel", ID_GET_SIG_LABEL)
ID_MAPPER (L"getSigSTip", ID_GET_SIG_STIP)
ID_MAPPER (L"getSigTip", ID_GET_SIG_TTIP)
ID_MAPPER (L"launchDetails", ID_LAUNCH_CERT_DETAILS)
ID_MAPPER (L"getIsDetailsEnabled", ID_GET_IS_DETAILS_ENABLED)
ID_MAPPER (L"getIsCrypto", ID_GET_IS_CRYPTO_MAIL)
ID_MAPPER (L"printDecrypted", ID_CMD_PRINT_DECRYPTED)
}
if (cNames > 1)
{
log_debug ("More then one name provided. Should not happen");
}
return found ? S_OK : E_NOINTERFACE;
}
STDMETHODIMP
GpgolRibbonExtender::Invoke (DISPID dispid, REFIID riid, LCID lcid,
WORD flags, DISPPARAMS *parms, VARIANT *result,
EXCEPINFO *exepinfo, UINT *argerr)
{
USE_INVOKE_ARGS
log_debug ("%s:%s: enter with dispid: %x",
SRCNAME, __func__, (int)dispid);
if (!(flags & DISPATCH_METHOD))
{
log_debug ("%s:%s: not called in method mode. Bailing out.",
SRCNAME, __func__);
return DISP_E_MEMBERNOTFOUND;
}
switch (dispid)
{
- case ID_CMD_DECRYPT:
- /* We can assume that this points to an implementation of
- IRibbonControl as we know the callback dispid. */
- return decryptAttachments (parms->rgvarg[0].pdispVal);
case ID_CMD_SIGN_ENCRYPT_MIME:
return mark_mime_action (parms->rgvarg[1].pdispVal,
OP_SIGN|OP_ENCRYPT, false);
case ID_CMD_SIGN_ENCRYPT_MIME_EX:
return mark_mime_action (parms->rgvarg[1].pdispVal,
OP_SIGN|OP_ENCRYPT, true);
case ID_CMD_MIME_ENCRYPT:
return mark_mime_action (parms->rgvarg[1].pdispVal, OP_ENCRYPT,
false);
case ID_CMD_MIME_SIGN:
return mark_mime_action (parms->rgvarg[1].pdispVal, OP_SIGN,
false);
case ID_GET_ENCRYPT_PRESSED:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_ENCRYPT,
result, false);
case ID_GET_SIGN_PRESSED:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_SIGN,
result, false);
case ID_GET_SIGN_ENCRYPT_PRESSED:
return get_crypt_pressed (parms->rgvarg[0].pdispVal,
OP_SIGN | OP_ENCRYPT,
result, false);
case ID_CMD_MIME_SIGN_EX:
return mark_mime_action (parms->rgvarg[1].pdispVal, OP_SIGN, true);
case ID_CMD_MIME_ENCRYPT_EX:
return mark_mime_action (parms->rgvarg[1].pdispVal, OP_ENCRYPT, true);
case ID_GET_ENCRYPT_PRESSED_EX:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_ENCRYPT,
result, true);
case ID_GET_SIGN_PRESSED_EX:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_SIGN,
result, true);
case ID_GET_SIGN_ENCRYPT_PRESSED_EX:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_SIGN | OP_ENCRYPT,
result, true);
case ID_GET_SIG_STIP:
return get_sig_stip (parms->rgvarg[0].pdispVal, result);
case ID_GET_SIG_TTIP:
return get_sig_ttip (parms->rgvarg[0].pdispVal, result);
case ID_GET_SIG_LABEL:
return get_sig_label (parms->rgvarg[0].pdispVal, result);
case ID_LAUNCH_CERT_DETAILS:
return launch_cert_details (parms->rgvarg[0].pdispVal);
case ID_GET_IS_DETAILS_ENABLED:
return get_is_details_enabled (parms->rgvarg[0].pdispVal, result);
case ID_ON_LOAD:
{
g_ribbon_uis.push_back (parms->rgvarg[0].pdispVal);
return S_OK;
}
case ID_CMD_OPEN_OPTIONS:
{
options_dialog_box (NULL);
return S_OK;
}
case ID_CMD_PRINT_DECRYPTED:
return print_decrypted (parms->rgvarg[0].pdispVal);
case ID_GET_IS_CRYPTO_MAIL:
return get_is_crypto_mail (parms->rgvarg[0].pdispVal, result);
case ID_BTN_ENCRYPT:
case ID_BTN_DECRYPT:
case ID_BTN_DECRYPT_LARGE:
case ID_BTN_ENCRYPT_LARGE:
case ID_BTN_ENCSIGN_LARGE:
case ID_BTN_SIGN_LARGE:
case ID_BTN_VERIFY_LARGE:
case IDI_SIGN_ENCRYPT_40_PNG:
case IDI_ENCRYPT_20_PNG:
case IDI_SIGN_20_PNG:
return getIcon (dispid, result);
case ID_BTN_SIGSTATE_LARGE:
return get_crypto_icon (parms->rgvarg[0].pdispVal, result);
}
log_debug ("%s:%s: leave", SRCNAME, __func__);
return DISP_E_MEMBERNOTFOUND;
}
/* Returns the XML markup for the various RibbonID's
The custom ui syntax is documented at:
http://msdn.microsoft.com/en-us/library/dd926139%28v=office.12%29.aspx
The outlook specific elements are documented at:
http://msdn.microsoft.com/en-us/library/office/ee692172%28v=office.14%29.aspx
*/
static STDMETHODIMP
GetCustomUI_MIME (BSTR RibbonID, BSTR * RibbonXml)
{
char * buffer = NULL;
/* const char *certManagerTTip =
_("Start the Certificate Management Software");
const char *certManagerSTip =
_("Open GPA or Kleopatra to manage your certificates. "
"You can use this you to generate your "
"own certificates. ");*/
const char *encryptTTip =
_("Encrypt the message");
const char *encryptSTip =
_("Encrypts the message and all attachments before sending");
const char *signTTip =
_("Sign the message");
const char *signSTip =
_("Sign the message and all attachments before sending");
const char *secureTTip =
_("Sign and encrypt the message");
const char *secureSTip =
_("Encrypting and cryptographically signing a message means that the "
"recipients can be sure that no one modified the message and only the "
"recipients can read it");
const char *optsSTip =
_("Open the settings dialog for GpgOL");
log_debug ("%s:%s: GetCustomUI_MIME for id: %ls", SRCNAME, __func__, RibbonID);
if (!RibbonXml || !RibbonID)
return E_POINTER;
if (!wcscmp (RibbonID, L"Microsoft.Outlook.Mail.Compose"))
{
gpgrt_asprintf (&buffer,
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
"", _("GpgOL"),
_("Secure"), secureTTip, secureSTip,
_("Sign"), signTTip, signSTip,
_("Encrypt"), encryptTTip, encryptSTip,
optsSTip
);
}
else if (!wcscmp (RibbonID, L"Microsoft.Outlook.Mail.Read"))
{
gpgrt_asprintf (&buffer,
""
" "
" "
" "
" "
" "
" "
" "
" "
- ""
- " "
- " "
- " "
- ""
"",
_("GpgOL"),
- optsSTip,
- _("Decrypt")
+ optsSTip
);
}
/* We don't use this code currently because calling the send
event for Inline Response mailitems fails. */
else if (!wcscmp (RibbonID, L"Microsoft.Outlook.Explorer") && g_ol_version_major > 14)
{
gpgrt_asprintf (&buffer,
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
"",
_("GpgOL"),
optsSTip,
_("GpgOL"),
_("Secure"), secureTTip, secureSTip,
_("Sign"), signTTip, signSTip,
_("Encrypt"), encryptTTip, encryptSTip,
optsSTip,
_("&Print decrypted")
);
}
else if (!wcscmp (RibbonID, L"Microsoft.Outlook.Explorer"))
{
// No TabComposeTools in Outlook 2010
gpgrt_asprintf (&buffer,
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
"",
_("GpgOL"),
optsSTip
);
}
if (buffer)
{
wchar_t *wbuf = utf8_to_wchar2 (buffer, strlen(buffer));
xfree (buffer);
*RibbonXml = SysAllocString (wbuf);
xfree (wbuf);
}
else
*RibbonXml = NULL;
return S_OK;
}
STDMETHODIMP
GpgolRibbonExtender::GetCustomUI (BSTR RibbonID, BSTR * RibbonXml)
{
return GetCustomUI_MIME (RibbonID, RibbonXml);
}
/* RibbonUi elements are created on demand but they are reused
in different inspectors. So far and from all documentation
I could find RibbonUi elments are never
deleted. When they are created the onLoad callback is called
to register them.
The callbacks registered in the XML description are only
executed on Load. So to have different information depending
on the available mails we have to invalidate the UI ourself.
This means that the callbacks will be reevaluated and the UI
Updated. Sadly we don't know which ribbon_ui needs updates
so we have to invalidate everything.
*/
void gpgoladdin_invalidate_ui ()
{
std::list::iterator it;
for (it = g_ribbon_uis.begin(); it != g_ribbon_uis.end(); ++it)
{
log_debug ("%s:%s: Invalidating ribbon: %p",
SRCNAME, __func__, *it);
invoke_oom_method (*it, "Invalidate", NULL);
}
}
GpgolAddin *
GpgolAddin::get_instance ()
{
if (!addin_instance)
{
addin_instance = new GpgolAddin ();
}
return addin_instance;
}
diff --git a/src/mimemaker.cpp b/src/mimemaker.cpp
index d0da7a9..d8d3684 100644
--- a/src/mimemaker.cpp
+++ b/src/mimemaker.cpp
@@ -1,2449 +1,1606 @@
/* mimemaker.c - Construct MIME message out of a MAPI
* Copyright (C) 2007, 2008 g10 Code GmbH
* Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#define COBJMACROS
#include
#include
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
-#include "engine.h"
#include "mapihelp.h"
#include "mimemaker.h"
#include "oomhelp.h"
#include "mail.h"
static const unsigned char oid_mimetag[] =
{0x2A, 0x86, 0x48, 0x86, 0xf7, 0x14, 0x03, 0x0a, 0x04};
/* The base-64 list used for base64 encoding. */
static unsigned char bintoasc[64+1] = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/");
/* Object used to collect data in a memory buffer. */
struct databuf_s
{
size_t len; /* Used length. */
size_t size; /* Allocated length of BUF. */
char *buf; /* Malloced buffer. */
};
/*** local prototypes ***/
static int write_multistring (sink_t sink, const char *text1,
...) GPGOL_GCC_A_SENTINEL(0);
/* Standard write method used with a sink_t object. */
int
sink_std_write (sink_t sink, const void *data, size_t datalen)
{
HRESULT hr;
LPSTREAM stream = static_cast(sink->cb_data);
if (!stream)
{
log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
return -1;
}
if (!data)
return 0; /* Flush - nothing to do here. */
hr = stream->Write(data, datalen, NULL);
if (hr)
{
log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr);
return -1;
}
return 0;
}
int
sink_string_write (sink_t sink, const void *data, size_t datalen)
{
Mail *mail = static_cast(sink->cb_data);
mail->append_to_inline_body (std::string((char*)data, datalen));
return 0;
}
/* Write method used with a sink_t that contains a file object. */
int
sink_file_write (sink_t sink, const void *data, size_t datalen)
{
HANDLE hFile = sink->cb_data;
DWORD written = 0;
if (!hFile || hFile == INVALID_HANDLE_VALUE)
{
log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
return -1;
}
if (!data)
return 0; /* Flush - nothing to do here. */
if (!WriteFile (hFile, data, datalen, &written, NULL))
{
log_error ("%s:%s: Write failed: ", SRCNAME, __func__);
return -1;
}
return 0;
}
-/* Make sure that PROTOCOL is usable or return a suitable protocol.
- On error PROTOCOL_UNKNOWN is returned. */
-static protocol_t
-check_protocol (protocol_t protocol)
-{
- switch (protocol)
- {
- case PROTOCOL_UNKNOWN:
- return PROTOCOL_UNKNOWN;
- case PROTOCOL_OPENPGP:
- case PROTOCOL_SMIME:
- return protocol;
- }
-
- log_error ("%s:%s: BUG", SRCNAME, __func__);
- return PROTOCOL_UNKNOWN;
-}
-
-
-
/* Create a new MAPI attchment for MESSAGE which will be used to
prepare the MIME message. On sucess the stream to write the data
to is stored at STREAM and the attachment object itself is
returned. The caller needs to call SaveChanges. Returns NULL on
failure in which case STREAM will be set to NULL. */
LPATTACH
create_mapi_attachment (LPMESSAGE message, sink_t sink,
const char *overrideMimeTag)
{
HRESULT hr;
ULONG pos;
SPropValue prop;
LPATTACH att = NULL;
LPUNKNOWN punk;
sink->cb_data = NULL;
sink->writefnc = NULL;
hr = message->CreateAttach(NULL, 0, &pos, &att);
if (hr)
{
log_error ("%s:%s: can't create attachment: hr=%#lx\n",
SRCNAME, __func__, hr);
return NULL;
}
prop.ulPropTag = PR_ATTACH_METHOD;
prop.Value.ul = ATTACH_BY_VALUE;
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
if (hr)
{
log_error ("%s:%s: can't set attach method: hr=%#lx\n",
SRCNAME, __func__, hr);
goto failure;
}
/* Mark that attachment so that we know why it has been created. */
if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
goto failure;
prop.Value.l = ATTACHTYPE_MOSSTEMPL;
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
if (hr)
{
log_error ("%s:%s: can't set %s property: hr=%#lx\n",
SRCNAME, __func__, "GpgOL Attach Type", hr);
goto failure;
}
/* We better insert a short filename. */
prop.ulPropTag = PR_ATTACH_FILENAME_A;
prop.Value.lpszA = strdup (MIMEATTACHFILENAME);
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
xfree (prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
SRCNAME, __func__, hr);
goto failure;
}
/* Even for encrypted messages we need to set the MAPI property to
multipart/signed. This seems to be a part of the trigger which
leads OL to process such a message in a special way. */
prop.ulPropTag = PR_ATTACH_TAG;
prop.Value.bin.cb = sizeof oid_mimetag;
prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
if (!hr)
{
prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
prop.Value.lpszA = overrideMimeTag ? strdup (overrideMimeTag) :
strdup ("multipart/signed");
if (overrideMimeTag)
{
log_debug ("%s:%s: using override mimetag: %s\n",
SRCNAME, __func__, overrideMimeTag);
}
hr = HrSetOneProp ((LPMAPIPROP)att, &prop);
xfree (prop.Value.lpszA);
}
if (hr)
{
log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
SRCNAME, __func__, hr);
goto failure;
}
punk = NULL;
hr = att->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0,
(MAPI_CREATE|MAPI_MODIFY), &punk);
if (FAILED (hr))
{
log_error ("%s:%s: can't create output stream: hr=%#lx\n",
SRCNAME, __func__, hr);
goto failure;
}
sink->cb_data = (LPSTREAM)punk;
sink->writefnc = sink_std_write;
return att;
failure:
gpgol_release (att);
return NULL;
}
/* Write data to a sink_t. */
int
write_buffer (sink_t sink, const void *data, size_t datalen)
{
if (!sink || !sink->writefnc)
{
log_error ("%s:%s: sink not properly setup", SRCNAME, __func__);
return -1;
}
return sink->writefnc (sink, data, datalen);
}
/* Same as above but used for passing as callback function. This
fucntion does not return an error code but the number of bytes
written. */
int
write_buffer_for_cb (void *opaque, const void *data, size_t datalen)
{
sink_t sink = (sink_t) opaque;
sink->enc_counter += datalen;
return write_buffer (sink, data, datalen) ? -1 : datalen;
}
/* Write the string TEXT to the IStream STREAM. Returns 0 on sucsess,
prints an error message and returns -1 on error. */
int
write_string (sink_t sink, const char *text)
{
return write_buffer (sink, text, strlen (text));
}
/* Write the string TEXT1 and all folloing arguments of type (const
char*) to the SINK. The list of argumens needs to be terminated
with a NULL. Returns 0 on sucsess, prints an error message and
returns -1 on error. */
static int
write_multistring (sink_t sink, const char *text1, ...)
{
va_list arg_ptr;
int rc;
const char *s;
va_start (arg_ptr, text1);
s = text1;
do
rc = write_string (sink, s);
while (!rc && (s=va_arg (arg_ptr, const char *)));
va_end (arg_ptr);
return rc;
}
/* Helper to write a boundary to the output sink. The leading LF
will be written as well. */
int
write_boundary (sink_t sink, const char *boundary, int lastone)
{
int rc = write_string (sink, "\r\n--");
if (!rc)
rc = write_string (sink, boundary);
if (!rc)
rc = write_string (sink, lastone? "--\r\n":"\r\n");
return rc;
}
/* Write DATALEN bytes of DATA to SINK in base64 encoding. This
creates a complete Base64 chunk including the trailing fillers. */
int
write_b64 (sink_t sink, const void *data, size_t datalen)
{
int rc;
const unsigned char *p;
unsigned char inbuf[4];
int idx, quads;
char outbuf[2048];
size_t outlen;
log_debug (" writing base64 of length %d\n", (int)datalen);
idx = quads = 0;
outlen = 0;
for (p = (const unsigned char*)data; datalen; p++, datalen--)
{
inbuf[idx++] = *p;
if (idx > 2)
{
/* We need space for a quad and a possible CR,LF. */
if (outlen+4+2 >= sizeof outbuf)
{
if ((rc = write_buffer (sink, outbuf, outlen)))
return rc;
outlen = 0;
}
outbuf[outlen++] = bintoasc[(*inbuf>>2)&077];
outbuf[outlen++] = bintoasc[(((*inbuf<<4)&060)
|((inbuf[1] >> 4)&017))&077];
outbuf[outlen++] = bintoasc[(((inbuf[1]<<2)&074)
|((inbuf[2]>>6)&03))&077];
outbuf[outlen++] = bintoasc[inbuf[2]&077];
idx = 0;
if (++quads >= (64/4))
{
quads = 0;
outbuf[outlen++] = '\r';
outbuf[outlen++] = '\n';
}
}
}
/* We need space for a quad and a final CR,LF. */
if (outlen+4+2 >= sizeof outbuf)
{
if ((rc = write_buffer (sink, outbuf, outlen)))
return rc;
outlen = 0;
}
if (idx)
{
outbuf[outlen++] = bintoasc[(*inbuf>>2)&077];
if (idx == 1)
{
outbuf[outlen++] = bintoasc[((*inbuf<<4)&060)&077];
outbuf[outlen++] = '=';
outbuf[outlen++] = '=';
}
else
{
outbuf[outlen++] = bintoasc[(((*inbuf<<4)&060)
|((inbuf[1]>>4)&017))&077];
outbuf[outlen++] = bintoasc[((inbuf[1]<<2)&074)&077];
outbuf[outlen++] = '=';
}
++quads;
}
if (quads)
{
outbuf[outlen++] = '\r';
outbuf[outlen++] = '\n';
}
if (outlen)
{
if ((rc = write_buffer (sink, outbuf, outlen)))
return rc;
}
return 0;
}
/* Write DATALEN bytes of DATA to SINK in quoted-prinable encoding. */
static int
write_qp (sink_t sink, const void *data, size_t datalen)
{
int rc;
const unsigned char *p;
char outbuf[80]; /* We only need 76 octect + 2 for the lineend. */
int outidx;
/* Check whether the current character is followed by a line ending.
Note that the end of the etxt also counts as a lineending */
#define nextlf_p() ((datalen > 2 && p[1] == '\r' && p[2] == '\n') \
|| (datalen > 1 && p[1] == '\n') \
|| datalen == 1 )
/* Macro to insert a soft line break if needed. */
# define do_softlf(n) \
do { \
if (outidx + (n) > 76 \
|| (outidx + (n) == 76 && !nextlf_p())) \
{ \
outbuf[outidx++] = '='; \
outbuf[outidx++] = '\r'; \
outbuf[outidx++] = '\n'; \
if ((rc = write_buffer (sink, outbuf, outidx))) \
return rc; \
outidx = 0; \
} \
} while (0)
log_debug (" writing qp of length %d\n", (int)datalen);
outidx = 0;
for (p = (const unsigned char*) data; datalen; p++, datalen--)
{
if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
{
/* Line break. */
outbuf[outidx++] = '\r';
outbuf[outidx++] = '\n';
if ((rc = write_buffer (sink, outbuf, outidx)))
return rc;
outidx = 0;
if (*p == '\r')
{
p++;
datalen--;
}
}
else if (*p == '\t' || *p == ' ')
{
/* Check whether tab or space is followed by a line break
which forbids verbatim encoding. If we are already at
the end of the buffer we take that as a line end too. */
if (nextlf_p())
{
do_softlf (3);
outbuf[outidx++] = '=';
outbuf[outidx++] = tohex ((*p>>4)&15);
outbuf[outidx++] = tohex (*p&15);
}
else
{
do_softlf (1);
outbuf[outidx++] = *p;
}
}
else if (!outidx && *p == '.' && nextlf_p () )
{
/* We better protect a line with just a single dot. */
outbuf[outidx++] = '=';
outbuf[outidx++] = tohex ((*p>>4)&15);
outbuf[outidx++] = tohex (*p&15);
}
else if (!outidx && datalen >= 5 && !memcmp (p, "From ", 5))
{
/* Protect the 'F' so that MTAs won't prefix the "From "
with an '>' */
outbuf[outidx++] = '=';
outbuf[outidx++] = tohex ((*p>>4)&15);
outbuf[outidx++] = tohex (*p&15);
}
else if (*p >= '!' && *p <= '~' && *p != '=')
{
do_softlf (1);
outbuf[outidx++] = *p;
}
else
{
do_softlf (3);
outbuf[outidx++] = '=';
outbuf[outidx++] = tohex ((*p>>4)&15);
outbuf[outidx++] = tohex (*p&15);
}
}
if (outidx)
{
outbuf[outidx++] = '\r';
outbuf[outidx++] = '\n';
if ((rc = write_buffer (sink, outbuf, outidx)))
return rc;
}
# undef do_softlf
# undef nextlf_p
return 0;
}
/* Write DATALEN bytes of DATA to SINK in plain ascii encoding. */
static int
write_plain (sink_t sink, const void *data, size_t datalen)
{
int rc;
const unsigned char *p;
char outbuf[100];
int outidx;
log_debug (" writing ascii of length %d\n", (int)datalen);
outidx = 0;
for (p = (const unsigned char*) data; datalen; p++, datalen--)
{
if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
{
outbuf[outidx++] = '\r';
outbuf[outidx++] = '\n';
if ((rc = write_buffer (sink, outbuf, outidx)))
return rc;
outidx = 0;
if (*p == '\r')
{
p++;
datalen--;
}
}
else if (!outidx && *p == '.'
&& ( (datalen > 2 && p[1] == '\r' && p[2] == '\n')
|| (datalen > 1 && p[1] == '\n')
|| datalen == 1))
{
/* Better protect a line with just a single dot. We do
this by adding a space. */
outbuf[outidx++] = *p;
outbuf[outidx++] = ' ';
}
else if (outidx > 80)
{
/* We should never be called for too long lines - QP should
have been used. */
log_error ("%s:%s: BUG: line longer than exepcted",
SRCNAME, __func__);
return -1;
}
else
outbuf[outidx++] = *p;
}
if (outidx)
{
outbuf[outidx++] = '\r';
outbuf[outidx++] = '\n';
if ((rc = write_buffer (sink, outbuf, outidx)))
return rc;
}
return 0;
}
/* Infer the conent type from the FILENAME. The return value is
a static string there won't be an error return. In case Bae 64
encoding is required for the type true will be stored at FORCE_B64;
however, this is only a shortcut and if that is not set, the caller
should infer the encoding by other means. */
static const char *
infer_content_type (const char * /*data*/, size_t /*datalen*/,
const char *filename, int is_mapibody, int *force_b64)
{
static struct {
char b64;
const char *suffix;
const char *ct;
} suffix_table[] =
{
{ 1, "3gp", "video/3gpp" },
{ 1, "abw", "application/x-abiword" },
{ 1, "ai", "application/postscript" },
{ 1, "au", "audio/basic" },
{ 1, "bin", "application/octet-stream" },
{ 1, "class", "application/java-vm" },
{ 1, "cpt", "application/mac-compactpro" },
{ 0, "css", "text/css" },
{ 0, "csv", "text/comma-separated-values" },
{ 1, "deb", "application/x-debian-package" },
{ 1, "dl", "video/dl" },
{ 1, "doc", "application/msword" },
{ 1, "dv", "video/dv" },
{ 1, "dvi", "application/x-dvi" },
{ 1, "eml", "message/rfc822" },
{ 1, "eps", "application/postscript" },
{ 1, "fig", "application/x-xfig" },
{ 1, "flac", "application/x-flac" },
{ 1, "fli", "video/fli" },
{ 1, "gif", "image/gif" },
{ 1, "gl", "video/gl" },
{ 1, "gnumeric", "application/x-gnumeric" },
{ 1, "hqx", "application/mac-binhex40" },
{ 1, "hta", "application/hta" },
{ 0, "htm", "text/html" },
{ 0, "html", "text/html" },
{ 0, "ics", "text/calendar" },
{ 1, "jar", "application/java-archive" },
{ 1, "jpeg", "image/jpeg" },
{ 1, "jpg", "image/jpeg" },
{ 1, "js", "application/x-javascript" },
{ 1, "latex", "application/x-latex" },
{ 1, "lha", "application/x-lha" },
{ 1, "lzh", "application/x-lzh" },
{ 1, "lzx", "application/x-lzx" },
{ 1, "m3u", "audio/mpegurl" },
{ 1, "m4a", "audio/mpeg" },
{ 1, "mdb", "application/msaccess" },
{ 1, "midi", "audio/midi" },
{ 1, "mov", "video/quicktime" },
{ 1, "mp2", "audio/mpeg" },
{ 1, "mp3", "audio/mpeg" },
{ 1, "mp4", "video/mp4" },
{ 1, "mpeg", "video/mpeg" },
{ 1, "mpega", "audio/mpeg" },
{ 1, "mpg", "video/mpeg" },
{ 1, "mpga", "audio/mpeg" },
{ 1, "msi", "application/x-msi" },
{ 1, "mxu", "video/vnd.mpegurl" },
{ 1, "nb", "application/mathematica" },
{ 1, "oda", "application/oda" },
{ 1, "odb", "application/vnd.oasis.opendocument.database" },
{ 1, "odc", "application/vnd.oasis.opendocument.chart" },
{ 1, "odf", "application/vnd.oasis.opendocument.formula" },
{ 1, "odg", "application/vnd.oasis.opendocument.graphics" },
{ 1, "odi", "application/vnd.oasis.opendocument.image" },
{ 1, "odm", "application/vnd.oasis.opendocument.text-master" },
{ 1, "odp", "application/vnd.oasis.opendocument.presentation" },
{ 1, "ods", "application/vnd.oasis.opendocument.spreadsheet" },
{ 1, "odt", "application/vnd.oasis.opendocument.text" },
{ 1, "ogg", "application/ogg" },
{ 1, "otg", "application/vnd.oasis.opendocument.graphics-template" },
{ 1, "oth", "application/vnd.oasis.opendocument.text-web" },
{ 1, "otp", "application/vnd.oasis.opendocument.presentation-template"},
{ 1, "ots", "application/vnd.oasis.opendocument.spreadsheet-template"},
{ 1, "ott", "application/vnd.oasis.opendocument.text-template" },
{ 1, "pdf", "application/pdf" },
{ 1, "png", "image/png" },
{ 1, "pps", "application/vnd.ms-powerpoint" },
{ 1, "ppt", "application/vnd.ms-powerpoint" },
{ 1, "prf", "application/pics-rules" },
{ 1, "ps", "application/postscript" },
{ 1, "qt", "video/quicktime" },
{ 1, "rar", "application/rar" },
{ 1, "rdf", "application/rdf+xml" },
{ 1, "rpm", "application/x-redhat-package-manager" },
{ 0, "rss", "application/rss+xml" },
{ 1, "ser", "application/java-serialized-object" },
{ 0, "sh", "application/x-sh" },
{ 0, "shtml", "text/html" },
{ 1, "sid", "audio/prs.sid" },
{ 0, "smil", "application/smil" },
{ 1, "snd", "audio/basic" },
{ 0, "svg", "image/svg+xml" },
{ 1, "tar", "application/x-tar" },
{ 0, "texi", "application/x-texinfo" },
{ 0, "texinfo", "application/x-texinfo" },
{ 1, "tif", "image/tiff" },
{ 1, "tiff", "image/tiff" },
{ 1, "torrent", "application/x-bittorrent" },
{ 1, "tsp", "application/dsptype" },
{ 0, "vrml", "model/vrml" },
{ 1, "vsd", "application/vnd.visio" },
{ 1, "wp5", "application/wordperfect5.1" },
{ 1, "wpd", "application/wordperfect" },
{ 0, "xhtml", "application/xhtml+xml" },
{ 1, "xlb", "application/vnd.ms-excel" },
{ 1, "xls", "application/vnd.ms-excel" },
{ 1, "xlt", "application/vnd.ms-excel" },
{ 0, "xml", "application/xml" },
{ 0, "xsl", "application/xml" },
{ 0, "xul", "application/vnd.mozilla.xul+xml" },
{ 1, "zip", "application/zip" },
{ 0, NULL, NULL }
};
int i;
std::string suffix;
*force_b64 = 0;
if (filename)
suffix = strrchr (filename, '.');
if (!suffix.empty())
{
suffix.erase(0, 1);
std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);
for (i=0; suffix_table[i].suffix; i++)
{
if (!strcmp (suffix_table[i].suffix, suffix.c_str()))
{
if (suffix_table[i].b64)
*force_b64 = 1;
return suffix_table[i].ct;
}
}
}
/* Not found via filename, look at the content. */
if (is_mapibody == 1)
{
return "text/plain";
}
else if (is_mapibody == 2)
{
return "text/html";
}
return "application/octet-stream";
}
/* Figure out the best encoding to be used for the part. Return values are
0: Plain ASCII.
1: Quoted Printable
2: Base64 */
static int
infer_content_encoding (const void *data, size_t datalen)
{
const unsigned char *p;
int need_qp;
size_t len, maxlen, highbin, lowbin, ntotal;
ntotal = datalen;
len = maxlen = lowbin = highbin = 0;
need_qp = 0;
for (p = (const unsigned char*) data; datalen; p++, datalen--)
{
len++;
if ((*p & 0x80))
highbin++;
else if ((datalen > 1 && *p == '\r' && p[1] == '\n') || *p == '\n')
{
len--;
if (len > maxlen)
maxlen = len;
len = 0;
}
else if (*p == '\r')
{
/* CR not followed by a linefeed. */
lowbin++;
}
else if (*p == '\t' || *p == ' ' || *p == '\f')
;
else if (*p < ' ' || *p == 127)
lowbin++;
else if (len == 1 && datalen > 2
&& *p == '-' && p[1] == '-' && p[2] == ' '
&& ( (datalen > 4 && p[3] == '\r' && p[4] == '\n')
|| (datalen > 3 && p[3] == '\n')
|| datalen == 3))
{
/* This is a "-- \r\n" line, thus it indicates the usual
signature line delimiter. We need to protect the
trailing space. */
need_qp = 1;
}
else if (len == 1 && datalen > 5 && !memcmp (p, "--=-=", 5))
{
/* This look pretty much like a our own boundary.
We better protect it by forcing QP encoding. */
need_qp = 1;
}
else if (len == 1 && datalen >= 5 && !memcmp (p, "From ", 5))
{
/* The usual From hack is required so that MTAs do not
prefix it with an '>'. */
need_qp = 1;
}
}
if (len > maxlen)
maxlen = len;
if (maxlen <= 76 && !lowbin && !highbin && !need_qp)
return 0; /* Plain ASCII is sufficient. */
/* Somewhere in the Outlook documentation 20% is mentioned as
discriminating value for Base64. Though our counting won't be
identical we use that value to behave closely to it. */
if (ntotal && ((float)(lowbin+highbin))/ntotal < 0.20)
return 1; /* Use quoted printable. */
return 2; /* Use base64. */
}
/* Convert an utf8 input string to RFC2047 base64 encoding which
is the subset of RFC2047 outlook likes.
Return value needs to be freed.
*/
static char *
utf8_to_rfc2047b (const char *input)
{
char *ret,
*encoded;
int inferred_encoding = 0;
if (!input)
{
return NULL;
}
inferred_encoding = infer_content_encoding (input, strlen (input));
if (!inferred_encoding)
{
return xstrdup (input);
}
log_debug ("%s:%s: Encoding attachment filename. With: %s ",
SRCNAME, __func__, inferred_encoding == 2 ? "Base64" : "QP");
if (inferred_encoding == 2)
{
encoded = b64_encode (input, strlen (input));
if (gpgrt_asprintf (&ret, "=?utf-8?B?%s?=", encoded) == -1)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
xfree (encoded);
return NULL;
}
}
else
{
/* There is a Bug here. If you encode 4 Byte UTF-8 outlook can't
handle it itself. And sends out a message with ?? inserted in
that place. This triggers an invalid signature. */
encoded = qp_encode (input, strlen (input), NULL);
if (gpgrt_asprintf (&ret, "=?utf-8?Q?%s?=", encoded) == -1)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
xfree (encoded);
return NULL;
}
}
xfree (encoded);
return ret;
}
/* Write a MIME part to SINK. First the BOUNDARY is written (unless
it is NULL) then the DATA is analyzed and appropriate headers are
written. If FILENAME is given it will be added to the part's
header. IS_MAPIBODY should be passed as true if the data has been
retrieved from the body property. */
static int
write_part (sink_t sink, const char *data, size_t datalen,
const char *boundary, const char *filename, int is_mapibody,
const char *content_id = NULL)
{
int rc;
const char *ct;
int use_b64, use_qp, is_text;
char *encoded_filename;
if (filename)
{
/* If there is a filename strip the directory part. Take care
that there might be slashes or backslashes. */
const char *s1 = strrchr (filename, '/');
const char *s2 = strrchr (filename, '\\');
if (!s1)
s1 = s2;
else if (s1 && s2 && s2 > s1)
s1 = s2;
if (s1)
filename = s1;
if (*filename && filename[1] == ':')
filename += 2;
if (!*filename)
filename = NULL;
}
log_debug ("Writing part of length %d%s filename=`%s'\n",
(int)datalen, is_mapibody? " (body)":"",
filename?filename:"[none]");
ct = infer_content_type (data, datalen, filename, is_mapibody, &use_b64);
use_qp = 0;
if (!use_b64)
{
switch (infer_content_encoding (data, datalen))
{
case 0: break;
case 1: use_qp = 1; break;
default: use_b64 = 1; break;
}
}
is_text = !strncmp (ct, "text/", 5);
if (boundary)
if ((rc = write_boundary (sink, boundary, 0)))
return rc;
if ((rc=write_multistring (sink,
"Content-Type: ", ct,
(is_text || filename? ";\r\n" :"\r\n"),
NULL)))
return rc;
/* OL inserts a charset parameter in many cases, so we do it right
away for all text parts. We can assume us-ascii if no special
encoding is required. */
if (is_text)
if ((rc=write_multistring (sink,
"\tcharset=\"",
(!use_qp && !use_b64? "us-ascii" : "utf-8"),
filename ? "\";\r\n" : "\"\r\n",
NULL)))
return rc;
encoded_filename = utf8_to_rfc2047b (filename);
if (encoded_filename)
if ((rc=write_multistring (sink,
"\tname=\"", encoded_filename, "\"\r\n",
NULL)))
return rc;
/* Note that we need to output even 7bit because OL inserts that
anyway. */
if ((rc = write_multistring (sink,
"Content-Transfer-Encoding: ",
(use_b64? "base64\r\n":
use_qp? "quoted-printable\r\n":"7bit\r\n"),
NULL)))
return rc;
if (content_id)
{
if ((rc=write_multistring (sink,
"Content-ID: <", content_id, ">\r\n",
NULL)))
return rc;
}
else if (encoded_filename)
if ((rc=write_multistring (sink,
"Content-Disposition: attachment;\r\n"
"\tfilename=\"", encoded_filename, "\"\r\n",
NULL)))
return rc;
xfree(encoded_filename);
/* Write delimiter. */
if ((rc = write_string (sink, "\r\n")))
return rc;
/* Write the content. */
if (use_b64)
rc = write_b64 (sink, data, datalen);
else if (use_qp)
rc = write_qp (sink, data, datalen);
else
rc = write_plain (sink, data, datalen);
return rc;
}
/* Return the number of attachments in TABLE to be put into the MIME
message. */
int
count_usable_attachments (mapi_attach_item_t *table)
{
int idx, count = 0;
if (table)
for (idx=0; !table[idx].end_of_table; idx++)
if (table[idx].attach_type == ATTACHTYPE_UNKNOWN
&& table[idx].method == ATTACH_BY_VALUE)
count++;
return count;
}
/* Write out all attachments from TABLE separated by BOUNDARY to SINK.
This function needs to be syncronized with count_usable_attachments.
If only_related is 1 only include attachments for multipart/related they
are excluded otherwise. */
static int
write_attachments (sink_t sink,
LPMESSAGE message, mapi_attach_item_t *table,
const char *boundary, int only_related)
{
int idx, rc;
char *buffer;
size_t buflen;
if (table)
for (idx=0; !table[idx].end_of_table; idx++)
if (table[idx].attach_type == ATTACHTYPE_UNKNOWN
&& table[idx].method == ATTACH_BY_VALUE)
{
if (only_related && !table[idx].content_id)
{
continue;
}
else if (!only_related && table[idx].content_id)
{
continue;
}
buffer = mapi_get_attach (message, table+idx, &buflen);
if (!buffer)
log_debug ("Attachment at index %d not found\n", idx);
else
log_debug ("Attachment at index %d: length=%d\n", idx, (int)buflen);
if (!buffer)
return -1;
rc = write_part (sink, buffer, buflen, boundary,
table[idx].filename, 0, table[idx].content_id);
if (rc)
{
log_error ("Write part returned err: %i", rc);
}
xfree (buffer);
}
return 0;
}
/* Returns 1 if all attachments are related. 2 if there is a
related and a mixed attachment. 0 if there are no other parts*/
static int
is_related (Mail *mail, mapi_attach_item_t *table)
{
if (!mail || !mail->is_html_alternative () || !table)
{
return 0;
}
int related = 0;
int mixed = 0;
for (int idx = 0; !table[idx].end_of_table; idx++)
{
if (table[idx].content_id)
{
related = 1;
}
else
{
mixed = 1;
}
}
return mixed + related;
}
/* Delete all attachments from TABLE except for the one we just created */
static int
delete_all_attachments (LPMESSAGE message, mapi_attach_item_t *table)
{
HRESULT hr;
int idx;
if (table)
for (idx=0; !table[idx].end_of_table; idx++)
{
if (table[idx].attach_type == ATTACHTYPE_MOSSTEMPL
&& table[idx].filename
&& !strcmp (table[idx].filename, MIMEATTACHFILENAME))
continue;
hr = message->DeleteAttach (table[idx].mapipos, 0, NULL, 0);
if (hr)
{
log_error ("%s:%s: DeleteAttach failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
}
return 0;
}
/* Commit changes to the attachment ATTACH and release the object.
SINK needs to be passed as well and will also be closed. Note that
the address of ATTACH is expected so that the fucntion can set it
to NULL. */
int
close_mapi_attachment (LPATTACH *attach, sink_t sink)
{
HRESULT hr;
LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
if (!stream)
{
log_error ("%s:%s: sink not setup", SRCNAME, __func__);
return -1;
}
hr = stream->Commit (0);
if (hr)
{
log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
SRCNAME, __func__, hr);
return -1;
}
gpgol_release (stream);
sink->cb_data = NULL;
hr = (*attach)->SaveChanges (0);
if (hr)
{
log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
gpgol_release ((*attach));
*attach = NULL;
return 0;
}
/* Cancel changes to the attachment ATTACH and release the object.
SINK needs to be passed as well and will also be closed. Note that
the address of ATTACH is expected so that the fucntion can set it
to NULL. */
void
cancel_mapi_attachment (LPATTACH *attach, sink_t sink)
{
LPSTREAM stream = sink ? (LPSTREAM) sink->cb_data : NULL;
if (stream)
{
stream->Revert();
gpgol_release (stream);
sink->cb_data = NULL;
}
if (*attach)
{
/* Fixme: Should we try to delete it or is there a Revert method? */
gpgol_release ((*attach));
*attach = NULL;
}
}
/* Do the final processing for a message. */
int
finalize_message (LPMESSAGE message, mapi_attach_item_t *att_table,
protocol_t protocol, int encrypt, bool is_inline)
{
HRESULT hr = 0;
SPropValue prop;
SPropTagArray proparray;
/* Set the message class. */
prop.ulPropTag = PR_MESSAGE_CLASS_A;
if (encrypt)
{
prop.Value.lpszA = strdup ("IPM.Note.InfoPathForm.GpgOL.SMIME.MultipartSigned");
}
else
{
prop.Value.lpszA = strdup ("IPM.Note.InfoPathForm.GpgOLS.SMIME.MultipartSigned");
}
if (!is_inline)
{
/* For inline we stick with IPM.Note because Exchange Online would
error out if we tried our S/MIME conversion trick with a text
plain message */
hr = message->SetProps(1, &prop, NULL);
}
xfree(prop.Value.lpszA);
if (hr)
{
log_error ("%s:%s: error setting the message class: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
/* Set a special property so that we are later able to identify
messages signed or encrypted by us. */
if (mapi_set_sig_status (message, "@"))
{
log_error ("%s:%s: error setting sigstatus",
SRCNAME, __func__);
return -1;
}
/* We also need to set the message class into our custom
property. This override is at least required for encrypted
messages. */
if (is_inline && mapi_set_gpgol_msg_class (message,
(encrypt?
(protocol == PROTOCOL_SMIME?
"IPM.Note.GpgOL.OpaqueEncrypted" :
"IPM.Note.GpgOL.PGPMessage") :
"IPM.Note.GpgOL.ClearSigned")))
{
log_error ("%s:%s: error setting gpgol msgclass",
SRCNAME, __func__);
return -1;
}
if (!is_inline && mapi_set_gpgol_msg_class (message,
(encrypt?
(protocol == PROTOCOL_SMIME?
"IPM.Note.GpgOL.OpaqueEncrypted" :
"IPM.Note.GpgOL.MultipartEncrypted") :
"IPM.Note.GpgOL.MultipartSigned")))
{
log_error ("%s:%s: error setting gpgol msgclass",
SRCNAME, __func__);
return -1;
}
proparray.cValues = 1;
proparray.aulPropTag[0] = PR_BODY;
hr = message->DeleteProps (&proparray, NULL);
if (hr)
{
log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed",
SRCNAME, __func__);
}
proparray.cValues = 1;
proparray.aulPropTag[0] = PR_BODY_HTML;
hr = message->DeleteProps (&proparray, NULL);
if (hr)
{
log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed",
SRCNAME, __func__);
}
/* Now delete all parts of the MAPI message except for the one
attachment we just created. */
if (delete_all_attachments (message, att_table))
{
log_error ("%s:%s: error deleting attachments",
SRCNAME, __func__);
return -1;
}
/* Remove the draft info so that we don't leak the information on
whether the message has been signed etc. */
mapi_set_gpgol_draft_info (message, NULL);
if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE))
{
log_error ("%s:%s: error saving changes.",
SRCNAME, __func__);
return -1;
}
return 0;
}
-
-/* Sink write method used by mime_sign. We write the data to the
- filter and also to the EXTRASINK but we don't pass a flush request
- to EXTRASINK. */
-static int
-sink_hashing_write (sink_t hashsink, const void *data, size_t datalen)
-{
- int rc;
- engine_filter_t filter = (engine_filter_t) hashsink->cb_data;
-
- if (!filter || !hashsink->extrasink)
- {
- log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
- return -1;
- }
-
- rc = engine_filter (filter, data, datalen);
- if (!rc && data && datalen)
- write_buffer (hashsink->extrasink, data, datalen);
- return rc;
-}
-
-
-/* This function is called by the filter to collect the output which
- is a detached signature. */
-static int
-collect_signature (void *opaque, const void *data, size_t datalen)
-{
- struct databuf_s *db = (databuf_s *)opaque;
-
- if (db->len + datalen >= db->size)
- {
- db->size += datalen + 1024;
- db->buf = (char*) xrealloc (db->buf, db->size);
- }
- memcpy (db->buf + db->len, data, datalen);
- db->len += datalen;
-
- return datalen;
-}
-
-
/* Helper to create the signing header. This includes enough space
for later fixup of the micalg parameter. The MIME version is only
written if FIRST is set. */
void
create_top_signing_header (char *buffer, size_t buflen, protocol_t protocol,
int first, const char *boundary, const char *micalg)
{
snprintf (buffer, buflen,
"%s"
"Content-Type: multipart/signed;\r\n"
"\tprotocol=\"application/%s\";\r\n"
"\tmicalg=%-15.15s;\r\n"
"\tboundary=\"%s\"\r\n"
"\r\n",
first? "MIME-Version: 1.0\r\n":"",
(protocol==PROTOCOL_OPENPGP? "pgp-signature":"pkcs7-signature"),
micalg, boundary);
}
/* Add the body, either as multipart/alternative or just as the
simple body part. Depending on the format set in outlook. To
avoid memory duplication it takes the plain body as parameter.
Boundary is the potential outer boundary of a multipart/mixed
mail. If it is null we assume the multipart/alternative is
the only part.
return is zero on success.
*/
static int
add_body (Mail *mail, const char *boundary, sink_t sink,
const char *plain_body)
{
if (!plain_body)
{
return 0;
}
bool is_alternative = false;
if (mail)
{
is_alternative = mail->is_html_alternative ();
}
int rc = 0;
if (!is_alternative || !plain_body)
{
if (plain_body)
{
rc = write_part (sink, plain_body, strlen (plain_body),
boundary, NULL, 1);
}
/* Just the plain body or no body. We are done. */
return rc;
}
/* Add a new multipart / mixed element. */
if (boundary && write_boundary (sink, boundary, 0))
{
TRACEPOINT;
return 1;
}
/* Now for the multipart/alternative part. We never do HTML only. */
char alt_boundary [BOUNDARYSIZE+1];
generate_boundary (alt_boundary);
if ((rc=write_multistring (sink,
"Content-Type: multipart/alternative;\r\n",
"\tboundary=\"", alt_boundary, "\"\r\n",
"\r\n", /* <-- extra line */
NULL)))
{
TRACEPOINT;
return rc;
}
/* Now the plain body part */
if ((rc = write_part (sink, plain_body, strlen (plain_body),
alt_boundary, NULL, 1)))
{
TRACEPOINT;
return rc;
}
/* Now the html body. It is somehow not accessible through PR_HTML,
OutlookSpy also shows MAPI Unsupported (but shows the data) strange.
We just cache it. Memory is cheap :-) */
char *html_body = mail->take_cached_html_body();
if (!html_body)
{
log_error ("%s:%s: BUG: Body but no html body in alternative mail?",
SRCNAME, __func__);
return -1;
}
rc = write_part (sink, html_body, strlen (html_body),
alt_boundary, NULL, 2);
xfree (html_body);
if (rc)
{
TRACEPOINT;
return rc;
}
/* Finish our multipart */
return write_boundary (sink, alt_boundary, 1);
}
/* Add the body and attachments. Does multipart handling. */
int
add_body_and_attachments (sink_t sink, LPMESSAGE message,
mapi_attach_item_t *att_table, Mail *mail,
const char *body, int n_att_usable)
{
int related = is_related (mail, att_table);
int rc = 0;
char inner_boundary[BOUNDARYSIZE+1];
char outer_boundary[BOUNDARYSIZE+1];
*outer_boundary = 0;
*inner_boundary = 0;
if (((body && n_att_usable) || n_att_usable > 1) && related == 1)
{
/* A body and at least one attachment or more than one attachment */
generate_boundary (outer_boundary);
if ((rc=write_multistring (sink,
"Content-Type: multipart/related;\r\n",
"\tboundary=\"", outer_boundary, "\"\r\n",
"\r\n", /* <--- Outlook adds an extra line. */
NULL)))
return rc;
}
else if ((body && n_att_usable) || n_att_usable > 1)
{
generate_boundary (outer_boundary);
if ((rc=write_multistring (sink,
"Content-Type: multipart/mixed;\r\n",
"\tboundary=\"", outer_boundary, "\"\r\n",
"\r\n", /* <--- Outlook adds an extra line. */
NULL)))
return rc;
}
/* Only one part. */
if (*outer_boundary && related == 2)
{
/* We have attachments that are related to the body and unrelated
attachments. So we need another part. */
if ((rc=write_boundary (sink, outer_boundary, 0)))
{
return rc;
}
generate_boundary (inner_boundary);
if ((rc=write_multistring (sink,
"Content-Type: multipart/related;\r\n",
"\tboundary=\"", inner_boundary, "\"\r\n",
"\r\n", /* <--- Outlook adds an extra line. */
NULL)))
{
return rc;
}
}
if ((rc=add_body (mail, *inner_boundary ? inner_boundary :
*outer_boundary ? outer_boundary : NULL,
sink, body)))
{
log_error ("%s:%s: Adding the body failed.",
SRCNAME, __func__);
return rc;
}
if (!rc && n_att_usable && related)
{
/* Write the related attachments. */
rc = write_attachments (sink, message, att_table,
*inner_boundary? inner_boundary :
*outer_boundary? outer_boundary : NULL, 1);
if (rc)
{
return rc;
}
/* Close the related part if neccessary.*/
if (*inner_boundary && (rc=write_boundary (sink, inner_boundary, 1)))
{
return rc;
}
}
/* Now write the other attachments */
if (!rc && n_att_usable)
rc = write_attachments (sink, message, att_table,
*outer_boundary? outer_boundary : NULL, 0);
/* Finish the possible multipart/mixed. */
if (*outer_boundary && (rc = write_boundary (sink, outer_boundary, 1)))
return rc;
return rc;
}
-/* Main body of mime_sign without the the code to delete the original
- attachments. On success the function returns the current
- attachment table at R_ATT_TABLE or sets this to NULL on error. If
- TMPSINK is set no attachment will be created but the output
- written to that sink. */
-static int
-do_mime_sign (LPMESSAGE message, HWND hwnd, protocol_t protocol,
- mapi_attach_item_t **r_att_table, sink_t tmpsink,
- unsigned int session_number, const char *sender,
- Mail *mail)
-{
- int result = -1;
- int rc;
- LPATTACH attach = NULL;
- struct sink_s sinkmem;
- sink_t sink = &sinkmem;
- struct sink_s hashsinkmem;
- sink_t hashsink = &hashsinkmem;
- char boundary[BOUNDARYSIZE+1];
- mapi_attach_item_t *att_table = NULL;
- char *body = NULL;
- int n_att_usable;
- char top_header[BOUNDARYSIZE+200];
- engine_filter_t filter = NULL;
- struct databuf_s sigbuffer;
- char *my_sender = NULL;
-
- *r_att_table = NULL;
-
- memset (sink, 0, sizeof *sink);
- memset (hashsink, 0, sizeof *hashsink);
- memset (&sigbuffer, 0, sizeof sigbuffer);
-
- if (tmpsink)
- {
- attach = NULL;
- sink = tmpsink;
- }
- else
- {
- attach = create_mapi_attachment (message, sink);
- if (!attach)
- return -1;
- }
-
- /* Take the Body from the mail if possible. This is a fix for
- GnuPG-Bug-ID: T3614 because the body is not always properly
- updated in MAPI when sending. */
- if (mail)
- {
- body = mail->take_cached_plain_body ();
- }
- /* Get the attachment info and the body. */
- if (!body)
- {
- body = mapi_get_body (message, NULL);
- }
- if (body && !*body)
- {
- xfree (body);
- body = NULL;
- }
- att_table = mapi_create_attach_table (message, 0);
- n_att_usable = count_usable_attachments (att_table);
- if (!n_att_usable && !body)
- {
- log_debug ("%s:%s: can't sign an empty message\n", SRCNAME, __func__);
- result = gpg_error (GPG_ERR_NO_DATA);
- goto failure;
- }
-
- /* Prepare the signing. */
- if (engine_create_filter (&filter, collect_signature, &sigbuffer))
- goto failure;
-
- if (session_number)
- {
- engine_set_session_number (filter, session_number);
- {
- char *tmp = mapi_get_subject (message);
- engine_set_session_title (filter, tmp);
- xfree (tmp);
- }
- }
-
- if (sender)
- my_sender = xstrdup (sender);
- else
- my_sender = mapi_get_sender (message);
- if (engine_sign_start (filter, hwnd, protocol, my_sender, &protocol))
- goto failure;
-
- protocol = check_protocol (protocol);
- if (protocol == PROTOCOL_UNKNOWN)
- {
- log_error ("%s:%s: no protocol selected", SRCNAME, __func__);
- goto failure;
- }
-
-
- /* Write the top header. */
- generate_boundary (boundary);
- create_top_signing_header (top_header, sizeof top_header,
- protocol, 1, boundary, "xxx");
- if ((rc = write_string (sink, top_header)))
- goto failure;
-
- /* Write the boundary so that it is not included in the hashing. */
- if ((rc = write_boundary (sink, boundary, 0)))
- goto failure;
-
- /* Create a new sink for hashing and write/hash our content. */
- hashsink->cb_data = filter;
- hashsink->extrasink = sink;
- hashsink->writefnc = sink_hashing_write;
-
- /* Add the plaintext */
- if (add_body_and_attachments (hashsink, message, att_table, mail,
- body, n_att_usable))
- goto failure;
-
- xfree (body);
- body = NULL;
-
- /* Here we are ready with the hashing. Flush the filter and wait
- for the signing process to finish. */
- if ((rc = write_buffer (hashsink, NULL, 0)))
- goto failure;
- if ((rc = engine_wait (filter)))
- goto failure;
- filter = NULL; /* Not valid anymore. */
- hashsink->cb_data = NULL; /* Not needed anymore. */
-
-
- /* Write signature attachment. */
- if ((rc = write_boundary (sink, boundary, 0)))
- goto failure;
-
- if (protocol == PROTOCOL_OPENPGP)
- {
- rc = write_string (sink,
- "Content-Type: application/pgp-signature\r\n");
- }
- else
- {
- rc = write_string (sink,
- "Content-Transfer-Encoding: base64\r\n"
- "Content-Type: application/pkcs7-signature\r\n");
- /* rc = write_string (sink, */
- /* "Content-Type: application/x-pkcs7-signature\r\n" */
- /* "\tname=\"smime.p7s\"\r\n" */
- /* "Content-Transfer-Encoding: base64\r\n" */
- /* "Content-Disposition: attachment;\r\n" */
- /* "\tfilename=\"smime.p7s\"\r\n"); */
-
- }
- /* About the above code:
- If we would add "Content-Transfer-Encoding: 7bit\r\n" to this
- attachment, Outlooks does not proceed with sending and even does
- not return any error. A wild guess is that while OL adds this
- header itself, it detects that it already exists and somehow gets
- into a problem. It is not a problem with the other parts,
- though. Hmmm, triggered by the top levels CT protocol parameter?
- Anyway, it is not required that we add it as we won't hash it.
- Note, that this only holds for OpenPGP; for S/MIME we need to set
- set CTE. We even write it before the CT because that is the same
- as Outlook would do it for a missing CTE. */
- if (rc)
- goto failure;
-
- if ((rc = write_string (sink, "\r\n")))
- goto failure;
-
- /* Write the signature. We add an extra CR,LF which should not harm
- and a terminating 0. */
- collect_signature (&sigbuffer, "\r\n", 3);
- if ((rc = write_string (sink, sigbuffer.buf)))
- goto failure;
-
-
- /* Write the final boundary and finish the attachment. */
- if ((rc = write_boundary (sink, boundary, 1)))
- goto failure;
-
- /* Fixup the micalg parameter. */
- {
- HRESULT hr;
- LARGE_INTEGER off;
- LPSTREAM stream = (LPSTREAM) sink->cb_data;
-
- off.QuadPart = 0;
- hr = stream->Seek (off, STREAM_SEEK_SET, NULL);
- if (hr)
- {
- log_error ("%s:%s: seeking back to the begin failed: hr=%#lx",
- SRCNAME, __func__, hr);
- goto failure;
- }
-
- create_top_signing_header (top_header, sizeof top_header,
- protocol, 1, boundary,
- protocol == PROTOCOL_SMIME? "sha1":"pgp-sha1");
-
- hr = stream->Write (top_header, strlen (top_header), NULL);
- if (hr)
- {
- log_error ("%s:%s: writing fixed micalg failed: hr=%#lx",
- SRCNAME, __func__, hr);
- goto failure;
- }
-
- /* Better seek again to the end. */
- off.QuadPart = 0;
- hr = stream->Seek (off, STREAM_SEEK_END, NULL);
- if (hr)
- {
- log_error ("%s:%s: seeking back to the end failed: hr=%#lx",
- SRCNAME, __func__, hr);
- goto failure;
- }
- }
-
-
- if (attach)
- {
- if (close_mapi_attachment (&attach, sink))
- goto failure;
- }
-
- result = 0; /* Everything is fine, fall through the cleanup now. */
-
- failure:
- engine_cancel (filter);
- if (attach)
- cancel_mapi_attachment (&attach, sink);
- xfree (body);
- if (result)
- mapi_release_attach_table (att_table);
- else
- *r_att_table = att_table;
- xfree (sigbuffer.buf);
- xfree (my_sender);
- return result;
-}
-
-
-/* Sign the MESSAGE using PROTOCOL. If PROTOCOL is PROTOCOL_UNKNOWN
- the engine decides what protocol to use. On return MESSAGE is
- modified so that sending it will result in a properly MOSS (that is
- PGP or S/MIME) signed message. On failure the function tries to
- keep the original message intact but there is no 100% guarantee for
- it. */
-int
-mime_sign (LPMESSAGE message, HWND hwnd, protocol_t protocol,
- const char *sender, Mail *mail)
-{
- int result = -1;
- mapi_attach_item_t *att_table;
-
- result = do_mime_sign (message, hwnd, protocol, &att_table, 0,
- engine_new_session_number (), sender, mail);
- if (!result)
- {
- if (!finalize_message (message, att_table, protocol, 0))
- result = 0;
- }
-
- mapi_release_attach_table (att_table);
- return result;
-}
-
-
-
-/* Sink write method used by mime_encrypt. */
-int
-sink_encryption_write (sink_t encsink, const void *data, size_t datalen)
-{
- engine_filter_t filter = (engine_filter_t) encsink->cb_data;
-
- if (!filter)
- {
- log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
- return -1;
- }
-
- return engine_filter (filter, data, datalen);
-}
-
-
-#if 0 /* Not used. */
-/* Sink write method used by mime_encrypt for writing Base64. */
-static int
-sink_encryption_write_b64 (sink_t encsink, const void *data, size_t datalen)
-{
- engine_filter_t filter = encsink->cb_data;
- int rc;
- const unsigned char *p;
- unsigned char inbuf[4];
- int idx, quads;
- char outbuf[6];
- size_t outbuflen;
-
- if (!filter)
- {
- log_error ("%s:%s: sink not setup for writing", SRCNAME, __func__);
- return -1;
- }
-
- idx = encsink->b64.idx;
- assert (idx < 4);
- memcpy (inbuf, encsink->b64.inbuf, idx);
- quads = encsink->b64.quads;
-
- if (!data) /* Flush. */
- {
- outbuflen = 0;
- if (idx)
- {
- outbuf[0] = bintoasc[(*inbuf>>2)&077];
- if (idx == 1)
- {
- outbuf[1] = bintoasc[((*inbuf<<4)&060)&077];
- outbuf[2] = '=';
- outbuf[3] = '=';
- }
- else
- {
- outbuf[1] = bintoasc[(((*inbuf<<4)&060)|
- ((inbuf[1]>>4)&017))&077];
- outbuf[2] = bintoasc[((inbuf[1]<<2)&074)&077];
- outbuf[3] = '=';
- }
- outbuflen = 4;
- quads++;
- }
-
- if (quads)
- {
- outbuf[outbuflen++] = '\r';
- outbuf[outbuflen++] = '\n';
- }
-
- if (outbuflen && (rc = engine_filter (filter, outbuf, outbuflen)))
- return rc;
- /* Send the flush command to the filter. */
- if ((rc = engine_filter (filter, data, datalen)))
- return rc;
- }
- else
- {
- for (p = data; datalen; p++, datalen--)
- {
- inbuf[idx++] = *p;
- if (idx > 2)
- {
- idx = 0;
- outbuf[0] = bintoasc[(*inbuf>>2)&077];
- outbuf[1] = bintoasc[(((*inbuf<<4)&060)
- |((inbuf[1] >> 4)&017))&077];
- outbuf[2] = bintoasc[(((inbuf[1]<<2)&074)
- |((inbuf[2]>>6)&03))&077];
- outbuf[3] = bintoasc[inbuf[2]&077];
- outbuflen = 4;
- if (++quads >= (64/4))
- {
- quads = 0;
- outbuf[4] = '\r';
- outbuf[5] = '\n';
- outbuflen += 2;
- }
- if ((rc = engine_filter (filter, outbuf, outbuflen)))
- return rc;
- }
- }
- }
-
- encsink->b64.idx = idx;
- memcpy (encsink->b64.inbuf, inbuf, idx);
- encsink->b64.quads = quads;
-
- return 0;
-}
-#endif /*Not used.*/
-
/* Helper from mime_encrypt. BOUNDARY is a buffer of at least
BOUNDARYSIZE+1 bytes which will be set on return from that
function. */
int
create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary,
bool is_inline, int exchange_major_version)
{
int rc;
if (is_inline)
{
*boundary = 0;
rc = 0;
/* This would be nice and worked for Google Sync but it failed
for Microsoft Exchange Online *sigh* so we put the body
instead into the oom body property and stick with IPM Note.
rc = write_multistring (sink,
"MIME-Version: 1.0\r\n"
"Content-Type: text/plain;\r\n"
"\tcharset=\"iso-8859-1\"\r\n"
"Content-Transfer-Encoding: 7BIT\r\n"
"\r\n",
NULL);
*/
}
else if (protocol == PROTOCOL_SMIME)
{
*boundary = 0;
if (exchange_major_version >= 15)
{
/*
For S/MIME encrypted mails we do not use the S/MIME conversion
code anymore. With Exchange 2016 this no longer works. Instead
we set an override mime tag, the extended headers in OOM in
Mail::update_crypt_oom and let outlook convert the attachment
to base64.
A bit more details can be found in T3853 / T3884
*/
rc = 0;
}
else
{
rc = write_multistring (sink,
"Content-Type: application/pkcs7-mime; "
"smime-type=enveloped-data;\r\n"
"\tname=\"smime.p7m\"\r\n"
"Content-Disposition: attachment; filename=\"smime.p7m\"\r\n"
"Content-Transfer-Encoding: base64\r\n"
"MIME-Version: 1.0\r\n"
"\r\n",
NULL);
}
}
else
{
generate_boundary (boundary);
rc = write_multistring (sink,
"MIME-Version: 1.0\r\n"
"Content-Type: multipart/encrypted;\r\n"
"\tprotocol=\"application/pgp-encrypted\";\r\n",
"\tboundary=\"", boundary, "\"\r\n",
NULL);
if (rc)
return rc;
/* Write the PGP/MIME encrypted part. */
rc = write_boundary (sink, boundary, 0);
if (rc)
return rc;
rc = write_multistring (sink,
"Content-Type: application/pgp-encrypted\r\n"
"\r\n"
"Version: 1\r\n", NULL);
if (rc)
return rc;
/* And start the second part. */
rc = write_boundary (sink, boundary, 0);
if (rc)
return rc;
rc = write_multistring (sink,
"Content-Type: application/octet-stream\r\n"
"\r\n", NULL);
}
return rc;
}
-/* Encrypt the MESSAGE. */
-int
-mime_encrypt (LPMESSAGE message, HWND hwnd,
- protocol_t protocol, char **recipients,
- const char *sender, Mail* mail)
-{
- int result = -1;
- int rc;
- LPATTACH attach = nullptr;
- struct sink_s sinkmem;
- sink_t sink = &sinkmem;
- struct sink_s encsinkmem;
- sink_t encsink = &encsinkmem;
- char boundary[BOUNDARYSIZE+1];
- mapi_attach_item_t *att_table = NULL;
- char *body = NULL;
- int n_att_usable;
- engine_filter_t filter = NULL;
- char *my_sender = NULL;
- bool is_inline = mail && mail->do_pgp_inline ();
-
- memset (sink, 0, sizeof *sink);
- memset (encsink, 0, sizeof *encsink);
-
- /* Get the attachment info and the body. We need to do this before
- creating the engine's filter because sending the cancel to
- the engine with nothing for the engine to process. Will result
- in an error. This is actually a bug in our engine code but
- we better avoid triggering this bug because the engine
- sometimes hangs. Fixme: Needs a proper fix. */
-
-
- /* Take the Body from the mail if possible. This is a fix for
- GnuPG-Bug-ID: T3614 because the body is not always properly
- updated in MAPI when sending. */
- if (mail)
- {
- body = mail->take_cached_plain_body ();
- }
- if (!body)
- {
- body = mapi_get_body (message, NULL);
- }
- if (body && !*body)
- {
- xfree (body);
- body = NULL;
- }
- att_table = mapi_create_attach_table (message, 0);
- n_att_usable = count_usable_attachments (att_table);
- if (!n_att_usable && !body)
- {
- log_debug ("%s:%s: can't encrypt an empty message\n", SRCNAME, __func__);
- result = gpg_error (GPG_ERR_NO_DATA);
- goto failure;
- }
-
- if (n_att_usable && is_inline)
- {
- log_debug ("%s:%s: PGP Inline not supported for attachments. Using PGP MIME",
- SRCNAME, __func__);
- is_inline = false;
- }
-
- if (!is_inline || !mail)
- {
- attach = create_mapi_attachment (message, sink);
- if (!attach)
- return -1;
- }
- else
- {
- sink->cb_data = mail;
- sink->writefnc = sink_string_write;
- }
-
- /* Prepare the encryption. We do this early as it is quite common
- that some recipient keys are not available and thus the
- encryption will fail early. */
- if (engine_create_filter (&filter, write_buffer_for_cb, sink))
- goto failure;
-
- engine_set_session_number (filter, engine_new_session_number ());
- {
- char *tmp = mapi_get_subject (message);
- engine_set_session_title (filter, tmp);
- xfree (tmp);
- }
-
- if (sender)
- my_sender = xstrdup (sender);
- else
- my_sender = mapi_get_sender (message);
- if (engine_encrypt_prepare (filter, hwnd, protocol, 0,
- my_sender, recipients, &protocol))
- goto failure;
- if (engine_encrypt_start (filter, 0))
- goto failure;
-
- protocol = check_protocol (protocol);
- if (protocol == PROTOCOL_UNKNOWN)
- goto failure;
-
- if (protocol != PROTOCOL_OPENPGP)
- {
- log_debug ("%s:%s: Inline not supported for S/MIME. Using MIME",
- SRCNAME, __func__);
- is_inline = false;
- }
-
- /* Write the top header. */
- rc = create_top_encryption_header (sink, protocol, boundary,
- is_inline);
- if (rc)
- goto failure;
-
- /* Create a new sink for encrypting the following stuff. */
- encsink->cb_data = filter;
- encsink->writefnc = sink_encryption_write;
-
- /* Add the plaintext */
- if (is_inline && body)
- {
- if ((rc = write_multistring (encsink, body, NULL)))
- {
- log_error ("%s:%s: Adding the body failed.",
- SRCNAME, __func__);
- goto failure;
- }
- }
- else if (add_body_and_attachments (encsink, message, att_table, mail,
- body, n_att_usable))
- {
- goto failure;
- }
-
- xfree (body);
- body = NULL;
-
- /* Flush the encryption sink and wait for the encryption to get
- ready. */
- if ((rc = write_buffer (encsink, NULL, 0)))
- goto failure;
- if ((rc = engine_wait (filter)))
- goto failure;
- filter = NULL; /* Not valid anymore. */
- encsink->cb_data = NULL; /* Not needed anymore. */
-
- if (!sink->enc_counter)
- {
- log_debug ("%s:%s: nothing received from engine", SRCNAME, __func__);
- goto failure;
- }
-
- /* Write the final boundary (for OpenPGP) and finish the attachment. */
- if (*boundary && (rc = write_boundary (sink, boundary, 1)))
- goto failure;
-
- if (attach && close_mapi_attachment (&attach, sink))
- goto failure;
-
- if (finalize_message (message, att_table, protocol, 1, is_inline))
- goto failure;
-
- result = 0; /* Everything is fine, fall through the cleanup now. */
-
- failure:
- engine_cancel (filter);
- if (attach)
- {
- cancel_mapi_attachment (&attach, sink);
- }
- xfree (body);
- mapi_release_attach_table (att_table);
- xfree (my_sender);
- return result;
-}
-
-
-
-
-/* Sign and Encrypt the MESSAGE. */
-int
-mime_sign_encrypt (LPMESSAGE message, HWND hwnd,
- protocol_t protocol, char **recipients,
- const char *sender, Mail *mail)
-{
- int result = -1;
- int rc = 0;
- HRESULT hr;
- LPATTACH attach;
- LPSTREAM tmpstream = NULL;
- struct sink_s sinkmem;
- sink_t sink = &sinkmem;
- struct sink_s encsinkmem;
- sink_t encsink = &encsinkmem;
- struct sink_s tmpsinkmem;
- sink_t tmpsink = &tmpsinkmem;
- char boundary[BOUNDARYSIZE+1];
- mapi_attach_item_t *att_table = NULL;
- engine_filter_t filter = NULL;
- unsigned int session_number;
- char *my_sender = NULL;
- char gpgstr[] = "GPG";
-
- memset (sink, 0, sizeof *sink);
- memset (encsink, 0, sizeof *encsink);
- memset (tmpsink, 0, sizeof *tmpsink);
-
- attach = create_mapi_attachment (message, sink);
- if (!attach)
- return -1;
-
- /* First check that we are not trying to process an empty message
- which might lock up our engine. Unfortunately we need to
- duplicate the code we use in do_mime_sign here. FIXME: The
- engine should be fixed instead of using such a workaround. */
- {
- char *body;
-
- body = mapi_get_body (message, NULL);
- if (body && !*body)
- {
- xfree (body);
- body = NULL;
- }
- att_table = mapi_create_attach_table (message, 0);
- if (!count_usable_attachments (att_table) && !body)
- result = gpg_error (GPG_ERR_NO_DATA);
- xfree (body);
- if (att_table)
- {
- mapi_release_attach_table (att_table);
- att_table = NULL;
- }
- if (gpg_err_code (result) == GPG_ERR_NO_DATA)
- {
- log_debug ("%s:%s: can't sign+encrypt an empty message\n",
- SRCNAME, __func__);
- goto failure;
- }
- }
-
- /* Create a temporary sink to construct the signed data. */
- hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
- (SOF_UNIQUEFILENAME | STGM_DELETEONRELEASE
- | STGM_CREATE | STGM_READWRITE),
- NULL, gpgstr, &tmpstream);
- if (FAILED (hr))
- {
- log_error ("%s:%s: can't create temp file: hr=%#lx\n",
- SRCNAME, __func__, hr);
- rc = -1;
- goto failure;
- }
- tmpsink->cb_data = tmpstream;
- tmpsink->writefnc = sink_std_write;
-
-
- /* Prepare the encryption. We do this early as it is quite common
- that some recipients are not available and thus the encryption
- will fail early. This is also required to allow the UIserver to
- figure out the protocol to use if we have not forced one. */
- if (engine_create_filter (&filter, write_buffer_for_cb, sink))
- goto failure;
-
- session_number = engine_new_session_number ();
- engine_set_session_number (filter, session_number);
- {
- char *tmp = mapi_get_subject (message);
- engine_set_session_title (filter, tmp);
- xfree (tmp);
- }
-
- if (sender)
- my_sender = xstrdup (sender);
- else
- my_sender = mapi_get_sender (message);
- if ((rc=engine_encrypt_prepare (filter, hwnd,
- protocol, ENGINE_FLAG_SIGN_FOLLOWS,
- my_sender, recipients, &protocol)))
- goto failure;
-
- protocol = check_protocol (protocol);
- if (protocol == PROTOCOL_UNKNOWN)
- goto failure;
-
- /* Now sign the message. This creates another attachment with the
- complete MIME object of the signed message. We can't do the
- encryption in streaming mode while running the encryption because
- we need to fix up that ugly micalg parameter after having created
- the signature. Note that the protocol to use is taken from the
- encryption operation. */
- if (do_mime_sign (message, hwnd, protocol, &att_table, tmpsink,
- session_number, sender, mail))
- goto failure;
-
- /* Now send the actual ENCRYPT command. This split up between
- prepare and start is necessary to help with the implementarion of
- the UI-server. If we would send the ENCRYPT command immediately
- the UI-server might block while reading from the input stream
- because we are first going to do a sign operation which in trun
- needs the attention of the UI server. A more robust but
- complicated approach to the UI-server would be to delay the
- reading (and thus the start of the underlying encrypt operation)
- until the first byte has been received. */
- if ((rc=engine_encrypt_start (filter, 0)))
- goto failure;
-
- /* Write the top header. */
- rc = create_top_encryption_header (sink, protocol, boundary);
- if (rc)
- goto failure;
-
- /* Create a new sink for encrypting the temporary attachment with
- the signed message. */
- encsink->cb_data = filter;
- encsink->writefnc = sink_encryption_write;
-
- /* Copy the temporary stream to the encryption sink. */
- {
- LARGE_INTEGER off;
- ULONG nread;
- char buffer[4096];
-
- off.QuadPart = 0;
- hr = tmpstream->Seek (off, STREAM_SEEK_SET, NULL);
- if (hr)
- {
- log_error ("%s:%s: seeking back to the begin failed: hr=%#lx",
- SRCNAME, __func__, hr);
- rc = gpg_error (GPG_ERR_EIO);
- goto failure;
- }
-
- for (;;)
- {
- hr = tmpstream->Read (buffer, sizeof buffer, &nread);
- if (hr)
- {
- log_error ("%s:%s: IStream::Read failed: hr=%#lx",
- SRCNAME, __func__, hr);
- rc = gpg_error (GPG_ERR_EIO);
- goto failure;
- }
- if (!nread)
- break; /* EOF */
- rc = write_buffer (encsink, buffer, nread);
- if (rc)
- {
- log_error ("%s:%s: writing tmpstream to encsink failed: %s",
- SRCNAME, __func__, gpg_strerror (rc));
- goto failure;
- }
- }
- }
-
-
- /* Flush the encryption sink and wait for the encryption to get
- ready. */
- if ((rc = write_buffer (encsink, NULL, 0)))
- goto failure;
- if ((rc = engine_wait (filter)))
- goto failure;
- filter = NULL; /* Not valid anymore. */
- encsink->cb_data = NULL; /* Not needed anymore. */
-
- if (!sink->enc_counter)
- {
- log_debug ("%s:%s: nothing received from engine", SRCNAME, __func__);
- goto failure;
- }
-
- /* Write the final boundary (for OpenPGP) and finish the attachment. */
- if (*boundary && (rc = write_boundary (sink, boundary, 1)))
- goto failure;
-
- if (close_mapi_attachment (&attach, sink))
- goto failure;
-
- if (finalize_message (message, att_table, protocol, 1))
- goto failure;
-
- result = 0; /* Everything is fine, fall through the cleanup now. */
-
- failure:
- if (result)
- log_debug ("%s:%s: failed rc=%d (%s) <%s>", SRCNAME, __func__, rc,
- gpg_strerror (rc), gpg_strsource (rc));
- engine_cancel (filter);
- gpgol_release (tmpstream);
- mapi_release_attach_table (att_table);
- xfree (my_sender);
- return result;
-}
-
int
restore_msg_from_moss (LPMESSAGE message, LPDISPATCH moss_att,
msgtype_t type, char *msgcls)
{
struct sink_s sinkmem;
sink_t sink = &sinkmem;
char *orig = NULL;
int err = -1;
char boundary[BOUNDARYSIZE+1];
(void)msgcls;
LPATTACH new_attach = create_mapi_attachment (message,
sink);
log_debug ("Restore message from moss called.");
if (!new_attach)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
// TODO MORE
if (type == MSGTYPE_SMIME)
{
create_top_encryption_header (sink, PROTOCOL_SMIME, boundary);
}
else if (type == MSGTYPE_GPGOL_MULTIPART_ENCRYPTED)
{
create_top_encryption_header (sink, PROTOCOL_OPENPGP, boundary);
}
else
{
log_error ("%s:%s: Unsupported messagetype: %i",
SRCNAME, __func__, type);
goto done;
}
orig = get_pa_string (moss_att, PR_ATTACH_DATA_BIN_DASL);
if (!orig)
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
if (write_string (sink, orig))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
if (*boundary && write_boundary (sink, boundary, 1))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
if (close_mapi_attachment (&new_attach, sink))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
/* Set a special property so that we are later able to identify
messages signed or encrypted by us. */
if (mapi_set_sig_status (message, "@"))
{
log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
goto done;
}
err = 0;
done:
xfree (orig);
return err;
}
diff --git a/src/mimemaker.h b/src/mimemaker.h
index eed47a3..047d7bf 100644
--- a/src/mimemaker.h
+++ b/src/mimemaker.h
@@ -1,100 +1,92 @@
/* mimemaker.h - Construct MIME from MAPI
* Copyright (C) 2007, 2008 g10 Code GmbH
* Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifndef MIMEMAKER_H
#define MIMEMAKER_H
class Mail;
#ifdef __cplusplus
extern "C" {
#if 0
}
#endif
#endif
/* The object we use instead of IStream. It allows us to have a
callback method for output and thus for processing stuff
recursively. */
struct sink_s;
typedef struct sink_s *sink_t;
struct sink_s
{
void *cb_data;
sink_t extrasink;
int (*writefnc)(sink_t sink, const void *data, size_t datalen);
unsigned long enc_counter; /* Used by write_buffer_for_cb. */
/* struct { */
/* int idx; */
/* unsigned char inbuf[4]; */
/* int quads; */
/* } b64; */
};
-int mime_sign (LPMESSAGE message, HWND hwnd, protocol_t protocol,
- const char *sender, Mail* mail);
-int mime_encrypt (LPMESSAGE message, HWND hwnd,
- protocol_t protocol, char **recipients,
- const char *sender, Mail* mail);
-int mime_sign_encrypt (LPMESSAGE message, HWND hwnd,
- protocol_t protocol, char **recipients,
- const char *sender, Mail* mail);
int sink_std_write (sink_t sink, const void *data, size_t datalen);
int sink_file_write (sink_t sink, const void *data, size_t datalen);
int sink_encryption_write (sink_t encsink, const void *data, size_t datalen);
int write_buffer_for_cb (void *opaque, const void *data, size_t datalen);
int write_buffer (sink_t sink, const void *data, size_t datalen);
/** @brief Try to restore a message from the moss attachment.
*
* Try to turn the moss attachment back into a Mail that other
* MUAs could handle. Uses all the tricks available to archive
* that. Returns 0 on success.
*/
int restore_msg_from_moss (LPMESSAGE message, LPDISPATCH moss_att,
msgtype_t type, char *msgcls);
int count_usable_attachments (mapi_attach_item_t *table);
int add_body_and_attachments (sink_t sink, LPMESSAGE message,
mapi_attach_item_t *att_table, Mail *mail,
const char *body, int n_att_usable);
int create_top_encryption_header (sink_t sink, protocol_t protocol, char *boundary,
bool is_inline = false, int exchange_major_version = -1);
/* Helper to write a boundary to the output sink. The leading LF
will be written as well. */
int write_boundary (sink_t sink, const char *boundary, int lastone);
LPATTACH create_mapi_attachment (LPMESSAGE message, sink_t sink,
const char *overrideMimeTag = nullptr);
int close_mapi_attachment (LPATTACH *attach, sink_t sink);
int finalize_message (LPMESSAGE message, mapi_attach_item_t *att_table,
protocol_t protocol, int encrypt, bool is_inline = false);
void cancel_mapi_attachment (LPATTACH *attach, sink_t sink);
void create_top_signing_header (char *buffer, size_t buflen, protocol_t protocol,
int first, const char *boundary, const char *micalg);
int write_string (sink_t sink, const char *text);
int write_b64 (sink_t sink, const void *data, size_t datalen);
#ifdef __cplusplus
}
#endif
#endif /*MIMEMAKER_H*/
diff --git a/src/olflange.cpp b/src/olflange.cpp
index eb4d79f..3b25340 100644
--- a/src/olflange.cpp
+++ b/src/olflange.cpp
@@ -1,435 +1,434 @@
/* olflange.cpp - Connect GpgOL to Outlook
* Copyright (C) 2001 G Data Software AG, http://www.gdata.de
* Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#ifndef INITGUID
/* Include every header that defines a GUID below this
macro. Otherwise the GUID's will only be declared and
not defined. */
#define INITGUID
#endif
#include
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
-#include "engine.h"
#include "mapihelp.h"
#include "olflange.h"
#include "gpgoladdin.h"
static char *olversion;
EXTERN_C int
get_ol_main_version (void)
{
return olversion? atoi (olversion): 0;
}
/* Registers this as an addin for outlook 2010.
This basically updates some Registry entries.
Documentation to be found at:
http://msdn.microsoft.com/en-us/library/bb386106%28v=vs.110%29.aspx
*/
STDAPI
DllRegisterServer (void)
{
HKEY hkey, hkey2;
CHAR szKeyBuf[MAX_PATH+1024];
CHAR szEntry[MAX_PATH+512];
TCHAR szModuleFileName[MAX_PATH];
DWORD dwTemp = 0;
long ec;
HKEY root_key;
int inst_global = is_elevated ();
if (inst_global)
{
root_key = HKEY_LOCAL_MACHINE;
}
else
{
root_key = HKEY_CURRENT_USER;
}
/* Get server location. */
if (!GetModuleFileName(glob_hinst, szModuleFileName, MAX_PATH))
return E_FAIL;
hkey = NULL;
lstrcpy (szKeyBuf, "Software\\GNU\\GpgOL");
RegCreateKeyEx (HKEY_CURRENT_USER, szKeyBuf, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL);
if (hkey != NULL)
RegCloseKey (hkey);
/* Register the CLSID in the registry */
hkey = NULL;
if (inst_global)
{
strcpy (szKeyBuf, "CLSID\\" CLSIDSTR_GPGOL);
ec = RegCreateKeyEx (HKEY_CLASSES_ROOT, szKeyBuf, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL);
OutputDebugString("Created: ");
OutputDebugString(szKeyBuf);
}
else
{
strcpy (szKeyBuf, "Software\\Classes\\CLSID\\" CLSIDSTR_GPGOL);
ec = RegCreateKeyEx (HKEY_CURRENT_USER, szKeyBuf, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL);
}
if (ec != ERROR_SUCCESS)
{
fprintf (stderr, "creating key `%s' failed: ec=%#lx\n", szKeyBuf, ec);
return E_ACCESSDENIED;
}
strcpy (szEntry, GPGOL_PRETTY);
dwTemp = strlen (szEntry) + 1;
RegSetValueEx (hkey, NULL, 0, REG_SZ, (BYTE*)szEntry, dwTemp);
/* Set the Inproc server value */
strcpy (szKeyBuf, "InprocServer32");
ec = RegCreateKeyEx (hkey, szKeyBuf, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, &hkey2, NULL);
if (ec != ERROR_SUCCESS)
{
fprintf (stderr, "creating key `%s' failed: ec=%#lx\n", szKeyBuf, ec);
RegCloseKey (hkey);
return E_ACCESSDENIED;
}
strcpy (szEntry, szModuleFileName);
dwTemp = strlen (szEntry) + 1;
RegSetValueEx (hkey2, NULL, 0, REG_SZ, (BYTE*)szEntry, dwTemp);
/* Set the threading model used */
strcpy (szEntry, "Both");
dwTemp = strlen (szEntry) + 1;
RegSetValueEx (hkey2, "ThreadingModel", 0, REG_SZ, (BYTE*)szEntry, dwTemp);
/* Set the Prog ID */
strcpy (szKeyBuf, "ProgID");
ec = RegCreateKeyEx (hkey, szKeyBuf, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, &hkey2, NULL);
if (ec != ERROR_SUCCESS)
{
fprintf (stderr, "creating key `%s' failed: ec=%#lx\n", szKeyBuf, ec);
RegCloseKey (hkey);
return E_ACCESSDENIED;
}
strcpy (szEntry, GPGOL_PROGID);
dwTemp = strlen (szEntry) + 1;
RegSetValueEx (hkey2, NULL, 0, REG_SZ, (BYTE*)szEntry, dwTemp);
/* Make the Prog ID known. This is basically the same as above
* but necessary so we can refer to the Prog ID as an Outlook
* Extension
*/
hkey = NULL;
if (inst_global)
{
strcpy (szKeyBuf, GPGOL_PROGID);
ec = RegCreateKeyEx (HKEY_CLASSES_ROOT, szKeyBuf, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL);
}
else
{
strcpy (szKeyBuf, "Software\\Classes\\" GPGOL_PROGID);
ec = RegCreateKeyEx (HKEY_CURRENT_USER, szKeyBuf, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL);
}
if (ec != ERROR_SUCCESS)
{
fprintf (stderr, "creating key `%s' failed: ec=%#lx\n", szKeyBuf, ec);
return E_ACCESSDENIED;
}
strcpy (szEntry, GPGOL_PRETTY);
dwTemp = strlen (szEntry) + 1;
RegSetValueEx (hkey, NULL, 0, REG_SZ, (BYTE*)szEntry, dwTemp);
/* Point from the Prog ID entry to the CSLID */
strcpy (szKeyBuf, "CLSID");
ec = RegCreateKeyEx (hkey, szKeyBuf, 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, &hkey2, NULL);
if (ec != ERROR_SUCCESS)
{
fprintf (stderr, "creating key `%s' failed: ec=%#lx\n", szKeyBuf, ec);
RegCloseKey (hkey);
return E_ACCESSDENIED;
}
strcpy (szEntry, CLSIDSTR_GPGOL);
dwTemp = strlen (szEntry) + 1;
RegSetValueEx (hkey2, NULL, 0, REG_SZ, (BYTE*)szEntry, dwTemp);
/* Register ourself as an extension for outlook >= 14 */
strcpy (szKeyBuf, "Software\\Microsoft\\Office\\Outlook\\Addins\\" GPGOL_PROGID);
ec = RegCreateKeyEx (root_key, szKeyBuf, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkey, NULL);
if (ec != ERROR_SUCCESS)
{
fprintf (stderr, "creating key `%s' failed: ec=%#lx\n", szKeyBuf, ec);
return E_ACCESSDENIED;
}
/* Load connected and load Bootload */
dwTemp = 0x01 | 0x02;
RegSetValueEx (hkey, "LoadBehavior", 0, REG_DWORD, (BYTE*)&dwTemp, 4);
/* We are not commandline save */
dwTemp = 0;
RegSetValueEx (hkey, "CommandLineSafe", 0, REG_DWORD, (BYTE*)&dwTemp, 4);
/* A friendly name (visible in outlook) */
strcpy (szEntry, GPGOL_PRETTY);
dwTemp = strlen (szEntry) + 1;
RegSetValueEx (hkey, "FriendlyName", 0, REG_SZ, (BYTE*)szEntry, dwTemp);
/* A short description (visible in outlook) */
strcpy (szEntry, GPGOL_DESCRIPTION);
dwTemp = strlen (szEntry) + 1;
RegSetValueEx (hkey, "Description", 0, REG_SZ, (BYTE*)szEntry, dwTemp);
RegCloseKey (hkey2);
RegCloseKey (hkey);
log_debug ("DllRegisterServer succeeded\n");
return S_OK;
}
/* Unregisters this module as an Exchange extension / Addin. */
STDAPI
DllUnregisterServer (void)
{
HKEY hkey;
CHAR buf[MAX_PATH+1024];
DWORD ntemp;
long res;
HKEY root_key;
if (is_elevated ())
{
root_key = HKEY_LOCAL_MACHINE;
}
else
{
root_key = HKEY_CURRENT_USER;
}
/* We still unregister the old client extension code */
strcpy (buf, "Software\\Microsoft\\Exchange\\Client\\Extensions");
/* Create and open key and subkey. */
res = RegCreateKeyEx (root_key, buf, 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,
NULL, &hkey, NULL);
if (res != ERROR_SUCCESS)
{
log_debug ("DllUnregisterServer: access denied.\n");
return E_ACCESSDENIED;
}
RegDeleteValue (hkey, "GpgOL");
/* Set outlook update flag. */
strcpy (buf, "4.0;Outxxx.dll;7;000000000000000;0000000000;OutXXX");
ntemp = strlen (buf) + 1;
RegSetValueEx (hkey, "Outlook Setup Extension", 0,
REG_SZ, (BYTE*) buf, ntemp);
RegCloseKey (hkey);
/* Delete CLSIDs. */
strcpy (buf, "CLSID\\" CLSIDSTR_GPGOL "\\InprocServer32");
RegDeleteKey (HKEY_CLASSES_ROOT, buf);
strcpy (buf, "CLSID\\" CLSIDSTR_GPGOL "\\ProgID");
RegDeleteKey (HKEY_CLASSES_ROOT, buf);
strcpy (buf, "CLSID\\" CLSIDSTR_GPGOL);
RegDeleteKey (HKEY_CLASSES_ROOT, buf);
/* Delete ProgID */
strcpy (buf, GPGOL_PROGID "\\CLSID");
RegDeleteKey (HKEY_CLASSES_ROOT, buf);
strcpy (buf, GPGOL_PROGID);
RegDeleteKey (HKEY_CLASSES_ROOT, buf);
/* Delete Addin entry */
strcpy (buf, "Software\\Microsoft\\Office\\Outlook\\Addins\\" GPGOL_PROGID);
RegDeleteKey (root_key, buf);
return S_OK;
}
static const char*
parse_version_number (const char *s, int *number)
{
int val = 0;
if (*s == '0' && digitp (s+1))
return NULL; /* Leading zeros are not allowed. */
for (; digitp (s); s++)
{
val *= 10;
val += *s - '0';
}
*number = val;
return val < 0 ? NULL : s;
}
static const char *
parse_version_string (const char *s, int *major, int *minor, int *micro)
{
s = parse_version_number (s, major);
if (!s || *s != '.')
return NULL;
s++;
s = parse_version_number (s, minor);
if (!s || *s != '.')
return NULL;
s++;
s = parse_version_number (s, micro);
if (!s)
return NULL;
return s; /* Patchlevel. */
}
static const char *
compare_versions (const char *my_version, const char *req_version)
{
int my_major, my_minor, my_micro;
int rq_major, rq_minor, rq_micro;
const char *my_plvl, *rq_plvl;
if (!req_version)
return my_version;
if (!my_version)
return NULL;
my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
if (!my_plvl)
return NULL; /* Very strange: our own version is bogus. */
rq_plvl = parse_version_string(req_version,
&rq_major, &rq_minor, &rq_micro);
if (!rq_plvl)
return NULL; /* Requested version string is invalid. */
if (my_major > rq_major
|| (my_major == rq_major && my_minor > rq_minor)
|| (my_major == rq_major && my_minor == rq_minor
&& my_micro > rq_micro)
|| (my_major == rq_major && my_minor == rq_minor
&& my_micro == rq_micro
&& strcmp( my_plvl, rq_plvl ) >= 0))
{
return my_version;
}
return NULL;
}
/* Check that the the version of GpgOL is at minimum the requested one
* and return GpgOL's version string; return NULL if that condition is
* not met. If a NULL is passed to this function, no check is done
* and the version string is simply returned. */
EXTERN_C const char * __stdcall
gpgol_check_version (const char *req_version)
{
return compare_versions (PACKAGE_VERSION, req_version);
}
void
install_forms (void)
{
HRESULT hr;
LPMAPIFORMCONTAINER formcontainer = NULL;
static char const *forms[] =
{
"gpgol",
"gpgol-ms",
"gpgol-cs",
/* The InfoPath we use for sending, to get outlook
to do the S/MIME handling. */
"gpgol-form-signed",
"gpgol-form-encrypted",
NULL,
};
int formidx;
char buffer[MAX_PATH+10];
char *datadir;
int any_error = 0;
MAPIOpenLocalFormContainer (&formcontainer);
if (!formcontainer)
{
log_error ("%s:%s: error getting local form container\n",
SRCNAME, __func__);
return;
}
datadir = get_data_dir ();
if (!datadir)
{
log_error ("%s:%s: error getting data directory\n",
SRCNAME, __func__);
gpgol_release (formcontainer);
return;
}
for (formidx=0; forms[formidx]; formidx++)
{
snprintf (buffer, MAX_PATH, "%s\\%s.cfg",
datadir, forms[formidx]);
hr = formcontainer->InstallForm (0, MAPIFORM_INSTALL_OVERWRITEONCONFLICT,
buffer);
if (hr)
{
any_error = 1;
LPMAPIERROR err;
formcontainer->GetLastError (hr, 0, &err);
log_error ("%s:%s: installing form `%s' failed: hr=%#lx err=%s\n",
SRCNAME, __func__, buffer, hr, err ? err->lpszError : "null");
MAPIFreeBuffer (err);
}
else
log_debug ("%s:%s: form `%s' installed\n", SRCNAME, __func__, buffer);
}
gpgol_release (formcontainer);
xfree (datadir);
if (!any_error)
{
opt.forms_revision = GPGOL_FORMS_REVISION;
write_options ();
}
}
diff --git a/src/ribbon-callbacks.cpp b/src/ribbon-callbacks.cpp
index f1184d4..450bc02 100644
--- a/src/ribbon-callbacks.cpp
+++ b/src/ribbon-callbacks.cpp
@@ -1,815 +1,708 @@
/* ribbon-callbacks.h - Callbacks for the ribbon extension interface
* Copyright (C) 2013 Intevation GmbH
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include "ribbon-callbacks.h"
#include "gpgoladdin.h"
#include "common.h"
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
-#include "engine.h"
-#include "engine-assuan.h"
#include "mapihelp.h"
#include "mimemaker.h"
#include "filetype.h"
#include "mail.h"
#include
#include
using namespace GpgME;
/* Gets the context of a ribbon control. And prints some
useful debug output */
HRESULT getContext (LPDISPATCH ctrl, LPDISPATCH *context)
{
*context = get_oom_object (ctrl, "get_Context");
if (*context)
{
char *name = get_object_name (*context);
log_debug ("%s:%s: contextObj: %s",
SRCNAME, __func__, name);
xfree (name);
}
return context ? S_OK : E_FAIL;
}
-#define OP_ENCRYPT 1 /* Encrypt the data */
-#define OP_SIGN 2 /* Sign the data */
-#define OP_DECRYPT 1 /* Decrypt the data */
-#define OP_VERIFY 2 /* Verify the data */
-#define DATA_BODY 4 /* Use text body as data */
-#define DATA_SELECTION 8 /* Use selection as data */
-
-HRESULT
-decryptAttachments (LPDISPATCH ctrl)
-{
- LPDISPATCH context = NULL;
- LPDISPATCH attachmentSelection;
- int attachmentCount;
- HRESULT hr = 0;
- int i = 0;
- HWND curWindow;
- int err;
-
- hr = getContext(ctrl, &context);
-
- attachmentSelection = get_oom_object (context, "AttachmentSelection");
- if (!attachmentSelection)
- {
- /* We can be called from a context menu, in that case we
- directly have an AttachmentSelection context. Otherwise
- we have an Explorer context with an Attachment Selection property. */
- attachmentSelection = context;
- }
-
- attachmentCount = get_oom_int (attachmentSelection, "Count");
-
- curWindow = get_oom_context_window (context);
-
- {
- char *filenames[attachmentCount + 1];
- filenames[attachmentCount] = NULL;
- /* Yes the items start at 1! */
- for (i = 1; i <= attachmentCount; i++)
- {
- char buf[16];
- char *filename;
- wchar_t *wcsOutFilename;
- DISPPARAMS saveParams;
- VARIANT aVariant[1];
- LPDISPATCH attachmentObj;
- DISPID saveID;
-
- snprintf (buf, sizeof (buf), "Item(%i)", i);
- attachmentObj = get_oom_object (attachmentSelection, buf);
- if (!attachmentObj)
- {
- /* Should be impossible */
- filenames[i-1] = NULL;
- log_error ("%s:%s: could not find Item %i;",
- SRCNAME, __func__, i);
- break;
- }
- filename = get_oom_string (attachmentObj, "FileName");
-
- saveID = lookup_oom_dispid (attachmentObj, "SaveAsFile");
-
- saveParams.rgvarg = aVariant;
- saveParams.rgvarg[0].vt = VT_BSTR;
- filenames[i-1] = get_save_filename (NULL, filename);
- xfree (filename);
-
- if (!filenames [i-1])
- continue;
-
- wcsOutFilename = utf8_to_wchar2 (filenames[i-1],
- strlen(filenames[i-1]));
- saveParams.rgvarg[0].bstrVal = SysAllocString (wcsOutFilename);
- saveParams.cArgs = 1;
- saveParams.cNamedArgs = 0;
-
- hr = attachmentObj->Invoke (saveID, IID_NULL, LOCALE_SYSTEM_DEFAULT,
- DISPATCH_METHOD, &saveParams,
- NULL, NULL, NULL);
- SysFreeString (saveParams.rgvarg[0].bstrVal);
- gpgol_release (attachmentObj);
- if (FAILED(hr))
- {
- int j;
- log_debug ("%s:%s: Saving to file failed. hr: %x",
- SRCNAME, __func__, (unsigned int) hr);
- for (j = 0; j < i; j++)
- xfree (filenames[j]);
- gpgol_release (attachmentSelection);
- return hr;
- }
- }
- gpgol_release (attachmentSelection);
- err = op_assuan_start_decrypt_files (curWindow, filenames);
- for (i = 0; i < attachmentCount; i++)
- xfree (filenames[i]);
- }
-
- log_debug ("%s:%s: Leaving. Err: %i",
- SRCNAME, __func__, err);
-
- return S_OK; /* If we return an error outlook will show that our
- callback function failed in an ugly window. */
-}
-
/* getIcon
Loads a PNG image from the resurce converts it into a Bitmap
and Wraps it in an PictureDispatcher that is returned as result.
Based on documentation from:
http://www.codeproject.com/Articles/3537/Loading-JPG-PNG-resources-using-GDI
*/
-
HRESULT
getIcon (int id, VARIANT* result)
{
PICTDESC pdesc;
LPDISPATCH pPict;
HRESULT hr;
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::Bitmap* pbitmap;
ULONG_PTR gdiplusToken;
HRSRC hResource;
DWORD imageSize;
const void* pResourceData;
HGLOBAL hBuffer;
memset (&pdesc, 0, sizeof pdesc);
pdesc.cbSizeofstruct = sizeof pdesc;
pdesc.picType = PICTYPE_BITMAP;
if (!result)
{
log_error ("getIcon called without result variant.");
return E_POINTER;
}
/* Initialize GDI */
gdiplusStartupInput.DebugEventCallback = NULL;
gdiplusStartupInput.SuppressBackgroundThread = FALSE;
gdiplusStartupInput.SuppressExternalCodecs = FALSE;
gdiplusStartupInput.GdiplusVersion = 1;
GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL);
/* Get the image from the resource file */
hResource = FindResource (glob_hinst, MAKEINTRESOURCE(id), RT_RCDATA);
if (!hResource)
{
log_error ("%s:%s: failed to find image: %i",
SRCNAME, __func__, id);
return E_FAIL;
}
imageSize = SizeofResource (glob_hinst, hResource);
if (!imageSize)
return E_FAIL;
pResourceData = LockResource (LoadResource(glob_hinst, hResource));
if (!pResourceData)
{
log_error ("%s:%s: failed to load image: %i",
SRCNAME, __func__, id);
return E_FAIL;
}
hBuffer = GlobalAlloc (GMEM_MOVEABLE, imageSize);
if (hBuffer)
{
void* pBuffer = GlobalLock (hBuffer);
if (pBuffer)
{
IStream* pStream = NULL;
CopyMemory (pBuffer, pResourceData, imageSize);
if (CreateStreamOnHGlobal (hBuffer, FALSE, &pStream) == S_OK)
{
pbitmap = Gdiplus::Bitmap::FromStream (pStream);
gpgol_release (pStream);
if (!pbitmap || pbitmap->GetHBITMAP (0, &pdesc.bmp.hbitmap))
{
log_error ("%s:%s: failed to get PNG.",
SRCNAME, __func__);
}
}
}
GlobalUnlock (pBuffer);
}
GlobalFree (hBuffer);
Gdiplus::GdiplusShutdown (gdiplusToken);
/* Wrap the image into an OLE object. */
hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp,
TRUE, (void **) &pPict);
if (hr != S_OK || !pPict)
{
log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return -1;
}
result->pdispVal = pPict;
result->vt = VT_DISPATCH;
return S_OK;
}
HRESULT
mark_mime_action (LPDISPATCH ctrl, int flags, bool is_explorer)
{
HRESULT hr;
HRESULT rc = E_FAIL;
LPDISPATCH context = NULL,
mailitem = NULL;
LPMESSAGE message = NULL;
int oldflags,
newflags;
log_debug ("%s:%s: enter", SRCNAME, __func__);
hr = getContext (ctrl, &context);
if (FAILED(hr))
return hr;
mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" :
"CurrentItem");
if (!mailitem)
{
log_error ("%s:%s: Failed to get mailitem.",
SRCNAME, __func__);
goto done;
}
message = get_oom_base_message (mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
goto done;
}
oldflags = get_gpgol_draft_info_flags (message);
if (flags == 3 && oldflags != 3)
{
// If only one sub button is active activate
// both now.
newflags = 3;
}
else
{
newflags = oldflags xor flags;
}
if (set_gpgol_draft_info_flags (message, newflags))
{
log_error ("%s:%s: Failed to set draft flags.",
SRCNAME, __func__);
}
rc = S_OK;
/* We need to invalidate the UI to update the toggle
states of the subbuttons and the top button. Yeah,
we invalidate a lot *sigh* */
gpgoladdin_invalidate_ui ();
if (newflags & 1)
{
Mail::locate_all_crypto_recipients ();
}
done:
gpgol_release (context);
gpgol_release (mailitem);
gpgol_release (message);
return rc;
}
/* Get the state of encrypt / sign toggle buttons.
flag values: 1 get the state of the encrypt button.
2 get the state of the sign button.
If is_explorer is set to true we look at the inline response.
*/
HRESULT get_crypt_pressed (LPDISPATCH ctrl, int flags, VARIANT *result,
bool is_explorer)
{
HRESULT hr;
bool value;
LPDISPATCH context = NULL,
mailitem = NULL;
LPMESSAGE message = NULL;
result->vt = VT_BOOL | VT_BYREF;
result->pboolVal = (VARIANT_BOOL*) xmalloc (sizeof (VARIANT_BOOL));
*(result->pboolVal) = VARIANT_FALSE;
/* First the usual defensive check about our parameters */
if (!ctrl || !result)
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return E_FAIL;
}
hr = getContext (ctrl, &context);
if (hr)
{
log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
hr);
return E_FAIL;
}
mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" :
"CurrentItem");
if (!mailitem)
{
log_error ("%s:%s: Failed to get mailitem.",
SRCNAME, __func__);
goto done;
}
message = get_oom_base_message (mailitem);
if (!message)
{
log_error ("%s:%s: No message found.",
SRCNAME, __func__);
goto done;
}
value = (get_gpgol_draft_info_flags (message) & flags) == flags;
*(result->pboolVal) = value ? VARIANT_TRUE : VARIANT_FALSE;
done:
gpgol_release (context);
gpgol_release (mailitem);
gpgol_release (message);
return S_OK;
}
static Mail *
get_mail_from_control (LPDISPATCH ctrl, bool *none_selected)
{
HRESULT hr;
LPDISPATCH context = NULL,
mailitem = NULL;
*none_selected = false;
if (!ctrl)
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return NULL;
}
hr = getContext (ctrl, &context);
if (hr)
{
log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
hr);
return NULL;
}
const auto ctx_name = std::string (get_object_name (context));
if (ctx_name.empty())
{
log_error ("%s:%s: Failed to get context name",
SRCNAME, __func__);
gpgol_release (context);
return NULL;
}
if (!strcmp (ctx_name.c_str(), "_Inspector"))
{
mailitem = get_oom_object (context, "CurrentItem");
}
else if (!strcmp (ctx_name.c_str(), "_Explorer"))
{
if (g_ol_version_major >= 16)
{
// Avoid showing wrong crypto state if we don't have a reading
// pane. In that case the parser will finish for a mail which is gone
// and the crypto state will not get updated.
//
//
// Somehow latest Outlook 2016 crashes when accessing the current view
// of the Explorer. This is even reproducible with
// GpgOL disabled and only with Outlook Spy active. If you select
// the explorer of an Outlook.com resource and then access
// the CurrentView and close the CurrentView again in Outlook Spy
// outlook crashes.
LPDISPATCH prevEdit = get_oom_object (context, "PreviewPane.WordEditor");
gpgol_release (prevEdit);
if (!prevEdit)
{
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
}
}
else
{
// Preview Pane is not available in older outlooks
LPDISPATCH tableView = get_oom_object (context, "CurrentView");
if (!tableView)
{
// Woops, should not happen.
TRACEPOINT;
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
}
else
{
int hasReadingPane = get_oom_bool (tableView, "ShowReadingPane");
gpgol_release (tableView);
if (!hasReadingPane)
{
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
}
}
}
if (!*none_selected)
{
/* Accessing the selection item can trigger a load event
so we only do this here if we think that there might be
something visible / selected. To avoid triggering a load
if there is no content shown. */
LPDISPATCH selection = get_oom_object (context, "Selection");
if (!selection)
{
log_error ("%s:%s: Failed to get selection.",
SRCNAME, __func__);
gpgol_release (context);
return NULL;
}
int count = get_oom_int (selection, "Count");
if (count == 1)
{
// If we call this on a selection with more items
// Outlook sends an ItemLoad event for each mail
// in that selection.
mailitem = get_oom_object (selection, "Item(1)");
}
gpgol_release (selection);
if (!mailitem)
{
*none_selected = true;
}
}
}
else if (!strcmp (ctx_name.c_str(), "Selection"))
{
int count = get_oom_int (context, "Count");
if (count == 1)
{
// If we call this on a selection with more items
// Outlook sends an ItemLoad event for each mail
// in that selection.
mailitem = get_oom_object (context, "Item(1)");
}
if (!mailitem)
{
*none_selected = true;
}
}
gpgol_release (context);
if (!mailitem)
{
log_debug ("%s:%s: No mailitem. From %s",
SRCNAME, __func__, ctx_name.c_str());
return NULL;
}
char *uid;
/* Get the uid of this item. */
uid = get_unique_id (mailitem, 0, nullptr);
if (!uid)
{
LPMESSAGE msg = get_oom_base_message (mailitem);
uid = mapi_get_uid (msg);
gpgol_release (msg);
if (!uid)
{
log_debug ("%s:%s: Failed to get uid for %p",
SRCNAME, __func__, mailitem);
gpgol_release (mailitem);
return NULL;
}
}
auto ret = Mail::get_mail_for_uuid (uid);
xfree (uid);
if (!ret)
{
log_error ("%s:%s: Failed to find mail %p in map.",
SRCNAME, __func__, mailitem);
}
gpgol_release (mailitem);
return ret;
}
/* Helper to reduce code duplication.*/
#define MY_MAIL_GETTER \
if (!ctrl) \
{ \
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); \
return E_FAIL; \
} \
bool none_selected; \
const auto mail = get_mail_from_control (ctrl, &none_selected); \
(void)none_selected; \
if (!mail) \
{ \
log_oom ("%s:%s:%i Failed to get mail", \
SRCNAME, __func__, __LINE__); \
}
HRESULT get_is_details_enabled (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
if (!result)
{
TRACEPOINT;
return S_OK;
}
result->vt = VT_BOOL | VT_BYREF;
result->pboolVal = (VARIANT_BOOL*) xmalloc (sizeof (VARIANT_BOOL));
*(result->pboolVal) = none_selected ? VARIANT_FALSE : VARIANT_TRUE;
return S_OK;
}
HRESULT get_sig_label (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BSTR;
wchar_t *w_result;
if (!mail)
{
log_debug ("%s:%s: No mail.",
SRCNAME, __func__);
w_result = utf8_to_wchar (_("Insecure"));
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
return S_OK;
}
w_result = utf8_to_wchar (mail->get_crypto_summary ().c_str ());
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
return S_OK;
}
HRESULT get_sig_ttip (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BSTR;
wchar_t *w_result;
if (mail)
{
w_result = utf8_to_wchar (mail->get_crypto_one_line().c_str());
}
else if (!none_selected)
{
w_result = utf8_to_wchar (_("Insecure message"));
}
else
{
w_result = utf8_to_wchar (_("No message selected"));
}
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
return S_OK;
}
HRESULT get_sig_stip (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BSTR;
if (none_selected)
{
result->bstrVal = SysAllocString (L"");
return S_OK;
}
if (!mail || !mail->is_crypto_mail ())
{
wchar_t *w_result;
w_result = utf8_to_wchar (utf8_gettext ("You cannot be sure who sent, "
"modified and read the message in transit.\n\n"
"Click here to learn more."));
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
return S_OK;
}
const auto message = mail->get_crypto_details ();
wchar_t *w_message = utf8_to_wchar (message.c_str());
result->bstrVal = SysAllocString (w_message);
xfree (w_message);
return S_OK;
}
HRESULT launch_cert_details (LPDISPATCH ctrl)
{
MY_MAIL_GETTER
if (!mail || (!mail->is_signed () && !mail->is_encrypted ()))
{
ShellExecuteA(NULL, NULL, "https://emailselfdefense.fsf.org/infographic",
0, 0, SW_SHOWNORMAL);
return S_OK;
}
if (!mail->is_signed () && mail->is_encrypted ())
{
/* Encrypt only, no information but show something. because
we want the button to be active.
Aheinecke: I don't think we should show to which keys the message
is encrypted here. This would confuse users if they see keyids
of unknown keys and the information can't be "true" because the
sender could have sent the same information to other people or
used throw keyids etc.
*/
char * buf;
gpgrt_asprintf (&buf, _("The message was not cryptographically signed.\n"
"There is no additional information available if it "
"was actually sent by '%s' or if someone faked the sender address."), mail->get_sender ().c_str());
MessageBox (NULL, buf, _("GpgOL"),
MB_ICONINFORMATION|MB_OK);
xfree (buf);
return S_OK;
}
if (!mail->get_sig_fpr())
{
std::string buf = _("There was an error verifying the signature.\n"
"Full details:\n");
buf += mail->get_verification_result_dump();
gpgol_message_box (get_active_hwnd(), buf.c_str(), _("GpgOL"), MB_OK);
}
char *uiserver = get_uiserver_name ();
bool showError = false;
if (uiserver)
{
std::string path (uiserver);
xfree (uiserver);
if (path.find("kleopatra.exe") != std::string::npos)
{
size_t dpos;
if ((dpos = path.find(" --daemon")) != std::string::npos)
{
path.erase(dpos, strlen(" --daemon"));
}
auto ctx = Context::createForEngine(SpawnEngine);
if (!ctx)
{
log_error ("%s:%s: No spawn engine.",
SRCNAME, __func__);
}
std::string parentWid = std::to_string ((int) (intptr_t) get_active_hwnd ());
const char *argv[] = {path.c_str(),
"--query",
mail->get_sig_fpr(),
"--parent-windowid",
parentWid.c_str(),
NULL };
log_debug ("%s:%s: Starting %s %s %s",
SRCNAME, __func__, path.c_str(), argv[1], argv[2]);
Data d(Data::null);
ctx->spawnAsync(path.c_str(), argv, d, d,
d, (GpgME::Context::SpawnFlags) (
GpgME::Context::SpawnAllowSetFg |
GpgME::Context::SpawnShowWindow));
}
else
{
showError = true;
}
}
else
{
showError = true;
}
if (showError)
{
MessageBox (NULL,
_("Could not find Kleopatra.\n"
"Please reinstall Gpg4win with the Kleopatra component enabled."),
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
}
return S_OK;
}
HRESULT get_crypto_icon (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
if (mail)
{
return getIcon (mail->get_crypto_icon_id (), result);
}
return getIcon (IDI_LEVEL_0, result);
}
HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BOOL | VT_BYREF;
result->pboolVal = (VARIANT_BOOL*) xmalloc (sizeof (VARIANT_BOOL));
*(result->pboolVal) = (mail && (mail->is_signed () || mail->is_encrypted ())) ?
VARIANT_TRUE : VARIANT_FALSE;
return S_OK;
}
HRESULT print_decrypted (LPDISPATCH ctrl)
{
MY_MAIL_GETTER
if (!mail)
{
log_error ("%s:%s: Failed to get mail.",
SRCNAME, __func__);
return S_OK;
}
invoke_oom_method (mail->item(), "PrintOut", NULL);
return S_OK;
}
diff --git a/src/ribbon-callbacks.h b/src/ribbon-callbacks.h
index 54369d6..e4658a1 100644
--- a/src/ribbon-callbacks.h
+++ b/src/ribbon-callbacks.h
@@ -1,90 +1,88 @@
/* ribbon-callbacks.h - Callbacks for the ribbon extension interface
* Copyright (C) 2013 Intevation GmbH
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifndef RIBBON_CALLBACKS_H
#define RIBBON_CALLBACKS_H
#include "gpgoladdin.h"
/* For the Icon IDS */
#include "dialogs.h"
/* Id's of our callbacks */
-#define ID_CMD_DECRYPT 2
#define ID_CMD_MIME_SIGN 13
#define ID_CMD_MIME_ENCRYPT 14
#define ID_GET_SIGN_PRESSED 15
#define ID_GET_ENCRYPT_PRESSED 16
#define ID_ON_LOAD 17
#define ID_CMD_OPEN_OPTIONS 18
#define ID_GET_IS_DETAILS_ENABLED 19
#define ID_CMD_MIME_SIGN_EX 21
#define ID_CMD_MIME_ENCRYPT_EX 22
#define ID_GET_SIGN_PRESSED_EX 23
#define ID_GET_ENCRYPT_PRESSED_EX 24
#define ID_GET_SIG_STIP 25
#define ID_GET_SIG_TTIP 26
#define ID_GET_SIG_LABEL 27
#define ID_LAUNCH_CERT_DETAILS 28
#define ID_BTN_SIGSTATE_LARGE 29
#define ID_GET_SIGN_ENCRYPT_PRESSED 30
#define ID_GET_SIGN_ENCRYPT_PRESSED_EX 31
#define ID_CMD_SIGN_ENCRYPT_MIME 32
#define ID_CMD_SIGN_ENCRYPT_MIME_EX 33
#define ID_CMD_PRINT_DECRYPTED 34
#define ID_GET_IS_CRYPTO_MAIL 35
#define ID_BTN_DECRYPT IDI_DECRYPT_16_PNG
#define ID_BTN_DECRYPT_LARGE IDI_DECRYPT_48_PNG
#define ID_BTN_ENCRYPT IDI_ENCRYPT_16_PNG
#define ID_BTN_ENCRYPT_LARGE IDI_ENCRYPT_48_PNG
#define ID_BTN_ENCSIGN_LARGE IDI_ENCSIGN_FILE_48_PNG
#define ID_BTN_SIGN_LARGE IDI_SIGN_48_PNG
#define ID_BTN_VERIFY_LARGE IDI_VERIFY_48_PNG
#define OP_ENCRYPT 1 /* Encrypt the data */
#define OP_SIGN 2 /* Sign the data */
-HRESULT decryptAttachments (LPDISPATCH ctrl);
HRESULT getIcon (int id, VARIANT* result);
/* Get the toggle state of a crypt button. Flag value 1: encrypt, 2: sign */
HRESULT get_crypt_pressed (LPDISPATCH ctrl, int flags, VARIANT *result, bool is_explorer);
/* Mark the mail to be mime encrypted on send. Flags as above */
HRESULT mark_mime_action (LPDISPATCH ctrl, int flags, bool is_explorer);
/* Check the if the gpgol button should be enabled */
HRESULT get_is_details_enabled (LPDISPATCH ctrl, VARIANT *result);
/* Get the label for the signature. Returns BSTR */
HRESULT get_sig_label (LPDISPATCH ctrl, VARIANT *result);
/* Get the tooltip for the signature. Returns BSTR */
HRESULT get_sig_ttip (LPDISPATCH ctrl, VARIANT *result);
/* Get the supertip for the signature. Returns BSTR */
HRESULT get_sig_stip (LPDISPATCH ctrl, VARIANT *result);
/* Show a certificate details dialog. Returns nothing. */
HRESULT launch_cert_details (LPDISPATCH ctrl);
/* Callback to get the sigstate icon. */
HRESULT get_crypto_icon (LPDISPATCH ctrl, VARIANT *result);
/* Callback to get our own control reference */
HRESULT ribbon_loaded (LPDISPATCH ctrl);
/* Is the currently selected mail a crypto mail ? */
HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result);
/* Print out the decrypted mail */
HRESULT print_decrypted (LPDISPATCH ctrl);
#endif