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, "" " " " " " " " " " " " " " " " " " " " " " " "