diff --git a/src/assuan-support.c b/src/assuan-support.c index 09db3295..0ddf29b6 100644 --- a/src/assuan-support.c +++ b/src/assuan-support.c @@ -1,362 +1,362 @@ /* assuan-support.c - Assuan wrappers * Copyright (C) 2009 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include "assuan.h" #include "gpgme.h" #include "ath.h" #include "priv-io.h" #include "debug.h" struct assuan_malloc_hooks _gpgme_assuan_malloc_hooks = { malloc, realloc, free }; int _gpgme_assuan_log_cb (assuan_context_t ctx, void *hook, unsigned int cat, const char *msg) { (void)ctx; (void)hook; (void)cat; if (msg == NULL) return 1; - _gpgme_debug (DEBUG_ASSUAN, -1, NULL, NULL, NULL, "%s", msg); + _gpgme_debug (NULL, DEBUG_ASSUAN, -1, NULL, NULL, NULL, "%s", msg); return 0; } static void my_usleep (assuan_context_t ctx, unsigned int usec) { /* FIXME: Add to ath. */ __assuan_usleep (ctx, usec); } /* Create a pipe with an inheritable end. */ static int my_pipe (assuan_context_t ctx, assuan_fd_t fds[2], int inherit_idx) { int res; int gfds[2]; (void)ctx; res = _gpgme_io_pipe (gfds, inherit_idx); /* For now... */ fds[0] = (assuan_fd_t) gfds[0]; fds[1] = (assuan_fd_t) gfds[1]; return res; } /* Close the given file descriptor, created with _assuan_pipe or one of the socket functions. */ static int my_close (assuan_context_t ctx, assuan_fd_t fd) { (void)ctx; return _gpgme_io_close ((int) fd); } static gpgme_ssize_t my_read (assuan_context_t ctx, assuan_fd_t fd, void *buffer, size_t size) { (void)ctx; return _gpgme_io_read ((int) fd, buffer, size); } static gpgme_ssize_t my_write (assuan_context_t ctx, assuan_fd_t fd, const void *buffer, size_t size) { (void)ctx; return _gpgme_io_write ((int) fd, buffer, size); } static int my_recvmsg (assuan_context_t ctx, assuan_fd_t fd, assuan_msghdr_t msg, int flags) { (void)ctx; #ifdef HAVE_W32_SYSTEM (void)fd; (void)msg; (void)flags; gpg_err_set_errno (ENOSYS); return -1; #else return _gpgme_io_recvmsg ((int) fd, msg, flags); #endif } static int my_sendmsg (assuan_context_t ctx, assuan_fd_t fd, const assuan_msghdr_t msg, int flags) { (void)ctx; #ifdef HAVE_W32_SYSTEM (void)fd; (void)msg; (void)flags; gpg_err_set_errno (ENOSYS); return -1; #else return _gpgme_io_sendmsg ((int) fd, msg, flags); #endif } /* If NAME is NULL, don't exec, just fork. FD_CHILD_LIST is modified to reflect the value of the FD in the peer process (on Windows). */ static int my_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 err = 0; struct spawn_fd_item_s *fd_items; int i; (void)ctx; (void)flags; assert (name); if (! name) { gpg_err_set_errno (ENOSYS); return -1; } i = 0; if (fd_child_list) { while (fd_child_list[i] != ASSUAN_INVALID_FD) i++; } /* fd_in, fd_out, terminator */ i += 3; fd_items = calloc (i, sizeof (struct spawn_fd_item_s)); if (! fd_items) return -1; i = 0; if (fd_child_list) { while (fd_child_list[i] != ASSUAN_INVALID_FD) { fd_items[i].fd = (int) fd_child_list[i]; fd_items[i].dup_to = -1; i++; } } if (fd_in != ASSUAN_INVALID_FD) { fd_items[i].fd = (int) fd_in; fd_items[i].dup_to = 0; i++; } if (fd_out != ASSUAN_INVALID_FD) { fd_items[i].fd = (int) fd_out; fd_items[i].dup_to = 1; i++; } fd_items[i].fd = -1; fd_items[i].dup_to = -1; #ifdef HAVE_W32_SYSTEM /* Fix up a potential logger fd so that on windows the fd * translation can work through gpgme-w32spawn. * * We do this here as a hack because we would * otherwise have to change assuan_api and the current * plan in 2019 is to change away from this to gpgrt * based IPC. */ if (argv) { int loc = 0; while (argv[loc]) { if (!strcmp ("--logger-fd", argv[loc])) { long logger_fd = -1; char *tail; int k = 0; loc++; if (!argv[loc]) { err = GPG_ERR_INV_ARG; break; } logger_fd = strtol (argv[loc], &tail, 10); if (tail == argv[loc] || logger_fd < 0) { err = GPG_ERR_INV_ARG; break; } while (fd_items[k++].fd != -1) { if (fd_items[k].fd == logger_fd) { fd_items[k].arg_loc = loc; break; } } break; } loc++; } } #endif if (!err) { err = _gpgme_io_spawn (name, (char*const*)argv, (IOSPAWN_FLAG_NOCLOSE | IOSPAWN_FLAG_DETACHED), fd_items, atfork, atforkvalue, r_pid); } if (!err) { i = 0; if (fd_child_list) { while (fd_child_list[i] != ASSUAN_INVALID_FD) { fd_child_list[i] = (assuan_fd_t) fd_items[i].peer_name; i++; } } } free (fd_items); return err; } /* If action is 0, like waitpid. If action is 1, just release the PID? */ static pid_t my_waitpid (assuan_context_t ctx, pid_t pid, int nowait, int *status, int options) { (void)ctx; #ifdef HAVE_W32_SYSTEM (void)nowait; (void)status; (void)options; (void)pid; /* Just a number without a kernel object. */ #else /* 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 _gpgme_ath_waitpid (pid, status, options); #endif return 0; } static int my_socketpair (assuan_context_t ctx, int namespace, int style, int protocol, assuan_fd_t filedes[2]) { #ifdef HAVE_W32_SYSTEM (void)ctx; (void)namespace; (void)style; (void)protocol; (void)filedes; gpg_err_set_errno (ENOSYS); return -1; #else /* FIXME: Debug output missing. */ return __assuan_socketpair (ctx, namespace, style, protocol, filedes); #endif } static int my_socket (assuan_context_t ctx, int namespace, int style, int protocol) { (void)ctx; return _gpgme_io_socket (namespace, style, protocol); } static int my_connect (assuan_context_t ctx, int sock, struct sockaddr *addr, socklen_t length) { (void)ctx; return _gpgme_io_connect (sock, addr, length); } /* Note for Windows: Ignore the incompatible pointer type warning for my_read and my_write. Mingw has been changed to use int for ssize_t on 32 bit systems while we use long. For 64 bit we use int64_t while mingw uses __int64_t. It doe not matter at all because under Windows long and int are both 32 bit even on 64 bit. */ struct assuan_system_hooks _gpgme_assuan_system_hooks = { ASSUAN_SYSTEM_HOOKS_VERSION, my_usleep, my_pipe, my_close, my_read, my_write, my_recvmsg, my_sendmsg, my_spawn, my_waitpid, my_socketpair, my_socket, my_connect }; diff --git a/src/data-compat.c b/src/data-compat.c index 64ed2d28..4960bf49 100644 --- a/src/data-compat.c +++ b/src/data-compat.c @@ -1,234 +1,234 @@ /* data-compat.c - Compatibility interfaces for data objects. * Copyright (C) 2002, 2003, 2004, 2007 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #include #include "data.h" #include "util.h" #include "debug.h" /* Create a new data buffer filled with LENGTH bytes starting from OFFSET within the file FNAME or stream STREAM (exactly one must be non-zero). */ gpgme_error_t gpgme_data_new_from_filepart (gpgme_data_t *r_dh, const char *fname, FILE *stream, gpgme_off_t offset, size_t length) { gpgme_error_t err; char *buf = NULL; int res; TRACE_BEG (DEBUG_DATA, "gpgme_data_new_from_filepart", r_dh, "file_name=%s, stream=%p, offset=%lli, length=%zu", fname, stream, (long long int)offset, length); if (stream && fname) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (fname) stream = fopen (fname, "rb"); if (!stream) return TRACE_ERR (gpg_error_from_syserror ()); #ifdef HAVE_FSEEKO res = fseeko (stream, offset, SEEK_SET); #else /* FIXME: Check for overflow, or at least bail at compilation. */ res = fseek (stream, offset, SEEK_SET); #endif if (res) { int saved_err = gpg_error_from_syserror (); if (fname) fclose (stream); return TRACE_ERR (saved_err); } buf = malloc (length); if (!buf) { int saved_err = gpg_error_from_syserror (); if (fname) fclose (stream); return TRACE_ERR (saved_err); } while (fread (buf, length, 1, stream) < 1 && ferror (stream) && errno == EINTR); if (ferror (stream)) { int saved_err = gpg_error_from_syserror (); if (buf) free (buf); if (fname) fclose (stream); return TRACE_ERR (saved_err); } if (fname) fclose (stream); err = gpgme_data_new (r_dh); if (err) { if (buf) free (buf); return err; } (*r_dh)->data.mem.buffer = buf; (*r_dh)->data.mem.size = length; (*r_dh)->data.mem.length = length; TRACE_SUC ("r_dh=%p", *r_dh); return 0; } /* Create a new data buffer filled with the content of file FNAME. COPY must be non-zero (delayed reads are not supported yet). */ gpgme_error_t gpgme_data_new_from_file (gpgme_data_t *r_dh, const char *fname, int copy) { gpgme_error_t err; struct stat statbuf; TRACE_BEG (DEBUG_DATA, "gpgme_data_new_from_file", r_dh, "file_name=%s, copy=%i (%s)", fname, copy, copy ? "yes" : "no"); if (!fname || !copy) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (stat (fname, &statbuf) < 0) return TRACE_ERR (gpg_error_from_syserror ()); err = gpgme_data_new_from_filepart (r_dh, fname, NULL, 0, statbuf.st_size); return TRACE_ERR (err); } static int gpgme_error_to_errno (gpgme_error_t err) { int res = gpg_err_code_to_errno (gpg_err_code (err)); if (!err) { switch (gpg_err_code (err)) { case GPG_ERR_EOF: res = 0; break; case GPG_ERR_INV_VALUE: res = EINVAL; break; case GPG_ERR_NOT_SUPPORTED: res = ENOSYS; break; default: /* FIXME: Yeah, well. */ res = EINVAL; break; } } - TRACE (DEBUG_DATA, "gpgme:gpgme_error_to_errno", 0, + TRACE (DEBUG_DATA, "gpgme:gpgme_error_to_errno", NULL, "mapping %s <%s> to: %s", gpgme_strerror (err), gpgme_strsource (err), strerror (res)); gpg_err_set_errno (res); return res ? -1 : 0; } static gpgme_ssize_t old_user_read (gpgme_data_t dh, void *buffer, size_t size) { gpgme_error_t err; size_t amt; TRACE_BEG (DEBUG_DATA, "gpgme:old_user_read", dh, "buffer=%p, size=%zu", buffer, size); err = (*dh->data.old_user.cb) (dh->data.old_user.handle, buffer, size, &amt); if (err) return TRACE_SYSRES (gpgme_error_to_errno (err)); return TRACE_SYSRES ((int)amt); } static gpgme_off_t old_user_seek (gpgme_data_t dh, gpgme_off_t offset, int whence) { gpgme_error_t err; TRACE_BEG (DEBUG_DATA, "gpgme:old_user_seek", dh, "offset=%llu, whence=%i", (long long int)offset, whence); if (whence != SEEK_SET || offset) { gpg_err_set_errno (EINVAL); return TRACE_SYSRES (-1); } err = (*dh->data.old_user.cb) (dh->data.old_user.handle, NULL, 0, NULL); if (err) return TRACE_SYSRES (gpgme_error_to_errno (err)); return TRACE_SYSRES (0); } static struct _gpgme_data_cbs old_user_cbs = { old_user_read, NULL, old_user_seek, NULL }; /* Create a new data buffer which retrieves the data from the callback function READ_CB. */ gpgme_error_t gpgme_data_new_with_read_cb (gpgme_data_t *r_dh, int (*read_cb) (void *, char *, size_t, size_t *), void *read_cb_value) { gpgme_error_t err; TRACE_BEG (DEBUG_DATA, "gpgme_data_new_with_read_cb", r_dh, "read_cb=%p/%p", read_cb, read_cb_value); err = _gpgme_data_new (r_dh, &old_user_cbs); if (err) return TRACE_ERR (err); (*r_dh)->data.old_user.cb = read_cb; (*r_dh)->data.old_user.handle = read_cb_value; return TRACE_ERR (0); } diff --git a/src/data-fd.c b/src/data-fd.c index 5c68130f..4bc8f610 100644 --- a/src/data-fd.c +++ b/src/data-fd.c @@ -1,87 +1,87 @@ /* data-fd.c - A file descriptor based data object. * Copyright (C) 2002, 2004 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #include "debug.h" #include "data.h" static gpgme_ssize_t fd_read (gpgme_data_t dh, void *buffer, size_t size) { return read (dh->data.fd, buffer, size); } static gpgme_ssize_t fd_write (gpgme_data_t dh, const void *buffer, size_t size) { return write (dh->data.fd, buffer, size); } static gpgme_off_t fd_seek (gpgme_data_t dh, gpgme_off_t offset, int whence) { return lseek (dh->data.fd, offset, whence); } static int fd_get_fd (gpgme_data_t dh) { return (dh->data.fd); } static struct _gpgme_data_cbs fd_cbs = { fd_read, fd_write, fd_seek, NULL, fd_get_fd }; gpgme_error_t gpgme_data_new_from_fd (gpgme_data_t *r_dh, int fd) { gpgme_error_t err; - TRACE_BEG (DEBUG_DATA, "gpgme_data_new_from_fd", r_dh, "fd=0x%x", fd); + TRACE_BEG (DEBUG_DATA, "gpgme_data_new_from_fd", r_dh, "fd=%d", fd); err = _gpgme_data_new (r_dh, &fd_cbs); if (err) return TRACE_ERR (err); (*r_dh)->data.fd = fd; TRACE_SUC ("dh=%p", *r_dh); return 0; } diff --git a/src/data-mem.c b/src/data-mem.c index f51d2fd7..539b4536 100644 --- a/src/data-mem.c +++ b/src/data-mem.c @@ -1,303 +1,303 @@ /* data-mem.c - A memory based data object. * Copyright (C) 2002, 2003, 2004, 2007 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #include "data.h" #include "util.h" #include "debug.h" static gpgme_ssize_t mem_read (gpgme_data_t dh, void *buffer, size_t size) { size_t amt = dh->data.mem.length - dh->data.mem.offset; const char *src; if (!amt) return 0; if (size < amt) amt = size; src = dh->data.mem.buffer ? dh->data.mem.buffer : dh->data.mem.orig_buffer; memcpy (buffer, src + dh->data.mem.offset, amt); dh->data.mem.offset += amt; return amt; } static gpgme_ssize_t mem_write (gpgme_data_t dh, const void *buffer, size_t size) { size_t unused; if (!dh->data.mem.buffer && dh->data.mem.orig_buffer) { size_t new_size = dh->data.mem.size; char *new_buffer; if (new_size < dh->data.mem.offset + size) new_size = dh->data.mem.offset + size; new_buffer = malloc (new_size); if (!new_buffer) return -1; memcpy (new_buffer, dh->data.mem.orig_buffer, dh->data.mem.length); dh->data.mem.buffer = new_buffer; dh->data.mem.size = new_size; } unused = dh->data.mem.size - dh->data.mem.offset; if (unused < size) { /* Allocate a large enough buffer with exponential backoff. */ #define INITIAL_ALLOC 512 size_t new_size = dh->data.mem.size ? (2 * dh->data.mem.size) : INITIAL_ALLOC; char *new_buffer; if (new_size < dh->data.mem.offset + size) new_size = dh->data.mem.offset + size; new_buffer = realloc (dh->data.mem.buffer, new_size); if (!new_buffer && new_size > dh->data.mem.offset + size) { /* Maybe we were too greedy, try again. */ new_size = dh->data.mem.offset + size; new_buffer = realloc (dh->data.mem.buffer, new_size); } if (!new_buffer) return -1; dh->data.mem.buffer = new_buffer; dh->data.mem.size = new_size; } memcpy (dh->data.mem.buffer + dh->data.mem.offset, buffer, size); dh->data.mem.offset += size; if (dh->data.mem.length < dh->data.mem.offset) dh->data.mem.length = dh->data.mem.offset; return size; } static gpgme_off_t mem_seek (gpgme_data_t dh, gpgme_off_t offset, int whence) { switch (whence) { case SEEK_SET: if (offset < 0 || offset > dh->data.mem.length) { gpg_err_set_errno (EINVAL); return -1; } dh->data.mem.offset = offset; break; case SEEK_CUR: if ((offset > 0 && dh->data.mem.length - dh->data.mem.offset < offset) || (offset < 0 && dh->data.mem.offset < -offset)) { gpg_err_set_errno (EINVAL); return -1; } dh->data.mem.offset += offset; break; case SEEK_END: if (offset > 0 || -offset > dh->data.mem.length) { gpg_err_set_errno (EINVAL); return -1; } dh->data.mem.offset = dh->data.mem.length + offset; break; default: gpg_err_set_errno (EINVAL); return -1; } return dh->data.mem.offset; } static void mem_release (gpgme_data_t dh) { if (dh->data.mem.buffer) free (dh->data.mem.buffer); } static struct _gpgme_data_cbs mem_cbs = { mem_read, mem_write, mem_seek, mem_release, NULL }; /* Create a new data buffer and return it in R_DH. */ gpgme_error_t gpgme_data_new (gpgme_data_t *r_dh) { gpgme_error_t err; TRACE_BEG (DEBUG_DATA, "gpgme_data_new", r_dh, ""); err = _gpgme_data_new (r_dh, &mem_cbs); if (err) return TRACE_ERR (err); TRACE_SUC ("dh=%p", *r_dh); return 0; } /* Create a new data buffer filled with SIZE bytes starting from BUFFER. If COPY is zero, copying is delayed until necessary, and the data is taken from the original location when needed. */ gpgme_error_t gpgme_data_new_from_mem (gpgme_data_t *r_dh, const char *buffer, size_t size, int copy) { gpgme_error_t err; TRACE_BEG (DEBUG_DATA, "gpgme_data_new_from_mem", r_dh, "buffer=%p, size=%zu, copy=%i (%s)", buffer, size, copy, copy ? "yes" : "no"); err = _gpgme_data_new (r_dh, &mem_cbs); if (err) return TRACE_ERR (err); if (copy) { char *bufcpy = malloc (size); if (!bufcpy) { int saved_err = gpg_error_from_syserror (); _gpgme_data_release (*r_dh); return TRACE_ERR (saved_err); } memcpy (bufcpy, buffer, size); (*r_dh)->data.mem.buffer = bufcpy; } else (*r_dh)->data.mem.orig_buffer = buffer; (*r_dh)->data.mem.size = size; (*r_dh)->data.mem.length = size; TRACE_SUC ("dh=%p", *r_dh); return 0; } /* Destroy the data buffer DH and return a pointer to its content. The memory has be to released with gpgme_free() by the user. It's size is returned in R_LEN. */ char * gpgme_data_release_and_get_mem (gpgme_data_t dh, size_t *r_len) { gpg_error_t err; char *str = NULL; size_t len; int blankout; TRACE_BEG (DEBUG_DATA, "gpgme_data_release_and_get_mem", dh, "r_len=%p", r_len); if (!dh || dh->cbs != &mem_cbs) { gpgme_data_release (dh); TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); return NULL; } err = _gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout); if (err) { gpgme_data_release (dh); TRACE_ERR (err); return NULL; } str = dh->data.mem.buffer; len = dh->data.mem.length; if (blankout && len) len = 1; if (!str && dh->data.mem.orig_buffer) { str = malloc (len); if (!str) { int saved_err = gpg_error_from_syserror (); gpgme_data_release (dh); TRACE_ERR (saved_err); return NULL; } if (blankout) memset (str, 0, len); else memcpy (str, dh->data.mem.orig_buffer, len); } else { if (blankout && len) *str = 0; /* Prevent mem_release from releasing the buffer memory. We * must not fail from this point. */ dh->data.mem.buffer = NULL; } if (r_len) *r_len = len; gpgme_data_release (dh); if (r_len) TRACE_SUC ("buffer=%p, len=%zu", str, *r_len); else TRACE_SUC ("buffer=%p", str); return str; } /* Release the memory returned by gpgme_data_release_and_get_mem() and some other functions. */ void gpgme_free (void *buffer) { - TRACE (DEBUG_DATA, "gpgme_free", buffer, ""); + TRACE (DEBUG_DATA, "gpgme_free", NULL, "p=%p", buffer); if (buffer) free (buffer); } diff --git a/src/data.c b/src/data.c index 44ef2d3d..70595907 100644 --- a/src/data.c +++ b/src/data.c @@ -1,663 +1,663 @@ /* data.c - An abstraction for data objects. * Copyright (C) 2002, 2003, 2004, 2005, 2007 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #ifdef HAVE_UNISTD_H # include #endif #include #include #include #include "gpgme.h" #include "data.h" #include "util.h" #include "ops.h" #include "priv-io.h" #include "debug.h" /* The property table which has an entry for each active data object. * The data object itself uses an index into this table and the table * has a pointer back to the data object. All access to that table is * controlled by the property_table_lock. * * We use a separate table instead of linking all data objects * together for faster locating properties of the data object using * the data objects serial number. We use 64 bit for the serial * number which is good enough to create a new data object every * nanosecond for more than 500 years. Thus no wrap around will ever * happen. */ struct property_s { gpgme_data_t dh; /* The data objcet or NULL if the slot is not used. */ uint64_t dserial; /* The serial number of the data object. */ struct { unsigned int blankout : 1; /* Void the held data. */ } flags; }; typedef struct property_s *property_t; static property_t property_table; static unsigned int property_table_size; DEFINE_STATIC_LOCK (property_table_lock); #define PROPERTY_TABLE_ALLOCATION_CHUNK 32 /* Insert the newly created data object DH into the property table and * store the index of it at R_IDX. An error code is returned on error * and the table is not changed. */ static gpg_error_t insert_into_property_table (gpgme_data_t dh, unsigned int *r_idx) { static uint64_t last_dserial; gpg_error_t err; unsigned int idx; LOCK (property_table_lock); if (!property_table) { property_table_size = PROPERTY_TABLE_ALLOCATION_CHUNK; property_table = calloc (property_table_size, sizeof *property_table); if (!property_table) { err = gpg_error_from_syserror (); goto leave; } } /* Find an empty slot. */ for (idx = 0; idx < property_table_size; idx++) if (!property_table[idx].dh) break; if (!(idx < property_table_size)) { /* No empty slot found. Enlarge the table. */ property_t newtbl; unsigned int newsize; newsize = property_table_size + PROPERTY_TABLE_ALLOCATION_CHUNK;; if ((newsize * sizeof *property_table) < (property_table_size * sizeof *property_table)) { err = gpg_error (GPG_ERR_ENOMEM); goto leave; } newtbl = realloc (property_table, newsize * sizeof *property_table); if (!newtbl) { err = gpg_error_from_syserror (); goto leave; } property_table = newtbl; for (idx = property_table_size; idx < newsize; idx++) property_table[idx].dh = NULL; idx = property_table_size; property_table_size = newsize; } /* Slot found. */ property_table[idx].dh = dh; property_table[idx].dserial = ++last_dserial; memset (&property_table[idx].flags, 0, sizeof property_table[idx].flags); *r_idx = idx; err = 0; leave: UNLOCK (property_table_lock); return err; } /* Remove the data object at PROPIDX from the table. DH is only used * for cross checking. */ static void remove_from_property_table (gpgme_data_t dh, unsigned int propidx) { LOCK (property_table_lock); assert (property_table); assert (propidx < property_table_size); assert (property_table[propidx].dh == dh); property_table[propidx].dh = NULL; UNLOCK (property_table_lock); } /* Return the data object's serial number for handle DH. This is a * unique serial number for each created data object. */ uint64_t _gpgme_data_get_dserial (gpgme_data_t dh) { uint64_t dserial; unsigned int idx; if (!dh) return 0; idx = dh->propidx; LOCK (property_table_lock); assert (property_table); assert (idx < property_table_size); assert (property_table[idx].dh == dh); dserial = property_table[idx].dserial; UNLOCK (property_table_lock); return dserial; } /* Set an internal property of a data object. The data object may * either be identified by the usual DH or by using the data serial * number DSERIAL. */ gpg_error_t _gpgme_data_set_prop (gpgme_data_t dh, uint64_t dserial, data_prop_t name, int value) { gpg_error_t err = 0; int idx; TRACE_BEG (DEBUG_DATA, "gpgme_data_set_prop", dh, "dserial=%llu %lu=%d", (unsigned long long)dserial, (unsigned long)name, value); LOCK (property_table_lock); if ((!dh && !dserial) || (dh && dserial)) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } if (dh) /* Lookup via handle. */ { idx = dh->propidx; assert (property_table); assert (idx < property_table_size); assert (property_table[idx].dh == dh); } else /* Lookup via DSERIAL. */ { if (!property_table) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } for (idx = 0; idx < property_table_size; idx++) if (property_table[idx].dh && property_table[idx].dserial == dserial) break; if (!(idx < property_table_size)) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } } switch (name) { case DATA_PROP_NONE: /* Nothing to to do. */ break; case DATA_PROP_BLANKOUT: property_table[idx].flags.blankout = !!value; break; default: err = gpg_error (GPG_ERR_UNKNOWN_NAME); break; } leave: UNLOCK (property_table_lock); return TRACE_ERR (err); } /* Get an internal property of a data object. This is the counter * part to _gpgme_data_set_property. The value of the property is * stored at R_VALUE. On error 0 is stored at R_VALUE. */ gpg_error_t _gpgme_data_get_prop (gpgme_data_t dh, uint64_t dserial, data_prop_t name, int *r_value) { gpg_error_t err = 0; int idx; TRACE_BEG (DEBUG_DATA, "gpgme_data_get_prop", dh, "dserial=%llu %lu", (unsigned long long)dserial, (unsigned long)name); *r_value = 0; LOCK (property_table_lock); if ((!dh && !dserial) || (dh && dserial)) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } if (dh) /* Lookup via handle. */ { idx = dh->propidx; assert (property_table); assert (idx < property_table_size); assert (property_table[idx].dh == dh); } else /* Lookup via DSERIAL. */ { if (!property_table) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } for (idx = 0; idx < property_table_size; idx++) if (property_table[idx].dh && property_table[idx].dserial == dserial) break; if (!(idx < property_table_size)) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } } switch (name) { case DATA_PROP_NONE: /* Nothing to to do. */ break; case DATA_PROP_BLANKOUT: *r_value = property_table[idx].flags.blankout; break; default: err = gpg_error (GPG_ERR_UNKNOWN_NAME); break; } leave: UNLOCK (property_table_lock); return TRACE_ERR (err); } gpgme_error_t _gpgme_data_new (gpgme_data_t *r_dh, struct _gpgme_data_cbs *cbs) { gpgme_error_t err; gpgme_data_t dh; if (!r_dh) return gpg_error (GPG_ERR_INV_VALUE); *r_dh = NULL; if (_gpgme_selftest) return _gpgme_selftest; dh = calloc (1, sizeof (*dh)); if (!dh) return gpg_error_from_syserror (); dh->cbs = cbs; err = insert_into_property_table (dh, &dh->propidx); if (err) { free (dh); return err; } *r_dh = dh; return 0; } void _gpgme_data_release (gpgme_data_t dh) { if (!dh) return; remove_from_property_table (dh, dh->propidx); if (dh->file_name) free (dh->file_name); free (dh); } /* Read up to SIZE bytes into buffer BUFFER from the data object with the handle DH. Return the number of characters read, 0 on EOF and -1 on error. If an error occurs, errno is set. */ gpgme_ssize_t gpgme_data_read (gpgme_data_t dh, void *buffer, size_t size) { gpgme_ssize_t res; int blankout; TRACE_BEG (DEBUG_DATA, "gpgme_data_read", dh, "buffer=%p, size=%zu", buffer, size); if (!dh) { gpg_err_set_errno (EINVAL); return TRACE_SYSRES (-1); } if (!dh->cbs->read) { gpg_err_set_errno (ENOSYS); return TRACE_SYSRES (-1); } if (_gpgme_data_get_prop (dh, 0, DATA_PROP_BLANKOUT, &blankout) || blankout) res = 0; else { do res = (*dh->cbs->read) (dh, buffer, size); while (res < 0 && errno == EINTR); } return TRACE_SYSRES ((int)res); } /* Write up to SIZE bytes from buffer BUFFER to the data object with the handle DH. Return the number of characters written, or -1 on error. If an error occurs, errno is set. */ gpgme_ssize_t gpgme_data_write (gpgme_data_t dh, const void *buffer, size_t size) { gpgme_ssize_t res; TRACE_BEG (DEBUG_DATA, "gpgme_data_write", dh, "buffer=%p, size=%zu", buffer, size); if (!dh) { gpg_err_set_errno (EINVAL); return TRACE_SYSRES (-1); } if (!dh->cbs->write) { gpg_err_set_errno (ENOSYS); return TRACE_SYSRES (-1); } do res = (*dh->cbs->write) (dh, buffer, size); while (res < 0 && errno == EINTR); return TRACE_SYSRES ((int)res); } /* Set the current position from where the next read or write starts in the data object with the handle DH to OFFSET, relative to WHENCE. */ gpgme_off_t gpgme_data_seek (gpgme_data_t dh, gpgme_off_t offset, int whence) { TRACE_BEG (DEBUG_DATA, "gpgme_data_seek", dh, "offset=%lli, whence=%i", (long long int)offset, whence); if (!dh) { gpg_err_set_errno (EINVAL); return TRACE_SYSRES (-1); } if (!dh->cbs->seek) { gpg_err_set_errno (ENOSYS); return TRACE_SYSRES (-1); } /* For relative movement, we must take into account the actual position of the read counter. */ if (whence == SEEK_CUR) offset -= dh->pending_len; offset = (*dh->cbs->seek) (dh, offset, whence); if (offset >= 0) dh->pending_len = 0; return TRACE_SYSRES ((int)offset); } /* Convenience function to do a gpgme_data_seek (dh, 0, SEEK_SET). */ gpgme_error_t gpgme_data_rewind (gpgme_data_t dh) { gpgme_error_t err; TRACE_BEG (DEBUG_DATA, "gpgme_data_rewind", dh, ""); err = ((gpgme_data_seek (dh, 0, SEEK_SET) == -1) ? gpg_error_from_syserror () : 0); return TRACE_ERR (err); } /* Release the data object with the handle DH. */ void gpgme_data_release (gpgme_data_t dh) { TRACE (DEBUG_DATA, "gpgme_data_release", dh, ""); if (!dh) return; if (dh->cbs->release) (*dh->cbs->release) (dh); _gpgme_data_release (dh); } /* Get the current encoding meta information for the data object with handle DH. */ gpgme_data_encoding_t gpgme_data_get_encoding (gpgme_data_t dh) { TRACE (DEBUG_DATA, "gpgme_data_get_encoding", dh, "dh->encoding=%i", dh ? dh->encoding : GPGME_DATA_ENCODING_NONE); return dh ? dh->encoding : GPGME_DATA_ENCODING_NONE; } /* Set the encoding meta information for the data object with handle DH to ENC. */ gpgme_error_t gpgme_data_set_encoding (gpgme_data_t dh, gpgme_data_encoding_t enc) { TRACE_BEG (DEBUG_DATA, "gpgme_data_set_encoding", dh, "encoding=%i", enc); if (!dh) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (enc < 0 || enc > GPGME_DATA_ENCODING_MIME) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); dh->encoding = enc; return TRACE_ERR (0); } /* Set the file name associated with the data object with handle DH to FILE_NAME. */ gpgme_error_t gpgme_data_set_file_name (gpgme_data_t dh, const char *file_name) { TRACE_BEG (DEBUG_DATA, "gpgme_data_set_file_name", dh, "file_name=%s", file_name); if (!dh) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (dh->file_name) free (dh->file_name); if (file_name) { dh->file_name = strdup (file_name); if (!dh->file_name) return TRACE_ERR (gpg_error_from_syserror ()); } else dh->file_name = 0; return TRACE_ERR (0); } /* Get the file name associated with the data object with handle DH, or NULL if there is none. */ char * gpgme_data_get_file_name (gpgme_data_t dh) { if (!dh) { TRACE (DEBUG_DATA, "gpgme_data_get_file_name", dh, ""); return NULL; } TRACE (DEBUG_DATA, "gpgme_data_get_file_name", dh, "dh->file_name=%s", dh->file_name); return dh->file_name; } /* Set a flag for the data object DH. See the manual for details. */ gpg_error_t gpgme_data_set_flag (gpgme_data_t dh, const char *name, const char *value) { TRACE_BEG (DEBUG_DATA, "gpgme_data_set_flag", dh, "%s=%s", name, value); if (!dh) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (!strcmp (name, "size-hint")) { dh->size_hint= value? _gpgme_string_to_off (value) : 0; } else return gpg_error (GPG_ERR_UNKNOWN_NAME); return 0; } /* Functions to support the wait interface. */ gpgme_error_t _gpgme_data_inbound_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; gpgme_data_t dh = (gpgme_data_t) data->handler_value; char buffer[BUFFER_SIZE]; char *bufp = buffer; gpgme_ssize_t buflen; TRACE_BEG (DEBUG_CTX, "_gpgme_data_inbound_handler", dh, - "fd=0x%x", fd); + "fd=%d", fd); buflen = _gpgme_io_read (fd, buffer, BUFFER_SIZE); if (buflen < 0) return gpg_error_from_syserror (); if (buflen == 0) { _gpgme_io_close (fd); return TRACE_ERR (0); } do { gpgme_ssize_t amt = gpgme_data_write (dh, bufp, buflen); if (amt == 0 || (amt < 0 && errno != EINTR)) return TRACE_ERR (gpg_error_from_syserror ()); bufp += amt; buflen -= amt; } while (buflen > 0); return TRACE_ERR (0); } gpgme_error_t _gpgme_data_outbound_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; gpgme_data_t dh = (gpgme_data_t) data->handler_value; gpgme_ssize_t nwritten; TRACE_BEG (DEBUG_CTX, "_gpgme_data_outbound_handler", dh, - "fd=0x%x", fd); + "fd=%d", fd); if (!dh->pending_len) { gpgme_ssize_t amt = gpgme_data_read (dh, dh->pending, BUFFER_SIZE); if (amt < 0) return TRACE_ERR (gpg_error_from_syserror ()); if (amt == 0) { _gpgme_io_close (fd); return TRACE_ERR (0); } dh->pending_len = amt; } nwritten = _gpgme_io_write (fd, dh->pending, dh->pending_len); if (nwritten == -1 && errno == EAGAIN) return TRACE_ERR (0); if (nwritten == -1 && errno == EPIPE) { /* Not much we can do. The other end closed the pipe, but we still have data. This should only ever happen if the other end is going to tell us what happened on some other channel. Silently close our end. */ _gpgme_io_close (fd); return TRACE_ERR (0); } if (nwritten <= 0) return TRACE_ERR (gpg_error_from_syserror ()); if (nwritten < dh->pending_len) memmove (dh->pending, dh->pending + nwritten, dh->pending_len - nwritten); dh->pending_len -= nwritten; return TRACE_ERR (0); } /* Get the file descriptor associated with DH, if possible. Otherwise return -1. */ int _gpgme_data_get_fd (gpgme_data_t dh) { if (!dh || !dh->cbs->get_fd) return -1; return (*dh->cbs->get_fd) (dh); } /* Get the size-hint value for DH or 0 if not available. */ gpgme_off_t _gpgme_data_get_size_hint (gpgme_data_t dh) { return dh ? dh->size_hint : 0; } diff --git a/src/debug.c b/src/debug.c index 69e02b6f..0ddb1539 100644 --- a/src/debug.c +++ b/src/debug.c @@ -1,443 +1,424 @@ /* debug.c - helpful output in desperate situations * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009, 2019 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #include #include #include #ifndef HAVE_DOSISH_SYSTEM # ifdef HAVE_SYS_TYPES_H # include # endif # ifdef HAVE_SYS_STAT_H # include # endif # include #endif #include #include "util.h" #include "ath.h" #include "sema.h" #include "sys-util.h" #include "debug.h" /* The amount of detail requested by the user, per environment variable GPGME_DEBUG. */ static int debug_level; /* The output stream for the debug messages. */ static FILE *errfp; /* If not NULL, this malloced string is used instead of the GPGME_DEBUG envvar. It must have been set before the debug subsystem has been initialized. Using it later may or may not have any effect. */ static char *envvar_override; #ifdef HAVE_TLS #define FRAME_NR static __thread int frame_nr = 0; #endif void _gpgme_debug_frame_begin (void) { #ifdef FRAME_NR frame_nr++; #endif } int _gpgme_debug_frame_end (void) { #ifdef FRAME_NR frame_nr--; #endif return 0; } /* Remove leading and trailing white spaces. */ static char * trim_spaces (char *str) { char *string, *p, *mark; string = str; /* Find first non space character. */ for (p = string; *p && isspace (*(unsigned char *) p); p++) ; /* Move characters. */ for (mark = NULL; (*string = *p); string++, p++) if (isspace (*(unsigned char *) p)) { if (!mark) mark = string; } else mark = NULL; if (mark) *mark = '\0'; /* Remove trailing spaces. */ return str; } /* This is an internal function to set debug info. The caller must assure that this function is called only by one thread at a time. The function may have no effect if called after the debug system has been initialized. Returns 0 on success. */ int _gpgme_debug_set_debug_envvar (const char *value) { free (envvar_override); envvar_override = strdup (value); return !envvar_override; } static void debug_init (void) { static int initialized; if (!initialized) { gpgme_error_t err; char *e; const char *s1, *s2;; if (envvar_override) { e = strdup (envvar_override); free (envvar_override); envvar_override = NULL; } else { err = _gpgme_getenv ("GPGME_DEBUG", &e); if (err) return; } initialized = 1; errfp = stderr; if (e) { debug_level = atoi (e); s1 = strchr (e, PATHSEP_C); if (s1) { #ifndef HAVE_DOSISH_SYSTEM if (getuid () == geteuid () #if defined(HAVE_GETGID) && defined(HAVE_GETEGID) && getgid () == getegid () #endif ) { #endif char *p; FILE *fp; s1++; if (!(s2 = strchr (s1, PATHSEP_C))) s2 = s1 + strlen (s1); p = malloc (s2 - s1 + 1); if (p) { memcpy (p, s1, s2 - s1); p[s2-s1] = 0; trim_spaces (p); fp = fopen (p,"a"); if (fp) { setvbuf (fp, NULL, _IOLBF, 0); errfp = fp; } free (p); } #ifndef HAVE_DOSISH_SYSTEM } #endif } free (e); } } if (debug_level > 0) { - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme_debug: level=%d\n", debug_level); #ifdef HAVE_W32_SYSTEM { const char *name = _gpgme_get_inst_dir (); - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme_debug: gpgme='%s'\n", name? name: "?"); } #endif } } /* This should be called as soon as possible. It is required so that * the assuan logging gets connected to the gpgme log stream as early * as possible. */ void _gpgme_debug_subsystem_init (void) { debug_init (); } /* Log the formatted string FORMAT prefixed with additional info * depending on MODE: * * -1 = Do not print any additional args. * 0 = standalone (used by macro TRACE) * 1 = enter a function (used by macro TRACE_BEG) * 2 = debug a function (used by macro TRACE_LOG) * 3 = leave a function (used by macro TRACE_SUC) * + * If LINE is not NULL the output will be stored in that variabale but + * without a LF. _gpgme_debug_add can be used to add more and + * _gpgme_debug_end to finally output it. + * * Returns: 0 * * Note that we always return 0 because the old TRACE macro evaluated * to 0 which issues a warning with newer gcc version about an unused * values. By using a return value of this function this can be * avoided. Fixme: It might be useful to check whether the return * value from the TRACE macros are actually used somewhere. */ int -_gpgme_debug (int level, int mode, const char *func, const char *tagname, +_gpgme_debug (void **line, int level, int mode, + const char *func, const char *tagname, const char *tagvalue, const char *format, ...) { va_list arg_ptr; int saved_errno; int need_lf; int indent; char *prefix, *stdinfo, *userinfo; - int no_stdinfo = 0; + const char *modestr; if (debug_level < level) return 0; #ifdef FRAME_NR indent = frame_nr > 0? (2 * (frame_nr - 1)):0; #else indent = 0; #endif saved_errno = errno; va_start (arg_ptr, format); { struct tm *tp; time_t atime = time (NULL); tp = localtime (&atime); prefix = gpgrt_bsprintf ("GPGME %04d%02d%02dT%02d%02d%02d %04llX %*s", 1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, (unsigned long long) ath_self (), indent < 40? indent : 40, ""); } switch (mode) { - case -1: /* Do nothing. */ - stdinfo = NULL; - no_stdinfo = 1; - break; - case 0: - stdinfo = gpgrt_bsprintf ("%s: call: %s=%p ", func, tagname, tagvalue); - break; - case 1: - stdinfo = gpgrt_bsprintf ("%s: enter: %s=%p ", func, tagname, tagvalue); - break; - case 2: - stdinfo = gpgrt_bsprintf ("%s: check: %s=%p ", func, tagname, tagvalue); - break; - case 3: - if (tagname) - stdinfo = gpgrt_bsprintf ("%s: leave: %s=%p ", func, tagname, tagvalue); - else - stdinfo = gpgrt_bsprintf ("%s: leave: ", func); - break; - default: - stdinfo = gpgrt_bsprintf ("%s: ?(mode=%d): %s=%p ", - func, mode, tagname, tagvalue); - break; + case -1: modestr = NULL; break; /* Do nothing. */ + case 0: modestr = "call"; break; + case 1: modestr = "enter"; break; + case 2: modestr = "check"; break; + case 3: modestr = "leave"; break; + default: modestr = "mode?"; break; } + if (!modestr) + stdinfo = NULL; + else if (tagname && strcmp (tagname, XSTRINGIFY (NULL))) + stdinfo = gpgrt_bsprintf ("%s: %s: %s=%p ", func,modestr,tagname,tagvalue); + else + stdinfo = gpgrt_bsprintf ("%s: %s: ", func, modestr); + userinfo = gpgrt_vbsprintf (format, arg_ptr); va_end (arg_ptr); if (mode != -1 && (!format || !*format)) need_lf = 1; else if (userinfo && *userinfo && userinfo[strlen (userinfo) - 1] != '\n') need_lf = 1; else need_lf = 0; - - fprintf (errfp, "%s%s%s%s", - prefix? prefix : "GPGME out-of-core ", - no_stdinfo? "" : stdinfo? stdinfo : "out-of-core ", - userinfo? userinfo : "out-of-core", - need_lf? "\n":""); - fflush (errfp); + if (line) + *line = gpgrt_bsprintf ("%s%s%s", + prefix? prefix : "GPGME out-of-core ", + !modestr? "" : stdinfo? stdinfo : + (!format || !*format)? "" :"out-of-core ", + userinfo? userinfo : "out-of-core"); + else + { + fprintf (errfp, "%s%s%s%s", + prefix? prefix : "GPGME out-of-core ", + !modestr? "" : stdinfo? stdinfo : + (!format || !*format)? "" :"out-of-core ", + userinfo? userinfo : "out-of-core", + need_lf? "\n":""); + fflush (errfp); + } gpgrt_free (userinfo); gpgrt_free (stdinfo); gpgrt_free (prefix); gpg_err_set_errno (saved_errno); return 0; } -/* Start a new debug line in *LINE, logged at level LEVEL or higher, - and starting with the formatted string FORMAT. */ -void -_gpgme_debug_begin (void **line, int level, const char *format, ...) -{ - va_list arg_ptr; - int res; - - if (debug_level < level) - { - /* Disable logging of this line. */ - *line = NULL; - return; - } - - va_start (arg_ptr, format); - res = gpgrt_vasprintf ((char **) line, format, arg_ptr); - va_end (arg_ptr); - if (res < 0) - *line = NULL; -} - - /* Add the formatted string FORMAT to the debug line *LINE. */ void _gpgme_debug_add (void **line, const char *format, ...) { va_list arg_ptr; char *toadd; char *result; int res; if (!*line) return; va_start (arg_ptr, format); res = gpgrt_vasprintf (&toadd, format, arg_ptr); va_end (arg_ptr); if (res < 0) { gpgrt_free (*line); *line = NULL; } res = gpgrt_asprintf (&result, "%s%s", *(char **) line, toadd); gpgrt_free (toadd); gpgrt_free (*line); if (res < 0) *line = NULL; else *line = result; } /* Finish construction of *LINE and send it to the debug output stream. */ void _gpgme_debug_end (void **line) { if (!*line) return; /* The smallest possible level is 1, so force logging here by using that. */ - _gpgme_debug (1, -1, NULL, NULL, NULL, "%s", (char*)*line); + _gpgme_debug (NULL, 1, -1, NULL, NULL, NULL, "%s", (char*)*line); gpgrt_free (*line); *line = NULL; } #define TOHEX(val) (((val) < 10) ? ((val) + '0') : ((val) - 10 + 'a')) void _gpgme_debug_buffer (int lvl, const char *const fmt, const char *const func, const char *const buffer, size_t len) { int idx = 0; int j; if (!_gpgme_debug_trace ()) return; if (!buffer) return; while (idx < len) { char str[51]; char *strp = str; char *strp2 = &str[34]; for (j = 0; j < 16; j++) { unsigned char val; if (idx < len) { val = buffer[idx++]; *(strp++) = TOHEX (val >> 4); *(strp++) = TOHEX (val % 16); *(strp2++) = isprint (val) ? val : '.'; } else { *(strp++) = ' '; *(strp++) = ' '; } if (j == 7) *(strp++) = ' '; } *(strp++) = ' '; *(strp2) = '\0'; - _gpgme_debug (lvl, -1, NULL, NULL, NULL, fmt, func, str); + _gpgme_debug (NULL, lvl, -1, NULL, NULL, NULL, fmt, func, str); } } diff --git a/src/debug.h b/src/debug.h index 7ef8cf23..08d063c6 100644 --- a/src/debug.h +++ b/src/debug.h @@ -1,228 +1,223 @@ /* debug.h - interface to debugging functions Copyright (C) 2002, 2004, 2005, 2007 g10 Code GmbH This file is part of GPGME. GPGME 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. GPGME 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef DEBUG_H #define DEBUG_H #include #ifdef HAVE_STDINT_H #include #endif #include "gpgme.h" /* Required for gpgme_error stuff. */ /* Indirect stringification, requires __STDC__ to work. */ #define STRINGIFY(v) #v #define XSTRINGIFY(v) STRINGIFY(v) /* * The debug levels. * * Note that TRACE_LOGBUFX uses the current debug level + 1. */ #define DEBUG_INIT 1 #define DEBUG_GLOBAL 2 #define DEBUG_CTX 3 #define DEBUG_ENGINE 4 #define DEBUG_DATA 5 #define DEBUG_ASSUAN 6 #define DEBUG_SYSIO 7 /* Remove path components from filenames (i.e. __FILE__) for cleaner logs. */ static inline const char *_gpgme_debug_srcname (const char *file) GPGME_GCC_A_PURE; static inline const char * _gpgme_debug_srcname (const char *file) { const char *s = strrchr (file, '/'); return s? s+1:file; } /* Initialization helper function; see debug.c. */ int _gpgme_debug_set_debug_envvar (const char *value); /* Called early to initialize the logging. */ void _gpgme_debug_subsystem_init (void); /* Log the formatted string FORMAT at debug level LEVEL or higher. */ -int _gpgme_debug (int level, int mode, +int _gpgme_debug (void **line, int level, int mode, const char *func, const char *tagname, const char *tagvalue, - const char *format, ...) GPGRT_ATTR_PRINTF(6,7); + const char *format, ...) GPGRT_ATTR_PRINTF(7,8); -/* Start a new debug line in *LINE, logged at level LEVEL or higher, - and starting with the formatted string FORMAT. */ -void _gpgme_debug_begin (void **helper, int level, const char *format, ...); - /* Add the formatted string FORMAT to the debug line *LINE. */ void _gpgme_debug_add (void **helper, const char *format, ...); /* Finish construction of *LINE and send it to the debug output stream. */ void _gpgme_debug_end (void **helper); void _gpgme_debug_buffer (int lvl, const char *const fmt, const char *const func, const char *const buffer, size_t len); void _gpgme_debug_frame_begin (void); int _gpgme_debug_frame_end (void); static inline gpgme_error_t _gpgme_trace_gpgme_error (gpgme_error_t err, const char *file, int line) { - _gpgme_debug (DEBUG_ENGINE, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_ENGINE, -1, NULL, NULL, NULL, "%s:%d: returning error: %s\n", _gpgme_debug_srcname (file), line, gpgme_strerror (err)); return err; } /* Trace support. */ /* FIXME: For now. */ #define _gpgme_debug_trace() 1 #define _TRACE(lvl, name, tag) \ int _gpgme_trace_level = lvl; \ const char *const _gpgme_trace_func = name; \ const char *const _gpgme_trace_tagname = STRINGIFY (tag); \ void *_gpgme_trace_tag = (void *) (uintptr_t) tag; \ _gpgme_debug_frame_begin () /* Note: We can't protect this with a do-while block. */ #define TRACE_BEG(lvl, name, tag, ...) \ _TRACE (lvl, name, tag); \ - _gpgme_debug (_gpgme_trace_level, 1, \ + _gpgme_debug (NULL, _gpgme_trace_level, 1, \ _gpgme_trace_func, _gpgme_trace_tagname, _gpgme_trace_tag, \ __VA_ARGS__) #define TRACE(lvl, name, tag, ...) do { \ _gpgme_debug_frame_begin (); \ - _gpgme_debug (lvl, 0, name, STRINGIFY (tag), (void *)(uintptr_t)tag, \ + _gpgme_debug (NULL, lvl, 0, name, STRINGIFY (tag), (void *)(uintptr_t)tag, \ __VA_ARGS__); \ _gpgme_debug_frame_end (); \ } while (0) /* Trace a gpg-error and return it. */ #define TRACE_ERR(err) \ _trace_err ((err), _gpgme_trace_level, _gpgme_trace_func, __LINE__) static inline gpg_error_t _trace_err (gpg_error_t err, int lvl, const char *func, int line) { if (!err) - _gpgme_debug (lvl, 3, func, NULL, NULL, ""); + _gpgme_debug (NULL, lvl, 3, func, NULL, NULL, ""); else - _gpgme_debug (lvl, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, lvl, -1, NULL, NULL, NULL, "%s:%d: error: %s <%s>\n", func, line, gpgme_strerror (err), gpgme_strsource (err)); _gpgme_debug_frame_end (); return err; } /* Trace a system call result and return it. */ #define TRACE_SYSRES(res) \ _trace_sysres ((res), _gpgme_trace_level, _gpgme_trace_func, __LINE__) static inline int _trace_sysres (int res, int lvl, const char *func, int line) { if (res >= 0) - _gpgme_debug (lvl, 3, func, NULL, NULL, "result=%d", res); + _gpgme_debug (NULL, lvl, 3, func, NULL, NULL, "result=%d", res); else - _gpgme_debug (lvl, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, lvl, -1, NULL, NULL, NULL, "%s:%d: error: %s (%d)\n", func, line, strerror (res), res); _gpgme_debug_frame_end (); return res; } /* Trace a system call error and return it. */ #define TRACE_SYSERR(rc) \ _trace_syserr ((rc), _gpgme_trace_level, _gpgme_trace_func, __LINE__) static inline int _trace_syserr (int rc, int lvl, const char *func, int line) { if (!rc) - _gpgme_debug (lvl, 3, func, NULL, NULL, "result=0"); + _gpgme_debug (NULL, lvl, 3, func, NULL, NULL, "result=0"); else - _gpgme_debug (lvl, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, lvl, -1, NULL, NULL, NULL, "%s:%d: error: %s (%d)\n", func, line, strerror (rc), rc); _gpgme_debug_frame_end (); return rc; } #define TRACE_SUC(...) do { \ - _gpgme_debug (_gpgme_trace_level, 3, _gpgme_trace_func, NULL, NULL, \ + _gpgme_debug (NULL, _gpgme_trace_level, 3, _gpgme_trace_func, NULL, NULL, \ __VA_ARGS__); \ _gpgme_debug_frame_end (); \ } while (0) #define TRACE_LOG(...) do { \ - _gpgme_debug (_gpgme_trace_level, 2, \ + _gpgme_debug (NULL, _gpgme_trace_level, 2, \ _gpgme_trace_func, _gpgme_trace_tagname, _gpgme_trace_tag, \ __VA_ARGS__); \ } while (0) #define TRACE_LOGBUF(buf, len) do { \ _gpgme_debug_buffer (_gpgme_trace_level, "%s: check: %s", \ _gpgme_trace_func, buf, len); \ } while (0) #define TRACE_LOGBUFX(buf, len) do { \ _gpgme_debug_buffer (_gpgme_trace_level+1, "%s: check: %s", \ _gpgme_trace_func, buf, len); \ } while (0) -#define TRACE_SEQ(hlp,fmt) do { \ - _gpgme_debug_begin (&(hlp), _gpgme_trace_level, \ - "%s: check: %s=%p, " fmt, _gpgme_trace_func, \ - _gpgme_trace_tagname, _gpgme_trace_tag); \ +#define TRACE_SEQ(hlp,...) do { \ + _gpgme_debug (&(hlp), _gpgme_trace_level, 2, _gpgme_trace_func, \ + _gpgme_trace_tagname, _gpgme_trace_tag, __VA_ARGS__); \ } while (0) #define TRACE_ADD0(hlp,fmt) \ _gpgme_debug_add (&(hlp), fmt) #define TRACE_ADD1(hlp,fmt,a) \ _gpgme_debug_add (&(hlp), fmt, (a)) #define TRACE_ADD2(hlp,fmt,a,b) \ _gpgme_debug_add (&(hlp), fmt, (a), (b)) #define TRACE_ADD3(hlp,fmt,a,b,c) \ _gpgme_debug_add (&(hlp), fmt, (a), (b), (c)) #define TRACE_END(hlp,fmt) \ _gpgme_debug_add (&(hlp), fmt); \ _gpgme_debug_end (&(hlp)) #define TRACE_ENABLED(hlp) (!!(hlp)) /* And finally a simple macro to trace the location of an error code. This macro is independent of the other trace macros and may be used without any preconditions. */ #define trace_gpg_error(e) \ _gpgme_trace_gpgme_error (gpg_error (e), __FILE__, __LINE__) #endif /* DEBUG_H */ diff --git a/src/dirinfo.c b/src/dirinfo.c index 5db61093..c4f0e4a0 100644 --- a/src/dirinfo.c +++ b/src/dirinfo.c @@ -1,481 +1,481 @@ /* dirinfo.c - Get directory information * Copyright (C) 2009, 2013 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include "gpgme.h" #include "util.h" #include "priv-io.h" #include "debug.h" #include "sema.h" #include "sys-util.h" DEFINE_STATIC_LOCK (dirinfo_lock); /* Constants used internally to select the data. */ enum { WANT_HOMEDIR, WANT_SYSCONFDIR, WANT_BINDIR, WANT_LIBEXECDIR, WANT_LIBDIR, WANT_DATADIR, WANT_LOCALEDIR, WANT_AGENT_SOCKET, WANT_AGENT_SSH_SOCKET, WANT_DIRMNGR_SOCKET, WANT_UISRV_SOCKET, WANT_GPGCONF_NAME, WANT_GPG_NAME, WANT_GPGSM_NAME, WANT_G13_NAME, WANT_GPG_WKS_CLIENT_NAME, WANT_GPG_ONE_MODE }; /* Values retrieved via gpgconf and cached here. */ static struct { int valid; /* Cached information is valid. */ int disable_gpgconf; char *homedir; char *sysconfdir; char *bindir; char *libexecdir; char *libdir; char *datadir; char *localedir; char *agent_socket; char *agent_ssh_socket; char *dirmngr_socket; char *uisrv_socket; char *gpgconf_name; char *gpg_name; char *gpgsm_name; char *g13_name; char *gpg_wks_client_name; int gpg_one_mode; /* System is in gpg1 mode. */ } dirinfo; /* Helper function to be used only by gpgme_set_global_flag. */ void _gpgme_dirinfo_disable_gpgconf (void) { dirinfo.disable_gpgconf = 1; } /* Return the length of the directory part including the trailing * slash of NAME. */ static size_t dirname_len (const char *name) { return _gpgme_get_basename (name) - name; } /* Parse the output of "gpgconf --list-dirs". This function expects that DIRINFO_LOCK is held by the caller. If COMPONENTS is set, the output of --list-components is expected. */ static void parse_output (char *line, int components) { char *value, *p; size_t n; value = strchr (line, ':'); if (!value) return; *value++ = 0; if (components) { /* Skip the second field. */ value = strchr (value, ':'); if (!value) return; *value++ = 0; } p = strchr (value, ':'); if (p) *p = 0; if (_gpgme_decode_percent_string (value, &value, strlen (value)+1, 0)) return; if (!*value) return; if (components) { if (!strcmp (line, "gpg") && !dirinfo.gpg_name) dirinfo.gpg_name = strdup (value); else if (!strcmp (line, "gpgsm") && !dirinfo.gpgsm_name) dirinfo.gpgsm_name = strdup (value); else if (!strcmp (line, "g13") && !dirinfo.g13_name) dirinfo.g13_name = strdup (value); } else { if (!strcmp (line, "homedir") && !dirinfo.homedir) dirinfo.homedir = strdup (value); else if (!strcmp (line, "sysconfdir") && !dirinfo.sysconfdir) dirinfo.sysconfdir = strdup (value); else if (!strcmp (line, "bindir") && !dirinfo.bindir) dirinfo.bindir = strdup (value); else if (!strcmp (line, "libexecdir") && !dirinfo.libexecdir) dirinfo.libexecdir = strdup (value); else if (!strcmp (line, "libdir") && !dirinfo.libdir) dirinfo.libdir = strdup (value); else if (!strcmp (line, "datadir") && !dirinfo.datadir) dirinfo.datadir = strdup (value); else if (!strcmp (line, "localedir") && !dirinfo.localedir) dirinfo.localedir = strdup (value); else if (!strcmp (line, "agent-socket") && !dirinfo.agent_socket) { const char name[] = "S.uiserver"; char *buffer; dirinfo.agent_socket = strdup (value); if (dirinfo.agent_socket) { n = dirname_len (dirinfo.agent_socket); buffer = malloc (n + strlen (name) + 1); if (buffer) { strncpy (buffer, dirinfo.agent_socket, n); strcpy (buffer + n, name); dirinfo.uisrv_socket = buffer; } } } else if (!strcmp (line, "dirmngr-socket") && !dirinfo.dirmngr_socket) dirinfo.dirmngr_socket = strdup (value); else if (!strcmp (line, "agent-ssh-socket") && !dirinfo.agent_ssh_socket) dirinfo.agent_ssh_socket = strdup (value); } } /* Read the directory information from gpgconf. This function expects that DIRINFO_LOCK is held by the caller. PGNAME is the name of the gpgconf binary. If COMPONENTS is set, not the directories bit the name of the componeNts are read. */ static void read_gpgconf_dirs (const char *pgmname, int components) { char linebuf[1024] = {0}; int linelen = 0; char * argv[3]; int rp[2]; struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, {-1, -1} }; int status; int nread; char *mark = NULL; argv[0] = (char *)pgmname; argv[1] = (char*)(components? "--list-components" : "--list-dirs"); argv[2] = NULL; if (_gpgme_io_pipe (rp, 1) < 0) return; cfd[0].fd = rp[1]; status = _gpgme_io_spawn (pgmname, argv, IOSPAWN_FLAG_DETACHED, cfd, NULL, NULL, NULL); if (status < 0) { _gpgme_io_close (rp[0]); _gpgme_io_close (rp[1]); return; } do { nread = _gpgme_io_read (rp[0], linebuf + linelen, sizeof linebuf - linelen - 1); if (nread > 0) { char *line; const char *lastmark = NULL; size_t nused; linelen += nread; linebuf[linelen] = '\0'; for (line=linebuf; (mark = strchr (line, '\n')); line = mark+1 ) { lastmark = mark; if (mark > line && mark[-1] == '\r') mark[-1] = '\0'; else mark[0] = '\0'; parse_output (line, components); } nused = lastmark? (lastmark + 1 - linebuf) : 0; memmove (linebuf, linebuf + nused, linelen - nused); linelen -= nused; } } while (nread > 0 && linelen < sizeof linebuf - 1); _gpgme_io_close (rp[0]); } static const char * get_gpgconf_item (int what) { const char *result = NULL; LOCK (dirinfo_lock); if (!dirinfo.valid) { char *pgmname; pgmname = dirinfo.disable_gpgconf? NULL : _gpgme_get_gpgconf_path (); if (pgmname && _gpgme_access (pgmname, F_OK)) { - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: gpgconf='%s' [not installed]\n", pgmname); free (pgmname); pgmname = NULL; /* Not available. */ } else - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: gpgconf='%s'\n", pgmname? pgmname : "[null]"); if (!pgmname) { /* Probably gpgconf is not installed. Assume we are using GnuPG-1. */ dirinfo.gpg_one_mode = 1; pgmname = _gpgme_get_gpg_path (); if (pgmname) dirinfo.gpg_name = pgmname; } else { dirinfo.gpg_one_mode = 0; read_gpgconf_dirs (pgmname, 0); read_gpgconf_dirs (pgmname, 1); dirinfo.gpgconf_name = pgmname; } /* Even if the reading of the directories failed (e.g. due to an too old version gpgconf or no gpgconf at all), we need to mark the entries as valid so that we won't try over and over to read them. Note further that we are not able to change the read values later because they are practically statically allocated. */ dirinfo.valid = 1; if (dirinfo.gpg_name) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: gpg='%s'\n", dirinfo.gpg_name); if (dirinfo.g13_name) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: g13='%s'\n", dirinfo.g13_name); if (dirinfo.gpgsm_name) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: gpgsm='%s'\n", dirinfo.gpgsm_name); if (dirinfo.homedir) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: homedir='%s'\n", dirinfo.homedir); if (dirinfo.agent_socket) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL,DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: agent='%s'\n", dirinfo.agent_socket); if (dirinfo.agent_ssh_socket) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: ssh='%s'\n", dirinfo.agent_ssh_socket); if (dirinfo.dirmngr_socket) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: dirmngr='%s'\n", dirinfo.dirmngr_socket); if (dirinfo.uisrv_socket) - _gpgme_debug (DEBUG_INIT, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_INIT, -1, NULL, NULL, NULL, "gpgme-dinfo: uisrv='%s'\n", dirinfo.uisrv_socket); } switch (what) { case WANT_HOMEDIR: result = dirinfo.homedir; break; case WANT_SYSCONFDIR: result = dirinfo.sysconfdir; break; case WANT_BINDIR: result = dirinfo.bindir; break; case WANT_LIBEXECDIR: result = dirinfo.libexecdir; break; case WANT_LIBDIR: result = dirinfo.libdir; break; case WANT_DATADIR: result = dirinfo.datadir; break; case WANT_LOCALEDIR: result = dirinfo.localedir; break; case WANT_AGENT_SOCKET: result = dirinfo.agent_socket; break; case WANT_AGENT_SSH_SOCKET: result = dirinfo.agent_ssh_socket; break; case WANT_DIRMNGR_SOCKET: result = dirinfo.dirmngr_socket; break; case WANT_GPGCONF_NAME: result = dirinfo.gpgconf_name; break; case WANT_GPG_NAME: result = dirinfo.gpg_name; break; case WANT_GPGSM_NAME: result = dirinfo.gpgsm_name; break; case WANT_G13_NAME: result = dirinfo.g13_name; break; case WANT_UISRV_SOCKET: result = dirinfo.uisrv_socket; break; case WANT_GPG_ONE_MODE: result = dirinfo.gpg_one_mode? "1":NULL; break; case WANT_GPG_WKS_CLIENT_NAME: if (!dirinfo.gpg_wks_client_name && dirinfo.libexecdir) dirinfo.gpg_wks_client_name = _gpgme_strconcat (dirinfo.libexecdir, "/", "gpg-wks-client", NULL); result = dirinfo.gpg_wks_client_name; break; } UNLOCK (dirinfo_lock); return result; } /* Return the default home directory. Returns NULL if not known. */ const char * _gpgme_get_default_homedir (void) { return get_gpgconf_item (WANT_HOMEDIR); } /* Return the default gpg-agent socket name. Returns NULL if not known. */ const char * _gpgme_get_default_agent_socket (void) { return get_gpgconf_item (WANT_AGENT_SOCKET); } /* Return the default gpg file name. Returns NULL if not known. */ const char * _gpgme_get_default_gpg_name (void) { return get_gpgconf_item (WANT_GPG_NAME); } /* Return the default gpgsm file name. Returns NULL if not known. */ const char * _gpgme_get_default_gpgsm_name (void) { return get_gpgconf_item (WANT_GPGSM_NAME); } /* Return the default g13 file name. Returns NULL if not known. */ const char * _gpgme_get_default_g13_name (void) { return get_gpgconf_item (WANT_G13_NAME); } /* Return the default gpgconf file name. Returns NULL if not known. */ const char * _gpgme_get_default_gpgconf_name (void) { return get_gpgconf_item (WANT_GPGCONF_NAME); } /* Return the default UI-server socket name. Returns NULL if not known. */ const char * _gpgme_get_default_uisrv_socket (void) { return get_gpgconf_item (WANT_UISRV_SOCKET); } /* Return true if we are in GnuPG-1 mode - ie. no gpgconf and agent being optional. */ int _gpgme_in_gpg_one_mode (void) { return !!get_gpgconf_item (WANT_GPG_ONE_MODE); } /* Helper function to return the basename of the passed filename. */ const char * _gpgme_get_basename (const char *name) { const char *s; if (!name || !*name) return name; for (s = name + strlen (name) -1; s >= name; s--) if (*s == '/' #ifdef HAVE_W32_SYSTEM || *s == '\\' || *s == ':' #endif ) return s+1; return name; } /* Return default values for various directories and file names. */ const char * gpgme_get_dirinfo (const char *what) { if (!what) return NULL; else if (!strcmp (what, "homedir")) return get_gpgconf_item (WANT_HOMEDIR); else if (!strcmp (what, "agent-socket")) return get_gpgconf_item (WANT_AGENT_SOCKET); else if (!strcmp (what, "uiserver-socket")) return get_gpgconf_item (WANT_UISRV_SOCKET); else if (!strcmp (what, "gpgconf-name")) return get_gpgconf_item (WANT_GPGCONF_NAME); else if (!strcmp (what, "gpg-name")) return get_gpgconf_item (WANT_GPG_NAME); else if (!strcmp (what, "gpgsm-name")) return get_gpgconf_item (WANT_GPGSM_NAME); else if (!strcmp (what, "g13-name")) return get_gpgconf_item (WANT_G13_NAME); else if (!strcmp (what, "gpg-wks-client-name")) return get_gpgconf_item (WANT_GPG_WKS_CLIENT_NAME); else if (!strcmp (what, "agent-ssh-socket")) return get_gpgconf_item (WANT_AGENT_SSH_SOCKET); else if (!strcmp (what, "dirmngr-socket")) return get_gpgconf_item (WANT_DIRMNGR_SOCKET); else if (!strcmp (what, "sysconfdir")) return get_gpgconf_item (WANT_SYSCONFDIR); else if (!strcmp (what, "bindir")) return get_gpgconf_item (WANT_BINDIR); else if (!strcmp (what, "libexecdir")) return get_gpgconf_item (WANT_LIBEXECDIR); else if (!strcmp (what, "libdir")) return get_gpgconf_item (WANT_LIBDIR); else if (!strcmp (what, "datadir")) return get_gpgconf_item (WANT_DATADIR); else if (!strcmp (what, "localedir")) return get_gpgconf_item (WANT_LOCALEDIR); else return NULL; } diff --git a/src/engine-assuan.c b/src/engine-assuan.c index 79e826e0..497397db 100644 --- a/src/engine-assuan.c +++ b/src/engine-assuan.c @@ -1,844 +1,844 @@ /* engine-assuan.c - Low-level Assuan protocol engine * Copyright (C) 2009 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ /* Note: This engine requires a modern Assuan server which uses gpg-error codes. In particular there is no backward compatible mapping of old Assuan error codes implemented. */ #if HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_LOCALE_H #include #endif #include #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "priv-io.h" #include "sema.h" #include "assuan.h" #include "debug.h" #include "engine-backend.h" typedef struct { int fd; /* FD we talk about. */ int server_fd;/* Server FD for this connection. */ int dir; /* Inbound/Outbound, maybe given implicit? */ void *data; /* Handler-specific data. */ void *tag; /* ID from the user for gpgme_remove_io_callback. */ } iocb_data_t; /* Engine instance data. */ struct engine_llass { assuan_context_t assuan_ctx; int lc_ctype_set; int lc_messages_set; iocb_data_t status_cb; struct gpgme_io_cbs io_cbs; /* Hack for old opassuan.c interface, see there the result struct. */ gpg_error_t last_op_err; /* User provided callbacks. */ struct { gpgme_assuan_data_cb_t data_cb; void *data_cb_value; gpgme_assuan_inquire_cb_t inq_cb; void *inq_cb_value; gpgme_assuan_status_cb_t status_cb; void *status_cb_value; } user; /* Option flags. */ struct { int gpg_agent:1; /* Assume this is a gpg-agent connection. */ } opt; char request_origin[10]; /* Copy from the CTX. */ }; typedef struct engine_llass *engine_llass_t; gpg_error_t _gpgme_engine_assuan_last_op_err (void *engine) { engine_llass_t llass = engine; return llass->last_op_err; } /* Prototypes. */ static void llass_io_event (void *engine, gpgme_event_io_t type, void *type_data); /* return the default home directory. */ static const char * llass_get_home_dir (void) { /* For this engine the home directory is not a filename but a string used to convey options. The exclamation mark is a marker to show that this is not a directory name. Options are strings delimited by a space. The only option defined for now is GPG_AGENT to enable GPG_AGENT specific commands to send to the server at connection startup. */ return "!GPG_AGENT"; } static char * llass_get_version (const char *file_name) { (void)file_name; return NULL; } static const char * llass_get_req_version (void) { return NULL; } static void close_notify_handler (int fd, void *opaque) { engine_llass_t llass = opaque; assert (fd != -1); if (llass->status_cb.fd == fd) { if (llass->status_cb.tag) llass->io_cbs.remove (llass->status_cb.tag); llass->status_cb.fd = -1; llass->status_cb.tag = NULL; } } static gpgme_error_t llass_cancel (void *engine) { engine_llass_t llass = engine; if (!llass) return gpg_error (GPG_ERR_INV_VALUE); if (llass->status_cb.fd != -1) _gpgme_io_close (llass->status_cb.fd); if (llass->assuan_ctx) { assuan_release (llass->assuan_ctx); llass->assuan_ctx = NULL; } return 0; } static gpgme_error_t llass_cancel_op (void *engine) { engine_llass_t llass = engine; if (!llass) return gpg_error (GPG_ERR_INV_VALUE); if (llass->status_cb.fd != -1) _gpgme_io_close (llass->status_cb.fd); return 0; } static void llass_release (void *engine) { engine_llass_t llass = engine; if (!llass) return; llass_cancel (engine); free (llass); } /* Create a new instance. If HOME_DIR is NULL standard options for use with gpg-agent are issued. */ static gpgme_error_t llass_new (void **engine, const char *file_name, const char *home_dir, const char *version) { gpgme_error_t err = 0; engine_llass_t llass; char *optstr; char *env_tty = NULL; (void)version; /* Not yet used. */ llass = calloc (1, sizeof *llass); if (!llass) return gpg_error_from_syserror (); llass->status_cb.fd = -1; llass->status_cb.dir = 1; llass->status_cb.tag = 0; llass->status_cb.data = llass; /* Parse_options. */ if (home_dir && *home_dir == '!') { home_dir++; /* Very simple parser only working for the one option we support. */ /* Note that wk promised to write a regression test if this parser will be extended. */ if (!strncmp (home_dir, "GPG_AGENT", 9) && (!home_dir[9] || home_dir[9] == ' ')) llass->opt.gpg_agent = 1; } err = assuan_new_ext (&llass->assuan_ctx, GPG_ERR_SOURCE_GPGME, &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb, NULL); if (err) goto leave; assuan_ctx_set_system_hooks (llass->assuan_ctx, &_gpgme_assuan_system_hooks); assuan_set_flag (llass->assuan_ctx, ASSUAN_CONVEY_COMMENTS, 1); err = assuan_socket_connect (llass->assuan_ctx, file_name, 0, 0); if (err) goto leave; if (llass->opt.gpg_agent) { char *dft_display = NULL; err = _gpgme_getenv ("DISPLAY", &dft_display); if (err) goto leave; if (dft_display) { if (gpgrt_asprintf (&optstr, "OPTION display=%s", dft_display) < 0) { err = gpg_error_from_syserror (); free (dft_display); goto leave; } free (dft_display); err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } } if (llass->opt.gpg_agent) err = _gpgme_getenv ("GPG_TTY", &env_tty); if (llass->opt.gpg_agent && (isatty (1) || env_tty || err)) { int rc = 0; char dft_ttyname[64]; char *dft_ttytype = NULL; if (err) goto leave; else if (env_tty) { snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty); free (env_tty); } else rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); /* Even though isatty() returns 1, ttyname_r() may fail in many ways, e.g., when /dev/pts is not accessible under chroot. */ if (!rc) { if (gpgrt_asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; err = _gpgme_getenv ("TERM", &dft_ttytype); if (err) goto leave; if (dft_ttytype) { if (gpgrt_asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype)< 0) { err = gpg_error_from_syserror (); free (dft_ttytype); goto leave; } free (dft_ttytype); err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } } } #ifdef HAVE_W32_SYSTEM /* Under Windows we need to use AllowSetForegroundWindow. Tell llass to tell us when it needs it. */ if (!err && llass->opt.gpg_agent) { err = assuan_transact (llass->assuan_ctx, "OPTION allow-pinentry-notify", NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* This work only with recent gpg-agents. */ } #endif /*HAVE_W32_SYSTEM*/ leave: /* Close the server ends of the pipes (because of this, we must use the stored server_fd_str in the function start). Our ends are closed in llass_release(). */ if (err) llass_release (llass); else *engine = llass; return err; } /* Copy flags from CTX into the engine object. */ static void llass_set_engine_flags (void *engine, const gpgme_ctx_t ctx) { engine_llass_t llass = engine; if (ctx->request_origin) { if (strlen (ctx->request_origin) + 1 > sizeof llass->request_origin) strcpy (llass->request_origin, "xxx"); /* Too long - force error */ else strcpy (llass->request_origin, ctx->request_origin); } else *llass->request_origin = 0; } static gpgme_error_t llass_set_locale (void *engine, int category, const char *value) { gpgme_error_t err; engine_llass_t llass = engine; char *optstr; const char *catstr; if (!llass->opt.gpg_agent) return 0; /* FIXME: If value is NULL, we need to reset the option to default. But we can't do this. So we error out here. gpg-agent needs support for this. */ if (0) ; #ifdef LC_CTYPE else if (category == LC_CTYPE) { catstr = "lc-ctype"; if (!value && llass->lc_ctype_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) llass->lc_ctype_set = 1; } #endif #ifdef LC_MESSAGES else if (category == LC_MESSAGES) { catstr = "lc-messages"; if (!value && llass->lc_messages_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) llass->lc_messages_set = 1; } #endif /* LC_MESSAGES */ else return gpg_error (GPG_ERR_INV_VALUE); /* FIXME: Reset value to default. */ if (!value) return 0; if (gpgrt_asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0) err = gpg_error_from_syserror (); else { err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); } return err; } /* This is the inquiry callback. It handles stuff which ee need to handle here and passes everything on to the user callback. */ static gpgme_error_t inquire_cb (engine_llass_t llass, const char *keyword, const char *args) { gpg_error_t err; if (llass->opt.gpg_agent && !strcmp (keyword, "PINENTRY_LAUNCHED")) { _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10)); } if (llass->user.inq_cb) { gpgme_data_t data = NULL; err = llass->user.inq_cb (llass->user.inq_cb_value, keyword, args, &data); if (!err && data) { /* FIXME: Returning data is not yet implemented. However we need to allow the caller to cleanup his data object. Thus we run the callback in finish mode immediately. */ err = llass->user.inq_cb (llass->user.inq_cb_value, NULL, NULL, &data); } } else err = 0; return err; } static gpgme_error_t llass_status_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; engine_llass_t llass = (engine_llass_t) data->handler_value; gpgme_error_t err = 0; char *line; size_t linelen; do { err = assuan_read_line (llass->assuan_ctx, &line, &linelen); if (err) { /* Reading a full line may not be possible when communicating over a socket in nonblocking mode. In this case, we are done for now. */ if (gpg_err_code (err) == GPG_ERR_EAGAIN) { TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: EAGAIN reading assuan line (ignored)", fd); err = 0; continue; } TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: error reading assuan line: %s", fd, gpg_strerror (err)); } else if (linelen >= 2 && line[0] == 'D' && line[1] == ' ') { char *src = line + 2; char *end = line + linelen; char *dst = src; linelen = 0; while (src < end) { if (*src == '%' && src + 2 < end) { /* Handle escaped characters. */ ++src; *dst++ = _gpgme_hextobyte (src); src += 2; } else *dst++ = *src++; linelen++; } src = line + 2; if (linelen && llass->user.data_cb) err = llass->user.data_cb (llass->user.data_cb_value, src, linelen); TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: D inlinedata; status from cb: %s", fd, (llass->user.data_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && (line[3] == '\0' || line[3] == ' ')) { /* END received. Tell the data callback. */ if (llass->user.data_cb) err = llass->user.data_cb (llass->user.data_cb_value, NULL, 0); TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: END line; status from cb: %s", fd, (llass->user.data_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen > 2 && line[0] == 'S' && line[1] == ' ') { char *args; char *src; for (src=line+2; *src == ' '; src++) ; args = strchr (src, ' '); if (!args) args = line + linelen; /* Let it point to an empty string. */ else *(args++) = 0; while (*args == ' ') args++; if (llass->user.status_cb) err = llass->user.status_cb (llass->user.status_cb_value, src, args); TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: S line (%s) - status from cb: %s", fd, line+2, (llass->user.status_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen >= 7 && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' && line[6] == 'E' && (line[7] == '\0' || line[7] == ' ')) { char *src; char *args; for (src=line+7; *src == ' '; src++) ; args = strchr (src, ' '); if (!args) args = line + linelen; /* Let it point to an empty string. */ else *(args++) = 0; while (*args == ' ') args++; err = inquire_cb (llass, src, args); if (!err) { /* Flush and send END. */ err = assuan_send_data (llass->assuan_ctx, NULL, 0); } else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED) { /* Flush and send CANcel. */ err = assuan_send_data (llass->assuan_ctx, NULL, 1); } } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (line[3] == '\0' || line[3] == ' ')) { if (line[3] == ' ') err = atoi (line+4); else err = gpg_error (GPG_ERR_GENERAL); TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: ERR line: %s", fd, err ? gpg_strerror (err) : "ok"); /* Command execution errors are not fatal, as we use a session based protocol. */ data->op_err = err; llass->last_op_err = err; /* The caller will do the rest (namely, call cancel_op, which closes status_fd). */ return 0; } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: OK line", fd); llass->last_op_err = 0; _gpgme_io_close (llass->status_cb.fd); return 0; } else { /* Comment line or invalid line. */ } } while (!err && assuan_pending_line (llass->assuan_ctx)); return err; } static gpgme_error_t add_io_cb (engine_llass_t llass, iocb_data_t *iocbd, gpgme_io_cb_t handler) { gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-assuan:add_io_cb", llass, - "fd %d, dir %d", iocbd->fd, iocbd->dir); + "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*llass->io_cbs.add) (llass->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); if (err) return TRACE_ERR (err); if (!iocbd->dir) /* FIXME Kludge around poll() problem. */ err = _gpgme_io_set_nonblocking (iocbd->fd); return TRACE_ERR (err); } static gpgme_error_t start (engine_llass_t llass, const char *command) { gpgme_error_t err; assuan_fd_t afdlist[5]; int fdlist[5]; int nfds; int i; if (*llass->request_origin && llass->opt.gpg_agent) { char *cmd; cmd = _gpgme_strconcat ("OPTION pretend-request-origin=", llass->request_origin, NULL); if (!cmd) return gpg_error_from_syserror (); err = assuan_transact (llass->assuan_ctx, cmd, NULL, NULL, NULL, NULL, NULL, NULL); free (cmd); if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION) return err; } /* We need to know the fd used by assuan for reads. We do this by using the assumption that the first returned fd from assuan_get_active_fds() is always this one. */ nfds = assuan_get_active_fds (llass->assuan_ctx, 0 /* read fds */, afdlist, DIM (afdlist)); if (nfds < 1) return gpg_error (GPG_ERR_GENERAL); /* FIXME */ /* For now... */ for (i = 0; i < nfds; i++) fdlist[i] = (int) afdlist[i]; /* We "duplicate" the file descriptor, so we can close it here (we can't close fdlist[0], as that is closed by libassuan, and closing it here might cause libassuan to close some unrelated FD later). Alternatively, we could special case status_fd and register/unregister it manually as needed, but this increases code duplication and is more complicated as we can not use the close notifications etc. A third alternative would be to let Assuan know that we closed the FD, but that complicates the Assuan interface. */ llass->status_cb.fd = _gpgme_io_dup (fdlist[0]); if (llass->status_cb.fd < 0) return gpg_error_from_syserror (); if (_gpgme_io_set_close_notify (llass->status_cb.fd, close_notify_handler, llass)) { _gpgme_io_close (llass->status_cb.fd); llass->status_cb.fd = -1; return gpg_error (GPG_ERR_GENERAL); } err = add_io_cb (llass, &llass->status_cb, llass_status_handler); if (!err) err = assuan_write_line (llass->assuan_ctx, command); /* FIXME: If *command == '#' no answer is expected. */ if (!err) llass_io_event (llass, GPGME_EVENT_START, NULL); return err; } static gpgme_error_t llass_transact (void *engine, const char *command, gpgme_assuan_data_cb_t data_cb, void *data_cb_value, gpgme_assuan_inquire_cb_t inq_cb, void *inq_cb_value, gpgme_assuan_status_cb_t status_cb, void *status_cb_value) { engine_llass_t llass = engine; gpgme_error_t err; if (!llass || !command || !*command) return gpg_error (GPG_ERR_INV_VALUE); llass->user.data_cb = data_cb; llass->user.data_cb_value = data_cb_value; llass->user.inq_cb = inq_cb; llass->user.inq_cb_value = inq_cb_value; llass->user.status_cb = status_cb; llass->user.status_cb_value = status_cb_value; err = start (llass, command); return err; } static void llass_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_llass_t llass = engine; llass->io_cbs = *io_cbs; } static void llass_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_llass_t llass = engine; TRACE (DEBUG_ENGINE, "gpgme:llass_io_event", llass, "event %p, type %d, type_data %p", llass->io_cbs.event, type, type_data); if (llass->io_cbs.event) (*llass->io_cbs.event) (llass->io_cbs.event_priv, type, type_data); } struct engine_ops _gpgme_engine_ops_assuan = { /* Static functions. */ _gpgme_get_default_agent_socket, llass_get_home_dir, llass_get_version, llass_get_req_version, llass_new, /* Member functions. */ llass_release, NULL, /* reset */ NULL, /* set_status_cb */ NULL, /* set_status_handler */ NULL, /* set_command_handler */ NULL, /* set_colon_line_handler */ llass_set_locale, NULL, /* set_protocol */ llass_set_engine_flags, NULL, /* decrypt */ NULL, /* delete */ NULL, /* edit */ NULL, /* encrypt */ NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ NULL, /* keylist_data */ NULL, /* keysign */ NULL, /* tofu_policy */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ NULL, /* getauditlog */ llass_transact, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ NULL, /* conf_dir */ NULL, /* query_swdb */ llass_set_io_cbs, llass_io_event, llass_cancel, llass_cancel_op, NULL, /* passwd */ NULL, /* set_pinentry_mode */ NULL /* opspawn */ }; diff --git a/src/engine-g13.c b/src/engine-g13.c index edb8d54f..19dd8f47 100644 --- a/src/engine-g13.c +++ b/src/engine-g13.c @@ -1,824 +1,824 @@ /* engine-g13.c - G13 engine. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_LOCALE_H #include #endif #include /* FIXME */ #include #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "priv-io.h" #include "sema.h" #include "assuan.h" #include "debug.h" #include "engine-backend.h" typedef struct { int fd; /* FD we talk about. */ int server_fd;/* Server FD for this connection. */ int dir; /* Inbound/Outbound, maybe given implicit? */ void *data; /* Handler-specific data. */ void *tag; /* ID from the user for gpgme_remove_io_callback. */ char server_fd_str[15]; /* Same as SERVER_FD but as a string. We need this because _gpgme_io_fd2str can't be used on a closed descriptor. */ } iocb_data_t; struct engine_g13 { assuan_context_t assuan_ctx; int lc_ctype_set; int lc_messages_set; iocb_data_t status_cb; struct gpgme_io_cbs io_cbs; /* User provided callbacks. */ struct { gpgme_assuan_data_cb_t data_cb; void *data_cb_value; gpgme_assuan_inquire_cb_t inq_cb; void *inq_cb_value; gpgme_assuan_status_cb_t status_cb; void *status_cb_value; } user; }; typedef struct engine_g13 *engine_g13_t; static void g13_io_event (void *engine, gpgme_event_io_t type, void *type_data); static char * g13_get_version (const char *file_name) { return _gpgme_get_program_version (file_name ? file_name : _gpgme_get_default_g13_name ()); } static const char * g13_get_req_version (void) { return "2.1.0"; } static void close_notify_handler (int fd, void *opaque) { engine_g13_t g13 = opaque; assert (fd != -1); if (g13->status_cb.fd == fd) { if (g13->status_cb.tag) (*g13->io_cbs.remove) (g13->status_cb.tag); g13->status_cb.fd = -1; g13->status_cb.tag = NULL; } } /* This is the default inquiry callback. We use it to handle the Pinentry notifications. */ static gpgme_error_t default_inq_cb (engine_g13_t g13, const char *keyword, const char *args) { gpg_error_t err; if (!strcmp (keyword, "PINENTRY_LAUNCHED")) { _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10)); } if (g13->user.inq_cb) { gpgme_data_t data = NULL; err = g13->user.inq_cb (g13->user.inq_cb_value, keyword, args, &data); if (!err && data) { /* FIXME: Returning data is not yet implemented. However we need to allow the caller to cleanup his data object. Thus we run the callback in finish mode immediately. */ err = g13->user.inq_cb (g13->user.inq_cb_value, NULL, NULL, &data); } } else err = 0; return err; } static gpgme_error_t g13_cancel (void *engine) { engine_g13_t g13 = engine; if (!g13) return gpg_error (GPG_ERR_INV_VALUE); if (g13->status_cb.fd != -1) _gpgme_io_close (g13->status_cb.fd); if (g13->assuan_ctx) { assuan_release (g13->assuan_ctx); g13->assuan_ctx = NULL; } return 0; } static gpgme_error_t g13_cancel_op (void *engine) { engine_g13_t g13 = engine; if (!g13) return gpg_error (GPG_ERR_INV_VALUE); if (g13->status_cb.fd != -1) _gpgme_io_close (g13->status_cb.fd); return 0; } static void g13_release (void *engine) { engine_g13_t g13 = engine; if (!g13) return; g13_cancel (engine); free (g13); } static gpgme_error_t g13_new (void **engine, const char *file_name, const char *home_dir, const char *version) { gpgme_error_t err = 0; engine_g13_t g13; const char *pgmname; int argc; const char *argv[5]; char *dft_display = NULL; char dft_ttyname[64]; char *env_tty = NULL; char *dft_ttytype = NULL; char *optstr; (void)version; /* Not yet used. */ g13 = calloc (1, sizeof *g13); if (!g13) return gpg_error_from_syserror (); g13->status_cb.fd = -1; g13->status_cb.dir = 1; g13->status_cb.tag = 0; g13->status_cb.data = g13; pgmname = file_name ? file_name : _gpgme_get_default_g13_name (); argc = 0; argv[argc++] = _gpgme_get_basename (pgmname); if (home_dir) { argv[argc++] = "--homedir"; argv[argc++] = home_dir; } argv[argc++] = "--server"; argv[argc++] = NULL; err = assuan_new_ext (&g13->assuan_ctx, GPG_ERR_SOURCE_GPGME, &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb, NULL); if (err) goto leave; assuan_ctx_set_system_hooks (g13->assuan_ctx, &_gpgme_assuan_system_hooks); #if USE_DESCRIPTOR_PASSING err = assuan_pipe_connect (g13->assuan_ctx, pgmname, argv, NULL, NULL, NULL, ASSUAN_PIPE_CONNECT_FDPASSING); #else err = assuan_pipe_connect (g13->assuan_ctx, pgmname, argv, NULL, NULL, NULL, 0); #endif if (err) goto leave; err = _gpgme_getenv ("DISPLAY", &dft_display); if (err) goto leave; if (dft_display) { if (gpgrt_asprintf (&optstr, "OPTION display=%s", dft_display) < 0) { free (dft_display); err = gpg_error_from_syserror (); goto leave; } free (dft_display); err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } err = _gpgme_getenv ("GPG_TTY", &env_tty); if (isatty (1) || env_tty || err) { int rc = 0; if (err) goto leave; else if (env_tty) { snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty); free (env_tty); } else rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); /* Even though isatty() returns 1, ttyname_r() may fail in many ways, e.g., when /dev/pts is not accessible under chroot. */ if (!rc) { if (gpgrt_asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; err = _gpgme_getenv ("TERM", &dft_ttytype); if (err) goto leave; if (dft_ttytype) { if (gpgrt_asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype)< 0) { free (dft_ttytype); err = gpg_error_from_syserror (); goto leave; } free (dft_ttytype); err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } } } #ifdef HAVE_W32_SYSTEM /* Under Windows we need to use AllowSetForegroundWindow. Tell g13 to tell us when it needs it. */ if (!err) { err = assuan_transact (g13->assuan_ctx, "OPTION allow-pinentry-notify", NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* This is a new feature of g13. */ } #endif /*HAVE_W32_SYSTEM*/ leave: if (err) g13_release (g13); else *engine = g13; return err; } static gpgme_error_t g13_set_locale (void *engine, int category, const char *value) { engine_g13_t g13 = engine; gpgme_error_t err; char *optstr; const char *catstr; /* FIXME: If value is NULL, we need to reset the option to default. But we can't do this. So we error out here. G13 needs support for this. */ if (0) ; #ifdef LC_CTYPE else if (category == LC_CTYPE) { catstr = "lc-ctype"; if (!value && g13->lc_ctype_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) g13->lc_ctype_set = 1; } #endif #ifdef LC_MESSAGES else if (category == LC_MESSAGES) { catstr = "lc-messages"; if (!value && g13->lc_messages_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) g13->lc_messages_set = 1; } #endif /* LC_MESSAGES */ else return gpg_error (GPG_ERR_INV_VALUE); /* FIXME: Reset value to default. */ if (!value) return 0; if (gpgrt_asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0) err = gpg_error_from_syserror (); else { err = assuan_transact (g13->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); } return err; } #if USE_DESCRIPTOR_PASSING static gpgme_error_t g13_assuan_simple_command (assuan_context_t ctx, const char *cmd, engine_status_handler_t status_fnc, void *status_fnc_value) { gpg_error_t err; char *line; size_t linelen; (void)status_fnc; (void)status_fnc_value; err = assuan_write_line (ctx, cmd); if (err) return err; do { err = assuan_read_line (ctx, &line, &linelen); if (err) return err; if (*line == '#' || !linelen) continue; if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) return 0; else if (linelen >= 4 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && line[3] == ' ') err = atoi (&line[4]); else if (linelen >= 2 && line[0] == 'S' && line[1] == ' ') { char *rest; rest = strchr (line + 2, ' '); if (!rest) rest = line + linelen; /* set to an empty string */ else *(rest++) = 0; /* Nothing to do with status lines. */ } else err = gpg_error (GPG_ERR_GENERAL); } while (!err); return err; } #endif static gpgme_error_t status_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; engine_g13_t g13 = (engine_g13_t) data->handler_value; gpgme_error_t err = 0; char *line; size_t linelen; do { err = assuan_read_line (g13->assuan_ctx, &line, &linelen); if (err) { /* Try our best to terminate the connection friendly. */ /* assuan_write_line (g13->assuan_ctx, "BYE"); */ TRACE (DEBUG_CTX, "gpgme:status_handler", g13, "fd 0x%x: error reading assuan line: %s", fd, gpg_strerror (err)); } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (line[3] == '\0' || line[3] == ' ')) { if (line[3] == ' ') err = atoi (&line[4]); if (! err) err = gpg_error (GPG_ERR_GENERAL); TRACE (DEBUG_CTX, "gpgme:status_handler", g13, "fd 0x%x: ERR line: %s", fd, err ? gpg_strerror (err) : "ok"); /* Command execution errors are not fatal, as we use a session based protocol. */ data->op_err = err; /* The caller will do the rest (namely, call cancel_op, which closes status_fd). */ return 0; } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { TRACE (DEBUG_CTX, "gpgme:status_handler", g13, "fd 0x%x: OK line", fd); _gpgme_io_close (g13->status_cb.fd); return 0; } else if (linelen > 2 && line[0] == 'D' && line[1] == ' ') { /* We are using the colon handler even for plain inline data - strange name for that function but for historic reasons we keep it. */ /* FIXME We can't use this for binary data because we assume this is a string. For the current usage of colon output it is correct. */ char *src = line + 2; char *end = line + linelen; char *dst = src; linelen = 0; while (src < end) { if (*src == '%' && src + 2 < end) { /* Handle escaped characters. */ ++src; *dst++ = _gpgme_hextobyte (src); src += 2; } else *dst++ = *src++; linelen++; } src = line + 2; if (linelen && g13->user.data_cb) err = g13->user.data_cb (g13->user.data_cb_value, src, linelen); else err = 0; TRACE (DEBUG_CTX, "gpgme:g13_status_handler", g13, "fd 0x%x: D inlinedata; status from cb: %s", fd, (g13->user.data_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen > 2 && line[0] == 'S' && line[1] == ' ') { char *src; char *args; src = line + 2; while (*src == ' ') src++; args = strchr (line + 2, ' '); if (!args) args = line + linelen; /* set to an empty string */ else *(args++) = 0; while (*args == ' ') args++; if (g13->user.status_cb) err = g13->user.status_cb (g13->user.status_cb_value, src, args); else err = 0; TRACE (DEBUG_CTX, "gpgme:g13_status_handler", g13, "fd 0x%x: S line (%s) - status from cb: %s", fd, line+2, (g13->user.status_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen >= 7 && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' && line[6] == 'E' && (line[7] == '\0' || line[7] == ' ')) { char *src; char *args; for (src=line+7; *src == ' '; src++) ; args = strchr (src, ' '); if (!args) args = line + linelen; /* Let it point to an empty string. */ else *(args++) = 0; while (*args == ' ') args++; err = default_inq_cb (g13, src, args); if (!err) { /* Flush and send END. */ err = assuan_send_data (g13->assuan_ctx, NULL, 0); } else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED) { /* Flush and send CANcel. */ err = assuan_send_data (g13->assuan_ctx, NULL, 1); } assuan_write_line (g13->assuan_ctx, "END"); } } while (!err && assuan_pending_line (g13->assuan_ctx)); return err; } static gpgme_error_t add_io_cb (engine_g13_t g13, iocb_data_t *iocbd, gpgme_io_cb_t handler) { gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-g13:add_io_cb", g13, - "fd %d, dir %d", iocbd->fd, iocbd->dir); + "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*g13->io_cbs.add) (g13->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); if (err) return TRACE_ERR (err); if (!iocbd->dir) /* FIXME Kludge around poll() problem. */ err = _gpgme_io_set_nonblocking (iocbd->fd); return TRACE_ERR (err); } static gpgme_error_t start (engine_g13_t g13, const char *command) { gpgme_error_t err; assuan_fd_t afdlist[5]; int fdlist[5]; int nfds; int i; /* We need to know the fd used by assuan for reads. We do this by using the assumption that the first returned fd from assuan_get_active_fds() is always this one. */ nfds = assuan_get_active_fds (g13->assuan_ctx, 0 /* read fds */, afdlist, DIM (afdlist)); if (nfds < 1) return gpg_error (GPG_ERR_GENERAL); /* FIXME */ /* For now... */ for (i = 0; i < nfds; i++) fdlist[i] = (int) afdlist[i]; /* We "duplicate" the file descriptor, so we can close it here (we can't close fdlist[0], as that is closed by libassuan, and closing it here might cause libassuan to close some unrelated FD later). Alternatively, we could special case status_fd and register/unregister it manually as needed, but this increases code duplication and is more complicated as we can not use the close notifications etc. A third alternative would be to let Assuan know that we closed the FD, but that complicates the Assuan interface. */ g13->status_cb.fd = _gpgme_io_dup (fdlist[0]); if (g13->status_cb.fd < 0) return gpg_error_from_syserror (); if (_gpgme_io_set_close_notify (g13->status_cb.fd, close_notify_handler, g13)) { _gpgme_io_close (g13->status_cb.fd); g13->status_cb.fd = -1; return gpg_error (GPG_ERR_GENERAL); } err = add_io_cb (g13, &g13->status_cb, status_handler); if (!err) err = assuan_write_line (g13->assuan_ctx, command); if (!err) g13_io_event (g13, GPGME_EVENT_START, NULL); return err; } #if USE_DESCRIPTOR_PASSING static gpgme_error_t g13_reset (void *engine) { engine_g13_t g13 = engine; /* We must send a reset because we need to reset the list of signers. Note that RESET does not reset OPTION commands. */ return g13_assuan_simple_command (g13->assuan_ctx, "RESET", NULL, NULL); } #endif static gpgme_error_t g13_transact (void *engine, const char *command, gpgme_assuan_data_cb_t data_cb, void *data_cb_value, gpgme_assuan_inquire_cb_t inq_cb, void *inq_cb_value, gpgme_assuan_status_cb_t status_cb, void *status_cb_value) { engine_g13_t g13 = engine; gpgme_error_t err; if (!g13 || !command || !*command) return gpg_error (GPG_ERR_INV_VALUE); g13->user.data_cb = data_cb; g13->user.data_cb_value = data_cb_value; g13->user.inq_cb = inq_cb; g13->user.inq_cb_value = inq_cb_value; g13->user.status_cb = status_cb; g13->user.status_cb_value = status_cb_value; err = start (g13, command); return err; } static void g13_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_g13_t g13 = engine; g13->io_cbs = *io_cbs; } static void g13_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_g13_t g13 = engine; TRACE (DEBUG_ENGINE, "gpgme:g13_io_event", g13, "event %p, type %d, type_data %p", g13->io_cbs.event, type, type_data); if (g13->io_cbs.event) (*g13->io_cbs.event) (g13->io_cbs.event_priv, type, type_data); } struct engine_ops _gpgme_engine_ops_g13 = { /* Static functions. */ _gpgme_get_default_g13_name, NULL, g13_get_version, g13_get_req_version, g13_new, /* Member functions. */ g13_release, #if USE_DESCRIPTOR_PASSING g13_reset, #else NULL, /* reset */ #endif NULL, /* set_status_cb */ NULL, /* set_status_handler */ NULL, /* set_command_handler */ NULL, /* set_colon_line_handler */ g13_set_locale, NULL, /* set_protocol */ NULL, /* set_engine_flags */ NULL, /* decrypt */ NULL, /* delete */ NULL, /* edit */ NULL, /* encrypt */ NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ NULL, /* keylist_data */ NULL, /* keysign */ NULL, /* tofu_policy */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ NULL, /* getauditlog */ g13_transact, NULL, /* conf_load */ NULL, /* conf_save */ NULL, /* conf_dir */ NULL, /* query_swdb */ g13_set_io_cbs, g13_io_event, g13_cancel, g13_cancel_op, NULL, /* passwd */ NULL, /* set_pinentry_mode */ NULL /* opspawn */ }; diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c index 62920473..ae5d8ef1 100644 --- a/src/engine-gpgsm.c +++ b/src/engine-gpgsm.c @@ -1,2342 +1,2342 @@ /* engine-gpgsm.c - GpgSM engine. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009, * 2010 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_LOCALE_H #include #endif #include /* FIXME */ #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "priv-io.h" #include "sema.h" #include "data.h" #include "assuan.h" #include "debug.h" #include "engine-backend.h" typedef struct { int fd; /* FD we talk about. */ int server_fd;/* Server FD for this connection. */ int dir; /* Inbound/Outbound, maybe given implicit? */ void *data; /* Handler-specific data. */ void *tag; /* ID from the user for gpgme_remove_io_callback. */ char server_fd_str[15]; /* Same as SERVER_FD but as a string. We need this because _gpgme_io_fd2str can't be used on a closed descriptor. */ } iocb_data_t; struct engine_gpgsm { assuan_context_t assuan_ctx; int lc_ctype_set; int lc_messages_set; iocb_data_t status_cb; /* Input, output etc are from the servers perspective. */ iocb_data_t input_cb; gpgme_data_t input_helper_data; /* Input helper data object. */ void *input_helper_memory; /* Input helper memory block. */ iocb_data_t output_cb; iocb_data_t message_cb; iocb_data_t diag_cb; struct { engine_status_handler_t fnc; void *fnc_value; gpgme_status_cb_t mon_cb; void *mon_cb_value; } status; struct { engine_colon_line_handler_t fnc; void *fnc_value; struct { char *line; int linesize; int linelen; } attic; int any; /* any data line seen */ } colon; gpgme_data_t inline_data; /* Used to collect D lines. */ char request_origin[10]; struct gpgme_io_cbs io_cbs; /* Memory data containing diagnostics (--logger-fd) of gpgsm */ gpgme_data_t diagnostics; }; typedef struct engine_gpgsm *engine_gpgsm_t; static void gpgsm_io_event (void *engine, gpgme_event_io_t type, void *type_data); static char * gpgsm_get_version (const char *file_name) { return _gpgme_get_program_version (file_name ? file_name : _gpgme_get_default_gpgsm_name ()); } static const char * gpgsm_get_req_version (void) { return "2.0.4"; } static void close_notify_handler (int fd, void *opaque) { engine_gpgsm_t gpgsm = opaque; assert (fd != -1); if (gpgsm->status_cb.fd == fd) { if (gpgsm->status_cb.tag) (*gpgsm->io_cbs.remove) (gpgsm->status_cb.tag); gpgsm->status_cb.fd = -1; gpgsm->status_cb.tag = NULL; /* Because the server keeps on running as long as the * gpgme_ctx_t is valid the diag fd will not receive a close and * thus the operation gets stuck trying to read the diag fd. * The status fd however is closed right after it received the * "OK" from the command. So we use this event to also close * the diag fd. */ _gpgme_io_close (gpgsm->diag_cb.fd); } else if (gpgsm->input_cb.fd == fd) { if (gpgsm->input_cb.tag) (*gpgsm->io_cbs.remove) (gpgsm->input_cb.tag); gpgsm->input_cb.fd = -1; gpgsm->input_cb.tag = NULL; if (gpgsm->input_helper_data) { gpgme_data_release (gpgsm->input_helper_data); gpgsm->input_helper_data = NULL; } if (gpgsm->input_helper_memory) { free (gpgsm->input_helper_memory); gpgsm->input_helper_memory = NULL; } } else if (gpgsm->output_cb.fd == fd) { if (gpgsm->output_cb.tag) (*gpgsm->io_cbs.remove) (gpgsm->output_cb.tag); gpgsm->output_cb.fd = -1; gpgsm->output_cb.tag = NULL; } else if (gpgsm->message_cb.fd == fd) { if (gpgsm->message_cb.tag) (*gpgsm->io_cbs.remove) (gpgsm->message_cb.tag); gpgsm->message_cb.fd = -1; gpgsm->message_cb.tag = NULL; } else if (gpgsm->diag_cb.fd == fd) { if (gpgsm->diag_cb.tag) (*gpgsm->io_cbs.remove) (gpgsm->diag_cb.tag); gpgsm->diag_cb.fd = -1; gpgsm->diag_cb.tag = NULL; } } /* This is the default inquiry callback. We use it to handle the Pinentry notifications. */ static gpgme_error_t default_inq_cb (engine_gpgsm_t gpgsm, const char *line) { (void)gpgsm; if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17])) { _gpgme_allow_set_foreground_window ((pid_t)strtoul (line+17, NULL, 10)); } return 0; } static gpgme_error_t gpgsm_cancel (void *engine) { engine_gpgsm_t gpgsm = engine; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); if (gpgsm->status_cb.fd != -1) _gpgme_io_close (gpgsm->status_cb.fd); if (gpgsm->input_cb.fd != -1) _gpgme_io_close (gpgsm->input_cb.fd); if (gpgsm->output_cb.fd != -1) _gpgme_io_close (gpgsm->output_cb.fd); if (gpgsm->message_cb.fd != -1) _gpgme_io_close (gpgsm->message_cb.fd); if (gpgsm->diag_cb.fd != -1) _gpgme_io_close (gpgsm->diag_cb.fd); if (gpgsm->assuan_ctx) { assuan_release (gpgsm->assuan_ctx); gpgsm->assuan_ctx = NULL; } return 0; } static void gpgsm_release (void *engine) { engine_gpgsm_t gpgsm = engine; if (!gpgsm) return; gpgsm_cancel (engine); gpgme_data_release (gpgsm->diagnostics); free (gpgsm->colon.attic.line); free (gpgsm); } static gpgme_error_t gpgsm_new (void **engine, const char *file_name, const char *home_dir, const char *version) { gpgme_error_t err = 0; engine_gpgsm_t gpgsm; const char *pgmname; const char *argv[7]; char *diag_fd_str = NULL; int argc; int fds[2]; int child_fds[5]; int nchild_fds; char *dft_display = NULL; char dft_ttyname[64]; char *env_tty = NULL; char *dft_ttytype = NULL; char *optstr; unsigned int connect_flags; (void)version; /* Not yet used. */ gpgsm = calloc (1, sizeof *gpgsm); if (!gpgsm) return gpg_error_from_syserror (); gpgsm->status_cb.fd = -1; gpgsm->status_cb.dir = 1; gpgsm->status_cb.tag = 0; gpgsm->status_cb.data = gpgsm; gpgsm->input_cb.fd = -1; gpgsm->input_cb.dir = 0; gpgsm->input_cb.tag = 0; gpgsm->input_cb.server_fd = -1; *gpgsm->input_cb.server_fd_str = 0; gpgsm->output_cb.fd = -1; gpgsm->output_cb.dir = 1; gpgsm->output_cb.tag = 0; gpgsm->output_cb.server_fd = -1; *gpgsm->output_cb.server_fd_str = 0; gpgsm->message_cb.fd = -1; gpgsm->message_cb.dir = 0; gpgsm->message_cb.tag = 0; gpgsm->message_cb.server_fd = -1; *gpgsm->message_cb.server_fd_str = 0; gpgsm->diag_cb.fd = -1; gpgsm->diag_cb.dir = 1; gpgsm->diag_cb.tag = 0; gpgsm->diag_cb.server_fd = -1; *gpgsm->diag_cb.server_fd_str = 0; gpgsm->status.fnc = 0; gpgsm->colon.fnc = 0; gpgsm->colon.attic.line = 0; gpgsm->colon.attic.linesize = 0; gpgsm->colon.attic.linelen = 0; gpgsm->colon.any = 0; gpgsm->inline_data = NULL; gpgsm->io_cbs.add = NULL; gpgsm->io_cbs.add_priv = NULL; gpgsm->io_cbs.remove = NULL; gpgsm->io_cbs.event = NULL; gpgsm->io_cbs.event_priv = NULL; if (_gpgme_io_pipe (fds, 1) < 0) { err = gpg_error_from_syserror (); goto leave; } gpgsm->diag_cb.fd = fds[0]; gpgsm->diag_cb.server_fd = fds[1]; #if USE_DESCRIPTOR_PASSING child_fds[0] = gpgsm->diag_cb.server_fd; child_fds[1] = -1; nchild_fds = 2; connect_flags = ASSUAN_PIPE_CONNECT_FDPASSING; #else /*!USE_DESCRIPTOR_PASSING*/ if (_gpgme_io_pipe (fds, 0) < 0) { err = gpg_error_from_syserror (); goto leave; } gpgsm->input_cb.fd = fds[1]; gpgsm->input_cb.server_fd = fds[0]; if (_gpgme_io_pipe (fds, 1) < 0) { err = gpg_error_from_syserror (); goto leave; } gpgsm->output_cb.fd = fds[0]; gpgsm->output_cb.server_fd = fds[1]; if (_gpgme_io_pipe (fds, 0) < 0) { err = gpg_error_from_syserror (); goto leave; } gpgsm->message_cb.fd = fds[1]; gpgsm->message_cb.server_fd = fds[0]; child_fds[0] = gpgsm->input_cb.server_fd; child_fds[1] = gpgsm->output_cb.server_fd; child_fds[2] = gpgsm->message_cb.server_fd; child_fds[3] = gpgsm->diag_cb.server_fd; child_fds[4] = -1; nchild_fds = 5; connect_flags = 0; #endif /*!USE_DESCRIPTOR_PASSING*/ pgmname = file_name ? file_name : _gpgme_get_default_gpgsm_name (); argc = 0; argv[argc++] = _gpgme_get_basename (pgmname); if (home_dir) { argv[argc++] = "--homedir"; argv[argc++] = home_dir; } /* Set up diagnostics */ err = gpgme_data_new (&gpgsm->diagnostics); if (err) goto leave; gpgsm->diag_cb.data = gpgsm->diagnostics; argv[argc++] = "--logger-fd"; if (gpgrt_asprintf (&diag_fd_str, "%i", gpgsm->diag_cb.server_fd) == -1) { err = gpg_error_from_syserror (); goto leave; } argv[argc++] = diag_fd_str; argv[argc++] = "--server"; argv[argc++] = NULL; err = assuan_new_ext (&gpgsm->assuan_ctx, GPG_ERR_SOURCE_GPGME, &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb, NULL); if (err) goto leave; assuan_ctx_set_system_hooks (gpgsm->assuan_ctx, &_gpgme_assuan_system_hooks); { assuan_fd_t achild_fds[5]; int i; /* For now... */ for (i = 0; i < nchild_fds; i++) achild_fds[i] = (assuan_fd_t) child_fds[i]; err = assuan_pipe_connect (gpgsm->assuan_ctx, pgmname, argv, achild_fds, NULL, NULL, connect_flags); /* FIXME: Check whether our Windows code still updates the list.*/ for (i = 0; i < nchild_fds; i++) child_fds[i] = (int) achild_fds[i]; } #if !USE_DESCRIPTOR_PASSING /* On Windows, handles are inserted in the spawned process with DuplicateHandle, and child_fds contains the server-local names for the inserted handles when assuan_pipe_connect returns. */ if (!err) { /* Note: We don't use _gpgme_io_fd2str here. On W32 the returned handles are real W32 system handles, not whatever GPGME uses internally (which may be a system handle, a C library handle or a GLib/Qt channel. Confusing, yes, but remember these are server-local names, so they are not part of GPGME at all. */ snprintf (gpgsm->input_cb.server_fd_str, sizeof gpgsm->input_cb.server_fd_str, "%d", child_fds[0]); snprintf (gpgsm->output_cb.server_fd_str, sizeof gpgsm->output_cb.server_fd_str, "%d", child_fds[1]); snprintf (gpgsm->message_cb.server_fd_str, sizeof gpgsm->message_cb.server_fd_str, "%d", child_fds[2]); snprintf (gpgsm->diag_cb.server_fd_str, sizeof gpgsm->diag_cb.server_fd_str, "%d", child_fds[3]); } #endif if (err) goto leave; err = _gpgme_getenv ("DISPLAY", &dft_display); if (err) goto leave; if (dft_display) { if (gpgrt_asprintf (&optstr, "OPTION display=%s", dft_display) < 0) { free (dft_display); err = gpg_error_from_syserror (); goto leave; } free (dft_display); err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } err = _gpgme_getenv ("GPG_TTY", &env_tty); if (isatty (1) || env_tty || err) { int rc = 0; if (err) goto leave; else if (env_tty) { snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty); free (env_tty); } else rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); /* Even though isatty() returns 1, ttyname_r() may fail in many ways, e.g., when /dev/pts is not accessible under chroot. */ if (!rc) { if (gpgrt_asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; err = _gpgme_getenv ("TERM", &dft_ttytype); if (err) goto leave; if (dft_ttytype) { if (gpgrt_asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype)< 0) { free (dft_ttytype); err = gpg_error_from_syserror (); goto leave; } free (dft_ttytype); err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } } } /* Ask gpgsm to enable the audit log support. */ if (!err) { err = assuan_transact (gpgsm->assuan_ctx, "OPTION enable-audit-log=1", NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* This is an optional feature of gpgsm. */ } #ifdef HAVE_W32_SYSTEM /* Under Windows we need to use AllowSetForegroundWindow. Tell gpgsm to tell us when it needs it. */ if (!err) { err = assuan_transact (gpgsm->assuan_ctx, "OPTION allow-pinentry-notify", NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* This is a new feature of gpgsm. */ } #endif /*HAVE_W32_SYSTEM*/ #if !USE_DESCRIPTOR_PASSING if (!err && (_gpgme_io_set_close_notify (gpgsm->input_cb.fd, close_notify_handler, gpgsm) || _gpgme_io_set_close_notify (gpgsm->output_cb.fd, close_notify_handler, gpgsm) || _gpgme_io_set_close_notify (gpgsm->message_cb.fd, close_notify_handler, gpgsm))) { err = gpg_error (GPG_ERR_GENERAL); goto leave; } #endif if (!err && _gpgme_io_set_close_notify (gpgsm->diag_cb.fd, close_notify_handler, gpgsm)) { err = gpg_error (GPG_ERR_GENERAL); goto leave; } leave: /* Close the server ends of the pipes (because of this, we must use the stored server_fd_str in the function start). Our ends are closed in gpgsm_release(). */ #if !USE_DESCRIPTOR_PASSING if (gpgsm->input_cb.server_fd != -1) _gpgme_io_close (gpgsm->input_cb.server_fd); if (gpgsm->output_cb.server_fd != -1) _gpgme_io_close (gpgsm->output_cb.server_fd); if (gpgsm->message_cb.server_fd != -1) _gpgme_io_close (gpgsm->message_cb.server_fd); if (gpgsm->diag_cb.server_fd != -1) _gpgme_io_close (gpgsm->diag_cb.server_fd); #endif if (err) gpgsm_release (gpgsm); else *engine = gpgsm; free (diag_fd_str); return err; } /* Copy flags from CTX into the engine object. */ static void gpgsm_set_engine_flags (void *engine, const gpgme_ctx_t ctx) { engine_gpgsm_t gpgsm = engine; if (ctx->request_origin) { if (strlen (ctx->request_origin) + 1 > sizeof gpgsm->request_origin) strcpy (gpgsm->request_origin, "xxx"); /* Too long - force error */ else strcpy (gpgsm->request_origin, ctx->request_origin); } else *gpgsm->request_origin = 0; } static gpgme_error_t gpgsm_set_locale (void *engine, int category, const char *value) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; char *optstr; const char *catstr; /* FIXME: If value is NULL, we need to reset the option to default. But we can't do this. So we error out here. GPGSM needs support for this. */ if (0) ; #ifdef LC_CTYPE else if (category == LC_CTYPE) { catstr = "lc-ctype"; if (!value && gpgsm->lc_ctype_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) gpgsm->lc_ctype_set = 1; } #endif #ifdef LC_MESSAGES else if (category == LC_MESSAGES) { catstr = "lc-messages"; if (!value && gpgsm->lc_messages_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) gpgsm->lc_messages_set = 1; } #endif /* LC_MESSAGES */ else return gpg_error (GPG_ERR_INV_VALUE); /* FIXME: Reset value to default. */ if (!value) return 0; if (gpgrt_asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0) err = gpg_error_from_syserror (); else { err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); } return err; } static gpgme_error_t gpgsm_assuan_simple_command (engine_gpgsm_t gpgsm, const char *cmd, engine_status_handler_t status_fnc, void *status_fnc_value) { assuan_context_t ctx = gpgsm->assuan_ctx; gpg_error_t err, cb_err; char *line; size_t linelen; err = assuan_write_line (ctx, cmd); if (err) return err; cb_err = 0; do { err = assuan_read_line (ctx, &line, &linelen); if (err) break; if (*line == '#' || !linelen) continue; if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) break; else if (linelen >= 4 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && line[3] == ' ') { /* We prefer a callback generated error because that one is more related to gpgme and thus probably more important than the error returned by the engine. */ err = cb_err? cb_err : atoi (&line[4]); cb_err = 0; } else if (linelen >= 2 && line[0] == 'S' && line[1] == ' ') { /* After an error from a status callback we skip all further status lines. */ if (!cb_err) { char *rest; gpgme_status_code_t r; rest = strchr (line + 2, ' '); if (!rest) rest = line + linelen; /* set to an empty string */ else *(rest++) = 0; r = _gpgme_parse_status (line + 2); if (gpgsm->status.mon_cb && r != GPGME_STATUS_PROGRESS) { /* Note that we call the monitor even if we do * not know the status code (r < 0). */ cb_err = gpgsm->status.mon_cb (gpgsm->status.mon_cb_value, line + 2, rest); } if (r >= 0 && status_fnc && !cb_err) cb_err = status_fnc (status_fnc_value, r, rest); } } else { /* Invalid line or INQUIRY. We can't do anything else than to stop. As with ERR we prefer a status callback generated error code, though. */ err = cb_err ? cb_err : gpg_error (GPG_ERR_GENERAL); cb_err = 0; } } while (!err); /* We only want the first error from the status handler, thus we * take the one saved in CB_ERR. */ if (!err && cb_err) err = cb_err; return err; } typedef enum { INPUT_FD, OUTPUT_FD, MESSAGE_FD } fd_type_t; static void gpgsm_clear_fd (engine_gpgsm_t gpgsm, fd_type_t fd_type) { #if !USE_DESCRIPTOR_PASSING switch (fd_type) { case INPUT_FD: _gpgme_io_close (gpgsm->input_cb.fd); break; case OUTPUT_FD: _gpgme_io_close (gpgsm->output_cb.fd); break; case MESSAGE_FD: _gpgme_io_close (gpgsm->message_cb.fd); break; } #else (void)gpgsm; (void)fd_type; #endif } #define COMMANDLINELEN 40 static gpgme_error_t gpgsm_set_fd (engine_gpgsm_t gpgsm, fd_type_t fd_type, const char *opt) { gpg_error_t err = 0; char line[COMMANDLINELEN]; const char *which; iocb_data_t *iocb_data; #if USE_DESCRIPTOR_PASSING int dir; #endif switch (fd_type) { case INPUT_FD: which = "INPUT"; iocb_data = &gpgsm->input_cb; break; case OUTPUT_FD: which = "OUTPUT"; iocb_data = &gpgsm->output_cb; break; case MESSAGE_FD: which = "MESSAGE"; iocb_data = &gpgsm->message_cb; break; default: return gpg_error (GPG_ERR_INV_VALUE); } #if USE_DESCRIPTOR_PASSING dir = iocb_data->dir; /* We try to short-cut the communication by giving GPGSM direct access to the file descriptor, rather than using a pipe. */ iocb_data->server_fd = _gpgme_data_get_fd (iocb_data->data); if (iocb_data->server_fd < 0) { int fds[2]; if (_gpgme_io_pipe (fds, dir) < 0) return gpg_error_from_syserror (); iocb_data->fd = dir ? fds[0] : fds[1]; iocb_data->server_fd = dir ? fds[1] : fds[0]; if (_gpgme_io_set_close_notify (iocb_data->fd, close_notify_handler, gpgsm)) { err = gpg_error (GPG_ERR_GENERAL); goto leave_set_fd; } } err = assuan_sendfd (gpgsm->assuan_ctx, iocb_data->server_fd); if (err) goto leave_set_fd; _gpgme_io_close (iocb_data->server_fd); iocb_data->server_fd = -1; if (opt) snprintf (line, COMMANDLINELEN, "%s FD %s", which, opt); else snprintf (line, COMMANDLINELEN, "%s FD", which); #else if (opt) snprintf (line, COMMANDLINELEN, "%s FD=%s %s", which, iocb_data->server_fd_str, opt); else snprintf (line, COMMANDLINELEN, "%s FD=%s", which, iocb_data->server_fd_str); #endif err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL); #if USE_DESCRIPTOR_PASSING leave_set_fd: if (err) { _gpgme_io_close (iocb_data->fd); iocb_data->fd = -1; if (iocb_data->server_fd != -1) { _gpgme_io_close (iocb_data->server_fd); iocb_data->server_fd = -1; } } #endif return err; } static const char * map_data_enc (gpgme_data_t d) { switch (gpgme_data_get_encoding (d)) { case GPGME_DATA_ENCODING_NONE: break; case GPGME_DATA_ENCODING_BINARY: return "--binary"; case GPGME_DATA_ENCODING_BASE64: return "--base64"; case GPGME_DATA_ENCODING_ARMOR: return "--armor"; default: break; } return NULL; } static gpgme_error_t status_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; engine_gpgsm_t gpgsm = (engine_gpgsm_t) data->handler_value; gpgme_error_t err = 0; char *line; size_t linelen; do { err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen); if (err) { /* Try our best to terminate the connection friendly. */ /* assuan_write_line (gpgsm->assuan_ctx, "BYE"); */ TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm, "fd 0x%x: error from assuan (%d) getting status line : %s", fd, err, gpg_strerror (err)); } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (line[3] == '\0' || line[3] == ' ')) { if (line[3] == ' ') err = atoi (&line[4]); if (! err) err = gpg_error (GPG_ERR_GENERAL); TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm, "fd 0x%x: ERR line - mapped to: %s", fd, err ? gpg_strerror (err) : "ok"); /* Try our best to terminate the connection friendly. */ /* assuan_write_line (gpgsm->assuan_ctx, "BYE"); */ } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { if (gpgsm->status.fnc) { char emptystring[1] = {0}; err = gpgsm->status.fnc (gpgsm->status.fnc_value, GPGME_STATUS_EOF, emptystring); if (gpg_err_code (err) == GPG_ERR_FALSE) err = 0; /* Drop special error code. */ } if (!err && gpgsm->colon.fnc && gpgsm->colon.any) { /* We must tell a colon function about the EOF. We do this only when we have seen any data lines. Note that this inlined use of colon data lines will eventually be changed into using a regular data channel. */ gpgsm->colon.any = 0; err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, NULL); } TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm, "fd 0x%x: OK line - final status: %s", fd, err ? gpg_strerror (err) : "ok"); _gpgme_io_close (gpgsm->status_cb.fd); return err; } else if (linelen > 2 && line[0] == 'D' && line[1] == ' ' && gpgsm->colon.fnc) { /* We are using the colon handler even for plain inline data - strange name for that function but for historic reasons we keep it. */ /* FIXME We can't use this for binary data because we assume this is a string. For the current usage of colon output it is correct. */ char *src = line + 2; char *end = line + linelen; char *dst; char **aline = &gpgsm->colon.attic.line; int *alinelen = &gpgsm->colon.attic.linelen; if (gpgsm->colon.attic.linesize < *alinelen + linelen + 1) { char *newline = realloc (*aline, *alinelen + linelen + 1); if (!newline) err = gpg_error_from_syserror (); else { *aline = newline; gpgsm->colon.attic.linesize = *alinelen + linelen + 1; } } if (!err) { dst = *aline + *alinelen; while (!err && src < end) { if (*src == '%' && src + 2 < end) { /* Handle escaped characters. */ ++src; *dst = _gpgme_hextobyte (src); (*alinelen)++; src += 2; } else { *dst = *src++; (*alinelen)++; } if (*dst == '\n') { /* Terminate the pending line, pass it to the colon handler and reset it. */ gpgsm->colon.any = 1; if (*alinelen > 1 && *(dst - 1) == '\r') dst--; *dst = '\0'; /* FIXME How should we handle the return code? */ err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, *aline); if (!err) { dst = *aline; *alinelen = 0; } } else dst++; } } TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm, "fd 0x%x: D line; final status: %s", fd, err? gpg_strerror (err):"ok"); } else if (linelen > 2 && line[0] == 'D' && line[1] == ' ' && gpgsm->inline_data) { char *src = line + 2; char *end = line + linelen; char *dst = src; gpgme_ssize_t nwritten; linelen = 0; while (src < end) { if (*src == '%' && src + 2 < end) { /* Handle escaped characters. */ ++src; *dst++ = _gpgme_hextobyte (src); src += 2; } else *dst++ = *src++; linelen++; } src = line + 2; while (linelen > 0) { nwritten = gpgme_data_write (gpgsm->inline_data, src, linelen); if (nwritten <= 0 || nwritten > linelen) { err = gpg_error_from_syserror (); break; } src += nwritten; linelen -= nwritten; } TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm, "fd 0x%x: D inlinedata; final status: %s", fd, err? gpg_strerror (err):"ok"); } else if (linelen > 2 && line[0] == 'S' && line[1] == ' ') { char *rest; gpgme_status_code_t r; rest = strchr (line + 2, ' '); if (!rest) rest = line + linelen; /* set to an empty string */ else *(rest++) = 0; r = _gpgme_parse_status (line + 2); if (gpgsm->status.mon_cb && r != GPGME_STATUS_PROGRESS) { /* Note that we call the monitor even if we do * not know the status code (r < 0). */ err = gpgsm->status.mon_cb (gpgsm->status.mon_cb_value, line + 2, rest); } else err = 0; if (r >= 0 && !err) { if (gpgsm->status.fnc) { err = gpgsm->status.fnc (gpgsm->status.fnc_value, r, rest); if (gpg_err_code (err) == GPG_ERR_FALSE) err = 0; /* Drop special error code. */ } } else fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest); TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm, "fd 0x%x: S line (%s) - final status: %s", fd, line+2, err? gpg_strerror (err):"ok"); } else if (linelen >= 7 && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' && line[6] == 'E' && (line[7] == '\0' || line[7] == ' ')) { char *keyword = line+7; while (*keyword == ' ') keyword++;; default_inq_cb (gpgsm, keyword); assuan_write_line (gpgsm->assuan_ctx, "END"); } } while (!err && assuan_pending_line (gpgsm->assuan_ctx)); return err; } static gpgme_error_t add_io_cb (engine_gpgsm_t gpgsm, iocb_data_t *iocbd, gpgme_io_cb_t handler) { gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-gpgsm:add_io_cb", gpgsm, - "fd %d, dir %d", iocbd->fd, iocbd->dir); + "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*gpgsm->io_cbs.add) (gpgsm->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); if (err) return TRACE_ERR (err); if (!iocbd->dir) /* FIXME Kludge around poll() problem. */ err = _gpgme_io_set_nonblocking (iocbd->fd); return TRACE_ERR (err); } static gpgme_error_t start (engine_gpgsm_t gpgsm, const char *command) { gpgme_error_t err; assuan_fd_t afdlist[5]; int fdlist[5]; int nfds; int i; if (*gpgsm->request_origin) { char *cmd; cmd = _gpgme_strconcat ("OPTION request-origin=", gpgsm->request_origin, NULL); if (!cmd) return gpg_error_from_syserror (); err = gpgsm_assuan_simple_command (gpgsm, cmd, NULL, NULL); free (cmd); if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION) return err; } /* We need to know the fd used by assuan for reads. We do this by using the assumption that the first returned fd from assuan_get_active_fds() is always this one. */ nfds = assuan_get_active_fds (gpgsm->assuan_ctx, 0 /* read fds */, afdlist, DIM (afdlist)); if (nfds < 1) return gpg_error (GPG_ERR_GENERAL); /* FIXME */ /* For now... */ for (i = 0; i < nfds; i++) fdlist[i] = (int) afdlist[i]; /* We "duplicate" the file descriptor, so we can close it here (we can't close fdlist[0], as that is closed by libassuan, and closing it here might cause libassuan to close some unrelated FD later). Alternatively, we could special case status_fd and register/unregister it manually as needed, but this increases code duplication and is more complicated as we can not use the close notifications etc. A third alternative would be to let Assuan know that we closed the FD, but that complicates the Assuan interface. */ gpgsm->status_cb.fd = _gpgme_io_dup (fdlist[0]); if (gpgsm->status_cb.fd < 0) return gpg_error_from_syserror (); if (_gpgme_io_set_close_notify (gpgsm->status_cb.fd, close_notify_handler, gpgsm)) { _gpgme_io_close (gpgsm->status_cb.fd); gpgsm->status_cb.fd = -1; return gpg_error (GPG_ERR_GENERAL); } err = add_io_cb (gpgsm, &gpgsm->status_cb, status_handler); if (!err && gpgsm->input_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->input_cb, _gpgme_data_outbound_handler); if (!err && gpgsm->output_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->output_cb, _gpgme_data_inbound_handler); if (!err && gpgsm->message_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->message_cb, _gpgme_data_outbound_handler); if (!err && gpgsm->diag_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->diag_cb, _gpgme_data_inbound_handler); if (!err) err = assuan_write_line (gpgsm->assuan_ctx, command); if (!err) gpgsm_io_event (gpgsm, GPGME_EVENT_START, NULL); return err; } #if USE_DESCRIPTOR_PASSING static gpgme_error_t gpgsm_reset (void *engine) { engine_gpgsm_t gpgsm = engine; /* IF we have an active connection we must send a reset because we need to reset the list of signers. Note that RESET does not reset OPTION commands. */ return (gpgsm->assuan_ctx ? gpgsm_assuan_simple_command (gpgsm, "RESET", NULL, NULL) : 0); } #endif static gpgme_error_t gpgsm_decrypt (void *engine, gpgme_decrypt_flags_t flags, gpgme_data_t ciph, gpgme_data_t plain, int export_session_key, const char *override_session_key, int auto_key_retrieve) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; (void)flags; /* gpgsm is not capable of exporting session keys right now, so we * will ignore this if requested. */ (void)export_session_key; (void)override_session_key; /* --auto-key-retrieve is also not supported. */ (void)auto_key_retrieve; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); gpgsm->input_cb.data = ciph; err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data)); if (err) return gpg_error (GPG_ERR_GENERAL); /* FIXME */ gpgsm->output_cb.data = plain; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0); if (err) return gpg_error (GPG_ERR_GENERAL); /* FIXME */ gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; err = start (engine, "DECRYPT"); return err; } static gpgme_error_t gpgsm_delete (void *engine, gpgme_key_t key, unsigned int flags) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; char *fpr = key->subkeys ? key->subkeys->fpr : NULL; char *linep = fpr; char *line; int length = 8; /* "DELKEYS " */ (void)flags; if (!fpr) return gpg_error (GPG_ERR_INV_VALUE); while (*linep) { length++; if (*linep == '%' || *linep == ' ' || *linep == '+') length += 2; linep++; } length++; line = malloc (length); if (!line) return gpg_error_from_syserror (); strcpy (line, "DELKEYS "); linep = &line[8]; while (*fpr) { switch (*fpr) { case '%': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '5'; break; case ' ': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '0'; break; case '+': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = 'B'; break; default: *(linep++) = *fpr; break; } fpr++; } *linep = '\0'; gpgsm_clear_fd (gpgsm, OUTPUT_FD); gpgsm_clear_fd (gpgsm, INPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; err = start (gpgsm, line); free (line); return err; } static gpgme_error_t set_recipients (engine_gpgsm_t gpgsm, gpgme_key_t recp[]) { gpgme_error_t err = 0; char *line; int linelen; int invalid_recipients = 0; int i; linelen = 10 + 40 + 1; /* "RECIPIENT " + guess + '\0'. */ line = malloc (10 + 40 + 1); if (!line) return gpg_error_from_syserror (); strcpy (line, "RECIPIENT "); for (i =0; !err && recp[i]; i++) { char *fpr; int newlen; if (!recp[i]->subkeys || !recp[i]->subkeys->fpr) { invalid_recipients++; continue; } fpr = recp[i]->subkeys->fpr; newlen = 11 + strlen (fpr); if (linelen < newlen) { char *newline = realloc (line, newlen); if (! newline) { int saved_err = gpg_error_from_syserror (); free (line); return saved_err; } line = newline; linelen = newlen; } strcpy (&line[10], fpr); err = gpgsm_assuan_simple_command (gpgsm, line, gpgsm->status.fnc, gpgsm->status.fnc_value); /* FIXME: This requires more work. */ if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) invalid_recipients++; else if (err) { free (line); return err; } } free (line); return gpg_error (invalid_recipients ? GPG_ERR_UNUSABLE_PUBKEY : GPG_ERR_NO_ERROR); } /* Take recipients from the LF delimited STRING and send RECIPIENT * commands to gpgsm. */ static gpgme_error_t set_recipients_from_string (engine_gpgsm_t gpgsm, const char *string) { gpg_error_t err = 0; char *line = NULL; int ignore = 0; int any = 0; const char *s; int n; do { while (*string == ' ' || *string == '\t') string++; if (!*string) break; s = strchr (string, '\n'); if (s) n = s - string; else n = strlen (string); while (n && (string[n-1] == ' ' || string[n-1] == '\t')) n--; if (!ignore && n == 2 && !memcmp (string, "--", 2)) ignore = 1; else if (!ignore && n > 2 && !memcmp (string, "--", 2)) err = gpg_error (GPG_ERR_UNKNOWN_OPTION); else if (n) /* Not empty - use it. */ { gpgrt_free (line); if (gpgrt_asprintf (&line, "RECIPIENT %.*s", n, string) < 0) err = gpg_error_from_syserror (); else { err = gpgsm_assuan_simple_command (gpgsm, line, gpgsm->status.fnc, gpgsm->status.fnc_value); if (!err) any = 1; } } string += n + !!s; } while (!err); if (!err && !any) err = gpg_error (GPG_ERR_MISSING_KEY); gpgrt_free (line); return err; } static gpgme_error_t gpgsm_encrypt (void *engine, gpgme_key_t recp[], const char *recpstring, gpgme_encrypt_flags_t flags, gpgme_data_t plain, gpgme_data_t ciph, int use_armor) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); if (!recp && !recpstring) /* Symmetric only */ return gpg_error (GPG_ERR_NOT_IMPLEMENTED); if ((flags & GPGME_ENCRYPT_NO_ENCRYPT_TO)) { err = gpgsm_assuan_simple_command (gpgsm, "OPTION no-encrypt-to", NULL, NULL); if (err) return err; } gpgsm->input_cb.data = plain; err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data)); if (err) return err; gpgsm->output_cb.data = ciph; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor" : map_data_enc (gpgsm->output_cb.data)); if (err) return err; gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; if (!recp && recpstring) err = set_recipients_from_string (gpgsm, recpstring); else err = set_recipients (gpgsm, recp); if (!err) err = start (gpgsm, "ENCRYPT"); return err; } static gpgme_error_t gpgsm_export (void *engine, const char *pattern, gpgme_export_mode_t mode, gpgme_data_t keydata, int use_armor) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err = 0; char *cmd; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); if (!pattern) pattern = ""; cmd = malloc (7 + 9 + 9 + strlen (pattern) + 1); if (!cmd) return gpg_error_from_syserror (); strcpy (cmd, "EXPORT "); if ((mode & GPGME_EXPORT_MODE_SECRET)) { strcat (cmd, "--secret "); if ((mode & GPGME_EXPORT_MODE_RAW)) strcat (cmd, "--raw "); else if ((mode & GPGME_EXPORT_MODE_PKCS12)) strcat (cmd, "--pkcs12 "); } strcat (cmd, pattern); gpgsm->output_cb.data = keydata; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor" : map_data_enc (gpgsm->output_cb.data)); if (err) return err; gpgsm_clear_fd (gpgsm, INPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; err = start (gpgsm, cmd); free (cmd); return err; } static gpgme_error_t gpgsm_export_ext (void *engine, const char *pattern[], gpgme_export_mode_t mode, gpgme_data_t keydata, int use_armor) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err = 0; char *line; /* Length is "EXPORT " + "--secret " + "--pkcs12 " + p + '\0'. */ int length = 7 + 9 + 9 + 1; char *linep; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); if (pattern && *pattern) { const char **pat = pattern; while (*pat) { const char *patlet = *pat; while (*patlet) { length++; if (*patlet == '%' || *patlet == ' ' || *patlet == '+') length += 2; patlet++; } pat++; length++; } } line = malloc (length); if (!line) return gpg_error_from_syserror (); strcpy (line, "EXPORT "); if ((mode & GPGME_EXPORT_MODE_SECRET)) { strcat (line, "--secret "); if ((mode & GPGME_EXPORT_MODE_RAW)) strcat (line, "--raw "); else if ((mode & GPGME_EXPORT_MODE_PKCS12)) strcat (line, "--pkcs12 "); } linep = &line[strlen (line)]; if (pattern && *pattern) { while (*pattern) { const char *patlet = *pattern; while (*patlet) { switch (*patlet) { case '%': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '5'; break; case ' ': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '0'; break; case '+': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = 'B'; break; default: *(linep++) = *patlet; break; } patlet++; } pattern++; if (*pattern) *linep++ = ' '; } } *linep = '\0'; gpgsm->output_cb.data = keydata; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor" : map_data_enc (gpgsm->output_cb.data)); if (err) return err; gpgsm_clear_fd (gpgsm, INPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; err = start (gpgsm, line); free (line); return err; } static gpgme_error_t gpgsm_genkey (void *engine, const char *userid, const char *algo, unsigned long reserved, unsigned long expires, gpgme_key_t key, unsigned int flags, gpgme_data_t help_data, unsigned int extraflags, gpgme_data_t pubkey, gpgme_data_t seckey) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; (void)reserved; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); if (help_data) { if (!pubkey || seckey) return gpg_error (GPG_ERR_INV_VALUE); gpgsm->input_cb.data = help_data; err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data)); if (err) return err; gpgsm->output_cb.data = pubkey; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, (extraflags & GENKEY_EXTRAFLAG_ARMOR)? "--armor" : map_data_enc (gpgsm->output_cb.data)); if (err) return err; gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; err = start (gpgsm, "GENKEY"); return err; } (void)userid; (void)algo; (void)expires; (void)key; (void)flags; /* The new interface has not yet been implemented, */ return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } static gpgme_error_t gpgsm_import (void *engine, gpgme_data_t keydata, gpgme_key_t *keyarray) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; gpgme_data_encoding_t dataenc; int idx; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); if (keydata && keyarray) return gpg_error (GPG_ERR_INV_VALUE); /* Only one is allowed. */ dataenc = gpgme_data_get_encoding (keydata); if (keyarray) { size_t buflen; char *buffer, *p; /* Fist check whether the engine already features the --re-import option. */ err = gpgsm_assuan_simple_command (gpgsm, "GETINFO cmd_has_option IMPORT re-import", NULL, NULL); if (err) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Create an internal data object with a list of all fingerprints. The data object and its memory (to avoid an extra copy by gpgme_data_new_from_mem) are stored in two variables which are released by the close_notify_handler. */ for (idx=0, buflen=0; keyarray[idx]; idx++) { if (keyarray[idx]->protocol == GPGME_PROTOCOL_CMS && keyarray[idx]->subkeys && keyarray[idx]->subkeys->fpr && *keyarray[idx]->subkeys->fpr) buflen += strlen (keyarray[idx]->subkeys->fpr) + 1; } /* Allocate a buffer with extra space for the trailing Nul introduced by the use of stpcpy. */ buffer = malloc (buflen+1); if (!buffer) return gpg_error_from_syserror (); for (idx=0, p = buffer; keyarray[idx]; idx++) { if (keyarray[idx]->protocol == GPGME_PROTOCOL_CMS && keyarray[idx]->subkeys && keyarray[idx]->subkeys->fpr && *keyarray[idx]->subkeys->fpr) p = stpcpy (stpcpy (p, keyarray[idx]->subkeys->fpr), "\n"); } err = gpgme_data_new_from_mem (&gpgsm->input_helper_data, buffer, buflen, 0); if (err) { free (buffer); return err; } gpgsm->input_helper_memory = buffer; gpgsm->input_cb.data = gpgsm->input_helper_data; err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data)); if (err) { gpgme_data_release (gpgsm->input_helper_data); gpgsm->input_helper_data = NULL; free (gpgsm->input_helper_memory); gpgsm->input_helper_memory = NULL; return err; } gpgsm_clear_fd (gpgsm, OUTPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; return start (gpgsm, "IMPORT --re-import"); } else if (dataenc == GPGME_DATA_ENCODING_URL || dataenc == GPGME_DATA_ENCODING_URL0 || dataenc == GPGME_DATA_ENCODING_URLESC) { return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } else { gpgsm->input_cb.data = keydata; err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data)); if (err) return err; gpgsm_clear_fd (gpgsm, OUTPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; return start (gpgsm, "IMPORT"); } } static gpgme_error_t gpgsm_keylist (void *engine, const char *pattern, int secret_only, gpgme_keylist_mode_t mode, int engine_flags) { engine_gpgsm_t gpgsm = engine; char *line; gpgme_error_t err; int list_mode = 0; if (mode & GPGME_KEYLIST_MODE_LOCAL) list_mode |= 1; if (mode & GPGME_KEYLIST_MODE_EXTERN) list_mode |= 2; if (!pattern) pattern = ""; /* Hack to make sure that the agent is started. Only if the agent has been started an application may connect to the agent via GPGME_PROTOCOL_ASSUAN - for example to look for smartcards. We do this only if a secret key listing has been requested. In general this is not needed because a secret key listing starts the agent. However on a fresh installation no public keys are available and thus there is no need for gpgsm to ask the agent whether a secret key exists for the public key. */ if (secret_only || (mode & GPGME_KEYLIST_MODE_WITH_SECRET)) gpgsm_assuan_simple_command (gpgsm, "GETINFO agent-check", NULL, NULL); /* Always send list-mode option because RESET does not reset it. */ if (gpgrt_asprintf (&line, "OPTION list-mode=%d", (list_mode & 3)) < 0) return gpg_error_from_syserror (); err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL); gpgrt_free (line); if (err) return err; /* Always send key validation because RESET does not reset it. */ /* Use the validation mode if requested. We don't check for an error yet because this is a pretty fresh gpgsm features. */ gpgsm_assuan_simple_command (gpgsm, (mode & GPGME_KEYLIST_MODE_VALIDATE)? "OPTION with-validation=1": "OPTION with-validation=0" , NULL, NULL); /* Include the ephemeral keys if requested. We don't check for an error yet because this is a pretty fresh gpgsm features. */ gpgsm_assuan_simple_command (gpgsm, (mode & GPGME_KEYLIST_MODE_EPHEMERAL)? "OPTION with-ephemeral-keys=1": "OPTION with-ephemeral-keys=0" , NULL, NULL); gpgsm_assuan_simple_command (gpgsm, (mode & GPGME_KEYLIST_MODE_WITH_SECRET)? "OPTION with-secret=1": "OPTION with-secret=0" , NULL, NULL); gpgsm_assuan_simple_command (gpgsm, (engine_flags & GPGME_ENGINE_FLAG_OFFLINE)? "OPTION offline=1": "OPTION offline=0" , NULL, NULL); /* Length is "LISTSECRETKEYS " + p + '\0'. */ line = malloc (15 + strlen (pattern) + 1); if (!line) return gpg_error_from_syserror (); if (secret_only) { strcpy (line, "LISTSECRETKEYS "); strcpy (&line[15], pattern); } else { strcpy (line, "LISTKEYS "); strcpy (&line[9], pattern); } gpgsm_clear_fd (gpgsm, INPUT_FD); gpgsm_clear_fd (gpgsm, OUTPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; err = start (gpgsm, line); free (line); return err; } static gpgme_error_t gpgsm_keylist_ext (void *engine, const char *pattern[], int secret_only, int reserved, gpgme_keylist_mode_t mode, int engine_flags) { engine_gpgsm_t gpgsm = engine; char *line; gpgme_error_t err; /* Length is "LISTSECRETKEYS " + p + '\0'. */ int length = 15 + 1; char *linep; int any_pattern = 0; int list_mode = 0; if (reserved) return gpg_error (GPG_ERR_INV_VALUE); if (mode & GPGME_KEYLIST_MODE_LOCAL) list_mode |= 1; if (mode & GPGME_KEYLIST_MODE_EXTERN) list_mode |= 2; /* Always send list-mode option because RESET does not reset it. */ if (gpgrt_asprintf (&line, "OPTION list-mode=%d", (list_mode & 3)) < 0) return gpg_error_from_syserror (); err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL); gpgrt_free (line); if (err) return err; /* Always send key validation because RESET does not reset it. */ /* Use the validation mode if required. We don't check for an error yet because this is a pretty fresh gpgsm features. */ gpgsm_assuan_simple_command (gpgsm, (mode & GPGME_KEYLIST_MODE_VALIDATE)? "OPTION with-validation=1": "OPTION with-validation=0" , NULL, NULL); gpgsm_assuan_simple_command (gpgsm, (mode & GPGME_KEYLIST_MODE_WITH_SECRET)? "OPTION with-secret=1": "OPTION with-secret=0" , NULL, NULL); gpgsm_assuan_simple_command (gpgsm, (engine_flags & GPGME_ENGINE_FLAG_OFFLINE)? "OPTION offline=1": "OPTION offline=0" , NULL, NULL); if (pattern && *pattern) { const char **pat = pattern; while (*pat) { const char *patlet = *pat; while (*patlet) { length++; if (*patlet == '%' || *patlet == ' ' || *patlet == '+') length += 2; patlet++; } pat++; length++; } } line = malloc (length); if (!line) return gpg_error_from_syserror (); if (secret_only) { strcpy (line, "LISTSECRETKEYS "); linep = &line[15]; } else { strcpy (line, "LISTKEYS "); linep = &line[9]; } if (pattern && *pattern) { while (*pattern) { const char *patlet = *pattern; while (*patlet) { switch (*patlet) { case '%': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '5'; break; case ' ': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = '0'; break; case '+': *(linep++) = '%'; *(linep++) = '2'; *(linep++) = 'B'; break; default: *(linep++) = *patlet; break; } patlet++; } any_pattern = 1; *linep++ = ' '; pattern++; } } if (any_pattern) linep--; *linep = '\0'; gpgsm_clear_fd (gpgsm, INPUT_FD); gpgsm_clear_fd (gpgsm, OUTPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; err = start (gpgsm, line); free (line); return err; } static gpgme_error_t gpgsm_sign (void *engine, gpgme_data_t in, gpgme_data_t out, gpgme_sig_mode_t mode, int use_armor, int use_textmode, int include_certs, gpgme_ctx_t ctx /* FIXME */) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; char *assuan_cmd; int i; gpgme_key_t key; (void)use_textmode; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); /* FIXME: This does not work as RESET does not reset it so we can't revert back to default. */ if (include_certs != GPGME_INCLUDE_CERTS_DEFAULT) { /* FIXME: Make sure that if we run multiple operations, that we can reset any previously set value in case the default is requested. */ if (gpgrt_asprintf (&assuan_cmd, "OPTION include-certs %i", include_certs) < 0) return gpg_error_from_syserror (); err = gpgsm_assuan_simple_command (gpgsm, assuan_cmd, NULL, NULL); gpgrt_free (assuan_cmd); if (err) return err; } for (i = 0; (key = gpgme_signers_enum (ctx, i)); i++) { const char *s = key->subkeys ? key->subkeys->fpr : NULL; if (s && strlen (s) < 80) { char buf[100]; strcpy (stpcpy (buf, "SIGNER "), s); err = gpgsm_assuan_simple_command (gpgsm, buf, gpgsm->status.fnc, gpgsm->status.fnc_value); } else err = gpg_error (GPG_ERR_INV_VALUE); gpgme_key_unref (key); if (err) return err; } gpgsm->input_cb.data = in; err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data)); if (err) return err; gpgsm->output_cb.data = out; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor" : map_data_enc (gpgsm->output_cb.data)); if (err) return err; gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; err = start (gpgsm, mode == GPGME_SIG_MODE_DETACH ? "SIGN --detached" : "SIGN"); return err; } static gpgme_error_t gpgsm_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext, gpgme_ctx_t ctx) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; (void)ctx; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); gpgsm->input_cb.data = sig; err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data)); if (err) return err; if (!signed_text) { /* Normal or cleartext signature. */ if (plaintext) { gpgsm->output_cb.data = plaintext; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0); } else { /* No output requested. */ gpgsm_clear_fd (gpgsm, OUTPUT_FD); } gpgsm_clear_fd (gpgsm, MESSAGE_FD); } else { /* Detached signature. */ gpgsm->message_cb.data = signed_text; err = gpgsm_set_fd (gpgsm, MESSAGE_FD, 0); gpgsm_clear_fd (gpgsm, OUTPUT_FD); } gpgsm->inline_data = NULL; if (!err) err = start (gpgsm, "VERIFY"); return err; } /* Send the GETAUDITLOG command. The result is saved to a gpgme data object. */ static gpgme_error_t gpgsm_getauditlog (void *engine, gpgme_data_t output, unsigned int flags) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err = 0; if (!gpgsm || !output) return gpg_error (GPG_ERR_INV_VALUE); if ((flags & GPGME_AUDITLOG_DIAG) && (flags & GPGME_AUDITLOG_HTML)) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); if ((flags & GPGME_AUDITLOG_DIAG)) { char buf[BUFFER_SIZE]; int nread; int any_written = 0; gpgme_data_rewind (gpgsm->diagnostics); while ((nread = gpgme_data_read (gpgsm->diagnostics, buf, BUFFER_SIZE)) > 0) { any_written = 1; if (gpgme_data_write (output, buf, nread) == -1) return gpg_error_from_syserror (); } if (!any_written) return gpg_error (GPG_ERR_NO_DATA); if (nread == -1) return gpg_error_from_syserror (); gpgme_data_rewind (output); return 0; } if (!gpgsm->assuan_ctx) return gpg_error (GPG_ERR_INV_VALUE); #if USE_DESCRIPTOR_PASSING gpgsm->output_cb.data = output; err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0); if (err) return err; gpgsm_clear_fd (gpgsm, INPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; # define CMD "GETAUDITLOG" #else gpgsm_clear_fd (gpgsm, OUTPUT_FD); gpgsm_clear_fd (gpgsm, INPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = output; # define CMD "GETAUDITLOG --data" #endif err = start (gpgsm, (flags & GPGME_AUDITLOG_HTML)? CMD " --html" : CMD); return err; } /* This sets a status callback for monitoring status lines before they * are passed to a caller set handler. */ static void gpgsm_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value) { engine_gpgsm_t gpgsm = engine; gpgsm->status.mon_cb = cb; gpgsm->status.mon_cb_value = cb_value; } static void gpgsm_set_status_handler (void *engine, engine_status_handler_t fnc, void *fnc_value) { engine_gpgsm_t gpgsm = engine; gpgsm->status.fnc = fnc; gpgsm->status.fnc_value = fnc_value; } static gpgme_error_t gpgsm_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc, void *fnc_value) { engine_gpgsm_t gpgsm = engine; gpgsm->colon.fnc = fnc; gpgsm->colon.fnc_value = fnc_value; gpgsm->colon.any = 0; return 0; } static void gpgsm_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_gpgsm_t gpgsm = engine; gpgsm->io_cbs = *io_cbs; } static void gpgsm_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_gpgsm_t gpgsm = engine; TRACE (DEBUG_ENGINE, "gpgme:gpgsm_io_event", gpgsm, "event %p, type %d, type_data %p", gpgsm->io_cbs.event, type, type_data); if (gpgsm->io_cbs.event) (*gpgsm->io_cbs.event) (gpgsm->io_cbs.event_priv, type, type_data); } static gpgme_error_t gpgsm_passwd (void *engine, gpgme_key_t key, unsigned int flags) { engine_gpgsm_t gpgsm = engine; gpgme_error_t err; char *line; (void)flags; if (!key || !key->subkeys || !key->subkeys->fpr) return gpg_error (GPG_ERR_INV_CERT_OBJ); if (gpgrt_asprintf (&line, "PASSWD -- %s", key->subkeys->fpr) < 0) return gpg_error_from_syserror (); gpgsm_clear_fd (gpgsm, OUTPUT_FD); gpgsm_clear_fd (gpgsm, INPUT_FD); gpgsm_clear_fd (gpgsm, MESSAGE_FD); gpgsm->inline_data = NULL; err = start (gpgsm, line); gpgrt_free (line); return err; } struct engine_ops _gpgme_engine_ops_gpgsm = { /* Static functions. */ _gpgme_get_default_gpgsm_name, NULL, gpgsm_get_version, gpgsm_get_req_version, gpgsm_new, /* Member functions. */ gpgsm_release, #if USE_DESCRIPTOR_PASSING gpgsm_reset, #else NULL, /* reset */ #endif gpgsm_set_status_cb, gpgsm_set_status_handler, NULL, /* set_command_handler */ gpgsm_set_colon_line_handler, gpgsm_set_locale, NULL, /* set_protocol */ gpgsm_set_engine_flags, gpgsm_decrypt, gpgsm_delete, /* decrypt_verify */ NULL, /* edit */ gpgsm_encrypt, NULL, /* encrypt_sign */ gpgsm_export, gpgsm_export_ext, gpgsm_genkey, gpgsm_import, gpgsm_keylist, gpgsm_keylist_ext, NULL, /* keylist_data */ NULL, /* keysign */ NULL, /* tofu_policy */ gpgsm_sign, NULL, /* trustlist */ gpgsm_verify, gpgsm_getauditlog, NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ NULL, /* conf_dir */ NULL, /* query_swdb */ gpgsm_set_io_cbs, gpgsm_io_event, gpgsm_cancel, NULL, /* cancel_op */ gpgsm_passwd, NULL, /* set_pinentry_mode */ NULL /* opspawn */ }; diff --git a/src/engine-uiserver.c b/src/engine-uiserver.c index 62c4e5b6..cb8e155d 100644 --- a/src/engine-uiserver.c +++ b/src/engine-uiserver.c @@ -1,1455 +1,1455 @@ /* engine-uiserver.c - Uiserver engine. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ /* Peculiar: Use special keys from email address for recipient and signer (==sender). Use no data objects with encryption for prep_encrypt. */ #if HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #include #ifdef HAVE_UNISTD_H # include #endif #include #include /* FIXME */ #include #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "priv-io.h" #include "sema.h" #include "data.h" #include "assuan.h" #include "debug.h" #include "engine-backend.h" typedef struct { int fd; /* FD we talk about. */ int server_fd;/* Server FD for this connection. */ int dir; /* Inbound/Outbound, maybe given implicit? */ void *data; /* Handler-specific data. */ void *tag; /* ID from the user for gpgme_remove_io_callback. */ char server_fd_str[15]; /* Same as SERVER_FD but as a string. We need this because _gpgme_io_fd2str can't be used on a closed descriptor. */ } iocb_data_t; struct engine_uiserver { assuan_context_t assuan_ctx; int lc_ctype_set; int lc_messages_set; gpgme_protocol_t protocol; iocb_data_t status_cb; /* Input, output etc are from the servers perspective. */ iocb_data_t input_cb; gpgme_data_t input_helper_data; /* Input helper data object. */ void *input_helper_memory; /* Input helper memory block. */ iocb_data_t output_cb; iocb_data_t message_cb; struct { engine_status_handler_t fnc; void *fnc_value; gpgme_status_cb_t mon_cb; void *mon_cb_value; } status; struct { engine_colon_line_handler_t fnc; void *fnc_value; struct { char *line; int linesize; int linelen; } attic; int any; /* any data line seen */ } colon; gpgme_data_t inline_data; /* Used to collect D lines. */ struct gpgme_io_cbs io_cbs; }; typedef struct engine_uiserver *engine_uiserver_t; static void uiserver_io_event (void *engine, gpgme_event_io_t type, void *type_data); static char * uiserver_get_version (const char *file_name) { (void)file_name; return NULL; } static const char * uiserver_get_req_version (void) { return NULL; } static void close_notify_handler (int fd, void *opaque) { engine_uiserver_t uiserver = opaque; assert (fd != -1); if (uiserver->status_cb.fd == fd) { if (uiserver->status_cb.tag) (*uiserver->io_cbs.remove) (uiserver->status_cb.tag); uiserver->status_cb.fd = -1; uiserver->status_cb.tag = NULL; } else if (uiserver->input_cb.fd == fd) { if (uiserver->input_cb.tag) (*uiserver->io_cbs.remove) (uiserver->input_cb.tag); uiserver->input_cb.fd = -1; uiserver->input_cb.tag = NULL; if (uiserver->input_helper_data) { gpgme_data_release (uiserver->input_helper_data); uiserver->input_helper_data = NULL; } if (uiserver->input_helper_memory) { free (uiserver->input_helper_memory); uiserver->input_helper_memory = NULL; } } else if (uiserver->output_cb.fd == fd) { if (uiserver->output_cb.tag) (*uiserver->io_cbs.remove) (uiserver->output_cb.tag); uiserver->output_cb.fd = -1; uiserver->output_cb.tag = NULL; } else if (uiserver->message_cb.fd == fd) { if (uiserver->message_cb.tag) (*uiserver->io_cbs.remove) (uiserver->message_cb.tag); uiserver->message_cb.fd = -1; uiserver->message_cb.tag = NULL; } } /* This is the default inquiry callback. We use it to handle the Pinentry notifications. */ static gpgme_error_t default_inq_cb (engine_uiserver_t uiserver, const char *line) { (void)uiserver; if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17])) { _gpgme_allow_set_foreground_window ((pid_t)strtoul (line+17, NULL, 10)); } return 0; } static gpgme_error_t uiserver_cancel (void *engine) { engine_uiserver_t uiserver = engine; if (!uiserver) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->status_cb.fd != -1) _gpgme_io_close (uiserver->status_cb.fd); if (uiserver->input_cb.fd != -1) _gpgme_io_close (uiserver->input_cb.fd); if (uiserver->output_cb.fd != -1) _gpgme_io_close (uiserver->output_cb.fd); if (uiserver->message_cb.fd != -1) _gpgme_io_close (uiserver->message_cb.fd); if (uiserver->assuan_ctx) { assuan_release (uiserver->assuan_ctx); uiserver->assuan_ctx = NULL; } return 0; } static void uiserver_release (void *engine) { engine_uiserver_t uiserver = engine; if (!uiserver) return; uiserver_cancel (engine); free (uiserver->colon.attic.line); free (uiserver); } static gpgme_error_t uiserver_new (void **engine, const char *file_name, const char *home_dir, const char *version) { gpgme_error_t err = 0; engine_uiserver_t uiserver; char *dft_display = NULL; char dft_ttyname[64]; char *env_tty = NULL; char *dft_ttytype = NULL; char *optstr; (void)home_dir; (void)version; /* Not yet used. */ uiserver = calloc (1, sizeof *uiserver); if (!uiserver) return gpg_error_from_syserror (); uiserver->protocol = GPGME_PROTOCOL_DEFAULT; uiserver->status_cb.fd = -1; uiserver->status_cb.dir = 1; uiserver->status_cb.tag = 0; uiserver->status_cb.data = uiserver; uiserver->input_cb.fd = -1; uiserver->input_cb.dir = 0; uiserver->input_cb.tag = 0; uiserver->input_cb.server_fd = -1; *uiserver->input_cb.server_fd_str = 0; uiserver->output_cb.fd = -1; uiserver->output_cb.dir = 1; uiserver->output_cb.tag = 0; uiserver->output_cb.server_fd = -1; *uiserver->output_cb.server_fd_str = 0; uiserver->message_cb.fd = -1; uiserver->message_cb.dir = 0; uiserver->message_cb.tag = 0; uiserver->message_cb.server_fd = -1; *uiserver->message_cb.server_fd_str = 0; uiserver->status.fnc = 0; uiserver->colon.fnc = 0; uiserver->colon.attic.line = 0; uiserver->colon.attic.linesize = 0; uiserver->colon.attic.linelen = 0; uiserver->colon.any = 0; uiserver->inline_data = NULL; uiserver->io_cbs.add = NULL; uiserver->io_cbs.add_priv = NULL; uiserver->io_cbs.remove = NULL; uiserver->io_cbs.event = NULL; uiserver->io_cbs.event_priv = NULL; err = assuan_new_ext (&uiserver->assuan_ctx, GPG_ERR_SOURCE_GPGME, &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb, NULL); if (err) goto leave; assuan_ctx_set_system_hooks (uiserver->assuan_ctx, &_gpgme_assuan_system_hooks); err = assuan_socket_connect (uiserver->assuan_ctx, file_name ? file_name : _gpgme_get_default_uisrv_socket (), 0, ASSUAN_SOCKET_SERVER_FDPASSING); if (err) goto leave; err = _gpgme_getenv ("DISPLAY", &dft_display); if (err) goto leave; if (dft_display) { if (gpgrt_asprintf (&optstr, "OPTION display=%s", dft_display) < 0) { err = gpg_error_from_syserror (); free (dft_display); goto leave; } free (dft_display); err = assuan_transact (uiserver->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } err = _gpgme_getenv ("GPG_TTY", &env_tty); if (isatty (1) || env_tty || err) { int rc = 0; if (err) goto leave; else if (env_tty) { snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty); free (env_tty); } else rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); /* Even though isatty() returns 1, ttyname_r() may fail in many ways, e.g., when /dev/pts is not accessible under chroot. */ if (!rc) { if (gpgrt_asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (uiserver->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; err = _gpgme_getenv ("TERM", &dft_ttytype); if (err) goto leave; if (dft_ttytype) { if (gpgrt_asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype)< 0) { err = gpg_error_from_syserror (); free (dft_ttytype); goto leave; } free (dft_ttytype); err = assuan_transact (uiserver->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } } } #ifdef HAVE_W32_SYSTEM /* Under Windows we need to use AllowSetForegroundWindow. Tell uiserver to tell us when it needs it. */ if (!err) { err = assuan_transact (uiserver->assuan_ctx, "OPTION allow-pinentry-notify", NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* This is a new feature of uiserver. */ } #endif /*HAVE_W32_SYSTEM*/ leave: if (err) uiserver_release (uiserver); else *engine = uiserver; return err; } static gpgme_error_t uiserver_set_locale (void *engine, int category, const char *value) { engine_uiserver_t uiserver = engine; gpgme_error_t err; char *optstr; const char *catstr; /* FIXME: If value is NULL, we need to reset the option to default. But we can't do this. So we error out here. UISERVER needs support for this. */ if (category == LC_CTYPE) { catstr = "lc-ctype"; if (!value && uiserver->lc_ctype_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) uiserver->lc_ctype_set = 1; } #ifdef LC_MESSAGES else if (category == LC_MESSAGES) { catstr = "lc-messages"; if (!value && uiserver->lc_messages_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) uiserver->lc_messages_set = 1; } #endif /* LC_MESSAGES */ else return gpg_error (GPG_ERR_INV_VALUE); /* FIXME: Reset value to default. */ if (!value) return 0; if (gpgrt_asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0) err = gpg_error_from_syserror (); else { err = assuan_transact (uiserver->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); } return err; } static gpgme_error_t uiserver_set_protocol (void *engine, gpgme_protocol_t protocol) { engine_uiserver_t uiserver = engine; if (protocol != GPGME_PROTOCOL_OpenPGP && protocol != GPGME_PROTOCOL_CMS && protocol != GPGME_PROTOCOL_DEFAULT) return gpg_error (GPG_ERR_INV_VALUE); uiserver->protocol = protocol; return 0; } static gpgme_error_t uiserver_assuan_simple_command (engine_uiserver_t uiserver, const char *cmd, engine_status_handler_t status_fnc, void *status_fnc_value) { assuan_context_t ctx = uiserver->assuan_ctx; gpg_error_t err; char *line; size_t linelen; err = assuan_write_line (ctx, cmd); if (err) return err; do { err = assuan_read_line (ctx, &line, &linelen); if (err) return err; if (*line == '#' || !linelen) continue; if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) return 0; else if (linelen >= 4 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && line[3] == ' ') err = atoi (&line[4]); else if (linelen >= 2 && line[0] == 'S' && line[1] == ' ') { char *rest; gpgme_status_code_t r; rest = strchr (line + 2, ' '); if (!rest) rest = line + linelen; /* set to an empty string */ else *(rest++) = 0; r = _gpgme_parse_status (line + 2); if (uiserver->status.mon_cb && r != GPGME_STATUS_PROGRESS) { /* Note that we call the monitor even if we do * not know the status code (r < 0). */ err = uiserver->status.mon_cb (uiserver->status.mon_cb_value, line + 2, rest); } if (err) ; else if (r >= 0 && status_fnc) err = status_fnc (status_fnc_value, r, rest); else err = gpg_error (GPG_ERR_GENERAL); } else err = gpg_error (GPG_ERR_GENERAL); } while (!err); return err; } typedef enum { INPUT_FD, OUTPUT_FD, MESSAGE_FD } fd_type_t; #define COMMANDLINELEN 40 static gpgme_error_t uiserver_set_fd (engine_uiserver_t uiserver, fd_type_t fd_type, const char *opt) { gpg_error_t err = 0; char line[COMMANDLINELEN]; const char *which; iocb_data_t *iocb_data; int dir; switch (fd_type) { case INPUT_FD: which = "INPUT"; iocb_data = &uiserver->input_cb; break; case OUTPUT_FD: which = "OUTPUT"; iocb_data = &uiserver->output_cb; break; case MESSAGE_FD: which = "MESSAGE"; iocb_data = &uiserver->message_cb; break; default: return gpg_error (GPG_ERR_INV_VALUE); } dir = iocb_data->dir; /* We try to short-cut the communication by giving UISERVER direct access to the file descriptor, rather than using a pipe. */ iocb_data->server_fd = _gpgme_data_get_fd (iocb_data->data); if (iocb_data->server_fd < 0) { int fds[2]; if (_gpgme_io_pipe (fds, 0) < 0) return gpg_error_from_syserror (); iocb_data->fd = dir ? fds[0] : fds[1]; iocb_data->server_fd = dir ? fds[1] : fds[0]; if (_gpgme_io_set_close_notify (iocb_data->fd, close_notify_handler, uiserver)) { err = gpg_error (GPG_ERR_GENERAL); goto leave_set_fd; } } err = assuan_sendfd (uiserver->assuan_ctx, iocb_data->server_fd); if (err) goto leave_set_fd; _gpgme_io_close (iocb_data->server_fd); iocb_data->server_fd = -1; if (opt) snprintf (line, COMMANDLINELEN, "%s FD %s", which, opt); else snprintf (line, COMMANDLINELEN, "%s FD", which); err = uiserver_assuan_simple_command (uiserver, line, NULL, NULL); leave_set_fd: if (err) { _gpgme_io_close (iocb_data->fd); iocb_data->fd = -1; if (iocb_data->server_fd != -1) { _gpgme_io_close (iocb_data->server_fd); iocb_data->server_fd = -1; } } return err; } static const char * map_data_enc (gpgme_data_t d) { switch (gpgme_data_get_encoding (d)) { case GPGME_DATA_ENCODING_NONE: break; case GPGME_DATA_ENCODING_BINARY: return "--binary"; case GPGME_DATA_ENCODING_BASE64: return "--base64"; case GPGME_DATA_ENCODING_ARMOR: return "--armor"; default: break; } return NULL; } static gpgme_error_t status_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; engine_uiserver_t uiserver = (engine_uiserver_t) data->handler_value; gpgme_error_t err = 0; char *line; size_t linelen; do { err = assuan_read_line (uiserver->assuan_ctx, &line, &linelen); if (err) { /* Try our best to terminate the connection friendly. */ /* assuan_write_line (uiserver->assuan_ctx, "BYE"); */ TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: error from assuan (%d) getting status line : %s", fd, err, gpg_strerror (err)); } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (line[3] == '\0' || line[3] == ' ')) { if (line[3] == ' ') err = atoi (&line[4]); if (! err) err = gpg_error (GPG_ERR_GENERAL); TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: ERR line - mapped to: %s", fd, err ? gpg_strerror (err) : "ok"); /* Try our best to terminate the connection friendly. */ /* assuan_write_line (uiserver->assuan_ctx, "BYE"); */ } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { if (uiserver->status.fnc) { char emptystring[1] = {0}; err = uiserver->status.fnc (uiserver->status.fnc_value, GPGME_STATUS_EOF, emptystring); if (gpg_err_code (err) == GPG_ERR_FALSE) err = 0; /* Drop special error code. */ } if (!err && uiserver->colon.fnc && uiserver->colon.any) { /* We must tell a colon function about the EOF. We do this only when we have seen any data lines. Note that this inlined use of colon data lines will eventually be changed into using a regular data channel. */ uiserver->colon.any = 0; err = uiserver->colon.fnc (uiserver->colon.fnc_value, NULL); } TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: OK line - final status: %s", fd, err ? gpg_strerror (err) : "ok"); _gpgme_io_close (uiserver->status_cb.fd); return err; } else if (linelen > 2 && line[0] == 'D' && line[1] == ' ' && uiserver->colon.fnc) { /* We are using the colon handler even for plain inline data - strange name for that function but for historic reasons we keep it. */ /* FIXME We can't use this for binary data because we assume this is a string. For the current usage of colon output it is correct. */ char *src = line + 2; char *end = line + linelen; char *dst; char **aline = &uiserver->colon.attic.line; int *alinelen = &uiserver->colon.attic.linelen; if (uiserver->colon.attic.linesize < *alinelen + linelen + 1) { char *newline = realloc (*aline, *alinelen + linelen + 1); if (!newline) err = gpg_error_from_syserror (); else { *aline = newline; uiserver->colon.attic.linesize = *alinelen + linelen + 1; } } if (!err) { dst = *aline + *alinelen; while (!err && src < end) { if (*src == '%' && src + 2 < end) { /* Handle escaped characters. */ ++src; *dst = _gpgme_hextobyte (src); (*alinelen)++; src += 2; } else { *dst = *src++; (*alinelen)++; } if (*dst == '\n') { /* Terminate the pending line, pass it to the colon handler and reset it. */ uiserver->colon.any = 1; if (*alinelen > 1 && *(dst - 1) == '\r') dst--; *dst = '\0'; /* FIXME How should we handle the return code? */ err = uiserver->colon.fnc (uiserver->colon.fnc_value, *aline); if (!err) { dst = *aline; *alinelen = 0; } } else dst++; } } TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: D line; final status: %s", fd, err? gpg_strerror (err):"ok"); } else if (linelen > 2 && line[0] == 'D' && line[1] == ' ' && uiserver->inline_data) { char *src = line + 2; char *end = line + linelen; char *dst = src; gpgme_ssize_t nwritten; linelen = 0; while (src < end) { if (*src == '%' && src + 2 < end) { /* Handle escaped characters. */ ++src; *dst++ = _gpgme_hextobyte (src); src += 2; } else *dst++ = *src++; linelen++; } src = line + 2; while (linelen > 0) { nwritten = gpgme_data_write (uiserver->inline_data, src, linelen); if (!nwritten || (nwritten < 0 && errno != EINTR) || nwritten > linelen) { err = gpg_error_from_syserror (); break; } src += nwritten; linelen -= nwritten; } TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: D inlinedata; final status: %s", fd, err? gpg_strerror (err):"ok"); } else if (linelen > 2 && line[0] == 'S' && line[1] == ' ') { char *rest; gpgme_status_code_t r; rest = strchr (line + 2, ' '); if (!rest) rest = line + linelen; /* set to an empty string */ else *(rest++) = 0; r = _gpgme_parse_status (line + 2); if (r >= 0) { if (uiserver->status.fnc) { err = uiserver->status.fnc (uiserver->status.fnc_value, r, rest); if (gpg_err_code (err) == GPG_ERR_FALSE) err = 0; /* Drop special error code. */ } } else fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest); TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: S line (%s) - final status: %s", fd, line+2, err? gpg_strerror (err):"ok"); } else if (linelen >= 7 && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' && line[6] == 'E' && (line[7] == '\0' || line[7] == ' ')) { char *keyword = line+7; while (*keyword == ' ') keyword++;; default_inq_cb (uiserver, keyword); assuan_write_line (uiserver->assuan_ctx, "END"); } } while (!err && assuan_pending_line (uiserver->assuan_ctx)); return err; } static gpgme_error_t add_io_cb (engine_uiserver_t uiserver, iocb_data_t *iocbd, gpgme_io_cb_t handler) { gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-uiserver:add_io_cb", uiserver, - "fd %d, dir %d", iocbd->fd, iocbd->dir); + "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*uiserver->io_cbs.add) (uiserver->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); if (err) return TRACE_ERR (err); if (!iocbd->dir) /* FIXME Kludge around poll() problem. */ err = _gpgme_io_set_nonblocking (iocbd->fd); return TRACE_ERR (err); } static gpgme_error_t start (engine_uiserver_t uiserver, const char *command) { gpgme_error_t err; int fdlist[5]; int nfds; /* We need to know the fd used by assuan for reads. We do this by using the assumption that the first returned fd from assuan_get_active_fds() is always this one. */ nfds = assuan_get_active_fds (uiserver->assuan_ctx, 0 /* read fds */, fdlist, DIM (fdlist)); if (nfds < 1) return gpg_error (GPG_ERR_GENERAL); /* FIXME */ /* We "duplicate" the file descriptor, so we can close it here (we can't close fdlist[0], as that is closed by libassuan, and closing it here might cause libassuan to close some unrelated FD later). Alternatively, we could special case status_fd and register/unregister it manually as needed, but this increases code duplication and is more complicated as we can not use the close notifications etc. A third alternative would be to let Assuan know that we closed the FD, but that complicates the Assuan interface. */ uiserver->status_cb.fd = _gpgme_io_dup (fdlist[0]); if (uiserver->status_cb.fd < 0) return gpg_error_from_syserror (); if (_gpgme_io_set_close_notify (uiserver->status_cb.fd, close_notify_handler, uiserver)) { _gpgme_io_close (uiserver->status_cb.fd); uiserver->status_cb.fd = -1; return gpg_error (GPG_ERR_GENERAL); } err = add_io_cb (uiserver, &uiserver->status_cb, status_handler); if (!err && uiserver->input_cb.fd != -1) err = add_io_cb (uiserver, &uiserver->input_cb, _gpgme_data_outbound_handler); if (!err && uiserver->output_cb.fd != -1) err = add_io_cb (uiserver, &uiserver->output_cb, _gpgme_data_inbound_handler); if (!err && uiserver->message_cb.fd != -1) err = add_io_cb (uiserver, &uiserver->message_cb, _gpgme_data_outbound_handler); if (!err) err = assuan_write_line (uiserver->assuan_ctx, command); if (!err) uiserver_io_event (uiserver, GPGME_EVENT_START, NULL); return err; } static gpgme_error_t uiserver_reset (void *engine) { engine_uiserver_t uiserver = engine; /* We must send a reset because we need to reset the list of signers. Note that RESET does not reset OPTION commands. */ return uiserver_assuan_simple_command (uiserver, "RESET", NULL, NULL); } static gpgme_error_t uiserver_decrypt (void *engine, gpgme_decrypt_flags_t flags, gpgme_data_t ciph, gpgme_data_t plain, int export_session_key, const char *override_session_key, int auto_key_retrieve) { engine_uiserver_t uiserver = engine; gpgme_error_t err; const char *protocol; char *cmd; int verify = !!(flags & GPGME_DECRYPT_VERIFY); (void)override_session_key; /* Fixme: We need to see now to add this * to the UI server protocol */ (void)auto_key_retrieve; /* Not yet supported. */ if (!uiserver) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT) protocol = ""; else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP) protocol = " --protocol=OpenPGP"; else if (uiserver->protocol == GPGME_PROTOCOL_CMS) protocol = " --protocol=CMS"; else return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); if (gpgrt_asprintf (&cmd, "DECRYPT%s%s%s", protocol, verify ? "" : " --no-verify", export_session_key ? " --export-session-key" : "") < 0) return gpg_error_from_syserror (); uiserver->input_cb.data = ciph; err = uiserver_set_fd (uiserver, INPUT_FD, map_data_enc (uiserver->input_cb.data)); if (err) { gpgrt_free (cmd); return gpg_error (GPG_ERR_GENERAL); /* FIXME */ } uiserver->output_cb.data = plain; err = uiserver_set_fd (uiserver, OUTPUT_FD, 0); if (err) { gpgrt_free (cmd); return gpg_error (GPG_ERR_GENERAL); /* FIXME */ } uiserver->inline_data = NULL; err = start (engine, cmd); gpgrt_free (cmd); return err; } static gpgme_error_t set_recipients (engine_uiserver_t uiserver, gpgme_key_t recp[]) { gpgme_error_t err = 0; char *line; int linelen; int invalid_recipients = 0; int i; linelen = 10 + 40 + 1; /* "RECIPIENT " + guess + '\0'. */ line = malloc (10 + 40 + 1); if (!line) return gpg_error_from_syserror (); strcpy (line, "RECIPIENT "); for (i=0; !err && recp[i]; i++) { char *uid; int newlen; /* We use only the first user ID of the key. */ if (!recp[i]->uids || !(uid=recp[i]->uids->uid) || !*uid) { invalid_recipients++; continue; } newlen = 11 + strlen (uid); if (linelen < newlen) { char *newline = realloc (line, newlen); if (! newline) { int saved_err = gpg_error_from_syserror (); free (line); return saved_err; } line = newline; linelen = newlen; } /* FIXME: need to do proper escaping */ strcpy (&line[10], uid); err = uiserver_assuan_simple_command (uiserver, line, uiserver->status.fnc, uiserver->status.fnc_value); /* FIXME: This might requires more work. */ if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) invalid_recipients++; else if (err) { free (line); return err; } } free (line); return gpg_error (invalid_recipients ? GPG_ERR_UNUSABLE_PUBKEY : GPG_ERR_NO_ERROR); } /* Take recipients from the LF delimited STRING and send RECIPIENT * commands to gpgsm. */ static gpgme_error_t set_recipients_from_string (engine_uiserver_t uiserver, const char *string) { gpg_error_t err = 0; char *line = NULL; int no_pubkey = 0; const char *s; int n; for (;;) { while (*string == ' ' || *string == '\t') string++; if (!*string) break; s = strchr (string, '\n'); if (s) n = s - string; else n = strlen (string); while (n && (string[n-1] == ' ' || string[n-1] == '\t')) n--; gpgrt_free (line); if (gpgrt_asprintf (&line, "RECIPIENT %.*s", n, string) < 0) { err = gpg_error_from_syserror (); break; } string += n + !!s; err = uiserver_assuan_simple_command (uiserver, line, uiserver->status.fnc, uiserver->status.fnc_value); /* Fixme: Improve error reporting. */ if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) no_pubkey++; else if (err) break; } gpgrt_free (line); return err? err : no_pubkey? gpg_error (GPG_ERR_NO_PUBKEY) : 0; } static gpgme_error_t uiserver_encrypt (void *engine, gpgme_key_t recp[], const char *recpstring, gpgme_encrypt_flags_t flags, gpgme_data_t plain, gpgme_data_t ciph, int use_armor) { engine_uiserver_t uiserver = engine; gpgme_error_t err; const char *protocol; char *cmd; if (!uiserver) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT) protocol = ""; else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP) protocol = " --protocol=OpenPGP"; else if (uiserver->protocol == GPGME_PROTOCOL_CMS) protocol = " --protocol=CMS"; else return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); if (flags & GPGME_ENCRYPT_PREPARE) { if (!recp || plain || ciph) return gpg_error (GPG_ERR_INV_VALUE); if (gpgrt_asprintf (&cmd, "PREP_ENCRYPT%s%s", protocol, (flags & GPGME_ENCRYPT_EXPECT_SIGN) ? " --expect-sign" : "") < 0) return gpg_error_from_syserror (); } else { if (!plain || !ciph) return gpg_error (GPG_ERR_INV_VALUE); if (gpgrt_asprintf (&cmd, "ENCRYPT%s", protocol) < 0) return gpg_error_from_syserror (); } if (plain) { uiserver->input_cb.data = plain; err = uiserver_set_fd (uiserver, INPUT_FD, map_data_enc (uiserver->input_cb.data)); if (err) { gpgrt_free (cmd); return err; } } if (ciph) { uiserver->output_cb.data = ciph; err = uiserver_set_fd (uiserver, OUTPUT_FD, use_armor ? "--armor" : map_data_enc (uiserver->output_cb.data)); if (err) { gpgrt_free (cmd); return err; } } uiserver->inline_data = NULL; if (recp || recpstring) { if (recp) err = set_recipients (uiserver, recp); else err = set_recipients_from_string (uiserver, recpstring); if (err) { gpgrt_free (cmd); return err; } } err = start (uiserver, cmd); gpgrt_free (cmd); return err; } static gpgme_error_t uiserver_sign (void *engine, gpgme_data_t in, gpgme_data_t out, gpgme_sig_mode_t mode, int use_armor, int use_textmode, int include_certs, gpgme_ctx_t ctx /* FIXME */) { engine_uiserver_t uiserver = engine; gpgme_error_t err = 0; const char *protocol; char *cmd; gpgme_key_t key; (void)use_textmode; (void)include_certs; if (!uiserver || !in || !out) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT) protocol = ""; else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP) protocol = " --protocol=OpenPGP"; else if (uiserver->protocol == GPGME_PROTOCOL_CMS) protocol = " --protocol=CMS"; else return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); if (gpgrt_asprintf (&cmd, "SIGN%s%s", protocol, (mode == GPGME_SIG_MODE_DETACH) ? " --detached" : "") < 0) return gpg_error_from_syserror (); key = gpgme_signers_enum (ctx, 0); if (key) { const char *s = NULL; if (key && key->uids) s = key->uids->email; if (s && strlen (s) < 80) { char buf[100]; strcpy (stpcpy (buf, "SENDER --info "), s); err = uiserver_assuan_simple_command (uiserver, buf, uiserver->status.fnc, uiserver->status.fnc_value); } else err = gpg_error (GPG_ERR_INV_VALUE); gpgme_key_unref (key); if (err) { gpgrt_free (cmd); return err; } } uiserver->input_cb.data = in; err = uiserver_set_fd (uiserver, INPUT_FD, map_data_enc (uiserver->input_cb.data)); if (err) { gpgrt_free (cmd); return err; } uiserver->output_cb.data = out; err = uiserver_set_fd (uiserver, OUTPUT_FD, use_armor ? "--armor" : map_data_enc (uiserver->output_cb.data)); if (err) { gpgrt_free (cmd); return err; } uiserver->inline_data = NULL; err = start (uiserver, cmd); gpgrt_free (cmd); return err; } /* FIXME: Missing a way to specify --silent. */ static gpgme_error_t uiserver_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext, gpgme_ctx_t ctx) { engine_uiserver_t uiserver = engine; gpgme_error_t err; const char *protocol; char *cmd; (void)ctx; /* FIXME: We should to add a --sender option to the * UISever protocol. */ if (!uiserver) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT) protocol = ""; else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP) protocol = " --protocol=OpenPGP"; else if (uiserver->protocol == GPGME_PROTOCOL_CMS) protocol = " --protocol=CMS"; else return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); if (gpgrt_asprintf (&cmd, "VERIFY%s", protocol) < 0) return gpg_error_from_syserror (); uiserver->input_cb.data = sig; err = uiserver_set_fd (uiserver, INPUT_FD, map_data_enc (uiserver->input_cb.data)); if (err) { gpgrt_free (cmd); return err; } if (plaintext) { /* Normal or cleartext signature. */ uiserver->output_cb.data = plaintext; err = uiserver_set_fd (uiserver, OUTPUT_FD, 0); } else { /* Detached signature. */ uiserver->message_cb.data = signed_text; err = uiserver_set_fd (uiserver, MESSAGE_FD, 0); } uiserver->inline_data = NULL; if (!err) err = start (uiserver, cmd); gpgrt_free (cmd); return err; } /* This sets a status callback for monitoring status lines before they * are passed to a caller set handler. */ static void uiserver_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value) { engine_uiserver_t uiserver = engine; uiserver->status.mon_cb = cb; uiserver->status.mon_cb_value = cb_value; } static void uiserver_set_status_handler (void *engine, engine_status_handler_t fnc, void *fnc_value) { engine_uiserver_t uiserver = engine; uiserver->status.fnc = fnc; uiserver->status.fnc_value = fnc_value; } static gpgme_error_t uiserver_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc, void *fnc_value) { engine_uiserver_t uiserver = engine; uiserver->colon.fnc = fnc; uiserver->colon.fnc_value = fnc_value; uiserver->colon.any = 0; return 0; } static void uiserver_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_uiserver_t uiserver = engine; uiserver->io_cbs = *io_cbs; } static void uiserver_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_uiserver_t uiserver = engine; TRACE (DEBUG_ENGINE, "gpgme:uiserver_io_event", uiserver, "event %p, type %d, type_data %p", uiserver->io_cbs.event, type, type_data); if (uiserver->io_cbs.event) (*uiserver->io_cbs.event) (uiserver->io_cbs.event_priv, type, type_data); } struct engine_ops _gpgme_engine_ops_uiserver = { /* Static functions. */ _gpgme_get_default_uisrv_socket, NULL, uiserver_get_version, uiserver_get_req_version, uiserver_new, /* Member functions. */ uiserver_release, uiserver_reset, uiserver_set_status_cb, uiserver_set_status_handler, NULL, /* set_command_handler */ uiserver_set_colon_line_handler, uiserver_set_locale, uiserver_set_protocol, NULL, /* set_engine_flags */ uiserver_decrypt, NULL, /* delete */ NULL, /* edit */ uiserver_encrypt, NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ NULL, /* keylist_data */ NULL, /* keysign */ NULL, /* tofu_policy */ uiserver_sign, NULL, /* trustlist */ uiserver_verify, NULL, /* getauditlog */ NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ NULL, /* conf_dir */ NULL, /* query_swdb */ uiserver_set_io_cbs, uiserver_io_event, uiserver_cancel, NULL, /* cancel_op */ NULL, /* passwd */ NULL, /* set_pinentry_mode */ NULL /* opspawn */ }; diff --git a/src/posix-io.c b/src/posix-io.c index be084312..aee7a4d9 100644 --- a/src/posix-io.c +++ b/src/posix-io.c @@ -1,877 +1,877 @@ /* posix-io.c - Posix I/O functions * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2004, 2005, 2007, 2010 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_STDINT_H # include #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #include #ifdef HAVE_SYS_UIO_H # include #endif #include #include #ifdef USE_LINUX_GETDENTS # include # include # include #endif /*USE_LINUX_GETDENTS*/ #include "util.h" #include "priv-io.h" #include "sema.h" #include "ath.h" #include "debug.h" void _gpgme_io_subsystem_init (void) { struct sigaction act; sigaction (SIGPIPE, NULL, &act); if (act.sa_handler == SIG_DFL) { act.sa_handler = SIG_IGN; sigemptyset (&act.sa_mask); act.sa_flags = 0; sigaction (SIGPIPE, &act, NULL); } } /* Write the printable version of FD to the buffer BUF of length BUFLEN. The printable version is the representation on the command line that the child process expects. */ int _gpgme_io_fd2str (char *buf, int buflen, int fd) { return snprintf (buf, buflen, "%d", fd); } /* The table to hold notification handlers. We use a linear search and extend the table as needed. */ struct notify_table_item_s { int fd; /* -1 indicates an unused entry. */ _gpgme_close_notify_handler_t handler; void *value; }; typedef struct notify_table_item_s *notify_table_item_t; static notify_table_item_t notify_table; static size_t notify_table_size; DEFINE_STATIC_LOCK (notify_table_lock); int _gpgme_io_read (int fd, void *buffer, size_t count) { int nread; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_read", fd, - "buffer=%p, count=%zu", buffer, count); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_read", NULL, + "fd=%d buffer=%p count=%zu", fd, buffer, count); do { nread = _gpgme_ath_read (fd, buffer, count); } while (nread == -1 && errno == EINTR); TRACE_LOGBUFX (buffer, nread); return TRACE_SYSRES (nread); } int _gpgme_io_write (int fd, const void *buffer, size_t count) { int nwritten; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_write", fd, - "buffer=%p, count=%zu", buffer, count); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_write", NULL, + "fd=%d buffer=%p count=%zu", fd, buffer, count); TRACE_LOGBUFX (buffer, count); do { nwritten = _gpgme_ath_write (fd, buffer, count); } while (nwritten == -1 && errno == EINTR); return TRACE_SYSRES (nwritten); } int _gpgme_io_pipe (int filedes[2], int inherit_idx) { int saved_errno; int err; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_pipe", filedes, + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_pipe", NULL, "inherit_idx=%i (GPGME uses it for %s)", inherit_idx, inherit_idx ? "reading" : "writing"); err = pipe (filedes); if (err < 0) return TRACE_SYSRES (err); /* FIXME: Should get the old flags first. */ err = fcntl (filedes[1 - inherit_idx], F_SETFD, FD_CLOEXEC); saved_errno = errno; if (err < 0) { close (filedes[0]); close (filedes[1]); } errno = saved_errno; if (err) return TRACE_SYSRES (err); - TRACE_SUC ("read=0x%x, write=0x%x", filedes[0], filedes[1]); + TRACE_SUC ("read fd=%d write fd=%d", filedes[0], filedes[1]); return 0; } int _gpgme_io_close (int fd) { int res; _gpgme_close_notify_handler_t handler = NULL; void *handler_value; int idx; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", fd, ""); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", NULL, "fd=%d", fd); if (fd == -1) { errno = EINVAL; return TRACE_SYSRES (-1); } /* First call the notify handler. */ LOCK (notify_table_lock); for (idx=0; idx < notify_table_size; idx++) { if (notify_table[idx].fd == fd) { handler = notify_table[idx].handler; handler_value = notify_table[idx].value; notify_table[idx].handler = NULL; notify_table[idx].value = NULL; notify_table[idx].fd = -1; /* Mark slot as free. */ break; } } UNLOCK (notify_table_lock); if (handler) { TRACE_LOG ("invoking close handler %p/%p", handler, handler_value); handler (fd, handler_value); } /* Then do the close. */ res = close (fd); return TRACE_SYSRES (res); } int _gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler, void *value) { int res = 0; int idx; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_close_notify", fd, - "close_handler=%p/%p", handler, value); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_close_notify", NULL, + "fd=%d close_handler=%p/%p", fd, handler, value); assert (fd != -1); LOCK (notify_table_lock); for (idx=0; idx < notify_table_size; idx++) if (notify_table[idx].fd == -1) break; if (idx == notify_table_size) { /* We need to increase the size of the table. The approach we take is straightforward to minimize the risk of bugs. */ notify_table_item_t newtbl; size_t newsize = notify_table_size + 64; newtbl = calloc (newsize, sizeof *newtbl); if (!newtbl) { res = -1; goto leave; } for (idx=0; idx < notify_table_size; idx++) newtbl[idx] = notify_table[idx]; for (; idx < newsize; idx++) { newtbl[idx].fd = -1; newtbl[idx].handler = NULL; newtbl[idx].value = NULL; } free (notify_table); notify_table = newtbl; idx = notify_table_size; notify_table_size = newsize; } notify_table[idx].fd = fd; notify_table[idx].handler = handler; notify_table[idx].value = value; leave: UNLOCK (notify_table_lock); return TRACE_SYSRES (res); } int _gpgme_io_set_nonblocking (int fd) { int flags; int res; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_nonblocking", fd, ""); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_nonblocking", NULL, "fd=%d", fd); flags = fcntl (fd, F_GETFL, 0); if (flags == -1) return TRACE_SYSRES (-1); flags |= O_NONBLOCK; res = fcntl (fd, F_SETFL, flags); return TRACE_SYSRES (res); } #ifdef USE_LINUX_GETDENTS /* This is not declared in public headers; getdents64(2) says that we must * define it ourselves. */ struct linux_dirent64 { ino64_t d_ino; off64_t d_off; unsigned short d_reclen; unsigned char d_type; char d_name[]; }; # define DIR_BUF_SIZE 1024 #endif /*USE_LINUX_GETDENTS*/ static long int get_max_fds (void) { const char *source = NULL; long int fds = -1; int rc; /* Under Linux we can figure out the highest used file descriptor by * reading /proc/self/fd. This is in the common cases much faster * than for example doing 4096 close calls where almost all of them * will fail. * * We can't use the normal opendir/readdir/closedir interface between * fork and exec in a multi-threaded process because opendir uses * malloc and thus a mutex which may deadlock with a malloc in another * thread. However, the underlying getdents system call is safe. */ #ifdef USE_LINUX_GETDENTS { int dir_fd; char dir_buf[DIR_BUF_SIZE]; struct linux_dirent64 *dir_entry; int r, pos; const char *s; int x; dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY); if (dir_fd != -1) { for (;;) { r = syscall(SYS_getdents64, dir_fd, dir_buf, DIR_BUF_SIZE); if (r == -1) { /* Fall back to other methods. */ fds = -1; break; } if (r == 0) break; for (pos = 0; pos < r; pos += dir_entry->d_reclen) { dir_entry = (struct linux_dirent64 *) (dir_buf + pos); s = dir_entry->d_name; if (*s < '0' || *s > '9') continue; /* atoi is not guaranteed to be async-signal-safe. */ for (x = 0; *s >= '0' && *s <= '9'; s++) x = x * 10 + (*s - '0'); if (!*s && x > fds && x != dir_fd) fds = x; } } close (dir_fd); } if (fds != -1) { fds++; source = "/proc"; } } #endif /*USE_LINUX_GETDENTS*/ #ifdef RLIMIT_NOFILE if (fds == -1) { struct rlimit rl; rc = getrlimit (RLIMIT_NOFILE, &rl); if (rc == 0) { source = "RLIMIT_NOFILE"; fds = rl.rlim_max; } } #endif #ifdef RLIMIT_OFILE if (fds == -1) { struct rlimit rl; rc = getrlimit (RLIMIT_OFILE, &rl); if (rc == 0) { source = "RLIMIT_OFILE"; fds = rl.rlim_max; } } #endif #ifdef _SC_OPEN_MAX if (fds == -1) { long int scres; scres = sysconf (_SC_OPEN_MAX); if (scres >= 0) { source = "_SC_OPEN_MAX"; return scres; } } #endif #ifdef OPEN_MAX if (fds == -1) { source = "OPEN_MAX"; fds = OPEN_MAX; } #endif #if !defined(RLIMIT_NOFILE) && !defined(RLIMIT_OFILE) \ && !defined(_SC_OPEN_MAX) && !defined(OPEN_MAX) #warning "No known way to get the maximum number of file descriptors." #endif if (fds == -1) { source = "arbitrary"; /* Arbitrary limit. */ fds = 1024; } /* AIX returns INT32_MAX instead of a proper value. We assume that * this is always an error and use a more reasonable limit. */ #ifdef INT32_MAX if (fds == INT32_MAX) { source = "aix-fix"; fds = 1024; } #endif - TRACE (DEBUG_SYSIO, "gpgme:max_fds", 0, "max fds=%ld (%s)", fds, source); + TRACE (DEBUG_SYSIO, "gpgme:max_fds", NULL, "max fds=%ld (%s)", fds, source); return fds; } int _gpgme_io_waitpid (int pid, int hang, int *r_status, int *r_signal) { int status; pid_t ret; *r_status = 0; *r_signal = 0; do ret = _gpgme_ath_waitpid (pid, &status, hang? 0 : WNOHANG); while (ret == (pid_t)(-1) && errno == EINTR); if (ret == pid) { if (WIFSIGNALED (status)) { *r_status = 4; /* Need some value here. */ *r_signal = WTERMSIG (status); } else if (WIFEXITED (status)) *r_status = WEXITSTATUS (status); else *r_status = 4; /* Oops. */ return 1; } return 0; } /* Returns 0 on success, -1 on error. */ int _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags, struct spawn_fd_item_s *fd_list, void (*atfork) (void *opaque, int reserved), void *atforkvalue, pid_t *r_pid) { pid_t pid; int i; int status; int signo; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_spawn", path, + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_spawn", NULL, "path=%s", path); i = 0; while (argv[i]) { TRACE_LOG ("argv[%2i] = %s", i, argv[i]); i++; } for (i = 0; fd_list[i].fd != -1; i++) if (fd_list[i].dup_to == -1) TRACE_LOG ("fd[%i] = 0x%x", i, fd_list[i].fd); else TRACE_LOG ("fd[%i] = 0x%x -> 0x%x", i, fd_list[i].fd, fd_list[i].dup_to); pid = fork (); if (pid == -1) return TRACE_SYSRES (-1); if (!pid) { /* Intermediate child to prevent zombie processes. */ if ((pid = fork ()) == 0) { /* Child. */ int max_fds = -1; int fd; int seen_stdin = 0; int seen_stdout = 0; int seen_stderr = 0; if (atfork) atfork (atforkvalue, 0); /* First close all fds which will not be inherited. If we * have closefrom(2) we first figure out the highest fd we * do not want to close, then call closefrom, and on success * use the regular code to close all fds up to the start * point of closefrom. Note that Solaris' and FreeBSD's closefrom do * not return errors. */ #ifdef HAVE_CLOSEFROM { fd = -1; for (i = 0; fd_list[i].fd != -1; i++) if (fd_list[i].fd > fd) fd = fd_list[i].fd; fd++; #if defined(__sun) || defined(__FreeBSD__) closefrom (fd); max_fds = fd; #else /*!__sun */ while ((i = closefrom (fd)) && errno == EINTR) ; if (!i || errno == EBADF) max_fds = fd; #endif /*!__sun*/ } #endif /*HAVE_CLOSEFROM*/ if (max_fds == -1) max_fds = get_max_fds (); for (fd = 0; fd < max_fds; fd++) { for (i = 0; fd_list[i].fd != -1; i++) if (fd_list[i].fd == fd) break; if (fd_list[i].fd == -1) close (fd); } /* And now dup and close those to be duplicated. */ for (i = 0; fd_list[i].fd != -1; i++) { int child_fd; int res; if (fd_list[i].dup_to != -1) child_fd = fd_list[i].dup_to; else child_fd = fd_list[i].fd; if (child_fd == 0) seen_stdin = 1; else if (child_fd == 1) seen_stdout = 1; else if (child_fd == 2) seen_stderr = 1; if (fd_list[i].dup_to == -1) continue; res = dup2 (fd_list[i].fd, fd_list[i].dup_to); if (res < 0) { #if 0 /* FIXME: The debug file descriptor is not dup'ed anyway, so we can't see this. */ TRACE_LOG ("dup2 failed in child: %s\n", strerror (errno)); #endif _exit (8); } close (fd_list[i].fd); } if (! seen_stdin || ! seen_stdout || !seen_stderr) { fd = open ("/dev/null", O_RDWR); if (fd == -1) { /* The debug file descriptor is not dup'ed, so we can't do a trace output. */ _exit (8); } /* Make sure that the process has connected stdin. */ if (! seen_stdin && fd != 0) { if (dup2 (fd, 0) == -1) _exit (8); } if (! seen_stdout && fd != 1) { if (dup2 (fd, 1) == -1) _exit (8); } if (! seen_stderr && fd != 2) { if (dup2 (fd, 2) == -1) _exit (8); } if (fd != 0 && fd != 1 && fd != 2) close (fd); } execv (path, (char *const *) argv); /* Hmm: in that case we could write a special status code to the status-pipe. */ _exit (8); /* End child. */ } if (pid == -1) _exit (1); else _exit (0); } TRACE_LOG ("waiting for child process pid=%i", pid); _gpgme_io_waitpid (pid, 1, &status, &signo); if (status) return TRACE_SYSRES (-1); for (i = 0; fd_list[i].fd != -1; i++) { if (! (flags & IOSPAWN_FLAG_NOCLOSE)) _gpgme_io_close (fd_list[i].fd); /* No handle translation. */ fd_list[i].peer_name = fd_list[i].fd; } if (r_pid) *r_pid = pid; return TRACE_SYSRES (0); } /* Select on the list of fds. Returns: -1 = error, 0 = timeout or nothing to select, > 0 = number of signaled fds. */ int _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock) { fd_set readfds; fd_set writefds; unsigned int i; int any; int max_fd; int n; int count; /* Use a 1s timeout. */ struct timeval timeout = { 1, 0 }; void *dbg_help = NULL; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_select", fds, + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_select", NULL, "nfds=%zu, nonblock=%u", nfds, nonblock); FD_ZERO (&readfds); FD_ZERO (&writefds); max_fd = 0; if (nonblock) timeout.tv_sec = 0; TRACE_SEQ (dbg_help, "select on [ "); any = 0; for (i = 0; i < nfds; i++) { if (fds[i].fd == -1) continue; if (fds[i].for_read) { if (fds[i].fd >= FD_SETSIZE) { TRACE_END (dbg_help, " -BAD- ]"); gpg_err_set_errno (EMFILE); return TRACE_SYSRES (-1); } assert (!FD_ISSET (fds[i].fd, &readfds)); FD_SET (fds[i].fd, &readfds); if (fds[i].fd > max_fd) max_fd = fds[i].fd; - TRACE_ADD1 (dbg_help, "r0x%x ", fds[i].fd); + TRACE_ADD1 (dbg_help, "r=%d ", fds[i].fd); any = 1; } else if (fds[i].for_write) { if (fds[i].fd >= FD_SETSIZE) { TRACE_END (dbg_help, " -BAD- ]"); gpg_err_set_errno (EMFILE); return TRACE_SYSRES (-1); } assert (!FD_ISSET (fds[i].fd, &writefds)); FD_SET (fds[i].fd, &writefds); if (fds[i].fd > max_fd) max_fd = fds[i].fd; - TRACE_ADD1 (dbg_help, "w0x%x ", fds[i].fd); + TRACE_ADD1 (dbg_help, "w=%d ", fds[i].fd); any = 1; } fds[i].signaled = 0; } TRACE_END (dbg_help, "]"); if (!any) return TRACE_SYSRES (0); do { count = _gpgme_ath_select (max_fd + 1, &readfds, &writefds, NULL, &timeout); } while (count < 0 && errno == EINTR); if (count < 0) return TRACE_SYSRES (-1); TRACE_SEQ (dbg_help, "select OK [ "); if (TRACE_ENABLED (dbg_help)) { for (i = 0; i <= max_fd; i++) { if (FD_ISSET (i, &readfds)) - TRACE_ADD1 (dbg_help, "r0x%x ", i); + TRACE_ADD1 (dbg_help, "r=%d ", i); if (FD_ISSET (i, &writefds)) - TRACE_ADD1 (dbg_help, "w0x%x ", i); + TRACE_ADD1 (dbg_help, "w=%d ", i); } TRACE_END (dbg_help, "]"); } /* The variable N is used to optimize it a little bit. */ for (n = count, i = 0; i < nfds && n; i++) { if (fds[i].fd == -1) ; else if (fds[i].for_read) { if (FD_ISSET (fds[i].fd, &readfds)) { fds[i].signaled = 1; n--; } } else if (fds[i].for_write) { if (FD_ISSET (fds[i].fd, &writefds)) { fds[i].signaled = 1; n--; } } } return TRACE_SYSRES (count); } int _gpgme_io_recvmsg (int fd, struct msghdr *msg, int flags) { int nread; int saved_errno; struct iovec *iov; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_recvmsg", fd, - "msg=%p, flags=%i", msg, flags); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_recvmsg", NULL, + "fd=%d msg=%p flags=%i", fd, msg, flags); nread = 0; iov = msg->msg_iov; while (iov < msg->msg_iov + msg->msg_iovlen) { nread += iov->iov_len; iov++; } TRACE_LOG ("about to receive %d bytes", nread); do { nread = _gpgme_ath_recvmsg (fd, msg, flags); } while (nread == -1 && errno == EINTR); saved_errno = errno; if (nread > 0) { int nr = nread; iov = msg->msg_iov; while (nr > 0) { int len = nr > iov->iov_len ? iov->iov_len : nr; TRACE_LOGBUFX (msg->msg_iov->iov_base, len); iov++; nr -= len; } } errno = saved_errno; return TRACE_SYSRES (nread); } int _gpgme_io_sendmsg (int fd, const struct msghdr *msg, int flags) { int nwritten; struct iovec *iov; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_sendmsg", fd, - "msg=%p, flags=%i", msg, flags); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_sendmsg", NULL, + "fd=%d msg=%p flags=%i", fd, msg, flags); nwritten = 0; iov = msg->msg_iov; while (iov < msg->msg_iov + msg->msg_iovlen) { nwritten += iov->iov_len; iov++; } TRACE_LOG ("about to receive %d bytes", nwritten); iov = msg->msg_iov; while (nwritten > 0) { int len = nwritten > iov->iov_len ? iov->iov_len : nwritten; TRACE_LOGBUFX (msg->msg_iov->iov_base, len); iov++; nwritten -= len; } do { nwritten = _gpgme_ath_sendmsg (fd, msg, flags); } while (nwritten == -1 && errno == EINTR); return TRACE_SYSRES (nwritten); } int _gpgme_io_dup (int fd) { int new_fd; do new_fd = dup (fd); while (new_fd == -1 && errno == EINTR); - TRACE (DEBUG_SYSIO, "_gpgme_io_dup", fd, "new fd==%i", new_fd); + TRACE (DEBUG_SYSIO, "_gpgme_io_dup", NULL, "fd=%d -> fd=%d", fd, new_fd); return new_fd; } int _gpgme_io_socket (int domain, int type, int proto) { int res; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_socket", domain, - "type=%i, proto=%i", type, proto); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_socket", NULL, + "domain=%d type=%i proto=%i", domain, type, proto); res = socket (domain, type, proto); return TRACE_SYSRES (res); } int _gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen) { int res; - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_connect", fd, - "addr=%p, addrlen=%i", addr, addrlen); + TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_connect", NULL, + "fd=%d addr=%p addrlen=%i", fd, addr, addrlen); do res = ath_connect (fd, addr, addrlen); while (res == -1 && errno == EINTR); return TRACE_SYSRES (res); } diff --git a/src/posix-util.c b/src/posix-util.c index cddb31f4..5c4f3390 100644 --- a/src/posix-util.c +++ b/src/posix-util.c @@ -1,166 +1,166 @@ /* posix-util.c - Utility functions for Posix * Copyright (C) 2001 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2004 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "util.h" #include "sys-util.h" #include "debug.h" /* These variables store the malloced name of alternative default binaries. The are set only once by gpgme_set_global_flag. */ static char *default_gpg_name; static char *default_gpgconf_name; /* Set the default name for the gpg binary. This function may only be called by gpgme_set_global_flag. Returns 0 on success. Leading directories are removed from NAME. */ int _gpgme_set_default_gpg_name (const char *name) { const char *s; s = strrchr (name, '/'); if (s) name = s + 1; if (!default_gpg_name) default_gpg_name = strdup (name); return !default_gpg_name; } /* Set the default name for the gpgconf binary. This function may only be called by gpgme_set_global_flag. Returns 0 on success. Leading directories are removed from NAME. */ int _gpgme_set_default_gpgconf_name (const char *name) { const char *s; s = strrchr (name, '/'); if (s) name = s + 1; if (!default_gpgconf_name) default_gpgconf_name = strdup (name); return !default_gpgconf_name; } /* Dummy function - see w32-util.c for the actual code. */ int _gpgme_set_override_inst_dir (const char *dir) { (void)dir; return 0; } /* Find an executable program PGM along the envvar PATH. */ static char * walk_path (const char *pgm) { const char *orig_path, *path, *s; char *fname, *p; #ifdef FIXED_SEARCH_PATH orig_path = FIXED_SEARCH_PATH; #else orig_path = getenv ("PATH"); if (!orig_path) orig_path = "/bin:/usr/bin"; #endif fname = malloc (strlen (orig_path) + 1 + strlen (pgm) + 1); if (!fname) return NULL; path = orig_path; for (;;) { for (s=path, p=fname; *s && *s != ':'; s++, p++) *p = *s; if (p != fname && p[-1] != '/') *p++ = '/'; strcpy (p, pgm); if (!access (fname, X_OK)) return fname; if (!*s) break; path = s + 1; } - _gpgme_debug (DEBUG_ENGINE, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_ENGINE, -1, NULL, NULL, NULL, "gpgme-walk_path: '%s' not found in '%s'", pgm, orig_path); free (fname); return NULL; } /* Return the full file name of the GPG binary. This function is used if gpgconf was not found and thus it can be assumed that gpg2 is not installed. This function is only called by get_gpgconf_item and may not be called concurrently. */ char * _gpgme_get_gpg_path (void) { return walk_path (default_gpg_name? default_gpg_name : "gpg"); } /* This function is only called by get_gpgconf_item and may not be called concurrently. */ char * _gpgme_get_gpgconf_path (void) { return walk_path (default_gpgconf_name? default_gpgconf_name : "gpgconf"); } /* See w32-util.c */ int _gpgme_get_conf_int (const char *key, int *value) { (void)key; (void)value; return 0; } void _gpgme_allow_set_foreground_window (pid_t pid) { (void)pid; /* Not needed. */ } /* See w32-util.c */ int _gpgme_access (const char *path, int mode) { return access (path, mode); } diff --git a/src/version.c b/src/version.c index 3bf12e94..5beb63a3 100644 --- a/src/version.c +++ b/src/version.c @@ -1,370 +1,370 @@ /* version.c - Version check routines. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2008 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef HAVE_W32_SYSTEM #include #endif #include "gpgme.h" #include "priv-io.h" #include "debug.h" #include "context.h" /* For _gpgme_sema_subsystem_init and _gpgme_status_init. */ #include "sema.h" #include "util.h" #ifdef HAVE_ASSUAN_H #include "assuan.h" #endif #ifdef HAVE_W32_SYSTEM #include "windows.h" #endif /* We implement this function, so we have to disable the overriding macro. */ #undef gpgme_check_version /* Bootstrap the subsystems needed for concurrent operation. This must be done once at startup. We can not guarantee this using a lock, though, because the semaphore subsystem needs to be initialized itself before it can be used. So we expect that the user performs the necessary synchronization. */ static void do_subsystem_inits (void) { static int done = 0; if (done) return; #ifdef HAVE_W32_SYSTEM /* We need to make sure that the sockets are initialized. */ { WSADATA wsadat; WSAStartup (0x202, &wsadat); } #endif _gpgme_debug_subsystem_init (); _gpgme_io_subsystem_init (); _gpgme_status_init (); done = 1; } /* Put vesion information into the binary. */ static const char * cright_blurb (void) { static const char blurb[] = "\n\n" "This is GPGME " PACKAGE_VERSION " - The GnuPG Made Easy library\n" CRIGHTBLURB "\n" "(" BUILD_REVISION " " BUILD_TIMESTAMP ")\n" "\n\n"; return blurb; } /* Read the next number in the version string STR and return it in *NUMBER. Return a pointer to the tail of STR after parsing, or *NULL if the version string was invalid. */ static const char * parse_version_number (const char *str, int *number) { #define MAXVAL ((INT_MAX - 10) / 10) int val = 0; /* Leading zeros are not allowed. */ if (*str == '0' && isdigit(str[1])) return NULL; while (isdigit (*str) && val <= MAXVAL) { val *= 10; val += *(str++) - '0'; } *number = val; return val > MAXVAL ? NULL : str; } /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for example, 9.3.2) and return the components in MAJOR, MINOR and MICRO as integers. The function returns the tail of the string that follows the version number. This might be the empty string if there is nothing following the version number, or a patchlevel. The function returns NULL if the version string is not valid. */ static const char * parse_version_string (const char *str, int *major, int *minor, int *micro) { str = parse_version_number (str, major); if (!str || *str != '.') return NULL; str++; str = parse_version_number (str, minor); if (!str || *str != '.') return NULL; str++; str = parse_version_number (str, micro); if (!str) return NULL; /* A patchlevel might follow. */ return str; } /* Return true if MY_VERSION is at least REQ_VERSION, and false otherwise. */ int _gpgme_compare_versions (const char *my_version, const char *rq_version) { int my_major, my_minor, my_micro; int rq_major, rq_minor, rq_micro; const char *my_plvl, *rq_plvl; if (!rq_version) return 1; if (!my_version) return 0; my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro); if (!my_plvl) return 0; rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro); if (!rq_plvl) return 0; if (my_major > rq_major || (my_major == rq_major && my_minor > rq_minor) || (my_major == rq_major && my_minor == rq_minor && my_micro > rq_micro) || (my_major == rq_major && my_minor == rq_minor && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0)) return 1; return 0; } /* Check that the the version of the library is at minimum the requested one and return the version string; return NULL if the condition is not met. If a NULL is passed to this function, no check is done and the version string is simply returned. This function must be run once at startup, as it also initializes some subsystems. Its invocation must be synchronized against calling any of the other functions in a multi-threaded environments. */ const char * gpgme_check_version (const char *req_version) { const char *result; do_subsystem_inits (); /* Catch-22: We need to get at least the debug subsystem ready before using the trace facility. If we won't the trace would automagically initialize the debug system with out the locks being initialized and missing the assuan log level setting. */ - TRACE (DEBUG_INIT, "gpgme_check_version", 0, + TRACE (DEBUG_INIT, "gpgme_check_version", NULL, "req_version=%s, VERSION=%s", req_version? req_version:"(null)", VERSION); result = _gpgme_compare_versions (VERSION, req_version) ? VERSION : NULL; if (result != NULL) _gpgme_selftest = 0; return result; } /* Check the version and also at runtime if the struct layout of the library matches the one of the user. This is particular useful for Windows targets (-mms-bitfields). */ const char * gpgme_check_version_internal (const char *req_version, size_t offset_sig_validity) { const char *result; if (req_version && req_version[0] == 1 && req_version[1] == 1) return cright_blurb (); result = gpgme_check_version (req_version); if (result == NULL) return result; /* Catch-22, see above. */ - TRACE (DEBUG_INIT, "gpgme_check_version_internal", 0, + TRACE (DEBUG_INIT, "gpgme_check_version_internal", NULL, "req_version=%s, offset_sig_validity=%zu", req_version ? req_version : "(null)", offset_sig_validity); if (offset_sig_validity != offsetof (struct _gpgme_signature, validity)) { - TRACE (DEBUG_INIT, "gpgme_check_version_internal", 0, + TRACE (DEBUG_INIT, "gpgme_check_version_internal", NULL, "offset_sig_validity mismatch: expected %i", (int)offsetof (struct _gpgme_signature, validity)); _gpgme_selftest = GPG_ERR_SELFTEST_FAILED; } return result; } #define LINELENGTH 80 /* Extract the version string of a program from STRING. The version number is expected to be in GNU style format: foo 1.2.3 foo (bar system) 1.2.3 foo 1.2.3 cruft foo (bar system) 1.2.3 cruft. Spaces and tabs are skipped and used as delimiters, a term in (nested) parenthesis before the version string is skipped, the version string may consist of any non-space and non-tab characters but needs to bstart with a digit. */ static const char * extract_version_string (const char *string, size_t *r_len) { const char *s; int count, len; for (s=string; *s; s++) if (*s == ' ' || *s == '\t') break; while (*s == ' ' || *s == '\t') s++; if (*s == '(') { for (count=1, s++; count && *s; s++) if (*s == '(') count++; else if (*s == ')') count--; } /* For robustness we look for a digit. */ while ( *s && !(*s >= '0' && *s <= '9') ) s++; if (*s >= '0' && *s <= '9') { for (len=0; s[len]; len++) if (s[len] == ' ' || s[len] == '\t') break; } else len = 0; *r_len = len; return s; } /* Retrieve the version number from the --version output of the program FILE_NAME. */ char * _gpgme_get_program_version (const char *const file_name) { char line[LINELENGTH] = ""; int linelen = 0; char *mark = NULL; int rp[2]; int nread; char *argv[] = {NULL /* file_name */, (char*)"--version", 0}; struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, {-1, -1} }; int status; if (!file_name) return NULL; argv[0] = (char *) file_name; if (_gpgme_io_pipe (rp, 1) < 0) return NULL; cfd[0].fd = rp[1]; status = _gpgme_io_spawn (file_name, argv, IOSPAWN_FLAG_DETACHED, cfd, NULL, NULL, NULL); if (status < 0) { _gpgme_io_close (rp[0]); _gpgme_io_close (rp[1]); return NULL; } do { nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1); if (nread > 0) { line[linelen + nread] = '\0'; mark = strchr (&line[linelen], '\n'); if (mark) { if (mark > &line[0] && mark[-1] == '\r') mark--; *mark = '\0'; break; } linelen += nread; } } while (nread > 0 && linelen < LINELENGTH - 1); _gpgme_io_close (rp[0]); if (mark) { size_t len; const char *s; s = extract_version_string (line, &len); if (!len) return NULL; mark = malloc (len + 1); if (!mark) return NULL; memcpy (mark, s, len); mark[len] = 0; return mark; } return NULL; } diff --git a/src/w32-glib-io.c b/src/w32-glib-io.c index e2e3b8ab..09ffffa2 100644 --- a/src/w32-glib-io.c +++ b/src/w32-glib-io.c @@ -1,1088 +1,1088 @@ /* w32-glib-io.c - W32 Glib I/O functions * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2004, 2005 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #include #include #include #include "util.h" #include "priv-io.h" #include "sema.h" #include "debug.h" #ifndef O_BINARY #ifdef _O_BINARY #define O_BINARY _O_BINARY #else #define O_BINARY 0 #endif #endif /* This file is an ugly hack to get GPGME working with glib on Windows targets. On Windows, you can not select() on file descriptors. The only way to check if there is something to read is to read something. This means that GPGME can not let glib check for data without letting glib also handle the data on Windows targets. The ugly consequence is that we need to work on GIOChannels in GPGME, creating a glib dependency. Also, we need to export an interface for the application to get at GPGME's GIOChannel. There is no good way to abstract all this with callbacks, because the whole thing is also interconnected with the creation of pipes and child processes. The following rule applies only to this I/O backend: * ALL operations must use the user defined event loop. GPGME can not anymore provide its own event loop. This is mostly a sanity requirement: Although we have in theory all information we need to make the GPGME W32 code for select still work, it would be a big complication and require changes throughout GPGME. Eventually, we probably have to bite the bullet and make some really nice callback interfaces to let the user control all this at a per-context level. */ #define MAX_SLAFD 256 static struct { int used; /* If this is not -1, then it's a libc file descriptor. */ int fd; /* If fd is -1, this is the Windows socket handle. */ int socket; GIOChannel *chan; /* The boolean PRIMARY is true if this file descriptor caused the allocation of CHAN. Only then should CHAN be destroyed when this FD is closed. This, together with the fact that dup'ed file descriptors are closed before the file descriptors from which they are dup'ed are closed, ensures that CHAN is always valid, and shared among all file descriptors referring to the same underlying object. The logic behind this is that there is only one reason for us to dup file descriptors anyway: to allow simpler book-keeping of file descriptors shared between GPGME and libassuan, which both want to close something. Using the same channel for these duplicates works just fine (and in fact, using different channels does not work because the W32 backend in glib does not support that: One would end up with several competing reader/writer threads. */ int primary; } giochannel_table[MAX_SLAFD]; static GIOChannel * find_channel (int fd) { if (fd < 0 || fd >= MAX_SLAFD || !giochannel_table[fd].used) return NULL; return giochannel_table[fd].chan; } /* Returns the FD or -1 on resource limit. */ int new_dummy_channel_from_fd (int cfd) { int idx; for (idx = 0; idx < MAX_SLAFD; idx++) if (! giochannel_table[idx].used) break; if (idx == MAX_SLAFD) { errno = EIO; return -1; } giochannel_table[idx].used = 1; giochannel_table[idx].chan = NULL; giochannel_table[idx].fd = cfd; giochannel_table[idx].socket = INVALID_SOCKET; giochannel_table[idx].primary = 1; return idx; } /* Returns the FD or -1 on resource limit. */ int new_channel_from_fd (int cfd) { int idx; for (idx = 0; idx < MAX_SLAFD; idx++) if (! giochannel_table[idx].used) break; if (idx == MAX_SLAFD) { errno = EIO; return -1; } giochannel_table[idx].used = 1; giochannel_table[idx].chan = g_io_channel_win32_new_fd (cfd); giochannel_table[idx].fd = cfd; giochannel_table[idx].socket = INVALID_SOCKET; giochannel_table[idx].primary = 1; g_io_channel_set_encoding (giochannel_table[idx].chan, NULL, NULL); g_io_channel_set_buffered (giochannel_table[idx].chan, FALSE); return idx; } /* Returns the FD or -1 on resource limit. */ int new_channel_from_socket (int sock) { int idx; for (idx = 0; idx < MAX_SLAFD; idx++) if (! giochannel_table[idx].used) break; if (idx == MAX_SLAFD) { errno = EIO; return -1; } giochannel_table[idx].used = 1; giochannel_table[idx].chan = g_io_channel_win32_new_socket (sock); giochannel_table[idx].fd = -1; giochannel_table[idx].socket = sock; giochannel_table[idx].primary = 1; g_io_channel_set_encoding (giochannel_table[idx].chan, NULL, NULL); g_io_channel_set_buffered (giochannel_table[idx].chan, FALSE); return idx; } /* Compatibility interface. Obsolete. */ void * gpgme_get_giochannel (int fd) { return find_channel (fd); } /* Look up the giochannel for "file descriptor" FD. */ void * gpgme_get_fdptr (int fd) { return find_channel (fd); } /* Write the printable version of FD to the buffer BUF of length BUFLEN. The printable version is the representation on the command line that the child process expects. */ int _gpgme_io_fd2str (char *buf, int buflen, int fd) { HANDLE hndl; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_fd2str", fd, "fd=%d", fd); if (giochannel_table[fd].fd != -1) hndl = (HANDLE) _get_osfhandle (giochannel_table[fd].fd); else hndl = (HANDLE) giochannel_table[fd].socket; TRACE_SUC ("syshd=%p", hndl); return snprintf (buf, buflen, "%d", (int) hndl); } void _gpgme_io_subsystem_init (void) { } static struct { _gpgme_close_notify_handler_t handler; void *value; } notify_table[MAX_SLAFD]; int _gpgme_io_read (int fd, void *buffer, size_t count) { int saved_errno = 0; gsize nread; GIOChannel *chan; GIOStatus status; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_read", fd, "buffer=%p, count=%u", buffer, count); chan = find_channel (fd); if (!chan) { TRACE_LOG ("no channel registered"); errno = EINVAL; return TRACE_SYSRES (-1); } TRACE_LOG ("channel %p", chan); { GError *err = NULL; status = g_io_channel_read_chars (chan, (gchar *) buffer, count, &nread, &err); if (err) { TRACE_LOG ("status %i, err %s", status, err->message); g_error_free (err); } } if (status == G_IO_STATUS_EOF) nread = 0; else if (status == G_IO_STATUS_AGAIN) { nread = -1; saved_errno = EAGAIN; } else if (status != G_IO_STATUS_NORMAL) { TRACE_LOG ("status %d", status); nread = -1; saved_errno = EIO; } if (nread != 0 && nread != -1) TRACE_LOGBUFX (buffer, nread); errno = saved_errno; return TRACE_SYSRES (nread); } int _gpgme_io_write (int fd, const void *buffer, size_t count) { int saved_errno = 0; gsize nwritten; GIOChannel *chan; GIOStatus status; GError *err = NULL; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_write", fd, "buffer=%p, count=%u", buffer, count); TRACE_LOGBUFX (buffer, count); chan = find_channel (fd); if (!chan) { - TRACE_LOG ("fd %d: no channel registered"); + TRACE_LOG ("fd=%d: no channel registered"); errno = EINVAL; return -1; } status = g_io_channel_write_chars (chan, (gchar *) buffer, count, &nwritten, &err); if (err) { TRACE_LOG ("write error: %s", err->message); g_error_free (err); } if (status == G_IO_STATUS_AGAIN) { nwritten = -1; saved_errno = EAGAIN; } else if (status != G_IO_STATUS_NORMAL) { nwritten = -1; saved_errno = EIO; } errno = saved_errno; return TRACE_SYSRES (nwritten); } int _gpgme_io_pipe (int filedes[2], int inherit_idx) { int fds[2]; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_pipe", filedes, "inherit_idx=%i (GPGME uses it for %s)", inherit_idx, inherit_idx ? "reading" : "writing"); #define PIPEBUF_SIZE 4096 if (_pipe (fds, PIPEBUF_SIZE, O_NOINHERIT | O_BINARY) == -1) return TRACE_SYSRES (-1); /* Make one end inheritable. */ if (inherit_idx == 0) { int new_read; new_read = _dup (fds[0]); _close (fds[0]); fds[0] = new_read; if (new_read < 0) { _close (fds[1]); return TRACE_SYSRES (-1); } } else if (inherit_idx == 1) { int new_write; new_write = _dup (fds[1]); _close (fds[1]); fds[1] = new_write; if (new_write < 0) { _close (fds[0]); return TRACE_SYSRES (-1); } } /* For _gpgme_io_close. */ filedes[inherit_idx] = new_dummy_channel_from_fd (fds[inherit_idx]); if (filedes[inherit_idx] < 0) { int saved_errno = errno; _close (fds[0]); _close (fds[1]); errno = saved_errno; return TRACE_SYSRES (-1); } /* Now we have a pipe with the correct end inheritable. The other end should have a giochannel. */ filedes[1 - inherit_idx] = new_channel_from_fd (fds[1 - inherit_idx]); if (filedes[1 - inherit_idx] < 0) { int saved_errno = errno; _gpgme_io_close (fds[inherit_idx]); _close (fds[1 - inherit_idx]); errno = saved_errno; return TRACE_SYSRES (-1); } TRACE_SUC ("read=0x%x/%p, write=0x%x/%p, channel=%p", filedes[0], (HANDLE) _get_osfhandle (giochannel_table[filedes[0]].fd), filedes[1], (HANDLE) _get_osfhandle (giochannel_table[filedes[1]].fd), giochannel_table[1 - inherit_idx].chan); return 0; } int _gpgme_io_close (int fd) { TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", fd, ""); if (fd < 0 || fd >= MAX_SLAFD) { errno = EBADF; return TRACE_SYSRES (-1); } assert (giochannel_table[fd].used); /* First call the notify handler. */ if (notify_table[fd].handler) { notify_table[fd].handler (fd, notify_table[fd].value); notify_table[fd].handler = NULL; notify_table[fd].value = NULL; } /* Then do the close. */ if (giochannel_table[fd].chan) { if (giochannel_table[fd].primary) g_io_channel_shutdown (giochannel_table[fd].chan, 1, NULL); g_io_channel_unref (giochannel_table[fd].chan); } else { /* Dummy entry, just close. */ assert (giochannel_table[fd].fd != -1); _close (giochannel_table[fd].fd); } giochannel_table[fd].used = 0; giochannel_table[fd].fd = -1; giochannel_table[fd].socket = INVALID_SOCKET; giochannel_table[fd].chan = NULL; giochannel_table[fd].primary = 0; TRACE_SUC (""); return 0; } int _gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler, void *value) { TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_close_notify", fd, "close_handler=%p/%p", handler, value); assert (fd != -1); if (fd < 0 || fd >= (int) DIM (notify_table)) { errno = EINVAL; return TRACE_SYSRES (-1); } notify_table[fd].handler = handler; notify_table[fd].value = value; return TRACE_SYSRES (0); } int _gpgme_io_set_nonblocking (int fd) { GIOChannel *chan; GIOStatus status; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_nonblocking", fd, ""); chan = find_channel (fd); if (!chan) { errno = EIO; return TRACE_SYSRES (-1); } status = g_io_channel_set_flags (chan, g_io_channel_get_flags (chan) | G_IO_FLAG_NONBLOCK, NULL); if (status != G_IO_STATUS_NORMAL) { #if 0 /* glib 1.9.2 does not implement set_flags and returns an error. */ errno = EIO; return TRACE_SYSRES (-1); #else TRACE_LOG ("g_io_channel_set_flags failed: status=%d (ignored)", status); #endif } return TRACE_SYSRES (0); } static char * build_commandline (char **argv) { int i; int n = 0; char *buf; char *p; /* We have to quote some things because under Windows the program parses the commandline and does some unquoting. We enclose the whole argument in double-quotes, and escape literal double-quotes as well as backslashes with a backslash. We end up with a trailing space at the end of the line, but that is harmless. */ for (i = 0; argv[i]; i++) { p = argv[i]; /* The leading double-quote. */ n++; while (*p) { /* An extra one for each literal that must be escaped. */ if (*p == '\\' || *p == '"') n++; n++; p++; } /* The trailing double-quote and the delimiter. */ n += 2; } /* And a trailing zero. */ n++; buf = p = malloc (n); if (!buf) return NULL; for (i = 0; argv[i]; i++) { char *argvp = argv[i]; *(p++) = '"'; while (*argvp) { if (*argvp == '\\' || *argvp == '"') *(p++) = '\\'; *(p++) = *(argvp++); } *(p++) = '"'; *(p++) = ' '; } *(p++) = 0; return buf; } int _gpgme_io_spawn (const char *path, char * const argv[], unsigned int flags, struct spawn_fd_item_s *fd_list, void (*atfork) (void *opaque, int reserved), void *atforkvalue, pid_t *r_pid) { 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 cr_flags = (CREATE_DEFAULT_ERROR_MODE | GetPriorityClass (GetCurrentProcess ())); int i; char **args; char *arg_string; /* FIXME. */ int debug_me = 0; int tmp_fd; char *tmp_name; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_spawn", path, "path=%s", path); i = 0; while (argv[i]) { TRACE_LOG ("argv[%2i] = %s", i, argv[i]); i++; } /* We do not inherit any handles by default, and just insert those handles we want the child to have afterwards. But some handle values occur on the command line, and we need to move stdin/out/err to the right location. So we use a wrapper program which gets the information from a temporary file. */ if (_gpgme_mkstemp (&tmp_fd, &tmp_name) < 0) { TRACE_LOG ("_gpgme_mkstemp failed: %s", strerror (errno)); return TRACE_SYSRES (-1); } TRACE_LOG ("tmp_name = %s", tmp_name); args = calloc (2 + i + 1, sizeof (*args)); args[0] = (char *) _gpgme_get_w32spawn_path (); args[1] = tmp_name; args[2] = path; memcpy (&args[3], &argv[1], i * sizeof (*args)); memset (&sec_attr, 0, sizeof sec_attr); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; arg_string = build_commandline (args); free (args); if (!arg_string) { close (tmp_fd); DeleteFile (tmp_name); return TRACE_SYSRES (-1); } memset (&si, 0, sizeof si); si.cb = sizeof (si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = debug_me ? SW_SHOW : SW_HIDE; si.hStdInput = INVALID_HANDLE_VALUE; si.hStdOutput = INVALID_HANDLE_VALUE; si.hStdError = INVALID_HANDLE_VALUE; cr_flags |= CREATE_SUSPENDED; if ((flags & IOSPAWN_FLAG_DETACHED)) cr_flags |= DETACHED_PROCESS; if (!CreateProcessA (_gpgme_get_w32spawn_path (), arg_string, &sec_attr, /* process security attributes */ &sec_attr, /* thread security attributes */ FALSE, /* inherit handles */ cr_flags, /* creation flags */ NULL, /* environment */ NULL, /* use current drive/directory */ &si, /* startup information */ &pi)) /* returns process information */ { TRACE_LOG ("CreateProcess failed: ec=%d", (int) GetLastError ()); free (arg_string); close (tmp_fd); DeleteFile (tmp_name); /* FIXME: Should translate the error code. */ errno = EIO; return TRACE_SYSRES (-1); } free (arg_string); if (flags & IOSPAWN_FLAG_ALLOW_SET_FG) _gpgme_allow_set_foreground_window ((pid_t)pi.dwProcessId); /* Insert the inherited handles. */ for (i = 0; fd_list[i].fd != -1; i++) { HANDLE hd; /* Make it inheritable for the wrapper process. */ if (!DuplicateHandle (GetCurrentProcess(), _get_osfhandle (giochannel_table[fd_list[i].fd].fd), pi.hProcess, &hd, 0, TRUE, DUPLICATE_SAME_ACCESS)) { TRACE_LOG ("DuplicateHandle failed: ec=%d", (int) GetLastError ()); TerminateProcess (pi.hProcess, 0); /* Just in case TerminateProcess didn't work, let the process fail on its own. */ ResumeThread (pi.hThread); CloseHandle (pi.hThread); CloseHandle (pi.hProcess); close (tmp_fd); DeleteFile (tmp_name); /* FIXME: Should translate the error code. */ errno = EIO; return TRACE_SYSRES (-1); } /* Return the child name of this handle. */ fd_list[i].peer_name = (int) hd; } /* Write the handle translation information to the temporary file. */ { /* Hold roughly MAX_TRANS quadruplets of 64 bit numbers in hex notation: "0xFEDCBA9876543210" with an extra white space after every quadruplet. 10*(19*4 + 1) - 1 = 769. This plans ahead for a time when a HANDLE is 64 bit. */ #define BUFFER_MAX 800 char line[BUFFER_MAX + 1]; int res; int written; size_t len; if ((flags & IOSPAWN_FLAG_ALLOW_SET_FG)) strcpy (line, "~1 \n"); else strcpy (line, "\n"); for (i = 0; fd_list[i].fd != -1; i++) { /* Strip the newline. */ len = strlen (line) - 1; /* Format is: Local name, stdin/stdout/stderr, peer name, argv idx. */ snprintf (&line[len], BUFFER_MAX - len, "0x%x %d 0x%x %d \n", fd_list[i].fd, fd_list[i].dup_to, fd_list[i].peer_name, fd_list[i].arg_loc); /* Rather safe than sorry. */ line[BUFFER_MAX - 1] = '\n'; line[BUFFER_MAX] = '\0'; } len = strlen (line); written = 0; do { res = write (tmp_fd, &line[written], len - written); if (res > 0) written += res; } while (res > 0 || (res < 0 && errno == EAGAIN)); } close (tmp_fd); /* The temporary file is deleted by the gpgme-w32spawn process (hopefully). */ TRACE_LOG ("CreateProcess ready: hProcess=%p, hThread=%p, " "dwProcessID=%d, dwThreadId=%d", pi.hProcess, pi.hThread, (int) pi.dwProcessId, (int) pi.dwThreadId); if (r_pid) *r_pid = (pid_t)pi.dwProcessId; if (ResumeThread (pi.hThread) < 0) TRACE_LOG ("ResumeThread failed: ec=%d", (int) GetLastError ()); if (!CloseHandle (pi.hThread)) TRACE_LOG ("CloseHandle of thread failed: ec=%d", (int) GetLastError ()); TRACE_LOG ("process=%p", pi.hProcess); /* We don't need to wait for the process. */ if (!CloseHandle (pi.hProcess)) TRACE_LOG ("CloseHandle of process failed: ec=%d", (int) GetLastError ()); if (! (flags & IOSPAWN_FLAG_NOCLOSE)) { for (i = 0; fd_list[i].fd != -1; i++) _gpgme_io_close (fd_list[i].fd); } for (i = 0; fd_list[i].fd != -1; i++) if (fd_list[i].dup_to == -1) TRACE_LOG ("fd[%i] = 0x%x -> 0x%x", i, fd_list[i].fd, fd_list[i].peer_name); else TRACE_LOG ("fd[%i] = 0x%x -> 0x%x (std%s)", i, fd_list[i].fd, fd_list[i].peer_name, (fd_list[i].dup_to == 0) ? "in" : ((fd_list[i].dup_to == 1) ? "out" : "err")); return TRACE_SYSRES (0); } /* Select on the list of fds. Returns: -1 = error, 0 = timeout or nothing to select, > 0 = number of signaled fds. */ int _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock) { int npollfds; GPollFD *pollfds; int *pollfds_map; int i; int j; int any; int n; int count; /* Use a 1s timeout. */ int timeout = 1000; void *dbg_help = NULL; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_select", fds, "nfds=%u, nonblock=%u", nfds, nonblock); if (nonblock) timeout = 0; pollfds = calloc (nfds, sizeof *pollfds); if (!pollfds) return -1; pollfds_map = calloc (nfds, sizeof *pollfds_map); if (!pollfds_map) { free (pollfds); return -1; } npollfds = 0; TRACE_SEQ (dbg_help, "select on [ "); any = 0; for (i = 0; i < nfds; i++) { GIOChannel *chan = NULL; if (fds[i].fd == -1) continue; if ((fds[i].for_read || fds[i].for_write) && !(chan = find_channel (fds[i].fd))) { TRACE_ADD1 (dbg_help, "[BAD0x%x ", fds[i].fd); TRACE_END (dbg_help, "]"); assert (!"see log file"); } else if (fds[i].for_read ) { assert(chan); g_io_channel_win32_make_pollfd (chan, G_IO_IN, pollfds + npollfds); pollfds_map[npollfds] = i; TRACE_ADD2 (dbg_help, "r0x%x<%d> ", fds[i].fd, pollfds[npollfds].fd); npollfds++; any = 1; } else if (fds[i].for_write) { assert(chan); g_io_channel_win32_make_pollfd (chan, G_IO_OUT, pollfds + npollfds); pollfds_map[npollfds] = i; TRACE_ADD2 (dbg_help, "w0x%x<%d> ", fds[i].fd, pollfds[npollfds].fd); npollfds++; any = 1; } fds[i].signaled = 0; } TRACE_END (dbg_help, "]"); if (!any) { count = 0; goto leave; } count = g_io_channel_win32_poll (pollfds, npollfds, timeout); if (count < 0) { int saved_errno = errno; errno = saved_errno; goto leave; } TRACE_SEQ (dbg_help, "select OK [ "); if (TRACE_ENABLED (dbg_help)) { for (i = 0; i < npollfds; i++) { if ((pollfds[i].revents & G_IO_IN)) TRACE_ADD1 (dbg_help, "r0x%x ", fds[pollfds_map[i]].fd); if ((pollfds[i].revents & G_IO_OUT)) TRACE_ADD1 (dbg_help, "w0x%x ", fds[pollfds_map[i]].fd); } TRACE_END (dbg_help, "]"); } /* COUNT is used to stop the lop as soon as possible. */ for (n = count, i = 0; i < npollfds && n; i++) { j = pollfds_map[i]; assert (j >= 0 && j < nfds); if (fds[j].fd == -1) ; else if (fds[j].for_read) { if ((pollfds[i].revents & G_IO_IN)) { fds[j].signaled = 1; n--; } } else if (fds[j].for_write) { if ((pollfds[i].revents & G_IO_OUT)) { fds[j].signaled = 1; n--; } } } leave: free (pollfds); free (pollfds_map); return TRACE_SYSRES (count); } int _gpgme_io_dup (int fd) { int newfd; GIOChannel *chan; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_dup", fd, ""); if (fd < 0 || fd >= MAX_SLAFD || !giochannel_table[fd].used) { errno = EINVAL; return TRACE_SYSRES (-1); } for (newfd = 0; newfd < MAX_SLAFD; newfd++) if (! giochannel_table[newfd].used) break; if (newfd == MAX_SLAFD) { errno = EIO; return TRACE_SYSRES (-1); } chan = giochannel_table[fd].chan; g_io_channel_ref (chan); giochannel_table[newfd].used = 1; giochannel_table[newfd].chan = chan; giochannel_table[newfd].fd = -1; giochannel_table[newfd].socket = INVALID_SOCKET; giochannel_table[newfd].primary = 0; return TRACE_SYSRES (newfd); } static int wsa2errno (int err) { switch (err) { case WSAENOTSOCK: return EINVAL; case WSAEWOULDBLOCK: return EAGAIN; case ERROR_BROKEN_PIPE: return EPIPE; case WSANOTINITIALISED: return ENOSYS; default: return EIO; } } int _gpgme_io_socket (int domain, int type, int proto) { int res; int fd; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_socket", domain, "type=%i, protp=%i", type, proto); res = socket (domain, type, proto); if (res == INVALID_SOCKET) { errno = wsa2errno (WSAGetLastError ()); return TRACE_SYSRES (-1); } fd = new_channel_from_socket (res); if (fd < 0) { int saved_errno = errno; closesocket (res); errno = saved_errno; return TRACE_SYSRES (-1); } TRACE_SUC ("fd=%i, socket=0x%x", fd, res); return fd; } int _gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen) { GIOChannel *chan; int sockfd; int res; GIOFlags flags; GIOStatus status; GError *err = NULL; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_connect", fd, "addr=%p, addrlen=%i", addr, addrlen); chan = find_channel (fd); if (! chan) { errno = EINVAL; return TRACE_SYSRES (-1); } flags = g_io_channel_get_flags (chan); if (flags & G_IO_FLAG_NONBLOCK) { status = g_io_channel_set_flags (chan, flags & ~G_IO_FLAG_NONBLOCK, &err); if (err) { TRACE_LOG ("setting flags error: %s", err->message); g_error_free (err); err = NULL; } if (status != G_IO_STATUS_NORMAL) { errno = EIO; return TRACE_SYSRES (-1); } } sockfd = giochannel_table[fd].socket; if (sockfd == INVALID_SOCKET) { errno = EINVAL; return TRACE_SYSRES (-1); } - TRACE_LOG ("connect sockfd=0x%x", sockfd); + TRACE_LOG ("connect socket fd=%d", sockfd); res = connect (sockfd, addr, addrlen); /* FIXME: Error ignored here. */ if (! (flags & G_IO_FLAG_NONBLOCK)) g_io_channel_set_flags (chan, flags, NULL); if (res) { TRACE_LOG ("connect failed: %i %i", res, WSAGetLastError ()); errno = wsa2errno (WSAGetLastError ()); return TRACE_SYSRES (-1); } TRACE_SUC (""); return 0; } diff --git a/src/w32-util.c b/src/w32-util.c index fba43448..82076762 100644 --- a/src/w32-util.c +++ b/src/w32-util.c @@ -1,910 +1,910 @@ /* w32-util.c - Utility functions for the W32 API * Copyright (C) 1999 Free Software Foundation, Inc * Copyright (C) 2001 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2007, 2013 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_SYS_STAT_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #include #include #if __MINGW64_VERSION_MAJOR >= 2 # define _WIN32_IE 0x0501 /* Required by mingw64 toolkit. */ #else # define _WIN32_IE 0x0400 /* Required for SHGetSpecialFolderPathA. */ #endif /* We need to include the windows stuff here prior to shlobj.h so that we get the right winsock version. This is usually done in util.h but that header also redefines some Windows functions which we need to avoid unless having included shlobj.h. */ #include #include #include #include #include "util.h" #include "ath.h" #include "sema.h" #include "debug.h" #include "sys-util.h" #define HAVE_ALLOW_SET_FOREGROUND_WINDOW 1 #ifndef F_OK # define F_OK 0 #endif /* The Registry key used by GNUPG. */ #ifdef _WIN64 # define GNUPG_REGKEY_2 "Software\\Wow6432Node\\GNU\\GnuPG" #else # define GNUPG_REGKEY_2 "Software\\GNU\\GnuPG" #endif #ifdef _WIN64 # define GNUPG_REGKEY_3 "Software\\Wow6432Node\\GnuPG" #else # define GNUPG_REGKEY_3 "Software\\GnuPG" #endif DEFINE_STATIC_LOCK (get_path_lock); /* The module handle of this DLL. If we are linked statically, dllmain does not exists and thus the value of my_hmodule will be NULL. The effect is that a GetModuleFileName always returns the file name of the DLL or executable which contains the gpgme code. */ static HMODULE my_hmodule; /* These variables store the malloced name of alternative default binaries. The are set only once by gpgme_set_global_flag. */ static char *default_gpg_name; static char *default_gpgconf_name; /* If this variable is not NULL the value is assumed to be the installation directory. The variable may only be set once by gpgme_set_global_flag and accessed by _gpgme_get_inst_dir. */ static char *override_inst_dir; #define RTLD_LAZY 0 static GPG_ERR_INLINE void * dlopen (const char * name, int flag) { void * hd = LoadLibrary (name); (void)flag; return hd; } static GPG_ERR_INLINE void * dlsym (void * hd, const char * sym) { if (hd && sym) { void * fnc = GetProcAddress (hd, sym); if (!fnc) return NULL; return fnc; } return NULL; } static GPG_ERR_INLINE int dlclose (void * hd) { if (hd) { FreeLibrary (hd); return 0; } return -1; } /* Return a malloced string encoded in UTF-8 from the wide char input string STRING. Caller must free this value. Returns NULL and sets ERRNO on failure. Calling this function with STRING set to NULL is not defined. */ static char * wchar_to_utf8 (const wchar_t *string) { int n; char *result; n = WideCharToMultiByte (CP_UTF8, 0, string, -1, NULL, 0, NULL, NULL); if (n < 0) { gpg_err_set_errno (EINVAL); return NULL; } result = malloc (n+1); if (!result) return NULL; n = WideCharToMultiByte (CP_UTF8, 0, string, -1, result, n, NULL, NULL); if (n < 0) { free (result); gpg_err_set_errno (EINVAL); result = NULL; } return result; } /* Return a malloced wide char string from an UTF-8 encoded input string STRING. Caller must free this value. On failure returns NULL; caller may use GetLastError to get the actual error number. Calling this function with STRING set to NULL is not defined. */ static wchar_t * utf8_to_wchar (const char *string) { int n; wchar_t *result; n = MultiByteToWideChar (CP_UTF8, 0, string, -1, NULL, 0); if (n < 0) return NULL; result = (wchar_t *) malloc ((n+1) * sizeof *result); if (!result) return NULL; n = MultiByteToWideChar (CP_UTF8, 0, string, -1, result, n); if (n < 0) { free (result); return NULL; } return result; } /* Same as utf8_to_wchar but calling it with NULL returns NULL. So a return value of NULL only indicates failure if STRING is not set to NULL. */ static wchar_t * utf8_to_wchar0 (const char *string) { if (!string) return NULL; return utf8_to_wchar (string); } /* Replace all forward slashes by backslashes. */ static void replace_slashes (char *string) { for (; *string; string++) if (*string == '/') *string = '\\'; } /* Get the base name of NAME. Returns a pointer into NAME right after the last slash or backslash or to NAME if no slash or backslash exists. */ static const char * get_basename (const char *name) { const char *mark, *s; for (mark=NULL, s=name; *s; s++) if (*s == '/' || *s == '\\') mark = s; return mark? mark+1 : name; } void _gpgme_allow_set_foreground_window (pid_t pid) { #ifdef HAVE_ALLOW_SET_FOREGROUND_WINDOW static int initialized; static BOOL (WINAPI * func)(DWORD); void *handle; if (!initialized) { /* Available since W2000; thus we dynload it. */ initialized = 1; handle = dlopen ("user32.dll", RTLD_LAZY); if (handle) { func = dlsym (handle, "AllowSetForegroundWindow"); if (!func) { dlclose (handle); handle = NULL; } } } if (!pid || pid == (pid_t)(-1)) { - TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", 0, + TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", NULL, "no action for pid %d", (int)pid); } else if (func) { int rc = func (pid); - TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", 0, + TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", NULL, "called for pid %d; result=%d", (int)pid, rc); } else { - TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", 0, + TRACE (DEBUG_ENGINE, "gpgme:AllowSetForegroundWindow", NULL, "function not available"); } #endif /* HAVE_ALLOW_SET_FOREGROUND_WINDOW */ } /* Wrapper around CancelSynchronousIo which is only available since * Vista. */ void _gpgme_w32_cancel_synchronous_io (HANDLE thread) { static int initialized; static BOOL (WINAPI * func)(DWORD); void *handle; if (!initialized) { /* Available since Vista; thus we dynload it. */ initialized = 1; handle = dlopen ("kernel32.dll", RTLD_LAZY); if (handle) { func = dlsym (handle, "CancelSynchronousIo"); if (!func) { dlclose (handle); handle = NULL; } } } if (func) { if (!func (thread) && GetLastError() != ERROR_NOT_FOUND) { - TRACE (DEBUG_ENGINE, "gpgme:CancelSynchronousIo", 0, + TRACE (DEBUG_ENGINE, "gpgme:CancelSynchronousIo", NULL, "called for thread %p: ec=%d", thread, GetLastError ()); } } else { - TRACE (DEBUG_ENGINE, "gpgme:CancelSynchronousIo", 0, + TRACE (DEBUG_ENGINE, "gpgme:CancelSynchronousIo", NULL, "function not available"); } } /* Return a string from the W32 Registry or NULL in case of error. Caller must release the return value. A NULL for root is an alias for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. */ static char * read_w32_registry_string (const char *root, const char *dir, const char *name) { HKEY root_key, key_handle; DWORD n1, nbytes, type; char *result = NULL; if (!root) root_key = HKEY_CURRENT_USER; else if (!strcmp( root, "HKEY_CLASSES_ROOT")) root_key = HKEY_CLASSES_ROOT; else if (!strcmp( root, "HKEY_CURRENT_USER")) root_key = HKEY_CURRENT_USER; else if (!strcmp( root, "HKEY_LOCAL_MACHINE")) root_key = HKEY_LOCAL_MACHINE; else if (!strcmp( root, "HKEY_USERS")) root_key = HKEY_USERS; else if (!strcmp( root, "HKEY_PERFORMANCE_DATA")) root_key = HKEY_PERFORMANCE_DATA; else if (!strcmp( root, "HKEY_CURRENT_CONFIG")) root_key = HKEY_CURRENT_CONFIG; else return NULL; if (RegOpenKeyExA (root_key, dir, 0, KEY_READ, &key_handle)) { if (root) return NULL; /* no need for a RegClose, so return direct */ /* It seems to be common practise to fall back to HKLM. */ if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle)) return NULL; /* still no need for a RegClose, so return direct */ } nbytes = 1; if (RegQueryValueExA (key_handle, name, 0, NULL, NULL, &nbytes)) { if (root) goto leave; /* Try to fallback to HKLM also vor a missing value. */ RegCloseKey (key_handle); if (RegOpenKeyExA (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle)) return NULL; /* Nope. */ if (RegQueryValueExA (key_handle, name, 0, NULL, NULL, &nbytes)) goto leave; } n1 = nbytes + 1; result = malloc (n1); if (!result) goto leave; if (RegQueryValueExA (key_handle, name, 0, &type, (LPBYTE) result, &n1)) { free (result); result = NULL; goto leave; } result[nbytes] = 0; /* Make sure it is really a string. */ leave: RegCloseKey (key_handle); return result; } /* Return the name of the directory with the gpgme DLL or the EXE (if statically linked). May return NULL on severe errors. */ const char * _gpgme_get_inst_dir (void) { static char *inst_dir; if (override_inst_dir) return override_inst_dir; LOCK (get_path_lock); if (!inst_dir) { wchar_t *moddir; moddir = malloc ((MAX_PATH+5) * sizeof *moddir); if (moddir) { if (!GetModuleFileNameW (my_hmodule, moddir, MAX_PATH)) *moddir = 0; if (!*moddir) gpg_err_set_errno (ENOENT); else { inst_dir = wchar_to_utf8 (moddir); if (inst_dir) { char *p = strrchr (inst_dir, '\\'); if (p) *p = 0; } } free (moddir); } } UNLOCK (get_path_lock); return inst_dir; } static char * find_program_in_dir (const char *dir, const char *name) { char *result; result = _gpgme_strconcat (dir, "\\", name, NULL); if (!result) return NULL; if (_gpgme_access (result, F_OK)) { free (result); return NULL; } return result; } static char * find_program_at_standard_place (const char *name) { wchar_t path[MAX_PATH]; char *result = NULL; /* See https://wiki.tcl-lang.org/page/Getting+Windows+%22special+folders%22+with+Ffidl for details on compatibility. We First try the generic place and then fallback to the x86 (i.e. 32 bit) place. This will prefer a 64 bit of the program over a 32 bit version on 64 bit Windows if installed. */ if (SHGetSpecialFolderPathW (NULL, path, CSIDL_PROGRAM_FILES, 0)) { char *utf8_path = wchar_to_utf8 (path); result = _gpgme_strconcat (utf8_path, "\\", name, NULL); free (utf8_path); if (result && _gpgme_access (result, F_OK)) { free (result); result = NULL; } } if (!result && SHGetSpecialFolderPathW (NULL, path, CSIDL_PROGRAM_FILESX86, 0)) { char *utf8_path = wchar_to_utf8 (path); result = _gpgme_strconcat (utf8_path, "\\", name, NULL); free (utf8_path); if (result && _gpgme_access (result, F_OK)) { free (result); result = NULL; } } return result; } /* Set the default name for the gpg binary. This function may only be called by gpgme_set_global_flag. Returns 0 on success. */ int _gpgme_set_default_gpg_name (const char *name) { if (!default_gpg_name) { default_gpg_name = _gpgme_strconcat (name, ".exe", NULL); if (default_gpg_name) replace_slashes (default_gpg_name); } return !default_gpg_name; } /* Set the default name for the gpgconf binary. This function may only be called by gpgme_set_global_flag. Returns 0 on success. */ int _gpgme_set_default_gpgconf_name (const char *name) { if (!default_gpgconf_name) { default_gpgconf_name = _gpgme_strconcat (name, ".exe", NULL); if (default_gpgconf_name) replace_slashes (default_gpgconf_name); } return !default_gpgconf_name; } /* Set the override installation directory. This function may only be called by gpgme_set_global_flag. Returns 0 on success. */ int _gpgme_set_override_inst_dir (const char *dir) { if (!override_inst_dir) { override_inst_dir = strdup (dir); if (override_inst_dir) { replace_slashes (override_inst_dir); /* Remove a trailing slash. */ if (*override_inst_dir && override_inst_dir[strlen (override_inst_dir)-1] == '\\') override_inst_dir[strlen (override_inst_dir)-1] = 0; } } return !override_inst_dir; } /* Return the full file name of the GPG binary. This function is used iff gpgconf was not found and thus it can be assumed that gpg2 is not installed. This function is only called by get_gpgconf_item and may not be called concurrently. */ char * _gpgme_get_gpg_path (void) { char *gpg = NULL; const char *name, *inst_dir; name = default_gpg_name? get_basename (default_gpg_name) : "gpg.exe"; /* 1. Try to find gpg.exe in the installation directory of gpgme. */ inst_dir = _gpgme_get_inst_dir (); if (inst_dir) { gpg = find_program_in_dir (inst_dir, name); } /* 2. Try to find gpg.exe using that ancient registry key. */ if (!gpg) { char *dir; dir = read_w32_registry_string ("HKEY_LOCAL_MACHINE", GNUPG_REGKEY_2, "Install Directory"); if (dir) { gpg = find_program_in_dir (dir, name); free (dir); } } /* 3. Try to find gpg.exe below CSIDL_PROGRAM_FILES. */ if (!gpg) { name = default_gpg_name? default_gpg_name : "GNU\\GnuPG\\gpg.exe"; gpg = find_program_at_standard_place (name); } /* 4. Print a debug message if not found. */ if (!gpg) - _gpgme_debug (DEBUG_ENGINE, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_ENGINE, -1, NULL, NULL, NULL, "_gpgme_get_gpg_path: '%s' not found", name); return gpg; } /* This function is only called by get_gpgconf_item and may not be called concurrently. */ char * _gpgme_get_gpgconf_path (void) { char *gpgconf = NULL; const char *inst_dir, *name; name = default_gpgconf_name? get_basename(default_gpgconf_name):"gpgconf.exe"; /* 1. Try to find gpgconf.exe in the installation directory of gpgme. */ inst_dir = _gpgme_get_inst_dir (); if (inst_dir) { gpgconf = find_program_in_dir (inst_dir, name); } /* 2. Try to find gpgconf.exe from GnuPG >= 2.1 below CSIDL_PROGRAM_FILES. */ if (!gpgconf) { const char *name2 = (default_gpgconf_name ? default_gpgconf_name /**/ : "GnuPG\\bin\\gpgconf.exe"); gpgconf = find_program_at_standard_place (name2); } /* 3. Try to find gpgconf.exe using the Windows registry. */ if (!gpgconf) { char *dir; dir = read_w32_registry_string (NULL, GNUPG_REGKEY_2, "Install Directory"); if (!dir) { char *tmp = read_w32_registry_string (NULL, GNUPG_REGKEY_3, "Install Directory"); if (tmp) { dir = _gpgme_strconcat (tmp, "\\bin", NULL); free (tmp); if (!dir) return NULL; } } if (dir) { gpgconf = find_program_in_dir (dir, name); free (dir); } } /* 4. Try to find gpgconf.exe from Gpg4win below CSIDL_PROGRAM_FILES. */ if (!gpgconf) { gpgconf = find_program_at_standard_place ("GNU\\GnuPG\\gpgconf.exe"); } /* 5. Try to find gpgconf.exe relative to us. */ if (!gpgconf && inst_dir) { char *dir = _gpgme_strconcat (inst_dir, "\\..\\..\\GnuPG\\bin", NULL); gpgconf = find_program_in_dir (dir, name); free (dir); } /* 5. Print a debug message if not found. */ if (!gpgconf) - _gpgme_debug (DEBUG_ENGINE, -1, NULL, NULL, NULL, + _gpgme_debug (NULL, DEBUG_ENGINE, -1, NULL, NULL, NULL, "_gpgme_get_gpgconf_path: '%s' not found",name); return gpgconf; } const char * _gpgme_get_w32spawn_path (void) { static char *w32spawn_program; const char *inst_dir; inst_dir = _gpgme_get_inst_dir (); LOCK (get_path_lock); if (!w32spawn_program) w32spawn_program = find_program_in_dir (inst_dir, "gpgme-w32spawn.exe"); UNLOCK (get_path_lock); return w32spawn_program; } /* Return an integer value from gpgme specific configuration entries. VALUE receives that value; function returns true if a value has been configured and false if not. */ int _gpgme_get_conf_int (const char *key, int *value) { char *tmp = read_w32_registry_string (NULL, "Software\\GNU\\gpgme", key); if (!tmp) return 0; *value = atoi (tmp); free (tmp); return 1; } /* mkstemp extracted from libc/sysdeps/posix/tempname.c. Copyright (C) 1991-1999, 2000, 2001, 2006 Free Software Foundation, Inc. The GNU C Library 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. */ static const char letters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; /* Generate a temporary file name based on TMPL. TMPL must match the rules for mk[s]temp (i.e. end in "XXXXXX"). The name constructed does not exist at the time of the call to mkstemp. TMPL is overwritten with the result. */ static int my_mkstemp (char *tmpl) { int len; char *XXXXXX; static uint64_t value; uint64_t random_time_bits; unsigned int count; int fd = -1; int save_errno = errno; /* A lower bound on the number of temporary files to attempt to generate. The maximum total number of temporary file names that can exist for a given template is 62**6. It should never be necessary to try all these combinations. Instead if a reasonable number of names is tried (we define reasonable as 62**3) fail to give the system administrator the chance to remove the problems. */ #define ATTEMPTS_MIN (62 * 62 * 62) /* The number of times to attempt to generate a temporary file. To conform to POSIX, this must be no smaller than TMP_MAX. */ #if ATTEMPTS_MIN < TMP_MAX unsigned int attempts = TMP_MAX; #else unsigned int attempts = ATTEMPTS_MIN; #endif len = strlen (tmpl); if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX")) { gpg_err_set_errno (EINVAL); return -1; } /* This is where the Xs start. */ XXXXXX = &tmpl[len - 6]; /* Get some more or less random data. */ { FILETIME ft; GetSystemTimeAsFileTime (&ft); random_time_bits = (((uint64_t)ft.dwHighDateTime << 32) | (uint64_t)ft.dwLowDateTime); } value += random_time_bits ^ ath_self (); for (count = 0; count < attempts; value += 7777, ++count) { uint64_t v = value; /* Fill in the random bits. */ XXXXXX[0] = letters[v % 62]; v /= 62; XXXXXX[1] = letters[v % 62]; v /= 62; XXXXXX[2] = letters[v % 62]; v /= 62; XXXXXX[3] = letters[v % 62]; v /= 62; XXXXXX[4] = letters[v % 62]; v /= 62; XXXXXX[5] = letters[v % 62]; fd = open (tmpl, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fd >= 0) { gpg_err_set_errno (save_errno); return fd; } else if (errno != EEXIST) return -1; } /* We got out of the loop because we ran out of combinations to try. */ gpg_err_set_errno (EEXIST); return -1; } int _gpgme_mkstemp (int *fd, char **name) { char tmp[MAX_PATH + 2]; char *tmpname; int err; *fd = -1; *name = NULL; err = GetTempPathA (MAX_PATH + 1, tmp); if (err == 0 || err > MAX_PATH + 1) strcpy (tmp,"c:\\windows\\temp"); else { int len = strlen(tmp); /* GetTempPath may return with \ on the end */ while(len > 0 && tmp[len - 1] == '\\') { tmp[len-1] = '\0'; len--; } } tmpname = _gpgme_strconcat (tmp, "\\gpgme-XXXXXX", NULL); if (!tmpname) return -1; *fd = my_mkstemp (tmpname); if (*fd < 0) { free (tmpname); return -1; } *name = tmpname; return 0; } /* Like access but using windows _waccess */ int _gpgme_access (const char *path, int mode) { wchar_t *u16 = utf8_to_wchar0 (path); int r = _waccess (u16, mode); free(u16); return r; } /* Like CreateProcessA but mapping the arguments to wchar API */ int _gpgme_create_process_utf8 (const char *application_name_utf8, char *command_line_utf8, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, void *lpEnvironment, char *working_directory_utf8, LPSTARTUPINFOA si, LPPROCESS_INFORMATION lpProcessInformation) { BOOL ret; wchar_t *application_name = utf8_to_wchar0 (application_name_utf8); wchar_t *command_line = utf8_to_wchar0 (command_line_utf8); wchar_t *working_directory = utf8_to_wchar0 (working_directory_utf8); STARTUPINFOW siw; memset (&siw, 0, sizeof siw); if (si) { siw.cb = sizeof (siw); siw.dwFlags = si->dwFlags; siw.wShowWindow = si->wShowWindow; siw.hStdInput = si->hStdInput; siw.hStdOutput = si->hStdOutput; siw.hStdError = si->hStdError; siw.dwX = si->dwX; siw.dwY = si->dwY; siw.dwXSize = si->dwXSize; siw.dwYSize = si->dwYSize; siw.dwXCountChars = si->dwXCountChars; siw.dwYCountChars = si->dwYCountChars; siw.dwFillAttribute = si->dwFillAttribute; siw.lpDesktop = utf8_to_wchar0 (si->lpDesktop); siw.lpTitle = utf8_to_wchar0 (si->lpTitle); } ret = CreateProcessW (application_name, command_line, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, working_directory, si ? &siw : NULL, lpProcessInformation); free (siw.lpTitle); free (siw.lpDesktop); free (application_name); free (command_line); free (working_directory); return ret; } /* Entry point called by the DLL loader. */ #ifdef DLL_EXPORT int WINAPI DllMain (HINSTANCE hinst, DWORD reason, LPVOID reserved) { (void)reserved; if (reason == DLL_PROCESS_ATTACH) my_hmodule = hinst; return TRUE; } #endif /*DLL_EXPORT*/ diff --git a/src/wait.c b/src/wait.c index 1da9e93d..e6161018 100644 --- a/src/wait.c +++ b/src/wait.c @@ -1,222 +1,222 @@ /* wait.c * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 g10 Code GmbH * * This file is part of GPGME. * * GPGME 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. * * GPGME 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 . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #include "util.h" #include "context.h" #include "ops.h" #include "wait.h" #include "sema.h" #include "priv-io.h" #include "engine.h" #include "debug.h" void _gpgme_fd_table_init (fd_table_t fdt) { fdt->fds = NULL; fdt->size = 0; } void _gpgme_fd_table_deinit (fd_table_t fdt) { if (fdt->fds) free (fdt->fds); } /* XXX We should keep a marker and roll over for speed. */ static gpgme_error_t fd_table_put (fd_table_t fdt, int fd, int dir, void *opaque, int *idx) { unsigned int i, j; struct io_select_fd_s *new_fds; for (i = 0; i < fdt->size; i++) { if (fdt->fds[i].fd == -1) break; } if (i == fdt->size) { #define FDT_ALLOCSIZE 10 new_fds = realloc (fdt->fds, (fdt->size + FDT_ALLOCSIZE) * sizeof (*new_fds)); if (!new_fds) return gpg_error_from_syserror (); fdt->fds = new_fds; fdt->size += FDT_ALLOCSIZE; for (j = 0; j < FDT_ALLOCSIZE; j++) fdt->fds[i + j].fd = -1; } fdt->fds[i].fd = fd; fdt->fds[i].for_read = (dir == 1); fdt->fds[i].for_write = (dir == 0); fdt->fds[i].signaled = 0; fdt->fds[i].opaque = opaque; *idx = i; return 0; } /* Register the file descriptor FD with the handler FNC (which gets FNC_DATA as its first argument) for the direction DIR. DATA should be the context for which the fd is added. R_TAG will hold the tag that can be used to remove the fd. */ gpgme_error_t _gpgme_add_io_cb (void *data, int fd, int dir, gpgme_io_cb_t fnc, void *fnc_data, void **r_tag) { gpgme_error_t err; gpgme_ctx_t ctx = (gpgme_ctx_t) data; fd_table_t fdt; struct wait_item_s *item; struct tag *tag; assert (fnc); assert (ctx); fdt = &ctx->fdt; assert (fdt); tag = malloc (sizeof *tag); if (!tag) return gpg_error_from_syserror (); tag->ctx = ctx; /* Allocate a structure to hold information about the handler. */ item = calloc (1, sizeof *item); if (!item) { free (tag); return gpg_error_from_syserror (); } item->ctx = ctx; item->dir = dir; item->handler = fnc; item->handler_value = fnc_data; err = fd_table_put (fdt, fd, dir, item, &tag->idx); if (err) { free (tag); free (item); return err; } TRACE (DEBUG_CTX, "_gpgme_add_io_cb", ctx, - "fd %d, dir=%d -> tag=%p", fd, dir, tag); + "fd=%d, dir=%d -> tag=%p", fd, dir, tag); *r_tag = tag; return 0; } void _gpgme_remove_io_cb (void *data) { struct tag *tag = data; gpgme_ctx_t ctx; fd_table_t fdt; int idx; assert (tag); ctx = tag->ctx; assert (ctx); fdt = &ctx->fdt; assert (fdt); idx = tag->idx; TRACE (DEBUG_CTX, "_gpgme_remove_io_cb", data, "setting fd 0x%x (item=%p) done", fdt->fds[idx].fd, fdt->fds[idx].opaque); free (fdt->fds[idx].opaque); free (tag); /* Free the table entry. */ fdt->fds[idx].fd = -1; fdt->fds[idx].for_read = 0; fdt->fds[idx].for_write = 0; fdt->fds[idx].opaque = NULL; } /* This is slightly embarrassing. The problem is that running an I/O callback _may_ influence the status of other file descriptors. Our own event loops could compensate for that, but the external event loops cannot. FIXME: We may still want to optimize this a bit when we are called from our own event loops. So if CHECKED is 1, the check is skipped. */ gpgme_error_t _gpgme_run_io_cb (struct io_select_fd_s *an_fds, int checked, gpgme_error_t *op_err) { struct wait_item_s *item; struct io_cb_data iocb_data; gpgme_error_t err; item = (struct wait_item_s *) an_fds->opaque; assert (item); if (!checked) { int nr; struct io_select_fd_s fds; TRACE (DEBUG_CTX, "_gpgme_run_io_cb", item, "need to check"); fds = *an_fds; fds.signaled = 0; /* Just give it a quick poll. */ nr = _gpgme_io_select (&fds, 1, 1); assert (nr <= 1); if (nr < 0) return errno; else if (nr == 0) /* The status changed in the meantime, there is nothing left to do. */ return 0; } TRACE (DEBUG_CTX, "_gpgme_run_io_cb", item, "handler (%p, %d)", item->handler_value, an_fds->fd); iocb_data.handler_value = item->handler_value; iocb_data.op_err = 0; err = item->handler (&iocb_data, an_fds->fd); *op_err = iocb_data.op_err; return err; }