diff options
Diffstat (limited to 'src/system.c')
-rw-r--r-- | src/system.c | 805 |
1 files changed, 805 insertions, 0 deletions
diff --git a/src/system.c b/src/system.c index dd0c079..91a5a10 100644 --- a/src/system.c +++ b/src/system.c @@ -24,10 +24,25 @@ #include <stdlib.h> #include <errno.h> +/* Solaris 8 needs sys/types.h before time.h. */ +#include <sys/types.h> +#include <time.h> +#include <fcntl.h> +#ifdef HAVE_W32_SYSTEM +# include <windows.h> +#else +# include <sys/wait.h> +#endif #include "assuan-defs.h" #include "debug.h" +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + /* Manage memory specific to a context. */ @@ -70,3 +85,793 @@ _assuan_free (assuan_context_t ctx, void *ptr) if (ptr) ctx->malloc_hooks.free (ptr); } + + +/* Copy the system hooks struct, paying attention to version + differences. SRC is usually from the user, DST MUST be from the + library. */ +void +_assuan_system_hooks_copy (assuan_system_hooks_t dst, + assuan_system_hooks_t src) + +{ + memset (dst, '\0', sizeof (*dst)); + + dst->version = ASSUAN_SYSTEM_HOOKS_VERSION; + if (src->version >= 1) + { + dst->usleep = src->usleep; + dst->pipe = src->pipe; + dst->close = src->close; + dst->read = src->read; + dst->write = src->write; + dst->sendmsg = src->sendmsg; + dst->recvmsg = src->recvmsg; + dst->spawn = src->spawn; + dst->waitpid = src->waitpid; + dst->socketpair = src->socketpair; + } + if (src->version > 1) + /* FIXME. Application uses newer version of the library. What to + do? */ + ; +} + + +/* Sleep for the given number of microseconds. Default + implementation. */ +static void +__assuan_usleep (assuan_context_t ctx, unsigned int usec) +{ + if (! usec) + return; + +#ifdef HAVE_NANOSLEEP + { + struct timespec req; + struct timespec rem; + + req.tv_sec = 0; + req.tv_nsec = usec * 1000; + + while (nanosleep (&req, &rem) < 0 && errno == EINTR) + req = rem; + } +#elif defined(HAVE_W32_SYSTEM) + Sleep (usec / 1000); +#else + { + struct timeval tv; + + tv.tv_sec = usec / 1000000; + tv.tv_usec = usec % 1000000; + select (0, NULL, NULL, NULL, &tv); + } +#endif +} + + +/* Sleep for the given number of microseconds. */ +void +_assuan_usleep (assuan_context_t ctx, unsigned int usec) +{ + TRACE1 (ctx, ASSUAN_LOG_SYSIO, "_assuan_usleep", ctx, + "usec=%u", usec); + + return (ctx->system.usleep) (ctx, usec); +} + + +/* Create a pipe with one inheritable end. Default implementation. */ +int +__assuan_pipe (assuan_context_t ctx, assuan_fd_t fd[2], int inherit_idx) +{ +#ifdef HAVE_W32_SYSTEM + HANDLE rh; + HANDLE wh; + HANDLE th; + SECURITY_ATTRIBUTES sec_attr; + TRACE_BEG2 (ctx, ASSUAN_LOG_SYSIO, "__assuan_pipe", ctx, + "inherit_idx=%i (Assuan uses it for %s)", + inherit_idx, inherit_idx ? "reading" : "writing"); + + memset (&sec_attr, 0, sizeof (sec_attr)); + sec_attr.nLength = sizeof (sec_attr); + sec_attr.bInheritHandle = FALSE; + + if (! CreatePipe (&rh, &wh, &sec_attr, 0)) + { + TRACE_LOG1 ("CreatePipe failed: %s", _assuan_w32_strerror (ctx, -1)); + errno = EIO; + return TRACE_SYSRES (-1); + } + + if (! DuplicateHandle (GetCurrentProcess(), (inherit_idx == 0) ? rh : wh, + GetCurrentProcess(), &th, 0, + TRUE, DUPLICATE_SAME_ACCESS )) + { + TRACE_LOG1 ("DuplicateHandle failed: %s", _assuan_w32_strerror (ctx, -1)); + CloseHandle (rh); + CloseHandle (wh); + errno = EIO; + return TRACE_SYSRES (-1); + } + if (inherit_idx == 0) + { + CloseHandle (rh); + rh = th; + } + else + { + CloseHandle (wh); + wh = th; + } + + fd[0] = rh; + fd[1] = wh; + + return TRACE_SUC (); +#else + return pipe (fd); +#endif +} + + +/* Create a pipe with one inheritable end. */ +int +_assuan_pipe (assuan_context_t ctx, assuan_fd_t fd[2], int inherit_idx) +{ + return (ctx->system.pipe) (ctx, fd, inherit_idx); +} + + +/* Close the given file descriptor, created with _assuan_pipe or one + of the socket functions. Default implementation. */ +int +__assuan_close (assuan_context_t ctx, assuan_fd_t fd) +{ +#ifdef HAVE_W32_SYSTEM + int rc = closesocket (HANDLE2SOCKET(fd)); + if (rc) + errno = _assuan_sock_wsa2errno (WSAGetLastError ()); + if (rc && WSAGetLastError () == WSAENOTSOCK) + { + rc = CloseHandle (fd); + if (rc) + /* FIXME. */ + errno = EIO; + } + return rc; +#else + return close (fd); +#endif +} + + +/* Close the given file descriptor, created with _assuan_pipe or one + of the socket functions. */ +int +_assuan_close (assuan_context_t ctx, assuan_fd_t fd) +{ + TRACE1 (ctx, ASSUAN_LOG_SYSIO, "_assuan_close", ctx, + "fd=0x%x", fd); + + return (ctx->system.close) (ctx, fd); +} + + +static ssize_t +__assuan_read (assuan_context_t ctx, assuan_fd_t fd, void *buffer, size_t size) +{ +#ifdef HAVE_W32_SYSTEM + /* Due to the peculiarities of the W32 API we can't use read for a + network socket and thus we try to use recv first and fallback to + read if recv detects that it is not a network socket. */ + int res; + + 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: + errno = EPIPE; + break; + + default: + errno = EIO; + } + res = -1; + } + else + res = (int) nread; + } + break; + + case WSAEWOULDBLOCK: + errno = EAGAIN; + break; + + case ERROR_BROKEN_PIPE: + errno = EPIPE; + break; + + default: + errno = EIO; + break; + } + } + return res; +#else /*!HAVE_W32_SYSTEM*/ + return read (fd, buffer, size); +#endif /*!HAVE_W32_SYSTEM*/ +} + + +ssize_t +_assuan_read (assuan_context_t ctx, assuan_fd_t fd, void *buffer, size_t size) +{ + return (ctx->system.read) (ctx, fd, buffer, size); +} + + +static ssize_t +__assuan_write (assuan_context_t ctx, assuan_fd_t fd, const void *buffer, + size_t size) +{ +#ifdef HAVE_W32_SYSTEM + /* Due to the peculiarities of the W32 API we can't use write for a + network socket and thus we try to use send first and fallback to + write if send detects that it is not a network socket. */ + int res; + + res = send (HANDLE2SOCKET (fd), buffer, size, 0); + if (res == -1 && WSAGetLastError () == WSAENOTSOCK) + { + DWORD nwrite; + + res = WriteFile (fd, buffer, size, &nwrite, NULL); + if (! res) + { + switch (GetLastError ()) + { + case ERROR_BROKEN_PIPE: + case ERROR_NO_DATA: + errno = EPIPE; + break; + + default: + errno = EIO; + break; + } + res = -1; + } + else + res = (int) nwrite; + } + return res; +#else /*!HAVE_W32_SYSTEM*/ + return write (fd, buffer, size); +#endif /*!HAVE_W32_SYSTEM*/ +} + + +ssize_t +_assuan_write (assuan_context_t ctx, assuan_fd_t fd, const void *buffer, + size_t size) +{ + return (ctx->system.write) (ctx, fd, buffer, size); +} + + +static int +__assuan_recvmsg (assuan_context_t ctx, assuan_fd_t fd, assuan_msghdr_t msg, + int flags) +{ +#ifdef HAVE_W32_SYSTEM + errno = ENOSYS; + return -1; +#else + int ret; + do + ret = recvmsg (fd, msg, flags); + while (ret == -1 && errno == EINTR); + + return ret; +#endif +} + + +int +_assuan_recvmsg (assuan_context_t ctx, assuan_fd_t fd, assuan_msghdr_t msg, + int flags) +{ + return (ctx->system.recvmsg) (ctx, fd, msg, flags); +} + + +static int +__assuan_sendmsg (assuan_context_t ctx, assuan_fd_t fd, assuan_msghdr_t msg, + int flags) +{ +#ifdef HAVE_W32_SYSTEM + errno = ENOSYS; + return -1; +#else + int ret; + do + ret = sendmsg (fd, msg, flags); + while (ret == -1 && errno == EINTR); + + return ret; +#endif +} + + +int +_assuan_sendmsg (assuan_context_t ctx, assuan_fd_t fd, assuan_msghdr_t msg, + int flags) +{ + return (ctx->system.sendmsg) (ctx, fd, msg, flags); +} + + +#ifdef HAVE_W32_SYSTEM +/* Build a command line for use with W32's CreateProcess. On success + CMDLINE gets the address of a newly allocated string. */ +static int +build_w32_commandline (assuan_context_t ctx, const char * const *argv, + char **cmdline) +{ + int i, n; + const char *s; + char *buf, *p; + + *cmdline = NULL; + n = 0; + for (i=0; (s = argv[i]); i++) + { + n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */ + for (; *s; s++) + if (*s == '\"') + n++; /* Need to double inner quotes. */ + } + n++; + + buf = p = _assuan_malloc (ctx, n); + if (! buf) + return -1; + + for (i = 0; argv[i]; i++) + { + if (i) + p = stpcpy (p, " "); + if (! *argv[i]) /* Empty string. */ + p = stpcpy (p, "\"\""); + else if (strpbrk (argv[i], " \t\n\v\f\"")) + { + p = stpcpy (p, "\""); + for (s = argv[i]; *s; s++) + { + *p++ = *s; + if (*s == '\"') + *p++ = *s; + } + *p++ = '\"'; + *p = 0; + } + else + p = stpcpy (p, argv[i]); + } + + *cmdline= buf; + return 0; +} + + +int +__assuan_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name, + const char **argv, + assuan_fd_t fd_in, assuan_fd_t fd_out, + assuan_fd_t *fd_child_list, + void (*atfork) (void *opaque, int reserved), + void *atforkvalue, unsigned int flags) +{ + SECURITY_ATTRIBUTES sec_attr; + PROCESS_INFORMATION pi = + { + NULL, /* Returns process handle. */ + 0, /* Returns primary thread handle. */ + 0, /* Returns pid. */ + 0 /* Returns tid. */ + }; + STARTUPINFO si; + int fd; + int *fdp; + char *cmdline; + HANDLE nullfd = INVALID_HANDLE_VALUE; + + /* fixme: Actually we should set the "_assuan_pipe_connect_pid" env + variable. However this requires us to write a full environment + handler, because the strings are expected in sorted order. The + suggestion given in the MS Reference Library, to save the old + value, changeit, create proces and restore it, is not thread + safe. */ + + /* Build the command line. */ + if (build_w32_commandline (ctx, argv, &cmdline)) + return -1; + + /* Start the process. */ + memset (&sec_attr, 0, sizeof sec_attr); + sec_attr.nLength = sizeof sec_attr; + sec_attr.bInheritHandle = FALSE; + + memset (&si, 0, sizeof si); + si.cb = sizeof (si); + si.dwFlags = STARTF_USESTDHANDLES; + /* FIXME: Dup to nul if ASSUAN_INVALID_FD. */ + si.hStdInput = fd_in; + si.hStdOutput = fd_out; + + /* Dup stderr to /dev/null unless it is in the list of FDs to be + passed to the child. */ + fd = fileno (stderr); + fdp = fd_child_list; + if (fdp) + { + for (; *fdp != -1 && *fdp != fd; fdp++) + ; + } + if (!fdp || *fdp == -1) + { + nullfd = CreateFile ("nul", GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if (nullfd == INVALID_HANDLE_VALUE) + { + TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx, + "can't open `nul': %s", w32_strerror (ctx, -1)); + _assuan_free (cmdline); + return -1; + } + si.hStdError = nullfd; + } + else + si.hStdError = (void*)_get_osfhandle (fd); + + + /* Note: We inherit all handles flagged as inheritable. This seems + to be a security flaw but there seems to be no way of selecting + handles to inherit. */ + /* _assuan_log_printf ("CreateProcess, path=`%s' cmdline=`%s'\n", */ + /* name, cmdline); */ + if (!CreateProcess (name, /* Program to start. */ + cmdline, /* Command line arguments. */ + &sec_attr, /* Process security attributes. */ + &sec_attr, /* Thread security attributes. */ + TRUE, /* Inherit handles. */ + (CREATE_DEFAULT_ERROR_MODE + | ((flags & 128)? DETACHED_PROCESS : 0) + | GetPriorityClass (GetCurrentProcess ()) + | CREATE_SUSPENDED), /* Creation flags. */ + NULL, /* Environment. */ + NULL, /* Use current drive/directory. */ + &si, /* Startup information. */ + &pi /* Returns process information. */ + )) + { + TRACE1 (ctx, ASSUAN_LOG_SYSIO, "pipe_connect_w32", ctx, + "CreateProcess failed: %s", w32_strerror (ctx, -1)); + _assuan_free (cmdline); + if (nullfd != INVALID_HANDLE_VALUE) + CloseHandle (nullfd); + + errno = EIO; + return -1; + } + + _assuan_free (cmdline); + if (nullfd != INVALID_HANDLE_VALUE) + CloseHandle (nullfd); + + ResumeThread (pi.hThread); + CloseHandle (pi.hThread); + + /* _assuan_log_printf ("CreateProcess ready: hProcess=%p hThread=%p" */ + /* " dwProcessID=%d dwThreadId=%d\n", */ + /* pi.hProcess, pi.hThread, */ + /* (int) pi.dwProcessId, (int) pi.dwThreadId); */ + + *r_pid = (pid_t) pi.hProcess; + + /* No need to modify peer process, as we don't change the handle + names. However this also means we are not safe, as we inherit + too many handles. Should use approach similar to gpgme and glib + using a helper process. */ + + return 0; +} + +#else + +static int +writen (int fd, const char *buffer, size_t length) +{ + while (length) + { + int nwritten = write (fd, buffer, length); + + if (nwritten < 0) + { + if (errno == EINTR) + continue; + return -1; /* write error */ + } + length -= nwritten; + buffer += nwritten; + } + return 0; /* okay */ +} + + +int +__assuan_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name, + const char **argv, + assuan_fd_t fd_in, assuan_fd_t fd_out, + assuan_fd_t *fd_child_list, + void (*atfork) (void *opaque, int reserved), + void *atforkvalue, unsigned int flags) +{ + int pid; + + pid = fork (); + if (pid < 0) + return -1; + + if (pid == 0) + { + /* Child process (server side). */ + int i; + int n; + char errbuf[512]; + int *fdp; + int fdnul; + + if (atfork) + atfork (atforkvalue, 0); + + fdnul = open ("/dev/null", O_WRONLY); + if (fdnul == -1) + { + TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx, + "can't open `/dev/null': %s", strerror (errno)); + _exit (4); + } + + /* Dup handles to stdin/stdout. */ + if (fd_out != STDOUT_FILENO) + { + if (dup2 (fd_out == ASSUAN_INVALID_FD ? fdnul : fd_out, + STDOUT_FILENO) == -1) + { + TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx, + "dup2 failed in child: %s", strerror (errno)); + _exit (4); + } + } + + if (fd_in != STDIN_FILENO) + { + if (dup2 (fd_in == ASSUAN_INVALID_FD ? fdnul : fd_in, + STDIN_FILENO) == -1) + { + TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx, + "dup2 failed in child: %s", strerror (errno)); + _exit (4); + } + } + + /* Dup stderr to /dev/null unless it is in the list of FDs to be + passed to the child. */ + fdp = fd_child_list; + if (fdp) + { + for (; *fdp != -1 && *fdp != STDERR_FILENO; fdp++) + ; + } + if (!fdp || *fdp == -1) + { + if (dup2 (fdnul, STDERR_FILENO) == -1) + { + TRACE1 (ctx, ASSUAN_LOG_SYSIO, "pipe_connect_unix", ctx, + "dup2(dev/null, 2) failed: %s", strerror (errno)); + _exit (4); + } + } + close (fdnul); + + /* Close all files which will not be duped and are not in the + fd_child_list. */ + n = sysconf (_SC_OPEN_MAX); + if (n < 0) + n = MAX_OPEN_FDS; + for (i = 0; i < n; i++) + { + if (i == STDIN_FILENO || i == STDOUT_FILENO || i == STDERR_FILENO) + continue; + fdp = fd_child_list; + if (fdp) + { + while (*fdp != -1 && *fdp != i) + fdp++; + } + + if (!(fdp && *fdp != -1)) + close (i); + } + errno = 0; + + if (! name) + { + /* No name and no args given, thus we don't do an exec + but continue the forked process. */ + *argv = "server"; + + /* FIXME: Cleanup. */ + return 0; + } + + execv (name, (char *const *) argv); + + /* oops - use the pipe to tell the parent about it */ + snprintf (errbuf, sizeof(errbuf)-1, + "ERR %d can't exec `%s': %.50s\n", + _assuan_error (ctx, GPG_ERR_ASS_SERVER_START), + name, strerror (errno)); + errbuf[sizeof(errbuf)-1] = 0; + writen (1, errbuf, strlen (errbuf)); + _exit (4); + } + + if (! name) + *argv = "client"; + + *r_pid = pid; + + return 0; +} +#endif /* ! HAVE_W32_SYSTEM */ + + +/* Create a new process from NAME and ARGV. Provide FD_IN and FD_OUT + as stdin and stdout. Inherit the ASSUAN_INVALID_FD-terminated + FD_CHILD_LIST as given (no remapping), which must be inheritable. + On Unix, call ATFORK with ATFORKVALUE after fork and before exec. */ +int +_assuan_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name, + const char **argv, + assuan_fd_t fd_in, assuan_fd_t fd_out, + assuan_fd_t *fd_child_list, + void (*atfork) (void *opaque, int reserved), + void *atforkvalue, unsigned int flags) +{ + int res; + int i; + TRACE_BEG6 (ctx, ASSUAN_LOG_CTX, "_assuan_spawn", ctx, + "name=%s,fd_in=0x%x,fd_out=0x%x," + "atfork=%p,atforkvalue=%p,flags=%i", + name ? name : "(null)", fd_in, fd_out, + atfork, atforkvalue, flags); + + if (name) + { + i = 0; + while (argv[i]) + { + TRACE_LOG2 ("argv[%2i] = %s", i, argv[i]); + i++; + } + } + i = 0; + while (fd_child_list[i] != ASSUAN_INVALID_FD) + { + TRACE_LOG2 ("fd_child_list[%2i] = 0x%x", i, fd_child_list[i]); + i++; + } + + res = (ctx->system.spawn) (ctx, r_pid, name, argv, fd_in, fd_out, + fd_child_list, atfork, atforkvalue, flags); + + if (name) + { + TRACE_LOG1 ("pid = 0x%x", *r_pid); + } + else + { + TRACE_LOG2 ("pid = 0x%x (%s)", *r_pid, *argv); + } + + return TRACE_SYSERR (res); +} + + +/* FIXME: Add some sort of waitpid function that covers GPGME and + gpg-agent's use of assuan. */ +static pid_t +__assuan_waitpid (assuan_context_t ctx, pid_t pid, int nowait, + int *status, int options) +{ +#ifndef HAVE_W32_SYSTEM + /* We can't just release the PID, a waitpid is mandatory. But + NOWAIT in POSIX systems just means the caller already did the + waitpid for this child. */ + if (! nowait) + return waitpid (pid, NULL, 0); +#else /* ! HAVE_W32_SYSTEM */ + CloseHandle ((HANDLE) pid); +#endif /* HAVE_W32_SYSTEM */ +} + + +pid_t +_assuan_waitpid (assuan_context_t ctx, pid_t pid, int action, + int *status, int options) +{ + return (ctx->system.waitpid) (ctx, pid, action, status, options); +} + + +int +__assuan_socketpair (assuan_context_t ctx, int namespace, int style, + int protocol, int filedes[2]) +{ +#if HAVE_W32_SYSTEM + errno = ENOSYS; + return -1; +#else + return socketpair (namespace, style, protocol, filedes); +#endif +} + +int +_assuan_socketpair (assuan_context_t ctx, int namespace, int style, + int protocol, assuan_fd_t filedes[2]) +{ + int res; + TRACE_BEG4 (ctx, ASSUAN_LOG_SYSIO, "_assuan_socketpair", ctx, + "namespace=%i,style=%i,protocol=%i,filedes=%p", + namespace, style, protocol, filedes); + + res = (ctx->system.socketpair) (ctx, namespace, style, protocol, filedes); + if (res == 0) + TRACE_LOG2 ("filedes = { 0x%x, 0x%x }", filedes[0], filedes[1]); + + return TRACE_SYSERR (res); +} + + +/* The default system hooks for assuan contexts. */ +struct assuan_system_hooks _assuan_system_hooks = + { + ASSUAN_SYSTEM_HOOKS_VERSION, + __assuan_usleep, + __assuan_pipe, + __assuan_close, + __assuan_read, + __assuan_write, + __assuan_recvmsg, + __assuan_sendmsg, + __assuan_spawn, + __assuan_waitpid, + __assuan_socketpair + }; |