diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Makefile.am | 11 | ||||
-rw-r--r-- | tests/ce-createpipe.c | 179 | ||||
-rw-r--r-- | tests/ce-server.c | 811 | ||||
-rw-r--r-- | tests/common.h | 20 |
4 files changed, 988 insertions, 33 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am index c243c27..692748c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -20,20 +20,25 @@ TESTS_ENVIRONMENT = -EXTRA_DIST = motd +EXTRA_DIST = motd ce-createpipe.c BUILT_SOURCES = CLEANFILES = -TESTS = ce-server +TESTS = + +if HAVE_W32CE_SYSTEM +w32cetools = ce-createpipe +endif if USE_DESCRIPTOR_PASSING TESTS += fdpassing endif + AM_CFLAGS = $(GPG_ERROR_CFLAGS) noinst_HEADERS = common.h -noinst_PROGRAMS = $(TESTS) +noinst_PROGRAMS = $(TESTS) ce-server $(w32cetools) LDADD = ../src/libassuan.la $(NETLIBS) $(GPG_ERROR_LIBS) diff --git a/tests/ce-createpipe.c b/tests/ce-createpipe.c new file mode 100644 index 0000000..810edbc --- /dev/null +++ b/tests/ce-createpipe.c @@ -0,0 +1,179 @@ +/* ce-createpipe.c - Test the W32CE CreatePipe implementation. + Copyright (C) 2010 Free Software Foundation, Inc. + + This file is part of Assuan. + + Assuan 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 3 of + the License, or (at your option) any later version. + + Assuan is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include "common.h" + +#include "../src/assuan.h" /* We need the CreatePipe prototype. */ + + +static DWORD +reader_thread (void *arg) +{ + HANDLE hd = arg; + DWORD nread; + char buffer[16]; + + for (;;) + { + if (!ReadFile (hd, buffer, sizeof buffer, &nread, FALSE)) + { + log_error ("reader: ReadFile failed: rc=%d\n", (int)GetLastError ()); + break; + } + log_info ("reader: red %d bytes\n", (int)nread); + log_printhex ("got: ", buffer, nread); + } + + log_info ("reader: finished\n"); + CloseHandle (hd); + return 0; +} + + +static DWORD +writer_thread (void *arg) +{ + HANDLE hd = arg; + DWORD nwritten; + int i = 0; + int j; + char buffer[20]; + int count; + + for (count=0; count < 30; count++) + { + for (j=0; j < sizeof buffer; j++) + buffer[j] = i++; + + if (!WriteFile (hd, buffer, sizeof buffer, &nwritten, NULL)) + { + log_error ("writer: WriteFile failed: rc=%d\n", (int)GetLastError ()); + break; + } + if (nwritten != sizeof buffer) + log_info ("writer: wrote only %d bytes\n", (int)nwritten); + } + + log_info ("writer: finished\n"); + CloseHandle (hd); + return 0; +} + + + +static void +run_test (void) +{ + HANDLE hd[2]; + HANDLE threads[2]; + + if (!CreatePipe (&hd[0], &hd[1], NULL, 0)) + { + log_error ("CreatePipe failed: rc=%d\n", (int)GetLastError ()); + return; + } + log_info ("pipe created read=%p write=%p\n", hd[0], hd[1]); + + threads[0] = CreateThread (NULL, 0, reader_thread, hd[0], 0, NULL); + if (!threads[0]) + log_fatal ("error creating reader thread: rc=%d\n", (int)GetLastError ()); + else + log_info ("reader thread created\n"); + threads[1] = CreateThread (NULL, 0, writer_thread, hd[1], 0, NULL); + if (!threads[0]) + log_fatal ("error creating writer thread: rc=%d\n", (int)GetLastError ()); + else + log_info ("writer thread created\n"); + + switch (WaitForMultipleObjects (2, threads, FALSE, INFINITE)) + { + case WAIT_OBJECT_0: + log_info ("reader thread finished firstt\n"); + break; + case WAIT_OBJECT_0 + 1: + log_info ("writer thread finished first\n"); + break; + default: + log_error ("WFMO failed: rc=%d\n", (int)GetLastError ()); + break; + } + + + + CloseHandle (threads[0]); + CloseHandle (threads[1]); +} + + + +/* + M A I N + */ +int +main (int argc, char **argv) +{ + int last_argc = -1; + + if (argc) + { + log_set_prefix (*argv); + argc--; argv++; + } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--help")) + { + printf ("usage: %s [options]\n" + "\n" + "Options:\n" + " --verbose Show what is going on\n", + log_get_prefix ()); + exit (0); + } + if (!strcmp (*argv, "--verbose")) + { + verbose = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose = debug = 1; + argc--; argv++; + } + } + + run_test (); + + return errorcount ? 1 : 0; +} + diff --git a/tests/ce-server.c b/tests/ce-server.c index 1b242aa..6ff3cae 100644 --- a/tests/ce-server.c +++ b/tests/ce-server.c @@ -39,10 +39,12 @@ #include <errno.h> #ifdef HAVE_W32CE_SYSTEM -#ifndef FILE_ATTRIBUTE_ROMSTATICREF -#define FILE_ATTRIBUTE_ROMSTATICREF FILE_ATTRIBUTE_OFFLINE -#endif -#endif +# ifndef FILE_ATTRIBUTE_ROMSTATICREF +# define FILE_ATTRIBUTE_ROMSTATICREF FILE_ATTRIBUTE_OFFLINE +# endif +extern BOOL GetStdioPathW (int, wchar_t *, DWORD *); +extern BOOL SetStdioPathW (int, const wchar_t *); +#endif /*!HAVE_W32CE_SYSTEM*/ #include "../src/assuan.h" @@ -54,26 +56,201 @@ static short server_port = 15898; /* Flag set to indicate a shutdown. */ static int shutdown_pending; +/* An object to keep track of file descriptors. */ +struct fdinfo_s +{ + struct fdinfo_s *next; + assuan_fd_t fd; /* The descriptor. */ +}; +typedef struct fdinfo_s *fdinfo_t; + + /* The local state of a connection. */ struct state_s { - char *cwd; /* The current working directory - access using get_cwd(). */ + /* The current working directory - access using get_cwd(). */ + char *cwd; + + /* If valid, a socket in listening state created by the dataport + command. */ + assuan_fd_t dataport_listen_fd; + + /* If valid the socket accepted for the dataport. */ + assuan_fd_t dataport_accepted_fd; + + /* The error code from a dataport accept operation. */ + gpg_error_t dataport_accept_err; + + /* A list of all unused descriptors created by dataport commands. */ + fdinfo_t dataport_fds; + + /* The descriptor set by the DATAPORT command. */ + assuan_fd_t dataport_fd; }; typedef struct state_s *state_t; +/* Local prototypes. */ +static gpg_error_t cmd_newdataport_cont (void *opaque, gpg_error_t err, + unsigned char *data, size_t datalen); + + + +/* A wrapper around read to make it work under Windows with HANDLES + and socket descriptors. Takes care of EINTR on POSIX. */ +static int +my_read (assuan_fd_t fd, void *buffer, size_t size) +{ + int res; + +#ifdef HAVE_W32_SYSTEM + res = recv (HANDLE2SOCKET (fd), buffer, size, 0); + if (res == -1) + { + switch (WSAGetLastError ()) + { + case WSAENOTSOCK: + { + DWORD nread = 0; + + res = ReadFile (fd, buffer, size, &nread, NULL); + if (!res) + { + switch (GetLastError ()) + { + case ERROR_BROKEN_PIPE: + gpg_err_set_errno (EPIPE); + break; + + default: + gpg_err_set_errno (EIO); + } + res = -1; + } + else + res = (int) nread; + } + break; + + case WSAEWOULDBLOCK: + gpg_err_set_errno (EAGAIN); + break; + + case ERROR_BROKEN_PIPE: + gpg_err_set_errno (EPIPE); + break; + + default: + gpg_err_set_errno (EIO); + break; + } + } + return res; +#else /*!HAVE_W32_SYSTEM*/ + do + res = read (fd, buffer, size); + while (res == -1 && errno == EINTR); + return res; +#endif /*!HAVE_W32_SYSTEM*/ +} + + +/* Extended version of write(2) to guarantee that all bytes are + written. Returns 0 on success or -1 and ERRNO on failure. NOTE: + This function does not return the number of bytes written, so any + error must be treated as fatal for this connection as the state of + the receiver is unknown. This works best if blocking is allowed + (so EAGAIN cannot occur). Under Windows this function handles + socket descriptors and system handles. */ +static int +my_writen (assuan_fd_t fd, const char *buffer, size_t length) +{ + while (length) + { + int nwritten; +#ifdef HAVE_W32_SYSTEM + nwritten = send (HANDLE2SOCKET (fd), buffer, length, 0); + if (nwritten == -1 && WSAGetLastError () == WSAENOTSOCK) + { + DWORD nwrite; + + nwritten = WriteFile (fd, buffer, length, &nwrite, NULL); + if (!nwritten) + { + switch (GetLastError ()) + { + case ERROR_BROKEN_PIPE: + case ERROR_NO_DATA: + gpg_err_set_errno (EPIPE); + break; + + default: + gpg_err_set_errno (EIO); + break; + } + nwritten= -1; + } + else + nwritten = (int)nwrite; + } +#else /*!HAVE_W32_SYSTEM*/ + nwritten = write (fd, buffer, length); +#endif /*!HAVE_W32_SYSTEM*/ + if (nwritten < 0) + { + if (errno == EINTR) + continue; + return -1; /* write error */ + } + length -= nwritten; + buffer += nwritten; + } + return 0; /* okay */ +} + + + +static state_t +new_state (void) +{ + state_t state = xcalloc (1, sizeof *state); + state->dataport_listen_fd = ASSUAN_INVALID_FD; + state->dataport_accepted_fd = ASSUAN_INVALID_FD; + state->dataport_fd = ASSUAN_INVALID_FD; + return state; +} + static void release_state (state_t state) { + fdinfo_t fi, fi2; + if (!state) return; + xfree (state->cwd); + + if (state->dataport_fd != ASSUAN_INVALID_FD) + assuan_sock_close (state->dataport_fd); + if (state->dataport_listen_fd != ASSUAN_INVALID_FD) + assuan_sock_close (state->dataport_listen_fd); + if (state->dataport_accepted_fd != ASSUAN_INVALID_FD) + assuan_sock_close (state->dataport_accepted_fd); + + for (fi=state->dataport_fds; fi; fi = fi2) + { + fi2 = fi->next; + if (fi->fd != ASSUAN_INVALID_FD) + assuan_sock_close (fi->fd); + } + xfree (state); } -/* Helper to print a message while leaving a command. */ +/* Helper to print a message while leaving a command and to + acknowledge the command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { @@ -88,7 +265,7 @@ leave_cmd (assuan_context_t ctx, gpg_error_t err) log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } - return err; + return assuan_process_done (ctx, err); } @@ -167,7 +344,9 @@ get_cwd (state_t state) { /* No working directory yet. On WindowsCE make it the module directory of this process. */ +#ifdef HAVE_W32_SYSTEM char *p; +#endif #ifdef HAVE_W32CE_SYSTEM wchar_t buf[MAX_PATH+1]; size_t n; @@ -197,10 +376,63 @@ get_cwd (state_t state) } + +static gpg_error_t +reset_notify (assuan_context_t ctx, char *line) +{ + state_t state = assuan_get_pointer (ctx); + fdinfo_t fi, fi2; + /* Close all lingering dataport connections. */ + for (fi=state->dataport_fds; fi; fi = fi2) + { + fi2 = fi->next; + if (fi->fd != ASSUAN_INVALID_FD) + assuan_sock_close (fi->fd); + } + state->dataport_fds = NULL; + return 0; +} +static gpg_error_t +input_notify (assuan_context_t ctx, char *line) +{ + state_t state = assuan_get_pointer (ctx); + assuan_fd_t fd = assuan_get_input_fd (ctx); + fdinfo_t fi; + + if (fd != ASSUAN_INVALID_FD) + { + /* The fd is now in use use - remove it from the unused list. */ + for (fi=state->dataport_fds; fi; fi = fi->next) + if (fi->fd == fd) + fi->fd = ASSUAN_INVALID_FD; + } + + return 0; +} + + +static gpg_error_t +output_notify (assuan_context_t ctx, char *line) +{ + state_t state = assuan_get_pointer (ctx); + assuan_fd_t fd = assuan_get_output_fd (ctx); + fdinfo_t fi; + + if (fd != ASSUAN_INVALID_FD) + { + /* The fd is now in use - remove it from the unused list. */ + for (fi=state->dataport_fds; fi; fi = fi->next) + if (fi->fd == fd) + fi->fd = ASSUAN_INVALID_FD; + } + + return 0; +} + static const char hlp_echo[] = @@ -218,6 +450,116 @@ cmd_echo (assuan_context_t ctx, char *line) } + +static const char hlp_cat[] = + "CAT [<filename>]\n" + "\n" + "Copy the content of FILENAME to the descriptor set by the OUTPUT\n" + "command. If no OUTPUT command has been given, send the content\n" + "using data lines. Without FILENAME take the content from the\n" + "descriptor set by the INPUT command; if a DATAPORT has been set\n" + "this descriptor is used for I/O and the INOPUT/OUTPUT descriptors\n" + "are not touched."; +static gpg_error_t +cmd_cat (assuan_context_t ctx, char *line) +{ + state_t state = assuan_get_pointer (ctx); + gpg_error_t err = 0; + assuan_fd_t fd_in = ASSUAN_INVALID_FD; + assuan_fd_t fd_out = ASSUAN_INVALID_FD; + FILE *fp_in = NULL; + char buf[256]; + size_t nread; + int use_dataport = 0; + + if (*line) + { + fp_in = fopen (line, "rb"); + if (!fp_in) + err = gpg_error_from_syserror (); + else + fd_out = assuan_get_output_fd (ctx); + } + else if (state->dataport_fd != ASSUAN_INVALID_FD) + { + use_dataport = 1; + fd_in = state->dataport_fd; + fd_out = state->dataport_fd; + } + else if ((fd_in = assuan_get_input_fd (ctx)) != ASSUAN_INVALID_FD) + { + /* This FD is actually a socket descriptor. We can't fdopen it + because under Windows we ust use recv(2) instead of read(2). + Note that on POSIX systems there is no difference between + libc file descriptors and socket descriptors. */ + + fd_out = assuan_get_output_fd (ctx); + } + else + err = gpg_error (GPG_ERR_ASS_NO_INPUT); + if (err) + goto leave; + + do + { + if (fp_in) + { + nread = fread (buf, 1, sizeof buf, fp_in); + if (nread < sizeof buf) + { + if (ferror (fp_in)) + err = gpg_error_from_syserror (); + else if (feof (fp_in)) + err = gpg_error (GPG_ERR_EOF); + } + } + else + { + int n; + + nread = 0; + n = my_read (fd_in, buf, sizeof buf); + if (n < 0) + err = gpg_error_from_syserror (); + else if (!n) + err = gpg_error (GPG_ERR_EOF); + else + nread = n; + } + + + if (fd_out != ASSUAN_INVALID_FD) + { + if (nread && my_writen (fd_out, buf, nread)) + err = gpg_error_from_syserror (); + } + else if (nread) + err = assuan_send_data (ctx, buf, nread); + } + while (!err); + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + +leave: + if (fp_in) + fclose (fp_in); + if (use_dataport) + { + if (state->dataport_fd != ASSUAN_INVALID_FD) + { + assuan_sock_close (state->dataport_fd); + state->dataport_fd = ASSUAN_INVALID_FD; + } + } + else + { + assuan_close_input_fd (ctx); + assuan_close_output_fd (ctx); + } + return leave_cmd (ctx, err); +} + + static const char hlp_pwd[] = "PWD\n" "\n" @@ -374,12 +716,25 @@ cmd_run (assuan_context_t ctx, char *line) { /* state_t state = assuan_get_pointer (ctx); */ gpg_error_t err; + BOOL w32ret; PROCESS_INFORMATION pi = { NULL, 0, 0, 0 }; char *p; wchar_t *pgmname = NULL; wchar_t *cmdline = NULL; int code; DWORD exc; + int idx; + struct { + HANDLE hd[2]; + int oldname_valid; + wchar_t oldname[MAX_PATH]; + } pipes[3]; + + for (idx=0; idx < 3; idx++) + { + pipes[idx].hd[0] = pipes[idx].hd[1] = INVALID_HANDLE_VALUE; + pipes[idx].oldname_valid = 0; + } p = strchr (line, ' '); if (p) @@ -399,19 +754,77 @@ cmd_run (assuan_context_t ctx, char *line) xfree (tmp2); xfree (tmp1); } - if (!CreateProcess (pgmname, /* Program to start. */ - cmdline, /* Command line arguments. */ - NULL, /* Process security attributes. notsup. */ - NULL, /* Thread security attributes. notsup. */ - FALSE, /* Inherit handles. notsup. */ - CREATE_SUSPENDED, /* Creation flags. */ - NULL, /* Environment. notsup. */ - NULL, /* Use current drive/directory. notsup. */ - NULL, /* Startup information. notsup. */ - &pi /* Returns process information. */ - )) - { - log_error ("CreateProcess failed: %d", GetLastError ()); + + /* Redirect the standard handles. */ + /* Create pipes. */ + for (idx=0; idx < 3; idx++) + { + if (!_assuan_w32ce_create_pipe (&pipes[idx].hd[0], &pipes[idx].hd[1], + NULL, 0)) + { + err = gpg_error_from_syserror (); + log_error ("CreatePipe failed: %d\n", GetLastError ()); + pipes[idx].hd[0] = pipes[idx].hd[1] = INVALID_HANDLE_VALUE; + goto leave; + } + } + + /* Save currently assigned devices. */ + for (idx=0; idx < 3; idx++) + { + DWORD dwlen = MAX_PATH; + if (!GetStdioPathW (idx, pipes[idx].oldname, &dwlen)) + { + err = gpg_error_from_syserror (); + log_error ("GetStdioPath failed: %d\n", GetLastError ()); + goto leave; + } + pipes[idx].oldname_valid = 1; + } + + /* Connect the pipes. */ + { + if (!SetStdioPathW (1, L"\\mystdout.log")) + { + err = gpg_error_from_syserror (); + log_error ("SetStdioPathW(%d) failed: %d\n", idx, GetLastError ()); + goto leave; + } + if (!SetStdioPathW (2, L"\\mystderr.log")) + { + err = gpg_error_from_syserror (); + log_error ("SetStdioPathW(%d) failed: %d\n", idx, GetLastError ()); + goto leave; + } + } + + /* Create the process, restore the devices and check the error. */ + w32ret = CreateProcess (pgmname, /* Program to start. */ + cmdline, /* Command line arguments. */ + NULL, /* Process security. Not used. */ + NULL, /* Thread security. Not used. */ + FALSE, /* Inherit handles. Not used. */ + CREATE_SUSPENDED, /* Creation flags. */ + NULL, /* Environment. Not used. */ + NULL, /* Use current dir. Not used. */ + NULL, /* Startup information. Not used. */ + &pi /* Returns process information. */ + ); + for (idx=0; idx < 3; idx++) + { + if (pipes[idx].oldname_valid) + { + if (!SetStdioPathW (idx, pipes[idx].oldname)) + log_error ("SetStdioPath(%d) failed during restore: %d\n", + idx, GetLastError ()); + else + pipes[idx].oldname_valid = 0; + } + } + if (!w32ret) + { + /* Error checking after restore so that the messages are visible. */ + log_error ("CreateProcess failed: %d\n", GetLastError ()); err = gpg_error_from_syserror (); goto leave; } @@ -460,6 +873,24 @@ cmd_run (assuan_context_t ctx, char *line) CloseHandle (pi.hProcess); leave: + for (idx=0; idx < 3; idx++) + { + if (pipes[idx].oldname_valid) + { + if (!SetStdioPathW (idx, pipes[idx].oldname)) + log_error ("SetStdioPath(%d) failed during restore: %d\n", + idx, GetLastError ()); + else + pipes[idx].oldname_valid = 0; + } + } + for (idx=0; idx < 3; idx++) + { + if (pipes[idx].hd[0] != INVALID_HANDLE_VALUE) + CloseHandle (pipes[idx].hd[0]); + if (pipes[idx].hd[1] != INVALID_HANDLE_VALUE) + CloseHandle (pipes[idx].hd[1]); + } xfree (cmdline); xfree (pgmname); return leave_cmd (ctx, err); @@ -467,17 +898,254 @@ cmd_run (assuan_context_t ctx, char *line) #endif /*HAVE_W32CE_SYSTEM*/ + +static const char hlp_newdataport[] = + "NEWDATAPORT\n" + "\n" + "Create a new dataport. The server creates a listening socket and\n" + "issues the inquiry:\n" + " INQUIRE CONNECT-TO <port>\n" + "The client is expected to connect to PORT of the server and confirm\n" + "this by sending just an \"END\". In turn the server sends:\n" + " S FDINFO <n>\n" + "With N being the local descriptor for the accepted connection. This\n" + "descriptor may now be used with INPUT or OUTPUT commands."; +struct cmd_dataport_locals +{ + assuan_context_t ctx; + int passthru; + int port; +}; +static gpg_error_t +cmd_newdataport (assuan_context_t ctx, char *line) +{ + state_t state = assuan_get_pointer (ctx); + gpg_error_t err = 0; + struct sockaddr_in addr; + socklen_t addrlen; + struct cmd_dataport_locals *cont; + char inqline[100]; + + cont = xmalloc (sizeof *cont); + cont->ctx = ctx; + cont->passthru = 0; + cont->port = 0; + + if (state->dataport_listen_fd != ASSUAN_INVALID_FD) + { + log_error ("Oops, still listening on a dataport socket\n"); + state->dataport_listen_fd = ASSUAN_INVALID_FD; + } + if (state->dataport_accepted_fd != ASSUAN_INVALID_FD) + { + log_error ("Oops, still holding an accepted dataport socket\n"); + state->dataport_accepted_fd = ASSUAN_INVALID_FD; + } + state->dataport_accept_err = 0; + + state->dataport_listen_fd = assuan_sock_new (PF_INET, SOCK_STREAM, 0); + if (state->dataport_listen_fd == ASSUAN_INVALID_FD) + { + err = gpg_error_from_syserror (); + log_error ("socket() failed: %s\n", strerror (errno)); + goto leave; + } + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = htonl (INADDR_ANY); + if (assuan_sock_bind (state->dataport_listen_fd, + (struct sockaddr *)&addr, sizeof addr)) + { + err = gpg_error_from_syserror (); + log_error ("listen() failed: %s\n", strerror (errno)); + goto leave; + } + + if (listen (HANDLE2SOCKET (state->dataport_listen_fd), 1)) + { + err = gpg_error_from_syserror (); + log_error ("listen() failed: %s\n", strerror (errno)); + goto leave; + } + + addrlen = sizeof addr; + if (getsockname (HANDLE2SOCKET (state->dataport_listen_fd), + (struct sockaddr *)&addr, &addrlen)) + { + err = gpg_error_from_syserror (); + log_error ("getsockname() failed: %s\n", strerror (errno)); + goto leave; + } + cont->port = ntohs (addr.sin_port); + + if (verbose) + log_info ("server now also listening on port %d\n", cont->port); + snprintf (inqline, sizeof inqline, "CONNECT-TO %d", cont->port); + err = assuan_inquire_ext (ctx, inqline, 0, cmd_newdataport_cont, cont); + if (!err) + return 0; /* Transfer to continuation. */ + + leave: + cont->passthru = 1; + return cmd_newdataport_cont (cont, err, NULL, 0); +} + +/* Continuation used by cmd_newdataport. */ +static gpg_error_t +cmd_newdataport_cont (void *opaque, gpg_error_t err, + unsigned char *data, size_t datalen) +{ + struct cmd_dataport_locals *cont = opaque; + assuan_context_t ctx = cont->ctx; + state_t state = assuan_get_pointer (ctx); + char numbuf[35]; + fdinfo_t fi; + + if (cont->passthru || err) + goto leave; + + err = state->dataport_accept_err; + if (err) + goto leave; + if (state->dataport_listen_fd != ASSUAN_INVALID_FD + || state->dataport_accepted_fd == ASSUAN_INVALID_FD) + { + err = gpg_error (GPG_ERR_MISSING_ACTION); + goto leave; + } + + for (fi = state->dataport_fds; fi; fi = fi->next) + if (fi->fd == ASSUAN_INVALID_FD) + break; + if (!fi) + { + fi = xcalloc (1, sizeof *fi); + fi->next = state->dataport_fds; + state->dataport_fds = fi; + } + fi->fd = state->dataport_accepted_fd; + state->dataport_accepted_fd = ASSUAN_INVALID_FD; + + /* Note that under Windows the FD is the socket descriptor. Socket + descriptors are neither handles nor libc file descriptors. */ + snprintf (numbuf, sizeof numbuf, "%d", HANDLE2SOCKET (fi->fd)); + err = assuan_write_status (ctx, "FDINFO", numbuf); + + leave: + if (state->dataport_listen_fd != ASSUAN_INVALID_FD) + { + assuan_sock_close (state->dataport_listen_fd); + state->dataport_listen_fd = ASSUAN_INVALID_FD; + } + if (state->dataport_accepted_fd != ASSUAN_INVALID_FD) + { + assuan_sock_close (state->dataport_accepted_fd); + state->dataport_accepted_fd = ASSUAN_INVALID_FD; + } + xfree (cont); + return leave_cmd (ctx, err); +} + + + +static const char hlp_dataport[] = + "DATAPORT FD[=<n>]\n" + "\n" + "Set the file descriptor to read and write data via port.\n" + "This is similar to the \"INPUT\" and \"OUTPUT\" commands\n" + "but useful for socketpairs."; +static gpg_error_t +cmd_dataport (assuan_context_t ctx, char *line) +{ + state_t state = assuan_get_pointer (ctx); + gpg_error_t err; + assuan_fd_t fd; + + if (state->dataport_fd != ASSUAN_INVALID_FD) + { + assuan_sock_close (state->dataport_fd); + state->dataport_fd = ASSUAN_INVALID_FD; + } + + err = assuan_command_parse_fd (ctx, line, &fd); + if (!err && fd != ASSUAN_INVALID_FD) + { + fdinfo_t fi; + + state->dataport_fd = fd; + + /* The fd is now in use use - remove it from the unused list. */ + for (fi=state->dataport_fds; fi; fi = fi->next) + if (fi->fd == fd) + fi->fd = ASSUAN_INVALID_FD; + } + + return leave_cmd (ctx, err); +} + + + +static const char hlp_getinfo[] = + "GETINFO <what>\n" + "\n" + "Multipurpose function to return a variety of information.\n" + "Supported values for WHAT are:\n" + "\n" + " version - Return the version of the program.\n" + " pid - Return the process id of the server.\n" + " dataports - Return a list of usused dataports."; +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + state_t state = assuan_get_pointer (ctx); + gpg_error_t err = 0; + char numbuf[50]; + + if (!strcmp (line, "version")) + { + const char *s = VERSION; + err = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "pid")) + { + snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); + err = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else if (!strcmp (line, "dataports")) + { + fdinfo_t fi; + int any = 0; + + for (fi=state->dataport_fds; !err && fi; fi = fi->next) + { + if (fi->fd != ASSUAN_INVALID_FD) + { + snprintf (numbuf, sizeof numbuf, "%s%d", + any? " ":"", HANDLE2SOCKET (fi->fd)); + any = 1; + err = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + } + } + else + err = gpg_error (GPG_ERR_ASS_PARAMETER); + + return leave_cmd (ctx, err); +} + + + static const char hlp_shutdown[] = "SHUTDOWN\n" "\n" - "Shutdown the server process after ending this connection\n"; + "Shutdown the server process\n"; static gpg_error_t cmd_shutdown (assuan_context_t ctx, char *line) { - (void)ctx; (void)line; shutdown_pending = 1; - return 0; + return leave_cmd (ctx, 0);; } @@ -498,8 +1166,12 @@ register_commands (assuan_context_t ctx) { "PWD", cmd_pwd, hlp_pwd }, { "CD", cmd_cd, hlp_cd }, { "ECHO", cmd_echo, hlp_echo }, + { "CAT", cmd_cat, hlp_cat }, + { "NEWDATAPORT", cmd_newdataport, hlp_newdataport }, + { "DATAPORT", cmd_dataport, hlp_dataport }, { "INPUT", NULL }, { "OUTPUT", NULL }, + { "GETINFO", cmd_getinfo, hlp_getinfo }, { "SHUTDOWN", cmd_shutdown, hlp_shutdown }, { NULL, NULL } }; @@ -517,6 +1189,20 @@ register_commands (assuan_context_t ctx) } + +static assuan_fd_t +get_connection_fd (assuan_context_t ctx) +{ + assuan_fd_t fds[5]; + + if (assuan_get_active_fds (ctx, 0, fds, DIM (fds)) < 1) + log_fatal ("assuan_get_active_fds failed\n"); + if (fds[0] == ASSUAN_INVALID_FD) + log_fatal ("assuan_get_active_fds returned invalid conenction fd\n"); + return fds[0]; +} + + /* Startup the server. */ static void server (void) @@ -535,20 +1221,20 @@ server (void) server_fd = assuan_sock_new (PF_INET, SOCK_STREAM, 0); if (server_fd == ASSUAN_INVALID_FD) - log_fatal ("socket() failed: %s", strerror (errno)); + log_fatal ("socket() failed: %s\n", strerror (errno)); if (setsockopt (HANDLE2SOCKET (server_fd), SOL_SOCKET, SO_REUSEADDR, (void*)&one, sizeof one)) - log_error ("setsockopt(SO_REUSEADDR) failed: %s", strerror (errno)); + log_error ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno)); name.sin_family = AF_INET; name.sin_port = htons (server_port); name.sin_addr.s_addr = htonl (INADDR_ANY); if (assuan_sock_bind (server_fd, (struct sockaddr *) &name, sizeof name)) - log_fatal ("bind() failed: %s", strerror (errno)); + log_fatal ("bind() failed: %s\n", strerror (errno)); if (assuan_sock_get_nonce ((struct sockaddr*)&name, sizeof name, &server_nonce)) - log_fatal ("assuan_sock_get_nonce failed: %s", strerror (errno)); + log_fatal ("assuan_sock_get_nonce failed: %s\n", strerror (errno)); /* Register the nonce with the context so that assuan_accept knows about it. We can't do that directly in assuan_sock_bind because @@ -572,12 +1258,19 @@ server (void) if (debug) assuan_set_log_stream (ctx, stderr); + assuan_register_reset_notify (ctx, reset_notify); + assuan_register_input_notify (ctx, input_notify); + assuan_register_output_notify (ctx, output_notify); + + + state = new_state (); - state = xcalloc (1, sizeof state); assuan_set_pointer (ctx, state); while (!shutdown_pending) { + int done; + err = assuan_accept (ctx); if (err) { @@ -588,8 +1281,66 @@ server (void) log_info ("client connected. Client's pid is %ld\n", (long)assuan_get_pid (ctx)); - - err = assuan_process (ctx); + do + { + /* We need to use select here so that we can accept + supplemental connections from the client as requested by + the DATAPORT command. */ + fd_set rfds; + int connfd, datafd, max_fd; + + connfd = HANDLE2SOCKET (get_connection_fd (ctx)); + FD_ZERO (&rfds); + FD_SET (connfd, &rfds); + max_fd = connfd; + + if (state->dataport_listen_fd != ASSUAN_INVALID_FD) + { + datafd = HANDLE2SOCKET (state->dataport_listen_fd); + FD_SET (datafd, &rfds); + if (datafd > max_fd) + max_fd = datafd; + } + else + datafd = -1; + + if (select (max_fd + 1, &rfds, NULL, NULL, NULL) > 0) + { + if (datafd != -1 && FD_ISSET (datafd, &rfds)) + { + struct sockaddr_in clnt_addr; + socklen_t len = sizeof clnt_addr; + int fd; + + fd = accept (datafd, (struct sockaddr*)&clnt_addr, &len); + if (fd == -1) + { + err = gpg_err_code_from_syserror (); + assuan_sock_close (state->dataport_listen_fd); + state->dataport_listen_fd = ASSUAN_INVALID_FD; + log_error ("accepting on dataport failed: %s\n", + gpg_strerror (err)); + state->dataport_accept_err = err; + err = 0; + } + else + { + /* No more need for the listening socket. */ + assuan_sock_close (state->dataport_listen_fd); + state->dataport_listen_fd = ASSUAN_INVALID_FD; + /* Record the accepted fd. */ + state->dataport_accept_err = 0; + state->dataport_accepted_fd = SOCKET2HANDLE (fd); + } + } + + if (FD_ISSET (connfd, &rfds)) + { + err = assuan_process_next (ctx, &done); + } + } + } + while (!err && !done && !shutdown_pending); if (err) log_error ("assuan_process failed: %s\n", gpg_strerror (err)); } diff --git a/tests/common.h b/tests/common.h index 4aca63a..c5ce811 100644 --- a/tests/common.h +++ b/tests/common.h @@ -34,9 +34,14 @@ #if HAVE_W32_SYSTEM #define SOCKET2HANDLE(s) ((void *)(s)) #define HANDLE2SOCKET(h) ((unsigned int)(h)) +CRITICAL_SECTION _log_critsect; +#define _log_enter() do { EnterCriticalSection (&_log_critsect); } while (0) +#define _log_leave() do { LeaveCriticalSection (&_log_critsect); } while (0) #else #define SOCKET2HANDLE(s) (s) #define HANDLE2SOCKET(h) (h) +#define _log_enter() do { } while (0) +#define _log_leave() do { } while (0) #endif #define DIM(v) (sizeof(v)/sizeof((v)[0])) @@ -71,9 +76,11 @@ xcalloc (size_t n, size_t m) char *p = calloc (n, m); if (!p) { + _log_enter (); if (log_prefix) fprintf (stderr, "%s[%u]: ", log_prefix, (unsigned int)getpid ()); fprintf (stderr, "out of core\n"); + _log_leave (); exit (1); } return p; @@ -98,7 +105,12 @@ xstrdup (const char *string) void log_set_prefix (const char *s) { +#ifdef HAVE_W32_SYSTEM + InitializeCriticalSection (&_log_critsect); + log_prefix = strrchr (s, '\\'); +#else log_prefix = strrchr (s, '/'); +#endif if (log_prefix) log_prefix++; else @@ -122,9 +134,11 @@ log_info (const char *format, ...) return; va_start (arg_ptr, format) ; + _log_enter (); if (log_prefix) fprintf (stderr, "%s[%u]: ", log_prefix, (unsigned int)getpid ()); vfprintf (stderr, format, arg_ptr ); + _log_leave (); va_end (arg_ptr); } @@ -135,9 +149,11 @@ log_error (const char *format, ...) va_list arg_ptr ; va_start (arg_ptr, format) ; + _log_enter (); if (log_prefix) fprintf (stderr, "%s[%u]: ", log_prefix, (unsigned int)getpid ()); vfprintf (stderr, format, arg_ptr ); + _log_leave (); va_end (arg_ptr); errorcount++; } @@ -149,9 +165,11 @@ log_fatal (const char *format, ...) va_list arg_ptr ; va_start (arg_ptr, format) ; + _log_enter (); if (log_prefix) fprintf (stderr, "%s[%u]: ", log_prefix, (unsigned int)getpid ()); vfprintf (stderr, format, arg_ptr ); + _log_leave (); va_end (arg_ptr); exit (2); } @@ -162,12 +180,14 @@ log_printhex (const char *text, const void *buffer, size_t length) { const unsigned char *s; + _log_enter (); if (log_prefix) fprintf (stderr, "%s[%u]: ", log_prefix, (unsigned int)getpid ()); fputs (text, stderr); for (s=buffer; length; s++, length--) fprintf (stderr, "%02X", *s); putc ('\n', stderr); + _log_leave (); } |