diff --git a/src/data-compat.c b/src/data-compat.c index 830b42c6..9091b968 100644 --- a/src/data-compat.c +++ b/src/data-compat.c @@ -1,233 +1,233 @@ /* 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=%u", - fname, stream, offset, length); + "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; return TRACE_SUC ("r_dh=%p", *r_dh); } /* 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, "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=%u", buffer, size); + "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 ((gpgme_ssize_t)amt); + 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", offset, whence); + "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-mem.c b/src/data-mem.c index c461a606..915c3e00 100644 --- a/src/data-mem.c +++ b/src/data-mem.c @@ -1,305 +1,305 @@ /* 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); return TRACE_SUC ("dh=%p", *r_dh); } /* 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=%u, copy=%i (%s)", buffer, size, + "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; return TRACE_SUC ("dh=%p", *r_dh); } /* 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=%u", str, *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, ""); if (buffer) free (buffer); } diff --git a/src/data.c b/src/data.c index f633e08c..44ef2d3d 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=%u", buffer, size); + "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 (res); + 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=%u", buffer, size); + "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 (res); + 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", offset, whence); + "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 (offset); + 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); 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); 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 29f7dc6d..f0b0da0e 100644 --- a/src/debug.c +++ b/src/debug.c @@ -1,433 +1,433 @@ /* debug.c - helpful output in desperate situations * 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 #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" /* Lock to serialize initialization of the debug output subsystem and output of actual debug messages. */ DEFINE_STATIC_LOCK (debug_lock); /* 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; LOCK (debug_lock); 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) { UNLOCK (debug_lock); 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); } } UNLOCK (debug_lock); if (debug_level > 0) { _gpgme_debug (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: gpgme='%s'\n", name? name: "?"); } #endif } } /* This should be called as soon as the locks are initialized. It is required so that the assuan logging gets conncted 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) * * 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, const char *tagvalue, const char *format, ...) { va_list arg_ptr; int saved_errno; saved_errno = errno; if (debug_level < level) return 0; va_start (arg_ptr, format); LOCK (debug_lock); { struct tm *tp; time_t atime = time (NULL); tp = localtime (&atime); fprintf (errfp, "GPGME %04d-%02d-%02d %02d:%02d:%02d <0x%04llx> ", 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 ()); } #ifdef FRAME_NR { int indent; indent = frame_nr > 0? (2 * (frame_nr - 1)):0; fprintf (errfp, "%*s", indent < 40? indent : 40, ""); } #endif switch (mode) { case -1: /* Do nothing. */ break; case 0: fprintf (errfp, "%s: call: %s=%p ", func, tagname, tagvalue); break; case 1: fprintf (errfp, "%s: enter: %s=%p ", func, tagname, tagvalue); break; case 2: fprintf (errfp, "%s: check: %s=%p ", func, tagname, tagvalue); break; case 3: if (tagname) fprintf (errfp, "%s: leave: %s=%p ", func, tagname, tagvalue); else fprintf (errfp, "%s: leave: ", func); break; default: fprintf (errfp, "%s: m=%d: %s=%p ", func, mode, tagname, tagvalue); break; } vfprintf (errfp, format, arg_ptr); va_end (arg_ptr); if(format && *format && format[strlen (format) - 1] != '\n') putc ('\n', errfp); UNLOCK (debug_lock); fflush (errfp); 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", *line); + _gpgme_debug (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); } } diff --git a/src/gpgme.c b/src/gpgme.c index ac98bd59..c4a1da11 100644 --- a/src/gpgme.c +++ b/src/gpgme.c @@ -1,1294 +1,1294 @@ /* gpgme.c - GnuPG Made Easy. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2012, * 2014, 2015 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 #include #ifdef HAVE_LOCALE_H #include #endif #include "util.h" #include "context.h" #include "ops.h" #include "wait.h" #include "debug.h" #include "priv-io.h" #include "sys-util.h" #include "mbox-util.h" /* The default locale. */ DEFINE_STATIC_LOCK (def_lc_lock); static char *def_lc_ctype; static char *def_lc_messages; gpgme_error_t _gpgme_selftest = GPG_ERR_NOT_OPERATIONAL; /* Protects all reference counters in result structures. All other accesses to a result structure are read only. */ DEFINE_STATIC_LOCK (result_ref_lock); /* Set the global flag NAME to VALUE. Return 0 on success. Note that this function does not use gpgme_error and thus a non-zero return value merely means "error". Certain flags may be set before gpgme_check_version is called. See the manual for a description of supported flags. The caller must assure that this function is called only by one thread at a time. */ int gpgme_set_global_flag (const char *name, const char *value) { if (!name || !value) return -1; else if (!strcmp (name, "debug")) return _gpgme_debug_set_debug_envvar (value); else if (!strcmp (name, "disable-gpgconf")) { _gpgme_dirinfo_disable_gpgconf (); return 0; } else if (!strcmp (name, "require-gnupg")) return _gpgme_set_engine_minimal_version (value); else if (!strcmp (name, "gpgconf-name")) return _gpgme_set_default_gpgconf_name (value); else if (!strcmp (name, "gpg-name")) return _gpgme_set_default_gpg_name (value); else if (!strcmp (name, "w32-inst-dir")) return _gpgme_set_override_inst_dir (value); else return -1; } /* Create a new context as an environment for GPGME crypto operations. */ gpgme_error_t gpgme_new (gpgme_ctx_t *r_ctx) { gpgme_error_t err; gpgme_ctx_t ctx; TRACE_BEG (DEBUG_CTX, "gpgme_new", r_ctx, ""); if (_gpgme_selftest) return TRACE_ERR (_gpgme_selftest); if (!r_ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); ctx = calloc (1, sizeof *ctx); if (!ctx) return TRACE_ERR (gpg_error_from_syserror ()); INIT_LOCK (ctx->lock); err = _gpgme_engine_info_copy (&ctx->engine_info); if (!err && !ctx->engine_info) err = gpg_error (GPG_ERR_NO_ENGINE); if (err) { free (ctx); return TRACE_ERR (err); } ctx->keylist_mode = GPGME_KEYLIST_MODE_LOCAL; ctx->include_certs = GPGME_INCLUDE_CERTS_DEFAULT; ctx->protocol = GPGME_PROTOCOL_OpenPGP; ctx->sub_protocol = GPGME_PROTOCOL_DEFAULT; _gpgme_fd_table_init (&ctx->fdt); LOCK (def_lc_lock); if (def_lc_ctype) { ctx->lc_ctype = strdup (def_lc_ctype); if (!ctx->lc_ctype) { int saved_err = gpg_error_from_syserror (); UNLOCK (def_lc_lock); _gpgme_engine_info_release (ctx->engine_info); free (ctx); return TRACE_ERR (saved_err); } } else def_lc_ctype = NULL; if (def_lc_messages) { ctx->lc_messages = strdup (def_lc_messages); if (!ctx->lc_messages) { int saved_err = gpg_error_from_syserror (); UNLOCK (def_lc_lock); if (ctx->lc_ctype) free (ctx->lc_ctype); _gpgme_engine_info_release (ctx->engine_info); free (ctx); return TRACE_ERR (saved_err); } } else def_lc_messages = NULL; UNLOCK (def_lc_lock); *r_ctx = ctx; return TRACE_SUC ("ctx=%p", ctx); } gpgme_error_t _gpgme_cancel_with_err (gpgme_ctx_t ctx, gpg_error_t ctx_err, gpg_error_t op_err) { gpgme_error_t err; struct gpgme_io_event_done_data data; TRACE_BEG (DEBUG_CTX, "_gpgme_cancel_with_err", ctx, "ctx_err=%i, op_err=%i", ctx_err, op_err); if (ctx_err) { err = _gpgme_engine_cancel (ctx->engine); if (err) return TRACE_ERR (err); } else { err = _gpgme_engine_cancel_op (ctx->engine); if (err) return TRACE_ERR (err); } data.err = ctx_err; data.op_err = op_err; _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data); return TRACE_ERR (0); } /* Cancel a pending asynchronous operation. */ gpgme_error_t gpgme_cancel (gpgme_ctx_t ctx) { gpg_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_cancel", ctx, ""); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = _gpgme_cancel_with_err (ctx, gpg_error (GPG_ERR_CANCELED), 0); return TRACE_ERR (err); } /* Cancel a pending operation asynchronously. */ gpgme_error_t gpgme_cancel_async (gpgme_ctx_t ctx) { TRACE_BEG (DEBUG_CTX, "gpgme_cancel_async", ctx, ""); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); LOCK (ctx->lock); ctx->canceled = 1; UNLOCK (ctx->lock); return TRACE_ERR (0); } /* Release all resources associated with the given context. */ void gpgme_release (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_release", ctx, ""); if (!ctx) return; _gpgme_engine_release (ctx->engine); ctx->engine = NULL; _gpgme_fd_table_deinit (&ctx->fdt); _gpgme_release_result (ctx); _gpgme_signers_clear (ctx); _gpgme_sig_notation_clear (ctx); free (ctx->sender); free (ctx->signers); free (ctx->lc_ctype); free (ctx->lc_messages); free (ctx->override_session_key); free (ctx->request_origin); free (ctx->auto_key_locate); free (ctx->trust_model); _gpgme_engine_info_release (ctx->engine_info); ctx->engine_info = NULL; DESTROY_LOCK (ctx->lock); free (ctx); } void gpgme_result_ref (void *result) { struct ctx_op_data *data; if (! result) return; data = (void*)((char*)result - sizeof (struct ctx_op_data)); assert (data->magic == CTX_OP_DATA_MAGIC); LOCK (result_ref_lock); data->references++; UNLOCK (result_ref_lock); } void gpgme_result_unref (void *result) { struct ctx_op_data *data; if (! result) return; data = (void*)((char*)result - sizeof (struct ctx_op_data)); assert (data->magic == CTX_OP_DATA_MAGIC); LOCK (result_ref_lock); if (--data->references) { UNLOCK (result_ref_lock); return; } UNLOCK (result_ref_lock); if (data->cleanup) (*data->cleanup) (data->hook); free (data); } void _gpgme_release_result (gpgme_ctx_t ctx) { struct ctx_op_data *data = ctx->op_data; while (data) { struct ctx_op_data *next_data = data->next; data->next = NULL; gpgme_result_unref (data->hook); data = next_data; } ctx->op_data = NULL; } gpgme_error_t gpgme_set_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol) { TRACE_BEG (DEBUG_CTX, "gpgme_set_protocol", ctx, "protocol=%i (%s)", protocol, gpgme_get_protocol_name (protocol) ? gpgme_get_protocol_name (protocol) : "invalid"); if (protocol != GPGME_PROTOCOL_OpenPGP && protocol != GPGME_PROTOCOL_CMS && protocol != GPGME_PROTOCOL_GPGCONF && protocol != GPGME_PROTOCOL_ASSUAN && protocol != GPGME_PROTOCOL_G13 && protocol != GPGME_PROTOCOL_UISERVER && protocol != GPGME_PROTOCOL_SPAWN) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (ctx->protocol != protocol) { /* Shut down the engine when switching protocols. */ if (ctx->engine) { TRACE_LOG ("releasing ctx->engine=%p", ctx->engine); _gpgme_engine_release (ctx->engine); ctx->engine = NULL; } ctx->protocol = protocol; } return TRACE_ERR (0); } gpgme_protocol_t gpgme_get_protocol (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_get_protocol", ctx, "ctx->protocol=%i (%s)", ctx->protocol, gpgme_get_protocol_name (ctx->protocol) ? gpgme_get_protocol_name (ctx->protocol) : "invalid"); return ctx->protocol; } gpgme_error_t gpgme_set_sub_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol) { TRACE (DEBUG_CTX, "gpgme_set_sub_protocol", ctx, "protocol=%i (%s)", protocol, gpgme_get_protocol_name (protocol) ? gpgme_get_protocol_name (protocol) : "invalid"); if (!ctx) return gpg_error (GPG_ERR_INV_VALUE); ctx->sub_protocol = protocol; return 0; } gpgme_protocol_t gpgme_get_sub_protocol (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_get_sub_protocol", ctx, "ctx->sub_protocol=%i (%s)", ctx->sub_protocol, gpgme_get_protocol_name (ctx->sub_protocol) ? gpgme_get_protocol_name (ctx->sub_protocol) : "invalid"); return ctx->sub_protocol; } const char * gpgme_get_protocol_name (gpgme_protocol_t protocol) { switch (protocol) { case GPGME_PROTOCOL_OpenPGP: return "OpenPGP"; case GPGME_PROTOCOL_CMS: return "CMS"; case GPGME_PROTOCOL_GPGCONF: return "GPGCONF"; case GPGME_PROTOCOL_ASSUAN: return "Assuan"; case GPGME_PROTOCOL_G13: return "G13"; case GPGME_PROTOCOL_UISERVER: return "UIServer"; case GPGME_PROTOCOL_SPAWN: return "Spawn"; case GPGME_PROTOCOL_DEFAULT: return "default"; case GPGME_PROTOCOL_UNKNOWN: return "unknown"; default: return NULL; } } /* Store the sender's address in the context. ADDRESS is addr-spec of * mailbox but my also be a complete mailbox, in which case this * function extracts the addr-spec from it. Returns 0 on success or * an error code if no valid addr-spec could be extracted from * ADDRESS. */ gpgme_error_t gpgme_set_sender (gpgme_ctx_t ctx, const char *address) { char *p = NULL; TRACE_BEG (DEBUG_CTX, "gpgme_set_sender", ctx, "sender='%s'", address?address:"(null)"); if (!ctx || (address && !(p = _gpgme_mailbox_from_userid (address)))) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); free (ctx->sender); ctx->sender = p; return TRACE_ERR (0); } /* Return the sender's address (addr-spec part) from the context or * NULL if none was set. The returned value is valid as long as the * CTX is valid and gpgme_set_sender has not been used. */ const char * gpgme_get_sender (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_get_sender", ctx, "sender='%s'", ctx?ctx->sender:""); return ctx->sender; } /* Enable or disable the use of an ascii armor for all output. */ void gpgme_set_armor (gpgme_ctx_t ctx, int use_armor) { TRACE (DEBUG_CTX, "gpgme_set_armor", ctx, "use_armor=%i (%s)", use_armor, use_armor ? "yes" : "no"); if (!ctx) return; ctx->use_armor = !!use_armor; } /* Return the state of the armor flag. */ int gpgme_get_armor (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_get_armor", ctx, "ctx->use_armor=%i (%s)", ctx->use_armor, ctx->use_armor ? "yes" : "no"); return ctx->use_armor; } /* Set the flag NAME for CTX to VALUE. Please consult the manual for * a description of the flags. */ gpgme_error_t gpgme_set_ctx_flag (gpgme_ctx_t ctx, const char *name, const char *value) { gpgme_error_t err = 0; int abool; TRACE (DEBUG_CTX, "gpgme_set_ctx_flag", ctx, "name='%s' value='%s'", name? name:"(null)", value?value:"(null)"); abool = (value && *value)? !!atoi (value) : 0; if (!ctx || !name || !value) err = gpg_error (GPG_ERR_INV_VALUE); else if (!strcmp (name, "redraw")) { ctx->redraw_suggested = abool; } else if (!strcmp (name, "full-status")) { ctx->full_status = abool; } else if (!strcmp (name, "raw-description")) { ctx->raw_description = abool; } else if (!strcmp (name, "export-session-key")) { ctx->export_session_keys = abool; } else if (!strcmp (name, "override-session-key")) { free (ctx->override_session_key); ctx->override_session_key = strdup (value); if (!ctx->override_session_key) err = gpg_error_from_syserror (); } else if (!strcmp (name, "auto-key-retrieve")) { ctx->auto_key_retrieve = abool; } else if (!strcmp (name, "request-origin")) { free (ctx->request_origin); ctx->request_origin = strdup (value); if (!ctx->request_origin) err = gpg_error_from_syserror (); } else if (!strcmp (name, "no-symkey-cache")) { ctx->no_symkey_cache = abool; } else if (!strcmp (name, "ignore-mdc-error")) { ctx->ignore_mdc_error = abool; } else if (!strcmp (name, "auto-key-locate")) { free (ctx->auto_key_locate); ctx->auto_key_locate = strdup (value); if (!ctx->auto_key_locate) err = gpg_error_from_syserror (); } else if (!strcmp (name, "trust-model")) { free (ctx->trust_model); ctx->trust_model = strdup (value); if (!ctx->trust_model) err = gpg_error_from_syserror (); } else err = gpg_error (GPG_ERR_UNKNOWN_NAME); return err; } /* Get the context flag named NAME. See gpgme_set_ctx_flag for a list * of valid names. If the NAME is unknown NULL is returned. For a * boolean flag an empty string is returned for False and the string * "1" for True; thus either atoi or a simple string test can be * used. */ const char * gpgme_get_ctx_flag (gpgme_ctx_t ctx, const char *name) { if (!ctx || !name) return NULL; else if (!strcmp (name, "redraw")) { return ctx->redraw_suggested? "1":""; } else if (!strcmp (name, "full-status")) { return ctx->full_status? "1":""; } else if (!strcmp (name, "raw-description")) { return ctx->raw_description? "1":""; } else if (!strcmp (name, "export-session-key")) { return ctx->export_session_keys? "1":""; } else if (!strcmp (name, "override-session-key")) { return ctx->override_session_key? ctx->override_session_key : ""; } else if (!strcmp (name, "auto-key-retrieve")) { return ctx->auto_key_retrieve? "1":""; } else if (!strcmp (name, "request-origin")) { return ctx->request_origin? ctx->request_origin : ""; } else if (!strcmp (name, "no-symkey-cache")) { return ctx->no_symkey_cache? "1":""; } else if (!strcmp (name, "ignore-mdc-error")) { return ctx->ignore_mdc_error? "1":""; } else if (!strcmp (name, "auto-key-locate")) { return ctx->auto_key_locate? ctx->auto_key_locate : ""; } else return NULL; } /* Enable or disable the use of the special textmode. Textmode is for example used for the RFC2015 signatures; note that the updated RFC 3156 mandates that the MUA does some preparations so that textmode is not needed anymore. */ void gpgme_set_textmode (gpgme_ctx_t ctx, int use_textmode) { TRACE (DEBUG_CTX, "gpgme_set_textmode", ctx, "use_textmode=%i (%s)", use_textmode, use_textmode ? "yes" : "no"); if (!ctx) return; ctx->use_textmode = !!use_textmode; } /* Return the state of the textmode flag. */ int gpgme_get_textmode (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_get_textmode", ctx, "ctx->use_textmode=%i (%s)", ctx->use_textmode, ctx->use_textmode ? "yes" : "no"); return ctx->use_textmode; } /* Enable offline mode for this context. In offline mode dirmngr will be disabled. */ void gpgme_set_offline (gpgme_ctx_t ctx, int offline) { TRACE (DEBUG_CTX, "gpgme_set_offline", ctx, "offline=%i (%s)", offline, offline ? "yes" : "no"); if (!ctx) return; ctx->offline = !!offline; } /* Return the state of the offline flag. */ int gpgme_get_offline (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_get_offline", ctx, "ctx->offline=%i (%s)", ctx->offline, ctx->offline ? "yes" : "no"); return ctx->offline; } /* Set the number of certifications to include in an S/MIME message. The default is GPGME_INCLUDE_CERTS_DEFAULT. -1 means all certs, and -2 means all certs except the root cert. */ void gpgme_set_include_certs (gpgme_ctx_t ctx, int nr_of_certs) { if (!ctx) return; if (nr_of_certs == GPGME_INCLUDE_CERTS_DEFAULT) ctx->include_certs = GPGME_INCLUDE_CERTS_DEFAULT; else if (nr_of_certs < -2) ctx->include_certs = -2; else ctx->include_certs = nr_of_certs; TRACE (DEBUG_CTX, "gpgme_set_include_certs", ctx, "nr_of_certs=%i%s", nr_of_certs, nr_of_certs == ctx->include_certs ? "" : " (-2)"); } /* Get the number of certifications to include in an S/MIME message. */ int gpgme_get_include_certs (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_get_include_certs", ctx, "ctx->include_certs=%i", ctx->include_certs); return ctx->include_certs; } /* This function changes the default behaviour of the keylisting functions. MODE is a bitwise-OR of the GPGME_KEYLIST_* flags. The default mode is GPGME_KEYLIST_MODE_LOCAL. */ gpgme_error_t gpgme_set_keylist_mode (gpgme_ctx_t ctx, gpgme_keylist_mode_t mode) { TRACE (DEBUG_CTX, "gpgme_set_keylist_mode", ctx, "keylist_mode=0x%x", mode); if (!ctx) return gpg_error (GPG_ERR_INV_VALUE); ctx->keylist_mode = mode; return 0; } /* This function returns the default behaviour of the keylisting functions. */ gpgme_keylist_mode_t gpgme_get_keylist_mode (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_get_keylist_mode", ctx, "ctx->keylist_mode=0x%x", ctx->keylist_mode); return ctx->keylist_mode; } /* Set the pinentry mode for CTX to MODE. */ gpgme_error_t gpgme_set_pinentry_mode (gpgme_ctx_t ctx, gpgme_pinentry_mode_t mode) { TRACE (DEBUG_CTX, "gpgme_set_pinentry_mode", ctx, "pinentry_mode=%u", (unsigned int)mode); if (!ctx) return gpg_error (GPG_ERR_INV_VALUE); switch (mode) { case GPGME_PINENTRY_MODE_DEFAULT: case GPGME_PINENTRY_MODE_ASK: case GPGME_PINENTRY_MODE_CANCEL: case GPGME_PINENTRY_MODE_ERROR: case GPGME_PINENTRY_MODE_LOOPBACK: break; default: return gpg_error (GPG_ERR_INV_VALUE); } ctx->pinentry_mode = mode; return 0; } /* Get the pinentry mode of CTX. */ gpgme_pinentry_mode_t gpgme_get_pinentry_mode (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_get_pinentry_mode", ctx, "ctx->pinentry_mode=%u", (unsigned int)ctx->pinentry_mode); return ctx->pinentry_mode; } /* This function sets a callback function to be used to pass a passphrase to gpg. */ void gpgme_set_passphrase_cb (gpgme_ctx_t ctx, gpgme_passphrase_cb_t cb, void *cb_value) { TRACE (DEBUG_CTX, "gpgme_set_passphrase_cb", ctx, "passphrase_cb=%p/%p", cb, cb_value); if (!ctx) return; ctx->passphrase_cb = cb; ctx->passphrase_cb_value = cb_value; } /* This function returns the callback function to be used to pass a passphrase to the crypto engine. */ void gpgme_get_passphrase_cb (gpgme_ctx_t ctx, gpgme_passphrase_cb_t *r_cb, void **r_cb_value) { TRACE (DEBUG_CTX, "gpgme_get_passphrase_cb", ctx, "ctx->passphrase_cb=%p/%p", ctx->passphrase_cb, ctx->passphrase_cb_value); if (r_cb) *r_cb = ctx->passphrase_cb; if (r_cb_value) *r_cb_value = ctx->passphrase_cb_value; } /* This function sets a callback function to be used as a progress indicator. */ void gpgme_set_progress_cb (gpgme_ctx_t ctx, gpgme_progress_cb_t cb, void *cb_value) { TRACE (DEBUG_CTX, "gpgme_set_progress_cb", ctx, "progress_cb=%p/%p", cb, cb_value); if (!ctx) return; ctx->progress_cb = cb; ctx->progress_cb_value = cb_value; } /* This function returns the callback function to be used as a progress indicator. */ void gpgme_get_progress_cb (gpgme_ctx_t ctx, gpgme_progress_cb_t *r_cb, void **r_cb_value) { TRACE (DEBUG_CTX, "gpgme_get_progress_cb", ctx, "ctx->progress_cb=%p/%p", ctx->progress_cb, ctx->progress_cb_value); if (r_cb) *r_cb = ctx->progress_cb; if (r_cb_value) *r_cb_value = ctx->progress_cb_value; } /* This function sets a callback function to be used as a status message forwarder. */ void gpgme_set_status_cb (gpgme_ctx_t ctx, gpgme_status_cb_t cb, void *cb_value) { TRACE (DEBUG_CTX, "gpgme_set_status_cb", ctx, "status_cb=%p/%p", cb, cb_value); if (!ctx) return; ctx->status_cb = cb; ctx->status_cb_value = cb_value; } /* This function returns the callback function to be used as a status message forwarder. */ void gpgme_get_status_cb (gpgme_ctx_t ctx, gpgme_status_cb_t *r_cb, void **r_cb_value) { TRACE (DEBUG_CTX, "gpgme_get_status_cb", ctx, "ctx->status_cb=%p/%p", ctx ? ctx->status_cb : NULL, ctx ? ctx->status_cb_value : NULL); if (r_cb) *r_cb = NULL; if (r_cb_value) *r_cb_value = NULL; if (!ctx || !ctx->status_cb) return; if (r_cb) *r_cb = ctx->status_cb; if (r_cb_value) *r_cb_value = ctx->status_cb_value; } /* Set the I/O callback functions for CTX to IO_CBS. */ void gpgme_set_io_cbs (gpgme_ctx_t ctx, gpgme_io_cbs_t io_cbs) { if (!ctx) return; if (io_cbs) { TRACE (DEBUG_CTX, "gpgme_set_io_cbs", ctx, "io_cbs=%p (add=%p/%p, remove=%p, event=%p/%p", io_cbs, io_cbs->add, io_cbs->add_priv, io_cbs->remove, io_cbs->event, io_cbs->event_priv); ctx->io_cbs = *io_cbs; } else { TRACE (DEBUG_CTX, "gpgme_set_io_cbs", ctx, "io_cbs=%p (default)", io_cbs); ctx->io_cbs.add = NULL; ctx->io_cbs.add_priv = NULL; ctx->io_cbs.remove = NULL; ctx->io_cbs.event = NULL; ctx->io_cbs.event_priv = NULL; } } /* This function provides access to the internal read function; it is normally not used. */ gpgme_ssize_t gpgme_io_read (int fd, void *buffer, size_t count) { int ret; TRACE_BEG (DEBUG_GLOBAL, "gpgme_io_read", fd, - "buffer=%p, count=%u", buffer, count); + "buffer=%p, count=%zu", buffer, count); ret = _gpgme_io_read (fd, buffer, count); return TRACE_SYSRES (ret); } /* This function provides access to the internal write function. It is to be used by user callbacks to return data to gpgme. See gpgme_passphrase_cb_t and gpgme_edit_cb_t. */ gpgme_ssize_t gpgme_io_write (int fd, const void *buffer, size_t count) { int ret; TRACE_BEG (DEBUG_GLOBAL, "gpgme_io_write", fd, - "buffer=%p, count=%u", buffer, count); + "buffer=%p, count=%zu", buffer, count); ret = _gpgme_io_write (fd, buffer, count); return TRACE_SYSRES (ret); } /* This function provides access to the internal write function. It is to be used by user callbacks to return data to gpgme. See gpgme_passphrase_cb_t and gpgme_edit_cb_t. Note that this is a variant of gpgme_io_write which guarantees that all COUNT bytes are written or an error is return. Returns: 0 on success or -1 on error and the sets errno. */ int gpgme_io_writen (int fd, const void *buffer_arg, size_t count) { const char *buffer = buffer_arg; int ret = 0; TRACE_BEG (DEBUG_GLOBAL, "gpgme_io_writen", fd, - "buffer=%p, count=%u", buffer, count); + "buffer=%p, count=%zu", buffer, count); while (count) { ret = _gpgme_io_write (fd, buffer, count); if (ret < 0) break; buffer += ret; count -= ret; ret = 0; } return TRACE_SYSRES (ret); } /* This function returns the callback function for I/O. */ void gpgme_get_io_cbs (gpgme_ctx_t ctx, gpgme_io_cbs_t io_cbs) { TRACE (DEBUG_CTX, "gpgme_get_io_cbs", ctx, "io_cbs=%p, ctx->io_cbs.add=%p/%p, .remove=%p, .event=%p/%p", io_cbs, io_cbs->add, io_cbs->add_priv, io_cbs->remove, io_cbs->event, io_cbs->event_priv); *io_cbs = ctx->io_cbs; } /* This function sets the locale for the context CTX, or the default locale if CTX is a null pointer. */ gpgme_error_t gpgme_set_locale (gpgme_ctx_t ctx, int category, const char *value) { int failed = 0; char *new_lc_ctype = NULL; char *new_lc_messages = NULL; TRACE_BEG (DEBUG_CTX, "gpgme_set_locale", ctx, "category=%i, value=%s", category, value ? value : "(null)"); #define PREPARE_ONE_LOCALE(lcat, ucat) \ if (!failed && value \ && (category == LC_ALL || category == LC_ ## ucat)) \ { \ new_lc_ ## lcat = strdup (value); \ if (!new_lc_ ## lcat) \ failed = 1; \ } #ifdef LC_CTYPE PREPARE_ONE_LOCALE (ctype, CTYPE); #endif #ifdef LC_MESSAGES PREPARE_ONE_LOCALE (messages, MESSAGES); #endif if (failed) { int saved_err = gpg_error_from_syserror (); if (new_lc_ctype) free (new_lc_ctype); if (new_lc_messages) free (new_lc_messages); return TRACE_ERR (saved_err); } #define SET_ONE_LOCALE(lcat, ucat) \ if (category == LC_ALL || category == LC_ ## ucat) \ { \ if (ctx) \ { \ if (ctx->lc_ ## lcat) \ free (ctx->lc_ ## lcat); \ ctx->lc_ ## lcat = new_lc_ ## lcat; \ } \ else \ { \ if (def_lc_ ## lcat) \ free (def_lc_ ## lcat); \ def_lc_ ## lcat = new_lc_ ## lcat; \ } \ } if (!ctx) LOCK (def_lc_lock); #ifdef LC_CTYPE SET_ONE_LOCALE (ctype, CTYPE); #endif #ifdef LC_MESSAGES SET_ONE_LOCALE (messages, MESSAGES); #endif if (!ctx) UNLOCK (def_lc_lock); return TRACE_ERR (0); } /* Get the information about the configured engines. A pointer to the first engine in the statically allocated linked list is returned. The returned data is valid until the next gpgme_ctx_set_engine_info. */ gpgme_engine_info_t gpgme_ctx_get_engine_info (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_ctx_get_engine_info", ctx, "ctx->engine_info=%p", ctx->engine_info); return ctx->engine_info; } /* Set the engine info for the context CTX, protocol PROTO, to the file name FILE_NAME and the home directory HOME_DIR. */ gpgme_error_t gpgme_ctx_set_engine_info (gpgme_ctx_t ctx, gpgme_protocol_t proto, const char *file_name, const char *home_dir) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_ctx_set_engine_info", ctx, "protocol=%i (%s), file_name=%s, home_dir=%s", proto, gpgme_get_protocol_name (proto) ? gpgme_get_protocol_name (proto) : "unknown", file_name ? file_name : "(default)", home_dir ? home_dir : "(default)"); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); /* Shut down the engine when changing engine info. */ if (ctx->engine) { TRACE_LOG ("releasing ctx->engine=%p", ctx->engine); _gpgme_engine_release (ctx->engine); ctx->engine = NULL; } err = _gpgme_set_engine_info (ctx->engine_info, proto, file_name, home_dir); return TRACE_ERR (err); } /* Clear all notation data from the context. */ void _gpgme_sig_notation_clear (gpgme_ctx_t ctx) { gpgme_sig_notation_t notation; if (!ctx) return; notation = ctx->sig_notations; while (notation) { gpgme_sig_notation_t next_notation = notation->next; _gpgme_sig_notation_free (notation); notation = next_notation; } ctx->sig_notations = NULL; } void gpgme_sig_notation_clear (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_sig_notation_clear", ctx, ""); if (!ctx) return; _gpgme_sig_notation_clear (ctx); } /* Add the human-readable notation data with name NAME and value VALUE to the context CTX, using the flags FLAGS. If NAME is NULL, then VALUE should be a policy URL. The flag GPGME_SIG_NOTATION_HUMAN_READABLE is forced to be true for notation data, and false for policy URLs. */ gpgme_error_t gpgme_sig_notation_add (gpgme_ctx_t ctx, const char *name, const char *value, gpgme_sig_notation_flags_t flags) { gpgme_error_t err; gpgme_sig_notation_t notation; gpgme_sig_notation_t *lastp; TRACE_BEG (DEBUG_CTX, "gpgme_sig_notation_add", ctx, "name=%s, value=%s, flags=0x%x", name ? name : "(null)", value ? value : "(null)", flags); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (name) flags |= GPGME_SIG_NOTATION_HUMAN_READABLE; else flags &= ~GPGME_SIG_NOTATION_HUMAN_READABLE; err = _gpgme_sig_notation_create (¬ation, name, name ? strlen (name) : 0, value, value ? strlen (value) : 0, flags); if (err) return TRACE_ERR (err); lastp = &ctx->sig_notations; while (*lastp) lastp = &(*lastp)->next; *lastp = notation; return TRACE_ERR (0); } /* Get the sig notations for this context. */ gpgme_sig_notation_t gpgme_sig_notation_get (gpgme_ctx_t ctx) { if (!ctx) { TRACE (DEBUG_CTX, "gpgme_sig_notation_get", ctx, ""); return NULL; } TRACE (DEBUG_CTX, "gpgme_sig_notation_get", ctx, "ctx->sig_notations=%p", ctx->sig_notations); return ctx->sig_notations; } /* Return a public key algorithm string made of the algorithm and size or the curve name. May return NULL on error. Caller must free the result using gpgme_free. */ char * gpgme_pubkey_algo_string (gpgme_subkey_t subkey) { const char *prefix = NULL; char *result; if (!subkey) { gpg_err_set_errno (EINVAL); return NULL; } switch (subkey->pubkey_algo) { case GPGME_PK_RSA: case GPGME_PK_RSA_E: case GPGME_PK_RSA_S: prefix = "rsa"; break; case GPGME_PK_ELG_E: prefix = "elg"; break; case GPGME_PK_DSA: prefix = "dsa"; break; case GPGME_PK_ELG: prefix = "xxx"; break; case GPGME_PK_ECC: case GPGME_PK_ECDH: case GPGME_PK_ECDSA: case GPGME_PK_EDDSA: prefix = ""; break; } if (prefix && *prefix) { char buffer[40]; snprintf (buffer, sizeof buffer, "%s%u", prefix, subkey->length); result = strdup (buffer); } else if (prefix && subkey->curve && *subkey->curve) result = strdup (subkey->curve); else if (prefix) result = strdup ("E_error"); else result = strdup ("unknown"); return result; } const char * gpgme_pubkey_algo_name (gpgme_pubkey_algo_t algo) { switch (algo) { case GPGME_PK_RSA: return "RSA"; case GPGME_PK_RSA_E: return "RSA-E"; case GPGME_PK_RSA_S: return "RSA-S"; case GPGME_PK_ELG_E: return "ELG-E"; case GPGME_PK_DSA: return "DSA"; case GPGME_PK_ECC: return "ECC"; case GPGME_PK_ELG: return "ELG"; case GPGME_PK_ECDSA: return "ECDSA"; case GPGME_PK_ECDH: return "ECDH"; case GPGME_PK_EDDSA: return "EdDSA"; default: return NULL; } } const char * gpgme_hash_algo_name (gpgme_hash_algo_t algo) { switch (algo) { case GPGME_MD_MD5: return "MD5"; case GPGME_MD_SHA1: return "SHA1"; case GPGME_MD_RMD160: return "RIPEMD160"; case GPGME_MD_MD2: return "MD2"; case GPGME_MD_TIGER: return "TIGER192"; case GPGME_MD_HAVAL: return "HAVAL"; case GPGME_MD_SHA256: return "SHA256"; case GPGME_MD_SHA384: return "SHA384"; case GPGME_MD_SHA512: return "SHA512"; case GPGME_MD_SHA224: return "SHA224"; case GPGME_MD_MD4: return "MD4"; case GPGME_MD_CRC32: return "CRC32"; case GPGME_MD_CRC32_RFC1510: return "CRC32RFC1510"; case GPGME_MD_CRC24_RFC2440: return "CRC24RFC2440"; default: return NULL; } } diff --git a/src/import.c b/src/import.c index 3d49146f..2834aec2 100644 --- a/src/import.c +++ b/src/import.c @@ -1,451 +1,452 @@ /* import.c - Import a key. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 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 #include #include #include #include "gpgme.h" #include "debug.h" #include "context.h" #include "ops.h" #include "util.h" typedef struct { struct _gpgme_op_import_result result; /* A pointer to the next pointer of the last import status in the list. This makes appending new imports painless while preserving the order. */ gpgme_import_status_t *lastp; } *op_data_t; static void release_op_data (void *hook) { op_data_t opd = (op_data_t) hook; gpgme_import_status_t import = opd->result.imports; while (import) { gpgme_import_status_t next = import->next; free (import->fpr); free (import); import = next; } } gpgme_import_result_t gpgme_op_import_result (gpgme_ctx_t ctx) { void *hook; op_data_t opd; gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_import_result", ctx, ""); err = _gpgme_op_data_lookup (ctx, OPDATA_IMPORT, &hook, -1, NULL); opd = hook; if (err || !opd) { TRACE_SUC ("result=(null)"); return NULL; } if (_gpgme_debug_trace ()) { gpgme_import_status_t impstat; int i; TRACE_LOG ("%i considered, %i no UID, %i imported, %i imported RSA, " "%i unchanged", opd->result.considered, opd->result.no_user_id, opd->result.imported, opd->result.imported_rsa, opd->result.unchanged); TRACE_LOG ("%i new UIDs, %i new sub keys, %i new signatures, " "%i new revocations", opd->result.new_user_ids, opd->result.new_sub_keys, opd->result.new_signatures, opd->result.new_revocations); TRACE_LOG ("%i secret keys, %i imported, %i unchanged", opd->result.secret_read, opd->result.secret_imported, opd->result.secret_unchanged); TRACE_LOG ("%i skipped new keys, %i not imported, %i v3 skipped", opd->result.skipped_new_keys, opd->result.not_imported, opd->result.skipped_v3_keys); impstat = opd->result.imports; i = 0; while (impstat) { TRACE_LOG ("import[%i] for %s = 0x%x (%s)", - i, impstat->fpr, impstat->status, impstat->result); + i, impstat->fpr, impstat->status, + gpgme_strerror (impstat->result)); impstat = impstat->next; i++; } } TRACE_SUC ("result=%p", &opd->result); return &opd->result; } static gpgme_error_t parse_import (char *args, gpgme_import_status_t *import_status, int problem) { gpgme_import_status_t import; char *tail; long int nr; import = malloc (sizeof (*import)); if (!import) return gpg_error_from_syserror (); import->next = NULL; gpg_err_set_errno (0); nr = strtol (args, &tail, 0); if (errno || args == tail || *tail != ' ') { /* The crypto backend does not behave. */ free (import); return trace_gpg_error (GPG_ERR_INV_ENGINE); } args = tail; if (problem) { switch (nr) { case 0: case 4: default: import->result = gpg_error (GPG_ERR_GENERAL); break; case 1: import->result = gpg_error (GPG_ERR_BAD_CERT); break; case 2: import->result = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); break; case 3: import->result = gpg_error (GPG_ERR_BAD_CERT_CHAIN); break; } import->status = 0; } else { import->result = gpg_error (GPG_ERR_NO_ERROR); import->status = nr; } while (*args == ' ') args++; tail = strchr (args, ' '); if (tail) *tail = '\0'; import->fpr = strdup (args); if (!import->fpr) { free (import); return gpg_error_from_syserror (); } *import_status = import; return 0; } gpgme_error_t parse_import_res (char *args, gpgme_import_result_t result) { char *tail; gpg_err_set_errno (0); #define PARSE_NEXT(x) \ (x) = strtol (args, &tail, 0); \ if (errno || args == tail || !(*tail == ' ' || !*tail)) \ /* The crypto backend does not behave. */ \ return trace_gpg_error (GPG_ERR_INV_ENGINE); \ args = tail; PARSE_NEXT (result->considered); PARSE_NEXT (result->no_user_id); PARSE_NEXT (result->imported); PARSE_NEXT (result->imported_rsa); PARSE_NEXT (result->unchanged); PARSE_NEXT (result->new_user_ids); PARSE_NEXT (result->new_sub_keys); PARSE_NEXT (result->new_signatures); PARSE_NEXT (result->new_revocations); PARSE_NEXT (result->secret_read); PARSE_NEXT (result->secret_imported); PARSE_NEXT (result->secret_unchanged); PARSE_NEXT (result->skipped_new_keys); PARSE_NEXT (result->not_imported); if (args && *args) { PARSE_NEXT (result->skipped_v3_keys); } return 0; } static gpgme_error_t import_status_handler (void *priv, gpgme_status_code_t code, char *args) { gpgme_ctx_t ctx = (gpgme_ctx_t) priv; gpgme_error_t err; void *hook; op_data_t opd; err = _gpgme_op_data_lookup (ctx, OPDATA_IMPORT, &hook, -1, NULL); opd = hook; if (err) return err; switch (code) { case GPGME_STATUS_IMPORT_OK: case GPGME_STATUS_IMPORT_PROBLEM: err = parse_import (args, opd->lastp, code == GPGME_STATUS_IMPORT_OK ? 0 : 1); if (err) return err; opd->lastp = &(*opd->lastp)->next; break; case GPGME_STATUS_IMPORT_RES: err = parse_import_res (args, &opd->result); break; default: break; } return err; } static gpgme_error_t _gpgme_op_import_start (gpgme_ctx_t ctx, int synchronous, gpgme_data_t keydata) { gpgme_error_t err; void *hook; op_data_t opd; err = _gpgme_op_reset (ctx, synchronous); if (err) return err; err = _gpgme_op_data_lookup (ctx, OPDATA_IMPORT, &hook, sizeof (*opd), release_op_data); opd = hook; if (err) return err; opd->lastp = &opd->result.imports; if (!keydata) return gpg_error (GPG_ERR_NO_DATA); _gpgme_engine_set_status_handler (ctx->engine, import_status_handler, ctx); return _gpgme_engine_op_import (ctx->engine, keydata, NULL); } gpgme_error_t gpgme_op_import_start (gpgme_ctx_t ctx, gpgme_data_t keydata) { gpg_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_import_start", ctx, "keydata=%p", keydata); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = _gpgme_op_import_start (ctx, 0, keydata); return TRACE_ERR (err); } /* Import the key in KEYDATA into the keyring. */ gpgme_error_t gpgme_op_import (gpgme_ctx_t ctx, gpgme_data_t keydata) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_import", ctx, "keydata=%p", keydata); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = _gpgme_op_import_start (ctx, 1, keydata); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } static gpgme_error_t _gpgme_op_import_keys_start (gpgme_ctx_t ctx, int synchronous, gpgme_key_t *keys) { gpgme_error_t err; void *hook; op_data_t opd; int idx, firstidx, nkeys; err = _gpgme_op_reset (ctx, synchronous); if (err) return err; err = _gpgme_op_data_lookup (ctx, OPDATA_IMPORT, &hook, sizeof (*opd), release_op_data); opd = hook; if (err) return err; opd->lastp = &opd->result.imports; if (!keys) return gpg_error (GPG_ERR_NO_DATA); for (idx=nkeys=0, firstidx=-1; keys[idx]; idx++) { /* We only consider keys of the current protocol. */ if (keys[idx]->protocol != ctx->protocol) continue; if (firstidx == -1) firstidx = idx; /* If a key has been found using a different key listing mode, we bail out. This makes the processing easier. Fixme: To allow a mix of keys we would need to sort them by key listing mode and start two import operations one after the other. */ if (keys[idx]->keylist_mode != keys[firstidx]->keylist_mode) return gpg_error (GPG_ERR_CONFLICT); nkeys++; } if (!nkeys) return gpg_error (GPG_ERR_NO_DATA); _gpgme_engine_set_status_handler (ctx->engine, import_status_handler, ctx); return _gpgme_engine_op_import (ctx->engine, NULL, keys); } /* Asynchronous version of gpgme_op_import_key. */ gpgme_error_t gpgme_op_import_keys_start (gpgme_ctx_t ctx, gpgme_key_t *keys) { gpg_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_import_keys_start", ctx, ""); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (_gpgme_debug_trace () && keys) { int i = 0; while (keys[i]) { TRACE_LOG ("keys[%i] = %p (%s)", i, keys[i], (keys[i]->subkeys && keys[i]->subkeys->fpr) ? keys[i]->subkeys->fpr : "invalid"); i++; } } err = _gpgme_op_import_keys_start (ctx, 0, keys); return TRACE_ERR (err); } /* Import the keys from the array KEYS into the keyring. In particular it is used to actually import keys retrieved from an external source (i.e. using GPGME_KEYLIST_MODE_EXTERN). It replaces the old workaround of exporting and then importing a key as used to make an X.509 key permanent. This function automagically does the right thing. KEYS is a NULL terminated array of gpgme key objects. The result is the usual import result structure. Only keys matching the current protocol are imported; other keys are ignored. */ gpgme_error_t gpgme_op_import_keys (gpgme_ctx_t ctx, gpgme_key_t *keys) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_import_keys", ctx, ""); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (_gpgme_debug_trace () && keys) { int i = 0; while (keys[i]) { TRACE_LOG ("keys[%i] = %p (%s)", i, keys[i], (keys[i]->subkeys && keys[i]->subkeys->fpr) ? keys[i]->subkeys->fpr : "invalid"); i++; } } err = _gpgme_op_import_keys_start (ctx, 1, keys); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } /* Deprecated interface. */ gpgme_error_t gpgme_op_import_ext (gpgme_ctx_t ctx, gpgme_data_t keydata, int *nr) { gpgme_error_t err = gpgme_op_import (ctx, keydata); if (!err && nr) { gpgme_import_result_t result = gpgme_op_import_result (ctx); *nr = result->considered; } return err; } diff --git a/src/posix-io.c b/src/posix-io.c index 78f523b1..77ecde05 100644 --- a/src/posix-io.c +++ b/src/posix-io.c @@ -1,876 +1,876 @@ /* 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=%u", buffer, count); + "buffer=%p, count=%zu", 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=%u", buffer, count); + "buffer=%p, count=%zu", 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, "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); return TRACE_SUC ("read=0x%x, write=0x%x", filedes[0], filedes[1]); } 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, ""); 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); 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, ""); 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=%i (%s)", fds, source); + TRACE (DEBUG_SYSIO, "gpgme:max_fds", 0, "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, "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, - "nfds=%u, nonblock=%u", nfds, nonblock); + "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); 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); 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); if (FD_ISSET (i, &writefds)) TRACE_ADD1 (dbg_help, "w0x%x ", 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); 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); 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); 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); 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); do res = ath_connect (fd, addr, addrlen); while (res == -1 && errno == EINTR); return TRACE_SYSRES (res); } diff --git a/src/queryswdb.c b/src/queryswdb.c index e72dabbc..2b7a07f5 100644 --- a/src/queryswdb.c +++ b/src/queryswdb.c @@ -1,122 +1,122 @@ /* queryswdb.c - Access to the SWDB file * Copyright (C) 2016 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 "debug.h" #include "context.h" #include "ops.h" typedef struct { struct _gpgme_op_query_swdb_result result; } *op_data_t; static void release_op_data (void *hook) { op_data_t opd = (op_data_t) hook; gpgme_query_swdb_result_t result = &opd->result; assert (!result->next); free (result->name); free (result->iversion); free (result->version); } gpgme_query_swdb_result_t gpgme_op_query_swdb_result (gpgme_ctx_t ctx) { void *hook; op_data_t opd; gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_query_swdb_result", ctx, ""); err = _gpgme_op_data_lookup (ctx, OPDATA_QUERY_SWDB, &hook, -1, NULL); opd = hook; if (err || !opd) { TRACE_SUC ("result=(null)"); return NULL; } TRACE_SUC ("result=%p", &opd->result); return &opd->result; } /* Query the swdb for software package NAME and check against the * installed version given by IVERSION. If IVERSION is NULL a check * is only done if GPGME can figure out the version by itself * (e.g. for "gpgme" or "gnupg"). RESERVED should be 0. * * Note that we only implemented the synchronous variant of this * function but the API is prepared for an asynchronous variant. */ gpgme_error_t gpgme_op_query_swdb (gpgme_ctx_t ctx, const char *name, const char *iversion, unsigned int reserved) { gpgme_error_t err; void *hook; op_data_t opd; TRACE_BEG (DEBUG_CTX, "gpgme_op_query_swdb", ctx, - "name=%s, iversion=%", name, iversion); + "name=%s, iversion=%s", name, iversion); if (!ctx || reserved) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (ctx->protocol != GPGME_PROTOCOL_GPGCONF) return TRACE_ERR (gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL)); if (!name) name = "gpgme"; if (!iversion && !strcmp (name, "gpgme")) iversion = VERSION; err = _gpgme_op_reset (ctx, 1); if (err) return err; err = _gpgme_op_data_lookup (ctx, OPDATA_QUERY_SWDB, &hook, sizeof (*opd), release_op_data); opd = hook; if (err) return TRACE_ERR (err); err = _gpgme_engine_op_query_swdb (ctx->engine, name, iversion, &opd->result); return TRACE_ERR (err); } diff --git a/src/trustlist.c b/src/trustlist.c index c7778379..a0e82385 100644 --- a/src/trustlist.c +++ b/src/trustlist.c @@ -1,279 +1,279 @@ /* trustlist.c - Trust item listing. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 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 #include #include #include #include #include "gpgme.h" #include "debug.h" #include "util.h" #include "context.h" #include "ops.h" struct trust_queue_item_s { struct trust_queue_item_s *next; gpgme_trust_item_t item; }; typedef struct { /* Something new is available. */ int trust_cond; struct trust_queue_item_s *trust_queue; } *op_data_t; static gpgme_error_t trustlist_status_handler (void *priv, gpgme_status_code_t code, char *args) { (void)priv; (void)code; (void)args; return 0; } /* This handler is used to parse the output of --list-trust-path: Format: level:keyid:type:recno:ot:val:mc:cc:name: With TYPE = U for a user ID K for a key The RECNO is either the one of the dir record or the one of the uid record. OT is the the usual trust letter and only available on K lines. VAL is the calculated validity MC is the marginal trust counter and only available on U lines CC is the same for the complete count NAME ist the username and only printed on U lines. */ static gpgme_error_t trustlist_colon_handler (void *priv, char *line) { gpgme_ctx_t ctx = (gpgme_ctx_t) priv; gpgme_error_t err; char *p, *pend; int field = 0; gpgme_trust_item_t item = NULL; if (!line) return 0; /* EOF */ for (p = line; p; p = pend) { field++; pend = strchr (p, ':'); if (pend) *pend++ = 0; switch (field) { case 1: /* level */ err = _gpgme_trust_item_new (&item); if (err) return err; item->level = atoi (p); break; case 2: /* long keyid */ if (strlen (p) == DIM(item->keyid) - 1) strcpy (item->keyid, p); break; case 3: /* type */ item->type = *p == 'K'? 1 : *p == 'U'? 2 : 0; break; case 5: /* owner trust */ item->_owner_trust[0] = *p; break; case 6: /* validity */ item->_validity[0] = *p; break; case 9: /* user ID */ item->name = strdup (p); if (!item->name) { int saved_err = gpg_error_from_syserror (); gpgme_trust_item_unref (item); return saved_err; } break; } } if (item) _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_NEXT_TRUSTITEM, item); return 0; } void _gpgme_op_trustlist_event_cb (void *data, gpgme_event_io_t type, void *type_data) { gpgme_ctx_t ctx = (gpgme_ctx_t) data; gpgme_error_t err; void *hook; op_data_t opd; gpgme_trust_item_t item = (gpgme_trust_item_t) type_data; struct trust_queue_item_s *q, *q2; assert (type == GPGME_EVENT_NEXT_TRUSTITEM); err = _gpgme_op_data_lookup (ctx, OPDATA_TRUSTLIST, &hook, -1, NULL); opd = hook; if (err) return; q = malloc (sizeof *q); if (!q) { gpgme_trust_item_unref (item); /* FIXME: GPGME_Out_Of_Core; */ return; } q->item = item; q->next = NULL; /* FIXME: Use a tail pointer */ q2 = opd->trust_queue; if (!q2) opd->trust_queue = q; else { while (q2->next) q2 = q2->next; q2->next = q; } /* FIXME: unlock queue */ opd->trust_cond = 1; } gpgme_error_t gpgme_op_trustlist_start (gpgme_ctx_t ctx, const char *pattern, int max_level) { gpgme_error_t err = 0; void *hook; op_data_t opd; TRACE_BEG (DEBUG_CTX, "gpgme_op_trustlist_start", ctx, "pattern=%s, max_level=%i", pattern, max_level); if (!ctx || !pattern || !*pattern) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = _gpgme_op_reset (ctx, 2); if (err) return TRACE_ERR (err); err = _gpgme_op_data_lookup (ctx, OPDATA_TRUSTLIST, &hook, sizeof (*opd), NULL); opd = hook; if (err) return TRACE_ERR (err); _gpgme_engine_set_status_handler (ctx->engine, trustlist_status_handler, ctx); err = _gpgme_engine_set_colon_line_handler (ctx->engine, trustlist_colon_handler, ctx); if (err) return TRACE_ERR (err); err = _gpgme_engine_op_trustlist (ctx->engine, pattern); return TRACE_ERR (err); } gpgme_error_t gpgme_op_trustlist_next (gpgme_ctx_t ctx, gpgme_trust_item_t *r_item) { gpgme_error_t err; void *hook; op_data_t opd; struct trust_queue_item_s *q; TRACE_BEG (DEBUG_CTX, "gpgme_op_trustlist_next", ctx, ""); if (!ctx || !r_item) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); *r_item = NULL; if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = _gpgme_op_data_lookup (ctx, OPDATA_TRUSTLIST, &hook, -1, NULL); opd = hook; if (err) return TRACE_ERR (err); if (opd == NULL) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); if (!opd->trust_queue) { err = _gpgme_wait_on_condition (ctx, &opd->trust_cond, NULL); if (err) return TRACE_ERR (err); if (!opd->trust_cond) return TRACE_ERR (gpg_error (GPG_ERR_EOF)); opd->trust_cond = 0; assert (opd->trust_queue); } q = opd->trust_queue; opd->trust_queue = q->next; *r_item = q->item; free (q); if ((*r_item)->type == 1) { TRACE_SUC ("trust_item=%p: %s: owner trust %s with level %i " - "and validity 0x%x", *r_item, (*r_item)->keyid, + "and validity %s", *r_item, (*r_item)->keyid, (*r_item)->owner_trust, (*r_item)->level, (*r_item)->validity); } else if ((*r_item)->type == 2) { TRACE_SUC ("trust_item=%p: %s: UID %s with level %i " - "and validity 0x%x", *r_item, (*r_item)->keyid, + "and validity %s", *r_item, (*r_item)->keyid, (*r_item)->name, (*r_item)->level, (*r_item)->validity); } else { TRACE_SUC ("trust_item=%p: %s: unknown type %i with level %i " - "and validity 0x%x", *r_item, (*r_item)->keyid, + "and validity %s", *r_item, (*r_item)->keyid, (*r_item)->type, (*r_item)->level, (*r_item)->validity); } return 0; } /* Terminate a pending trustlist operation within CTX. */ gpgme_error_t gpgme_op_trustlist_end (gpgme_ctx_t ctx) { TRACE (DEBUG_CTX, "gpgme_op_trustlist_end", ctx, ""); if (!ctx) return gpg_error (GPG_ERR_INV_VALUE); return 0; } diff --git a/src/verify.c b/src/verify.c index 78e549ed..8aa9d281 100644 --- a/src/verify.c +++ b/src/verify.c @@ -1,1394 +1,1394 @@ /* verify.c - Signature verification. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 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 */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "gpgme.h" #include "debug.h" #include "util.h" #include "context.h" #include "ops.h" typedef struct { struct _gpgme_op_verify_result result; /* The error code from a FAILURE status line or 0. */ gpg_error_t failure_code; gpgme_signature_t current_sig; int did_prepare_new_sig; int only_newsig_seen; int plaintext_seen; int conflict_user_seen; } *op_data_t; static void release_op_data (void *hook) { op_data_t opd = (op_data_t) hook; gpgme_signature_t sig = opd->result.signatures; while (sig) { gpgme_signature_t next = sig->next; gpgme_sig_notation_t notation = sig->notations; while (notation) { gpgme_sig_notation_t next_nota = notation->next; _gpgme_sig_notation_free (notation); notation = next_nota; } if (sig->fpr) free (sig->fpr); if (sig->pka_address) free (sig->pka_address); if (sig->key) gpgme_key_unref (sig->key); free (sig); sig = next; } if (opd->result.file_name) free (opd->result.file_name); } gpgme_verify_result_t gpgme_op_verify_result (gpgme_ctx_t ctx) { void *hook; op_data_t opd; gpgme_error_t err; gpgme_signature_t sig; TRACE_BEG (DEBUG_CTX, "gpgme_op_verify_result", ctx, ""); err = _gpgme_op_data_lookup (ctx, OPDATA_VERIFY, &hook, -1, NULL); opd = hook; if (err || !opd) { TRACE_SUC ("result=(null)"); return NULL; } /* It is possible that we saw a new signature only followed by an ERROR line for that. In particular a missing X.509 key triggers this. In this case it is surprising that the summary field has not been updated. We fix it here by explicitly looking for this case. The real fix would be to have GPGME emit ERRSIG. */ for (sig = opd->result.signatures; sig; sig = sig->next) { if (!sig->summary) { switch (gpg_err_code (sig->status)) { case GPG_ERR_KEY_EXPIRED: sig->summary |= GPGME_SIGSUM_KEY_EXPIRED; break; case GPG_ERR_NO_PUBKEY: sig->summary |= GPGME_SIGSUM_KEY_MISSING; break; default: break; } } } /* Now for some tracing stuff. */ if (_gpgme_debug_trace ()) { int i; for (sig = opd->result.signatures, i = 0; sig; sig = sig->next, i++) { TRACE_LOG ("sig[%i] = fpr %s, summary 0x%x, status %s", i, sig->fpr, sig->summary, gpg_strerror (sig->status)); - TRACE_LOG ("sig[%i] = timestamps 0x%x/0x%x flags:%s%s%s", + TRACE_LOG ("sig[%i] = timestamps 0x%lx/0x%lx flags:%s%s%s", i, sig->timestamp, sig->exp_timestamp, sig->wrong_key_usage ? "wrong key usage" : "", sig->pka_trust == 1 ? "pka bad" : (sig->pka_trust == 2 ? "pka_okay" : "pka RFU"), sig->chain_model ? "chain model" : ""); TRACE_LOG ("sig[%i] = validity 0x%x (%s), algos %s/%s", i, sig->validity, gpg_strerror (sig->validity_reason), gpgme_pubkey_algo_name (sig->pubkey_algo), gpgme_hash_algo_name (sig->hash_algo)); if (sig->pka_address) { TRACE_LOG ("sig[%i] = PKA address %s", i, sig->pka_address); } if (sig->notations) { TRACE_LOG ("sig[%i] = has notations (not shown)", i); } } } TRACE_SUC ("result=%p", &opd->result); return &opd->result; } /* Build a summary vector from RESULT. */ static void calc_sig_summary (gpgme_signature_t sig) { unsigned long sum = 0; /* Calculate the red/green flag. */ if (sig->validity == GPGME_VALIDITY_FULL || sig->validity == GPGME_VALIDITY_ULTIMATE) { if (gpg_err_code (sig->status) == GPG_ERR_NO_ERROR || gpg_err_code (sig->status) == GPG_ERR_SIG_EXPIRED || gpg_err_code (sig->status) == GPG_ERR_KEY_EXPIRED) sum |= GPGME_SIGSUM_GREEN; } else if (sig->validity == GPGME_VALIDITY_NEVER) { if (gpg_err_code (sig->status) == GPG_ERR_NO_ERROR || gpg_err_code (sig->status) == GPG_ERR_SIG_EXPIRED || gpg_err_code (sig->status) == GPG_ERR_KEY_EXPIRED) sum |= GPGME_SIGSUM_RED; } else if (gpg_err_code (sig->status) == GPG_ERR_BAD_SIGNATURE) sum |= GPGME_SIGSUM_RED; /* FIXME: handle the case when key and message are expired. */ switch (gpg_err_code (sig->status)) { case GPG_ERR_SIG_EXPIRED: sum |= GPGME_SIGSUM_SIG_EXPIRED; break; case GPG_ERR_KEY_EXPIRED: sum |= GPGME_SIGSUM_KEY_EXPIRED; break; case GPG_ERR_NO_PUBKEY: sum |= GPGME_SIGSUM_KEY_MISSING; break; case GPG_ERR_CERT_REVOKED: sum |= GPGME_SIGSUM_KEY_REVOKED; break; case GPG_ERR_BAD_SIGNATURE: case GPG_ERR_NO_ERROR: break; default: sum |= GPGME_SIGSUM_SYS_ERROR; break; } /* Now look at the certain reason codes. */ switch (gpg_err_code (sig->validity_reason)) { case GPG_ERR_CRL_TOO_OLD: if (sig->validity == GPGME_VALIDITY_UNKNOWN) sum |= GPGME_SIGSUM_CRL_TOO_OLD; break; case GPG_ERR_CERT_REVOKED: /* Note that this is a second way to set this flag. It may also have been set due to a sig->status of STATUS_REVKEYSIG from parse_new_sig. */ sum |= GPGME_SIGSUM_KEY_REVOKED; break; default: break; } /* Check other flags. */ if (sig->wrong_key_usage) sum |= GPGME_SIGSUM_BAD_POLICY; /* Set the valid flag when the signature is unquestionable valid. (The test is identical to if(sum == GPGME_SIGSUM_GREEN)). */ if ((sum & GPGME_SIGSUM_GREEN) && !(sum & ~GPGME_SIGSUM_GREEN)) sum |= GPGME_SIGSUM_VALID; sig->summary = sum; } static gpgme_error_t prepare_new_sig (op_data_t opd) { gpgme_signature_t sig; if (opd->only_newsig_seen && opd->current_sig) { /* We have only seen the NEWSIG status and nothing else - we better skip this signature therefore and reuse it for the next possible signature. */ sig = opd->current_sig; memset (sig, 0, sizeof *sig); assert (opd->result.signatures == sig); } else { sig = calloc (1, sizeof (*sig)); if (!sig) return gpg_error_from_syserror (); if (!opd->result.signatures) opd->result.signatures = sig; if (opd->current_sig) opd->current_sig->next = sig; opd->current_sig = sig; } opd->did_prepare_new_sig = 1; opd->only_newsig_seen = 0; return 0; } static gpgme_error_t parse_new_sig (op_data_t opd, gpgme_status_code_t code, char *args, gpgme_protocol_t protocol) { gpgme_signature_t sig; char *end = strchr (args, ' '); char *tail; int got_fpr = 0; if (end) { *end = '\0'; end++; } if (!opd->did_prepare_new_sig) { gpg_error_t err; err = prepare_new_sig (opd); if (err) return err; } assert (opd->did_prepare_new_sig); opd->did_prepare_new_sig = 0; assert (opd->current_sig); sig = opd->current_sig; /* FIXME: We should set the source of the state. */ switch (code) { case GPGME_STATUS_GOODSIG: sig->status = gpg_error (GPG_ERR_NO_ERROR); break; case GPGME_STATUS_EXPSIG: sig->status = gpg_error (GPG_ERR_SIG_EXPIRED); break; case GPGME_STATUS_EXPKEYSIG: sig->status = gpg_error (GPG_ERR_KEY_EXPIRED); break; case GPGME_STATUS_BADSIG: sig->status = gpg_error (GPG_ERR_BAD_SIGNATURE); break; case GPGME_STATUS_REVKEYSIG: sig->status = gpg_error (GPG_ERR_CERT_REVOKED); break; case GPGME_STATUS_ERRSIG: /* Parse the pubkey algo. */ if (!end) goto parse_err_sig_fail; gpg_err_set_errno (0); sig->pubkey_algo = _gpgme_map_pk_algo (strtol (end, &tail, 0), protocol); if (errno || end == tail || *tail != ' ') goto parse_err_sig_fail; end = tail; while (*end == ' ') end++; /* Parse the hash algo. */ if (!*end) goto parse_err_sig_fail; gpg_err_set_errno (0); sig->hash_algo = strtol (end, &tail, 0); if (errno || end == tail || *tail != ' ') goto parse_err_sig_fail; end = tail; while (*end == ' ') end++; /* Skip the sig class. */ end = strchr (end, ' '); if (!end) goto parse_err_sig_fail; while (*end == ' ') end++; /* Parse the timestamp. */ sig->timestamp = _gpgme_parse_timestamp (end, &tail); if (sig->timestamp == -1 || end == tail || (*tail && *tail != ' ')) return trace_gpg_error (GPG_ERR_INV_ENGINE); end = tail; while (*end == ' ') end++; /* Parse the return code. */ if (!*end) goto parse_err_sig_fail; gpg_err_set_errno (0); sig->status = strtoul (end, &tail, 10); if (errno || end == tail || (*tail && *tail != ' ')) goto parse_err_sig_fail; if (!*tail) goto parse_err_sig_ok; end = tail; while (*end == ' ') end++; /* Parse the new fingerprint (from the ISSUER_FPR subpacket). */ if (!*end || (*end == '-' && (end[1] == ' ' || !end[1]))) goto parse_err_sig_ok; /* Okay (just trailing spaces). */ sig->fpr = strdup (end); if (!sig->fpr) return gpg_error_from_syserror (); got_fpr = 1; goto parse_err_sig_ok; parse_err_sig_fail: sig->status = gpg_error (GPG_ERR_GENERAL); parse_err_sig_ok: break; default: return gpg_error (GPG_ERR_GENERAL); } if (*args && !got_fpr) { sig->fpr = strdup (args); if (!sig->fpr) return gpg_error_from_syserror (); } return 0; } static gpgme_error_t parse_valid_sig (gpgme_signature_t sig, char *args, gpgme_protocol_t protocol) { char *end = strchr (args, ' '); if (end) { *end = '\0'; end++; } if (!*args) /* We require at least the fingerprint. */ return gpg_error (GPG_ERR_GENERAL); if (sig->fpr) free (sig->fpr); sig->fpr = strdup (args); if (!sig->fpr) return gpg_error_from_syserror (); /* Skip the creation date. */ end = strchr (end, ' '); if (end) { char *tail; sig->timestamp = _gpgme_parse_timestamp (end, &tail); if (sig->timestamp == -1 || end == tail || (*tail && *tail != ' ')) return trace_gpg_error (GPG_ERR_INV_ENGINE); end = tail; sig->exp_timestamp = _gpgme_parse_timestamp (end, &tail); if (sig->exp_timestamp == -1 || end == tail || (*tail && *tail != ' ')) return trace_gpg_error (GPG_ERR_INV_ENGINE); end = tail; while (*end == ' ') end++; /* Skip the signature version. */ end = strchr (end, ' '); if (end) { while (*end == ' ') end++; /* Skip the reserved field. */ end = strchr (end, ' '); if (end) { /* Parse the pubkey algo. */ gpg_err_set_errno (0); sig->pubkey_algo = _gpgme_map_pk_algo (strtol (end, &tail, 0), protocol); if (errno || end == tail || *tail != ' ') return trace_gpg_error (GPG_ERR_INV_ENGINE); end = tail; while (*end == ' ') end++; if (*end) { /* Parse the hash algo. */ gpg_err_set_errno (0); sig->hash_algo = strtol (end, &tail, 0); if (errno || end == tail || *tail != ' ') return trace_gpg_error (GPG_ERR_INV_ENGINE); end = tail; } } } } return 0; } static gpgme_error_t parse_notation (gpgme_signature_t sig, gpgme_status_code_t code, char *args) { gpgme_error_t err; gpgme_sig_notation_t *lastp = &sig->notations; gpgme_sig_notation_t notation = sig->notations; char *p; if (code == GPGME_STATUS_NOTATION_NAME || code == GPGME_STATUS_POLICY_URL) { p = strchr (args, ' '); if (p) *p = '\0'; /* FIXME: We could keep a pointer to the last notation in the list. */ while (notation && notation->value) { lastp = ¬ation->next; notation = notation->next; } if (notation) /* There is another notation name without data for the previous one. The crypto backend misbehaves. */ return trace_gpg_error (GPG_ERR_INV_ENGINE); err = _gpgme_sig_notation_create (¬ation, NULL, 0, NULL, 0, 0); if (err) return err; if (code == GPGME_STATUS_NOTATION_NAME) { err = _gpgme_decode_percent_string (args, ¬ation->name, 0, 0); if (err) { _gpgme_sig_notation_free (notation); return err; } notation->name_len = strlen (notation->name); /* Set default flags for use with older gpg versions which * do not emit a NOTATIONS_FLAG line. */ notation->flags = GPGME_SIG_NOTATION_HUMAN_READABLE; notation->human_readable = 1; } else { /* This is a policy URL. */ err = _gpgme_decode_percent_string (args, ¬ation->value, 0, 0); if (err) { _gpgme_sig_notation_free (notation); return err; } notation->value_len = strlen (notation->value); } *lastp = notation; } else if (code == GPGME_STATUS_NOTATION_FLAGS) { char *field[2]; while (notation && notation->next) { lastp = ¬ation->next; notation = notation->next; } if (!notation || !notation->name) { /* There are notation flags without a previous notation name. * The crypto backend misbehaves. */ return trace_gpg_error (GPG_ERR_INV_ENGINE); } if (_gpgme_split_fields (args, field, DIM (field)) < 2) { /* Required args missing. */ return trace_gpg_error (GPG_ERR_INV_ENGINE); } notation->flags = 0; if (atoi (field[0])) { notation->flags |= GPGME_SIG_NOTATION_CRITICAL; notation->critical = 1; } if (atoi (field[1])) { notation->flags |= GPGME_SIG_NOTATION_HUMAN_READABLE; notation->human_readable = 1; } } else if (code == GPGME_STATUS_NOTATION_DATA) { int len = strlen (args) + 1; char *dest; /* FIXME: We could keep a pointer to the last notation in the list. */ while (notation && notation->next) { lastp = ¬ation->next; notation = notation->next; } if (!notation || !notation->name) /* There is notation data without a previous notation name. The crypto backend misbehaves. */ return trace_gpg_error (GPG_ERR_INV_ENGINE); if (!notation->value) { dest = notation->value = malloc (len); if (!dest) return gpg_error_from_syserror (); } else { int cur_len = strlen (notation->value); dest = realloc (notation->value, len + strlen (notation->value)); if (!dest) return gpg_error_from_syserror (); notation->value = dest; dest += cur_len; } err = _gpgme_decode_percent_string (args, &dest, len, 0); if (err) return err; notation->value_len += strlen (dest); } else return trace_gpg_error (GPG_ERR_INV_ENGINE); return 0; } static gpgme_error_t parse_trust (gpgme_signature_t sig, gpgme_status_code_t code, char *args) { char *end = strchr (args, ' '); if (end) *end = '\0'; switch (code) { case GPGME_STATUS_TRUST_UNDEFINED: default: sig->validity = GPGME_VALIDITY_UNKNOWN; break; case GPGME_STATUS_TRUST_NEVER: sig->validity = GPGME_VALIDITY_NEVER; break; case GPGME_STATUS_TRUST_MARGINAL: sig->validity = GPGME_VALIDITY_MARGINAL; break; case GPGME_STATUS_TRUST_FULLY: case GPGME_STATUS_TRUST_ULTIMATE: sig->validity = GPGME_VALIDITY_FULL; break; } sig->validity_reason = 0; sig->chain_model = 0; if (*args) { sig->validity_reason = atoi (args); while (*args && *args != ' ') args++; if (*args) { while (*args == ' ') args++; if (!strncmp (args, "chain", 2) && (args[2] == ' ' || !args[2])) sig->chain_model = 1; } } return 0; } /* Parse a TOFU_USER line and put the info into SIG. */ static gpgme_error_t parse_tofu_user (gpgme_signature_t sig, char *args, gpgme_protocol_t protocol) { gpg_error_t err; char *tail; gpgme_user_id_t uid; gpgme_tofu_info_t ti; char *fpr = NULL; char *address = NULL; tail = strchr (args, ' '); if (!tail || tail == args) { err = trace_gpg_error (GPG_ERR_INV_ENGINE); /* No fingerprint. */ goto leave; } *tail++ = 0; fpr = strdup (args); if (!fpr) { err = gpg_error_from_syserror (); goto leave; } if (sig->key && sig->key->fpr && strcmp (sig->key->fpr, fpr)) { /* GnuPG since 2.1.17 emits multiple TOFU_USER lines with different fingerprints in case of conflicts for a signature. */ err = gpg_error (GPG_ERR_DUP_VALUE); goto leave; } args = tail; tail = strchr (args, ' '); if (tail == args) { err = trace_gpg_error (GPG_ERR_INV_ENGINE); /* No addr-spec. */ goto leave; } if (tail) *tail = 0; err = _gpgme_decode_percent_string (args, &address, 0, 0); if (err) goto leave; if (!sig->key) { err = _gpgme_key_new (&sig->key); if (err) goto leave; sig->key->fpr = fpr; sig->key->protocol = protocol; fpr = NULL; } else if (!sig->key->fpr) { err = trace_gpg_error (GPG_ERR_INTERNAL); goto leave; } err = _gpgme_key_append_name (sig->key, address, 0); if (err) goto leave; uid = sig->key->_last_uid; assert (uid); ti = calloc (1, sizeof *ti); if (!ti) { err = gpg_error_from_syserror (); goto leave; } uid->tofu = ti; leave: free (fpr); free (address); return err; } /* Parse a TOFU_STATS line and store it in the last tofu info of SIG. * * TOFU_STATS \ * [ [ ]] */ static gpgme_error_t parse_tofu_stats (gpgme_signature_t sig, char *args) { gpgme_error_t err; gpgme_tofu_info_t ti; char *field[8]; int nfields; unsigned long uval; if (!sig->key || !sig->key->_last_uid || !(ti = sig->key->_last_uid->tofu)) return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No TOFU_USER seen. */ if (ti->signfirst || ti->signcount || ti->validity || ti->policy) return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already set. */ nfields = _gpgme_split_fields (args, field, DIM (field)); if (nfields < 3) return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Required args missing. */ /* Note that we allow a value of up to 7 which is what we can store * in the ti->validity. */ err = _gpgme_strtoul_field (field[0], &uval); if (err || uval > 7) return trace_gpg_error (GPG_ERR_INV_ENGINE); ti->validity = uval; /* Parse the sign-count. */ err = _gpgme_strtoul_field (field[1], &uval); if (err) return trace_gpg_error (GPG_ERR_INV_ENGINE); if (uval > USHRT_MAX) uval = USHRT_MAX; ti->signcount = uval; /* Parse the encr-count. */ err = _gpgme_strtoul_field (field[2], &uval); if (err) return trace_gpg_error (GPG_ERR_INV_ENGINE); if (uval > USHRT_MAX) uval = USHRT_MAX; ti->encrcount = uval; if (nfields == 3) return 0; /* All mandatory fields parsed. */ /* Parse the policy. */ if (!strcmp (field[3], "none")) ti->policy = GPGME_TOFU_POLICY_NONE; else if (!strcmp (field[3], "auto")) ti->policy = GPGME_TOFU_POLICY_AUTO; else if (!strcmp (field[3], "good")) ti->policy = GPGME_TOFU_POLICY_GOOD; else if (!strcmp (field[3], "bad")) ti->policy = GPGME_TOFU_POLICY_BAD; else if (!strcmp (field[3], "ask")) ti->policy = GPGME_TOFU_POLICY_ASK; else /* "unknown" and invalid policy strings. */ ti->policy = GPGME_TOFU_POLICY_UNKNOWN; if (nfields == 4) return 0; /* No more optional fields. */ /* Parse first and last seen timestamps (none or both are required). */ if (nfields < 6) return trace_gpg_error (GPG_ERR_INV_ENGINE); /* "tm2" missing. */ err = _gpgme_strtoul_field (field[4], &uval); if (err) return trace_gpg_error (GPG_ERR_INV_ENGINE); ti->signfirst = uval; err = _gpgme_strtoul_field (field[5], &uval); if (err) return trace_gpg_error (GPG_ERR_INV_ENGINE); ti->signlast = uval; if (nfields > 7) { /* This condition is only to allow for gpg 2.1.15 - can * eventually be removed. */ err = _gpgme_strtoul_field (field[6], &uval); if (err) return trace_gpg_error (GPG_ERR_INV_ENGINE); ti->encrfirst = uval; err = _gpgme_strtoul_field (field[7], &uval); if (err) return trace_gpg_error (GPG_ERR_INV_ENGINE); ti->encrlast = uval; } return 0; } /* Parse a TOFU_STATS_LONG line and store it in the last tofu info of SIG. */ static gpgme_error_t parse_tofu_stats_long (gpgme_signature_t sig, char *args, int raw) { gpgme_error_t err; gpgme_tofu_info_t ti; char *p; if (!sig->key || !sig->key->_last_uid || !(ti = sig->key->_last_uid->tofu)) return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No TOFU_USER seen. */ if (ti->description) return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already set. */ err = _gpgme_decode_percent_string (args, &ti->description, 0, 0); if (err) return err; /* Remove the non-breaking spaces. */ if (!raw) { for (p = ti->description; *p; p++) if (*p == '~') *p = ' '; } return 0; } /* Parse an error status line and if SET_STATUS is true update the result status as appropriate. With SET_STATUS being false, only check for an error. */ static gpgme_error_t parse_error (gpgme_signature_t sig, char *args, int set_status) { gpgme_error_t err; char *where = strchr (args, ' '); char *which; if (where) { *where = '\0'; which = where + 1; where = strchr (which, ' '); if (where) *where = '\0'; where = args; } else return trace_gpg_error (GPG_ERR_INV_ENGINE); err = atoi (which); if (!strcmp (where, "proc_pkt.plaintext") && gpg_err_code (err) == GPG_ERR_BAD_DATA) { /* This indicates a double plaintext. The only solid way to handle this is by failing the oepration. */ return gpg_error (GPG_ERR_BAD_DATA); } else if (!set_status) ; else if (!strcmp (where, "verify.findkey")) sig->status = err; else if (!strcmp (where, "verify.keyusage") && gpg_err_code (err) == GPG_ERR_WRONG_KEY_USAGE) sig->wrong_key_usage = 1; return 0; } gpgme_error_t _gpgme_verify_status_handler (void *priv, gpgme_status_code_t code, char *args) { gpgme_ctx_t ctx = (gpgme_ctx_t) priv; gpgme_error_t err; void *hook; op_data_t opd; gpgme_signature_t sig; char *end; err = _gpgme_op_data_lookup (ctx, OPDATA_VERIFY, &hook, -1, NULL); opd = hook; if (err) return err; sig = opd->current_sig; switch (code) { case GPGME_STATUS_NEWSIG: if (sig) calc_sig_summary (sig); err = prepare_new_sig (opd); opd->only_newsig_seen = 1; opd->conflict_user_seen = 0; return err; case GPGME_STATUS_GOODSIG: case GPGME_STATUS_EXPSIG: case GPGME_STATUS_EXPKEYSIG: case GPGME_STATUS_BADSIG: case GPGME_STATUS_ERRSIG: case GPGME_STATUS_REVKEYSIG: if (sig && !opd->did_prepare_new_sig) calc_sig_summary (sig); opd->only_newsig_seen = 0; return parse_new_sig (opd, code, args, ctx->protocol); case GPGME_STATUS_VALIDSIG: opd->only_newsig_seen = 0; return sig ? parse_valid_sig (sig, args, ctx->protocol) : trace_gpg_error (GPG_ERR_INV_ENGINE); case GPGME_STATUS_NODATA: opd->only_newsig_seen = 0; if (!sig) return gpg_error (GPG_ERR_NO_DATA); sig->status = gpg_error (GPG_ERR_NO_DATA); break; case GPGME_STATUS_UNEXPECTED: opd->only_newsig_seen = 0; if (!sig) return gpg_error (GPG_ERR_GENERAL); sig->status = gpg_error (GPG_ERR_NO_DATA); break; case GPGME_STATUS_NOTATION_NAME: case GPGME_STATUS_NOTATION_FLAGS: case GPGME_STATUS_NOTATION_DATA: case GPGME_STATUS_POLICY_URL: opd->only_newsig_seen = 0; return sig ? parse_notation (sig, code, args) : trace_gpg_error (GPG_ERR_INV_ENGINE); case GPGME_STATUS_TRUST_UNDEFINED: case GPGME_STATUS_TRUST_NEVER: case GPGME_STATUS_TRUST_MARGINAL: case GPGME_STATUS_TRUST_FULLY: case GPGME_STATUS_TRUST_ULTIMATE: opd->only_newsig_seen = 0; return sig ? parse_trust (sig, code, args) : trace_gpg_error (GPG_ERR_INV_ENGINE); case GPGME_STATUS_PKA_TRUST_BAD: case GPGME_STATUS_PKA_TRUST_GOOD: opd->only_newsig_seen = 0; /* Check that we only get one of these status codes per signature; if not the crypto backend misbehaves. */ if (!sig || sig->pka_trust || sig->pka_address) return trace_gpg_error (GPG_ERR_INV_ENGINE); sig->pka_trust = code == GPGME_STATUS_PKA_TRUST_GOOD? 2 : 1; end = strchr (args, ' '); if (end) *end = 0; sig->pka_address = strdup (args); break; case GPGME_STATUS_TOFU_USER: opd->only_newsig_seen = 0; if (!sig) return trace_gpg_error (GPG_ERR_INV_ENGINE); err = parse_tofu_user (sig, args, ctx->protocol); /* gpg emits TOFU User lines for each conflicting key. * GPGME does not expose this to have a clean API and * a GPGME user can do a keylisting with the address * normalisation. * So when a duplicated TOFU_USER line is encountered * we ignore the conflicting tofu stats emitted afterwards. */ if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) { opd->conflict_user_seen = 1; break; } opd->conflict_user_seen = 0; return trace_gpg_error (err); case GPGME_STATUS_TOFU_STATS: opd->only_newsig_seen = 0; if (opd->conflict_user_seen) break; return sig ? parse_tofu_stats (sig, args) /* */ : trace_gpg_error (GPG_ERR_INV_ENGINE); case GPGME_STATUS_TOFU_STATS_LONG: opd->only_newsig_seen = 0; if (opd->conflict_user_seen) break; return sig ? parse_tofu_stats_long (sig, args, ctx->raw_description) /* */ : trace_gpg_error (GPG_ERR_INV_ENGINE); case GPGME_STATUS_ERROR: opd->only_newsig_seen = 0; /* Some error stati are informational, so we don't return an error code if we are not ready to process this status. */ return parse_error (sig, args, !!sig ); case GPGME_STATUS_FAILURE: opd->failure_code = _gpgme_parse_failure (args); break; case GPGME_STATUS_EOF: if (sig && !opd->did_prepare_new_sig) calc_sig_summary (sig); if (opd->only_newsig_seen && sig) { gpgme_signature_t sig2; /* The last signature has no valid information - remove it from the list. */ assert (!sig->next); if (sig == opd->result.signatures) opd->result.signatures = NULL; else { for (sig2 = opd->result.signatures; sig2; sig2 = sig2->next) if (sig2->next == sig) { sig2->next = NULL; break; } } /* Note that there is no need to release the members of SIG because we won't be here if they have been set. */ free (sig); opd->current_sig = NULL; } opd->only_newsig_seen = 0; if (opd->failure_code) return opd->failure_code; break; case GPGME_STATUS_PLAINTEXT: if (++opd->plaintext_seen > 1) return gpg_error (GPG_ERR_BAD_DATA); { int mime = 0; err = _gpgme_parse_plaintext (args, &opd->result.file_name, &mime); if (err) return err; opd->result.is_mime = !!mime; } break; case GPGME_STATUS_VERIFICATION_COMPLIANCE_MODE: PARSE_COMPLIANCE_FLAGS (args, opd->current_sig); break; default: break; } return 0; } static gpgme_error_t verify_status_handler (void *priv, gpgme_status_code_t code, char *args) { gpgme_error_t err; err = _gpgme_progress_status_handler (priv, code, args); if (!err) err = _gpgme_verify_status_handler (priv, code, args); return err; } gpgme_error_t _gpgme_op_verify_init_result (gpgme_ctx_t ctx) { void *hook; op_data_t opd; return _gpgme_op_data_lookup (ctx, OPDATA_VERIFY, &hook, sizeof (*opd), release_op_data); } static gpgme_error_t verify_start (gpgme_ctx_t ctx, int synchronous, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext) { gpgme_error_t err; err = _gpgme_op_reset (ctx, synchronous); if (err) return err; err = _gpgme_op_verify_init_result (ctx); if (err) return err; _gpgme_engine_set_status_handler (ctx->engine, verify_status_handler, ctx); if (!sig) return gpg_error (GPG_ERR_NO_DATA); return _gpgme_engine_op_verify (ctx->engine, sig, signed_text, plaintext, ctx); } /* Decrypt ciphertext CIPHER and make a signature verification within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_verify_start (gpgme_ctx_t ctx, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext) { gpg_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_verify_start", ctx, "sig=%p, signed_text=%p, plaintext=%p", sig, signed_text, plaintext); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = verify_start (ctx, 0, sig, signed_text, plaintext); return TRACE_ERR (err); } /* Decrypt ciphertext CIPHER and make a signature verification within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_verify (gpgme_ctx_t ctx, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext) { gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_verify", ctx, "sig=%p, signed_text=%p, plaintext=%p", sig, signed_text, plaintext); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = verify_start (ctx, 1, sig, signed_text, plaintext); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); } /* Compatibility interfaces. */ /* Get the key used to create signature IDX in CTX and return it in R_KEY. */ gpgme_error_t gpgme_get_sig_key (gpgme_ctx_t ctx, int idx, gpgme_key_t *r_key) { gpgme_verify_result_t result; gpgme_signature_t sig; if (!ctx) return gpg_error (GPG_ERR_INV_VALUE); result = gpgme_op_verify_result (ctx); sig = result->signatures; while (sig && idx) { sig = sig->next; idx--; } if (!sig || idx) return gpg_error (GPG_ERR_EOF); return gpgme_get_key (ctx, sig->fpr, r_key, 0); } /* Retrieve the signature status of signature IDX in CTX after a successful verify operation in R_STAT (if non-null). The creation time stamp of the signature is returned in R_CREATED (if non-null). The function returns a string containing the fingerprint. */ const char * gpgme_get_sig_status (gpgme_ctx_t ctx, int idx, _gpgme_sig_stat_t *r_stat, time_t *r_created) { gpgme_verify_result_t result; gpgme_signature_t sig; result = gpgme_op_verify_result (ctx); sig = result->signatures; while (sig && idx) { sig = sig->next; idx--; } if (!sig || idx) return NULL; if (r_stat) { switch (gpg_err_code (sig->status)) { case GPG_ERR_NO_ERROR: *r_stat = GPGME_SIG_STAT_GOOD; break; case GPG_ERR_BAD_SIGNATURE: *r_stat = GPGME_SIG_STAT_BAD; break; case GPG_ERR_NO_PUBKEY: *r_stat = GPGME_SIG_STAT_NOKEY; break; case GPG_ERR_NO_DATA: *r_stat = GPGME_SIG_STAT_NOSIG; break; case GPG_ERR_SIG_EXPIRED: *r_stat = GPGME_SIG_STAT_GOOD_EXP; break; case GPG_ERR_KEY_EXPIRED: *r_stat = GPGME_SIG_STAT_GOOD_EXPKEY; break; default: *r_stat = GPGME_SIG_STAT_ERROR; break; } } if (r_created) *r_created = sig->timestamp; return sig->fpr; } /* Retrieve certain attributes of a signature. IDX is the index number of the signature after a successful verify operation. WHAT is an attribute where GPGME_ATTR_EXPIRE is probably the most useful one. WHATIDX is to be passed as 0 for most attributes . */ unsigned long gpgme_get_sig_ulong_attr (gpgme_ctx_t ctx, int idx, _gpgme_attr_t what, int whatidx) { gpgme_verify_result_t result; gpgme_signature_t sig; (void)whatidx; result = gpgme_op_verify_result (ctx); sig = result->signatures; while (sig && idx) { sig = sig->next; idx--; } if (!sig || idx) return 0; switch (what) { case GPGME_ATTR_CREATED: return sig->timestamp; case GPGME_ATTR_EXPIRE: return sig->exp_timestamp; case GPGME_ATTR_VALIDITY: return (unsigned long) sig->validity; case GPGME_ATTR_SIG_STATUS: switch (gpg_err_code (sig->status)) { case GPG_ERR_NO_ERROR: return GPGME_SIG_STAT_GOOD; case GPG_ERR_BAD_SIGNATURE: return GPGME_SIG_STAT_BAD; case GPG_ERR_NO_PUBKEY: return GPGME_SIG_STAT_NOKEY; case GPG_ERR_NO_DATA: return GPGME_SIG_STAT_NOSIG; case GPG_ERR_SIG_EXPIRED: return GPGME_SIG_STAT_GOOD_EXP; case GPG_ERR_KEY_EXPIRED: return GPGME_SIG_STAT_GOOD_EXPKEY; default: return GPGME_SIG_STAT_ERROR; } case GPGME_ATTR_SIG_SUMMARY: return sig->summary; default: break; } return 0; } const char * gpgme_get_sig_string_attr (gpgme_ctx_t ctx, int idx, _gpgme_attr_t what, int whatidx) { gpgme_verify_result_t result; gpgme_signature_t sig; result = gpgme_op_verify_result (ctx); sig = result->signatures; while (sig && idx) { sig = sig->next; idx--; } if (!sig || idx) return NULL; switch (what) { case GPGME_ATTR_FPR: return sig->fpr; case GPGME_ATTR_ERRTOK: if (whatidx == 1) return sig->wrong_key_usage ? "Wrong_Key_Usage" : ""; else return ""; default: break; } return NULL; } diff --git a/src/version.c b/src/version.c index 8c35ed0e..3bf12e94 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, "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, - "req_version=%s, offset_sig_validity=%i", + "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, "offset_sig_validity mismatch: expected %i", - offsetof (struct _gpgme_signature, validity)); + (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; }