diff --git a/src/conversion.c b/src/conversion.c index 92dd2141..5b84f672 100644 --- a/src/conversion.c +++ b/src/conversion.c @@ -1,577 +1,577 @@ /* conversion.c - String conversion helper functions. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_SYS_TYPES_H /* Solaris 8 needs sys/types.h before time.h. */ # include #endif #include #include #include #include "gpgme.h" #include "util.h" #include "debug.h" #define atoi_1(p) (*(p) - '0' ) #define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1)) #define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2)) static char * do_strconcat (const char *s1, va_list arg_ptr) { const char *argv[16]; size_t argc; size_t needed; char *buffer, *p; argc = 0; argv[argc++] = s1; needed = strlen (s1); while (((argv[argc] = va_arg (arg_ptr, const char *)))) { needed += strlen (argv[argc]); if (argc >= DIM (argv)-1) { gpg_err_set_errno (EINVAL); return NULL; } argc++; } needed++; buffer = malloc (needed); if (buffer) { for (p = buffer, argc=0; argv[argc]; argc++) p = stpcpy (p, argv[argc]); } return buffer; } /* Concatenate the string S1 with all the following strings up to a * NULL. Returns a malloced buffer with the new string or NULL on a malloc error or if too many arguments are given. */ char * _gpgme_strconcat (const char *s1, ...) { va_list arg_ptr; char *result; if (!s1) result = strdup (""); else { va_start (arg_ptr, s1); result = do_strconcat (s1, arg_ptr); va_end (arg_ptr); } return result; } /* Convert two hexadecimal digits from STR to the value they represent. Returns -1 if one of the characters is not a hexadecimal digit. */ int _gpgme_hextobyte (const char *str) { int val = 0; int i; #define NROFHEXDIGITS 2 for (i = 0; i < NROFHEXDIGITS; i++) { if (*str >= '0' && *str <= '9') val += *str - '0'; else if (*str >= 'A' && *str <= 'F') val += 10 + *str - 'A'; else if (*str >= 'a' && *str <= 'f') val += 10 + *str - 'a'; else return -1; if (i < NROFHEXDIGITS - 1) val *= 16; str++; } return val; } /* Decode the C formatted string SRC and store the result in the buffer *DESTP which is LEN bytes long. If LEN is zero, then a large enough buffer is allocated with malloc and *DESTP is set to the result. Currently, LEN is only used to specify if allocation is desired or not, the caller is expected to make sure that *DESTP is large enough if LEN is not zero. */ gpgme_error_t _gpgme_decode_c_string (const char *src, char **destp, size_t len) { char *dest; /* Set up the destination buffer. */ if (len) { if (len < strlen (src) + 1) return gpg_error (GPG_ERR_INTERNAL); dest = *destp; } else { /* The converted string will never be larger than the original string. */ dest = malloc (strlen (src) + 1); if (!dest) return gpg_error_from_syserror (); *destp = dest; } /* Convert the string. */ while (*src) { if (*src != '\\') { *(dest++) = *(src++); continue; } switch (src[1]) { #define DECODE_ONE(match,result) \ case match: \ src += 2; \ *(dest++) = result; \ break; DECODE_ONE ('\'', '\''); DECODE_ONE ('\"', '\"'); DECODE_ONE ('\?', '\?'); DECODE_ONE ('\\', '\\'); DECODE_ONE ('a', '\a'); DECODE_ONE ('b', '\b'); DECODE_ONE ('f', '\f'); DECODE_ONE ('n', '\n'); DECODE_ONE ('r', '\r'); DECODE_ONE ('t', '\t'); DECODE_ONE ('v', '\v'); case 'x': { int val = _gpgme_hextobyte (&src[2]); if (val == -1) { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); if (*src) *(dest++) = *(src++); if (*src) *(dest++) = *(src++); } else { if (!val) { /* A binary zero is not representable in a C string. */ *(dest++) = '\\'; *(dest++) = '0'; } else *((unsigned char *) dest++) = val; src += 4; } } break; default: { /* Should not happen. */ *(dest++) = *(src++); *(dest++) = *(src++); } } } *(dest++) = 0; return 0; } /* Decode the percent escaped string SRC and store the result in the buffer *DESTP which is LEN bytes long. If LEN is zero, then a large enough buffer is allocated with malloc and *DESTP is set to the result. Currently, LEN is only used to specify if allocation is desired or not, the caller is expected to make sure that *DESTP is large enough if LEN is not zero. If BINARY is 1, then '\0' characters are allowed in the output. */ gpgme_error_t _gpgme_decode_percent_string (const char *src, char **destp, size_t len, int binary) { char *dest; /* Set up the destination buffer. */ if (len) { if (len < strlen (src) + 1) return gpg_error (GPG_ERR_INTERNAL); dest = *destp; } else { /* The converted string will never be larger than the original string. */ dest = malloc (strlen (src) + 1); if (!dest) return gpg_error_from_syserror (); *destp = dest; } /* Convert the string. */ while (*src) { if (*src != '%') { *(dest++) = *(src++); continue; } else { int val = _gpgme_hextobyte (&src[1]); if (val == -1) { /* Should not happen. */ *(dest++) = *(src++); if (*src) *(dest++) = *(src++); if (*src) *(dest++) = *(src++); } else { if (!val && !binary) { /* A binary zero is not representable in a C string. */ *(dest++) = '\\'; *(dest++) = '0'; } else *((unsigned char *) dest++) = val; src += 3; } } } *(dest++) = 0; return 0; } /* Encode the string SRC with percent escaping and store the result in the buffer *DESTP which is LEN bytes long. If LEN is zero, then a large enough buffer is allocated with malloc and *DESTP is set to the result. Currently, LEN is only used to specify if allocation is desired or not, the caller is expected to make sure that *DESTP is large enough if LEN is not zero. If BINARY is 1, then '\0' characters are allowed in the output. */ gpgme_error_t _gpgme_encode_percent_string (const char *src, char **destp, size_t len) { size_t destlen; char *dest; const char *str; destlen = 0; str = src; /* We percent-escape the + character because the user might need a "percent plus" escaped string (special gpg format). But we percent-escape the space character, which works with and without the special plus format. */ while (*str) { if (*str == '+' || *str == '\"' || *str == '%' || *(const unsigned char *)str <= 0x20) destlen += 3; else destlen++; str++; } /* Terminating nul byte. */ destlen++; /* Set up the destination buffer. */ if (len) { if (len < destlen) return gpg_error (GPG_ERR_INTERNAL); dest = *destp; } else { /* The converted string will never be larger than the original string. */ dest = malloc (destlen); if (!dest) return gpg_error_from_syserror (); *destp = dest; } /* Convert the string. */ while (*src) { if (*src == '+' || *src == '\"' || *src == '%' || *(const unsigned char *)src <= 0x20) { snprintf (dest, 4, "%%%02X", *(unsigned char *)src); dest += 3; } else *(dest++) = *src; src++; } *(dest++) = 0; return 0; } /* Split a string into space delimited fields and remove leading and - * trailing spaces from each field. A pointer to the each field is + * trailing spaces from each field. A pointer to each field is * stored in ARRAY. Stop splitting at ARRAYSIZE fields. The function * modifies STRING. The number of parsed fields is returned. */ int _gpgme_split_fields (char *string, char **array, int arraysize) { int n = 0; char *p, *pend; for (p = string; *p == ' '; p++) ; do { if (n == arraysize) break; array[n++] = p; pend = strchr (p, ' '); if (!pend) break; *pend++ = 0; for (p = pend; *p == ' '; p++) ; } while (*p); return n; } /* Convert the field STRING into an unsigned long value. Check for * trailing garbage. */ gpgme_error_t _gpgme_strtoul_field (const char *string, unsigned long *result) { char *endp; gpg_err_set_errno (0); *result = strtoul (string, &endp, 0); if (errno) return gpg_error_from_syserror (); if (endp == string || *endp) return gpg_error (GPG_ERR_INV_VALUE); return 0; } /* Convert STRING into an offset value. Note that this functions only * allows for a base-10 length. This function is similar to atoi() * and thus there is no error checking. */ gpgme_off_t _gpgme_string_to_off (const char *string) { gpgme_off_t value = 0; while (*string == ' ' || *string == '\t') string++; for (; *string >= '0' && *string <= '9'; string++) { value *= 10; value += atoi_1 (string); } return value; } #ifdef HAVE_W32_SYSTEM static time_t _gpgme_timegm (struct tm *tm) { /* This one is thread safe. */ SYSTEMTIME st; FILETIME ft; unsigned long long cnsecs; st.wYear = tm->tm_year + 1900; st.wMonth = tm->tm_mon + 1; st.wDay = tm->tm_mday; st.wHour = tm->tm_hour; st.wMinute = tm->tm_min; st.wSecond = tm->tm_sec; st.wMilliseconds = 0; /* Not available. */ st.wDayOfWeek = 0; /* Ignored. */ /* System time is UTC thus the conversion is pretty easy. */ if (!SystemTimeToFileTime (&st, &ft)) { gpg_err_set_errno (EINVAL); return (time_t)(-1); } cnsecs = (((unsigned long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime); cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ return (time_t)(cnsecs / 10000000ULL); } #endif /* Parse the string TIMESTAMP into a time_t. The string may either be seconds since Epoch or in the ISO 8601 format like "20390815T143012". Returns 0 for an empty string or seconds since Epoch. Leading spaces are skipped. If ENDP is not NULL, it will point to the next non-parsed character in TIMESTRING. */ time_t _gpgme_parse_timestamp (const char *timestamp, char **endp) { /* Need to skip leading spaces, because that is what strtoul does but not our ISO 8601 checking code. */ while (*timestamp && *timestamp== ' ') timestamp++; if (!*timestamp) return 0; if (strlen (timestamp) >= 15 && timestamp[8] == 'T') { struct tm buf; int year; year = atoi_4 (timestamp); if (year < 1900) return (time_t)(-1); if (endp) *endp = (char*)(timestamp + 15); /* Fixme: We would better use a configure test to see whether mktime can handle dates beyond 2038. */ if (sizeof (time_t) <= 4 && year >= 2038) return (time_t)2145914603; /* 2037-12-31 23:23:23 */ memset (&buf, 0, sizeof buf); buf.tm_year = year - 1900; buf.tm_mon = atoi_2 (timestamp+4) - 1; buf.tm_mday = atoi_2 (timestamp+6); buf.tm_hour = atoi_2 (timestamp+9); buf.tm_min = atoi_2 (timestamp+11); buf.tm_sec = atoi_2 (timestamp+13); #ifdef HAVE_W32_SYSTEM return _gpgme_timegm (&buf); #else #ifdef HAVE_TIMEGM return timegm (&buf); #else { time_t tim; putenv ("TZ=UTC"); tim = mktime (&buf); #ifdef __GNUC__ #warning fixme: we must somehow reset TZ here. It is not threadsafe anyway. #endif return tim; } #endif /* !HAVE_TIMEGM */ #endif /* !HAVE_W32_SYSTEM */ } else return (time_t)strtoul (timestamp, endp, 10); } /* This function is similar to _gpgme_parse_timestamp but returns an * unsigned long and 0 on error. */ unsigned long _gpgme_parse_timestamp_ul (const char *timestamp) { time_t tim; char *tail; if (!*timestamp) return 0; /* Shortcut empty strings. */ tim = _gpgme_parse_timestamp (timestamp, &tail); if (tim == -1 || timestamp == tail || (*tail && *tail != ' ')) tim = 0; /* No time given or invalid engine. */ return (unsigned long)tim; } /* The GPG backend uses OpenPGP algorithm numbers which we need to map to our algorithm numbers. This function MUST not change ERRNO. */ int _gpgme_map_pk_algo (int algo, gpgme_protocol_t protocol) { if (protocol == GPGME_PROTOCOL_OPENPGP) { switch (algo) { case 1: case 2: case 3: case 16: case 17: break; case 18: algo = GPGME_PK_ECDH; break; case 19: algo = GPGME_PK_ECDSA; break; case 20: break; case 22: algo = GPGME_PK_EDDSA; break; default: algo = 0; break; /* Unknown. */ } } return algo; } diff --git a/src/decrypt.c b/src/decrypt.c index 3b189093..91a32aef 100644 --- a/src/decrypt.c +++ b/src/decrypt.c @@ -1,443 +1,441 @@ /* decrypt.c - Decrypt function. 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include "debug.h" #include "gpgme.h" #include "util.h" #include "context.h" #include "ops.h" typedef struct { struct _gpgme_op_decrypt_result result; /* The error code from a FAILURE status line or 0. */ gpg_error_t failure_code; int okay; int failed; /* A pointer to the next pointer of the last recipient in the list. This makes appending new invalid signers painless while preserving the order. */ gpgme_recipient_t *last_recipient_p; } *op_data_t; static void release_op_data (void *hook) { op_data_t opd = (op_data_t) hook; gpgme_recipient_t recipient = opd->result.recipients; if (opd->result.unsupported_algorithm) free (opd->result.unsupported_algorithm); if (opd->result.file_name) free (opd->result.file_name); if (opd->result.session_key) free (opd->result.session_key); while (recipient) { gpgme_recipient_t next = recipient->next; free (recipient); recipient = next; } } gpgme_decrypt_result_t gpgme_op_decrypt_result (gpgme_ctx_t ctx) { void *hook; op_data_t opd; gpgme_error_t err; TRACE_BEG (DEBUG_CTX, "gpgme_op_decrypt_result", ctx); err = _gpgme_op_data_lookup (ctx, OPDATA_DECRYPT, &hook, -1, NULL); opd = hook; if (err || !opd) { TRACE_SUC0 ("result=(null)"); return NULL; } if (_gpgme_debug_trace ()) { gpgme_recipient_t rcp; if (opd->result.unsupported_algorithm) { TRACE_LOG1 ("result: unsupported_algorithm: %s", opd->result.unsupported_algorithm); } if (opd->result.wrong_key_usage) { TRACE_LOG ("result: wrong key usage"); } rcp = opd->result.recipients; while (rcp) { TRACE_LOG3 ("result: recipient: keyid=%s, pubkey_algo=%i, " "status=%s", rcp->keyid, rcp->pubkey_algo, gpg_strerror (rcp->status)); rcp = rcp->next; } if (opd->result.file_name) { TRACE_LOG1 ("result: original file name: %s", opd->result.file_name); } } TRACE_SUC1 ("result=%p", &opd->result); return &opd->result; } + +/* Parse the ARGS of an error status line and record some error + * conditions at OPD. Returns 0 on success. */ +static gpgme_error_t +parse_status_error (char *args, op_data_t opd) +{ + gpgme_error_t err; + char *field[3]; + int nfields; + + nfields = _gpgme_split_fields (args, field, DIM (field)); + if (nfields < 1) + return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Required arg missing. */ + err = nfields < 2 ? 0 : atoi (field[1]); + + if (!strcmp (field[0], "decrypt.algorithm")) + { + if (gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM + && nfields > 2 + && strcmp (field[2], "?")) + { + opd->result.unsupported_algorithm = strdup (field[2]); + if (!opd->result.unsupported_algorithm) + return gpg_error_from_syserror (); + } + } + else if (!strcmp (field[0], "decrypt.keyusage")) + { + if (gpg_err_code (err) == GPG_ERR_WRONG_KEY_USAGE) + opd->result.wrong_key_usage = 1; + } + + return 0; +} + + static gpgme_error_t parse_enc_to (char *args, gpgme_recipient_t *recp, gpgme_protocol_t protocol) { gpgme_recipient_t rec; char *tail; int i; rec = malloc (sizeof (*rec)); if (!rec) return gpg_error_from_syserror (); rec->next = NULL; rec->keyid = rec->_keyid; rec->status = 0; for (i = 0; i < sizeof (rec->_keyid) - 1; i++) { if (args[i] == '\0' || args[i] == ' ') break; rec->_keyid[i] = args[i]; } rec->_keyid[i] = '\0'; args = &args[i]; if (*args != '\0' && *args != ' ') { free (rec); return trace_gpg_error (GPG_ERR_INV_ENGINE); } while (*args == ' ') args++; if (*args) { gpg_err_set_errno (0); rec->pubkey_algo = _gpgme_map_pk_algo (strtol (args, &tail, 0), protocol); if (errno || args == tail || *tail != ' ') { /* The crypto backend does not behave. */ free (rec); return trace_gpg_error (GPG_ERR_INV_ENGINE); } } /* FIXME: The key length is always 0 right now, so no need to parse it. */ *recp = rec; return 0; } gpgme_error_t _gpgme_decrypt_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_passphrase_status_handler (priv, code, args); if (err) return err; err = _gpgme_op_data_lookup (ctx, OPDATA_DECRYPT, &hook, -1, NULL); opd = hook; if (err) return err; switch (code) { case GPGME_STATUS_FAILURE: opd->failure_code = _gpgme_parse_failure (args); break; case GPGME_STATUS_EOF: /* FIXME: These error values should probably be attributed to the underlying crypto engine (as error source). */ if (opd->failed) return gpg_error (GPG_ERR_DECRYPT_FAILED); else if (!opd->okay) return gpg_error (GPG_ERR_NO_DATA); else if (opd->failure_code) return opd->failure_code; break; case GPGME_STATUS_DECRYPTION_INFO: /* Fixme: Provide a way to return the used symmetric algorithm. */ break; case GPGME_STATUS_DECRYPTION_OKAY: opd->okay = 1; break; case GPGME_STATUS_DECRYPTION_FAILED: opd->failed = 1; break; case GPGME_STATUS_ERROR: /* Note that this is an informational status code which should not lead to an error return unless it is something not related to the backend. */ - { - const char d_alg[] = "decrypt.algorithm"; - const char k_alg[] = "decrypt.keyusage"; - - if (!strncmp (args, d_alg, sizeof (d_alg) - 1)) - { - args += sizeof (d_alg) - 1; - while (*args == ' ') - args++; - - if (gpg_err_code (atoi (args)) == GPG_ERR_UNSUPPORTED_ALGORITHM) - { - char *end; - - while (*args && *args != ' ') - args++; - while (*args == ' ') - args++; - - end = strchr (args, ' '); - if (end) - *end = '\0'; - - if (!(*args == '?' && *(args + 1) == '\0')) - { - opd->result.unsupported_algorithm = strdup (args); - if (!opd->result.unsupported_algorithm) - return gpg_error_from_syserror (); - } - } - } - else if (!strncmp (args, k_alg, sizeof (k_alg) - 1)) - { - args += sizeof (k_alg) - 1; - while (*args == ' ') - args++; - - if (gpg_err_code (atoi (args)) == GPG_ERR_WRONG_KEY_USAGE) - opd->result.wrong_key_usage = 1; - } - } + err = parse_status_error (args, opd); + if (err) + return err; break; case GPGME_STATUS_ENC_TO: err = parse_enc_to (args, opd->last_recipient_p, ctx->protocol); if (err) return err; opd->last_recipient_p = &(*opd->last_recipient_p)->next; break; case GPGME_STATUS_SESSION_KEY: if (opd->result.session_key) free (opd->result.session_key); opd->result.session_key = strdup(args); break; case GPGME_STATUS_NO_SECKEY: { gpgme_recipient_t rec = opd->result.recipients; while (rec) { if (!strcmp (rec->keyid, args)) { rec->status = gpg_error (GPG_ERR_NO_SECKEY); break; } rec = rec->next; } /* FIXME: Is this ok? */ if (!rec) return trace_gpg_error (GPG_ERR_INV_ENGINE); } break; case GPGME_STATUS_PLAINTEXT: err = _gpgme_parse_plaintext (args, &opd->result.file_name); if (err) return err; break; case GPGME_STATUS_INQUIRE_MAXLEN: if (ctx->status_cb && !ctx->full_status) { err = ctx->status_cb (ctx->status_cb_value, "INQUIRE_MAXLEN", args); if (err) return err; } break; case GPGME_STATUS_DECRYPTION_COMPLIANCE_MODE: PARSE_COMPLIANCE_FLAGS (args, &opd->result); break; default: break; } return 0; } static gpgme_error_t decrypt_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_decrypt_status_handler (priv, code, args); return err; } gpgme_error_t _gpgme_op_decrypt_init_result (gpgme_ctx_t ctx) { gpgme_error_t err; void *hook; op_data_t opd; err = _gpgme_op_data_lookup (ctx, OPDATA_DECRYPT, &hook, sizeof (*opd), release_op_data); opd = hook; if (err) return err; opd->last_recipient_p = &opd->result.recipients; return 0; } gpgme_error_t _gpgme_decrypt_start (gpgme_ctx_t ctx, int synchronous, gpgme_decrypt_flags_t flags, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; assert (!(flags & GPGME_DECRYPT_VERIFY)); err = _gpgme_op_reset (ctx, synchronous); if (err) return err; err = _gpgme_op_decrypt_init_result (ctx); if (err) return err; if (!cipher) return gpg_error (GPG_ERR_NO_DATA); if (!plain) return gpg_error (GPG_ERR_INV_VALUE); if (err) return err; if (ctx->passphrase_cb) { err = _gpgme_engine_set_command_handler (ctx->engine, _gpgme_passphrase_command_handler, ctx, NULL); if (err) return err; } _gpgme_engine_set_status_handler (ctx->engine, decrypt_status_handler, ctx); return _gpgme_engine_op_decrypt (ctx->engine, flags, cipher, plain, ctx->export_session_keys, ctx->override_session_key); } gpgme_error_t gpgme_op_decrypt_start (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; TRACE_BEG2 (DEBUG_CTX, "gpgme_op_decrypt_start", ctx, "cipher=%p, plain=%p", cipher, plain); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = _gpgme_decrypt_start (ctx, 0, 0, cipher, plain); return TRACE_ERR (err); } /* Decrypt ciphertext CIPHER within CTX and store the resulting plaintext in PLAIN. */ gpgme_error_t gpgme_op_decrypt (gpgme_ctx_t ctx, gpgme_data_t cipher, gpgme_data_t plain) { gpgme_error_t err; TRACE_BEG2 (DEBUG_CTX, "gpgme_op_decrypt", ctx, "cipher=%p, plain=%p", cipher, plain); if (!ctx) return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE)); err = _gpgme_decrypt_start (ctx, 1, 0, cipher, plain); if (!err) err = _gpgme_wait_one (ctx); return TRACE_ERR (err); }