diff --git a/src/engine-assuan.c b/src/engine-assuan.c
index 87036d6..8907c18 100644
--- a/src/engine-assuan.c
+++ b/src/engine-assuan.c
@@ -1,2200 +1,2245 @@
/* 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;
+ {
+ 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;
}