diff --git a/src/gpgme-json.c b/src/gpgme-json.c index d3309b8c..7bb1baf1 100644 --- a/src/gpgme-json.c +++ b/src/gpgme-json.c @@ -1,3595 +1,3611 @@ /* gpgme-json.c - JSON based interface to gpgme (server) * Copyright (C) 2018 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+ */ /* This is tool implements the Native Messaging protocol of web * browsers and provides the server part of it. A Javascript based * client can be found in lang/javascript. */ #include #include #include #include #include #ifdef HAVE_LOCALE_H #include #endif #include #include #define GPGRT_ENABLE_ES_MACROS 1 #define GPGRT_ENABLE_LOG_MACROS 1 #define GPGRT_ENABLE_ARGPARSE_MACROS 1 #include "gpgme.h" #include "cJSON.h" #if GPGRT_VERSION_NUMBER < 0x011c00 /* 1.28 */ int main (void){fputs ("Build with Libgpg-error >= 1.28!\n", stderr);return 1;} #else /* libgpg-error >= 1.28 */ /* We don't allow a request with more than 64 MiB. */ #define MAX_REQUEST_SIZE (64 * 1024 * 1024) -/* Minimal, default and maximum chunk size for returned data. The - * first chunk is returned directly. If the "more" flag is also - * returned, a "getmore" command needs to be used to get the next - * chunk. Right now this value covers just the value of the "data" - * element; so to cover for the other returned objects this values - * needs to be lower than the maximum allowed size of the browser. */ -#define MIN_REPLY_CHUNK_SIZE 512 -#define DEF_REPLY_CHUNK_SIZE (512 * 1024) +/* Minimal chunk size for returned data.*/ +#define MIN_REPLY_CHUNK_SIZE 30 + +/* If no chunksize is provided we print everything. Changing + * this to a positive value will result in all messages beeing + * chunked. */ +#define DEF_REPLY_CHUNK_SIZE 0 #define MAX_REPLY_CHUNK_SIZE (10 * 1024 * 1024) static void xoutofcore (const char *type) GPGRT_ATTR_NORETURN; static cjson_t error_object_v (cjson_t json, const char *message, va_list arg_ptr, gpg_error_t err) GPGRT_ATTR_PRINTF(2,0); static cjson_t error_object (cjson_t json, const char *message, ...) GPGRT_ATTR_PRINTF(2,3); static char *error_object_string (const char *message, ...) GPGRT_ATTR_PRINTF(1,2); +static char *process_request (const char *request); /* True if interactive mode is active. */ static int opt_interactive; /* True is debug mode is active. */ static int opt_debug; /* Pending data to be returned by a getmore command. */ static struct { char *buffer; /* Malloced data or NULL if not used. */ size_t length; /* Length of that data. */ size_t written; /* # of already written bytes from BUFFER. */ - const char *type;/* The "type" of the data. */ - int base64; /* The "base64" flag of the data. */ } pending_data; /* * Helper functions and macros */ #define xtrymalloc(a) gpgrt_malloc ((a)) #define xtrystrdup(a) gpgrt_strdup ((a)) #define xmalloc(a) ({ \ void *_r = gpgrt_malloc ((a)); \ if (!_r) \ xoutofcore ("malloc"); \ _r; }) #define xcalloc(a,b) ({ \ void *_r = gpgrt_calloc ((a), (b)); \ if (!_r) \ xoutofcore ("calloc"); \ _r; }) #define xstrdup(a) ({ \ char *_r = gpgrt_strdup ((a)); \ if (!_r) \ xoutofcore ("strdup"); \ _r; }) #define xstrconcat(a, ...) ({ \ char *_r = gpgrt_strconcat ((a), __VA_ARGS__); \ if (!_r) \ xoutofcore ("strconcat"); \ _r; }) #define xfree(a) gpgrt_free ((a)) #define spacep(p) (*(p) == ' ' || *(p) == '\t') #ifndef HAVE_STPCPY static GPGRT_INLINE char * _my_stpcpy (char *a, const char *b) { while (*b) *a++ = *b++; *a = 0; return a; } #define stpcpy(a,b) _my_stpcpy ((a), (b)) #endif /*!HAVE_STPCPY*/ /* Free a NULL terminated array */ static void xfree_array (char **array) { if (array) { int idx; for (idx = 0; array[idx]; idx++) xfree (array[idx]); xfree (array); } } static void xoutofcore (const char *type) { gpg_error_t err = gpg_error_from_syserror (); log_error ("%s failed: %s\n", type, gpg_strerror (err)); exit (2); } /* Call cJSON_CreateObject but terminate in case of an error. */ static cjson_t xjson_CreateObject (void) { cjson_t json = cJSON_CreateObject (); if (!json) xoutofcore ("cJSON_CreateObject"); return json; } /* Call cJSON_CreateArray but terminate in case of an error. */ static cjson_t xjson_CreateArray (void) { cjson_t json = cJSON_CreateArray (); if (!json) xoutofcore ("cJSON_CreateArray"); return json; } /* Wrapper around cJSON_AddStringToObject which returns an gpg-error * code instead of the NULL or the new object. */ static gpg_error_t cjson_AddStringToObject (cjson_t object, const char *name, const char *string) { if (!cJSON_AddStringToObject (object, name, string)) return gpg_error_from_syserror (); return 0; } /* Same as cjson_AddStringToObject but prints an error message and * terminates the process. */ static void xjson_AddStringToObject (cjson_t object, const char *name, const char *string) { if (!cJSON_AddStringToObject (object, name, string)) xoutofcore ("cJSON_AddStringToObject"); } /* Same as xjson_AddStringToObject but ignores NULL strings */ static void xjson_AddStringToObject0 (cjson_t object, const char *name, const char *string) { if (!string) return; xjson_AddStringToObject (object, name, string); } /* Wrapper around cJSON_AddBoolToObject which terminates the process * in case of an error. */ static void xjson_AddBoolToObject (cjson_t object, const char *name, int abool) { if (!cJSON_AddBoolToObject (object, name, abool)) xoutofcore ("cJSON_AddStringToObject"); return ; } /* Wrapper around cJSON_AddNumberToObject which terminates the process * in case of an error. */ static void xjson_AddNumberToObject (cjson_t object, const char *name, double dbl) { if (!cJSON_AddNumberToObject (object, name, dbl)) xoutofcore ("cJSON_AddNumberToObject"); return ; } /* Wrapper around cJSON_AddItemToObject which terminates the process * in case of an error. */ static void xjson_AddItemToObject (cjson_t object, const char *name, cjson_t item) { if (!cJSON_AddItemToObject (object, name, item)) xoutofcore ("cJSON_AddItemToObject"); return ; } /* This is similar to cJSON_AddStringToObject but takes (DATA, * DATALEN) and adds it under NAME as a base 64 encoded string to * OBJECT. */ static gpg_error_t add_base64_to_object (cjson_t object, const char *name, const void *data, size_t datalen) { #if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */ return gpg_error (GPG_ERR_NOT_SUPPORTED); #else gpg_err_code_t err; estream_t fp = NULL; gpgrt_b64state_t state = NULL; cjson_t j_str = NULL; void *buffer = NULL; fp = es_fopenmem (0, "rwb"); if (!fp) { err = gpg_err_code_from_syserror (); goto leave; } state = gpgrt_b64enc_start (fp, ""); if (!state) { err = gpg_err_code_from_syserror (); goto leave; } err = gpgrt_b64enc_write (state, data, datalen); if (err) goto leave; err = gpgrt_b64enc_finish (state); state = NULL; if (err) return err; es_fputc (0, fp); if (es_fclose_snatch (fp, &buffer, NULL)) { fp = NULL; err = gpg_error_from_syserror (); goto leave; } fp = NULL; j_str = cJSON_CreateStringConvey (buffer); if (!j_str) { err = gpg_error_from_syserror (); goto leave; } buffer = NULL; if (!cJSON_AddItemToObject (object, name, j_str)) { err = gpg_error_from_syserror (); cJSON_Delete (j_str); j_str = NULL; goto leave; } j_str = NULL; leave: xfree (buffer); cJSON_Delete (j_str); gpgrt_b64enc_finish (state); es_fclose (fp); return err; #endif } /* Create a JSON error object. If JSON is not NULL the error message * is appended to that object. An existing "type" item will be replaced. */ static cjson_t error_object_v (cjson_t json, const char *message, va_list arg_ptr, gpg_error_t err) { cjson_t response, j_tmp; char *msg; msg = gpgrt_vbsprintf (message, arg_ptr); if (!msg) xoutofcore ("error_object"); response = json? json : xjson_CreateObject (); if (!(j_tmp = cJSON_GetObjectItem (response, "type"))) xjson_AddStringToObject (response, "type", "error"); else /* Replace existing "type". */ { j_tmp = cJSON_CreateString ("error"); if (!j_tmp) xoutofcore ("cJSON_CreateString"); cJSON_ReplaceItemInObject (response, "type", j_tmp); } xjson_AddStringToObject (response, "msg", msg); xfree (msg); xjson_AddNumberToObject (response, "code", err); return response; } /* Call cJSON_Print but terminate in case of an error. */ static char * xjson_Print (cjson_t object) { char *buf; buf = cJSON_Print (object); if (!buf) xoutofcore ("cJSON_Print"); return buf; } static cjson_t error_object (cjson_t json, const char *message, ...) { cjson_t response; va_list arg_ptr; va_start (arg_ptr, message); response = error_object_v (json, message, arg_ptr, 0); va_end (arg_ptr); return response; } static cjson_t gpg_error_object (cjson_t json, gpg_error_t err, const char *message, ...) { cjson_t response; va_list arg_ptr; va_start (arg_ptr, message); response = error_object_v (json, message, arg_ptr, err); va_end (arg_ptr); return response; } static char * error_object_string (const char *message, ...) { cjson_t response; va_list arg_ptr; char *msg; va_start (arg_ptr, message); response = error_object_v (NULL, message, arg_ptr, 0); va_end (arg_ptr); msg = xjson_Print (response); cJSON_Delete (response); return msg; } /* Get the boolean property NAME from the JSON object and store true * or valse at R_VALUE. If the name is unknown the value of DEF_VALUE * is returned. If the type of the value is not boolean, * GPG_ERR_INV_VALUE is returned and R_VALUE set to DEF_VALUE. */ static gpg_error_t get_boolean_flag (cjson_t json, const char *name, int def_value, int *r_value) { cjson_t j_item; j_item = cJSON_GetObjectItem (json, name); if (!j_item) *r_value = def_value; else if (cjson_is_true (j_item)) *r_value = 1; else if (cjson_is_false (j_item)) *r_value = 0; else { *r_value = def_value; return gpg_error (GPG_ERR_INV_VALUE); } return 0; } /* Get the boolean property PROTOCOL from the JSON object and store * its value at R_PROTOCOL. The default is OpenPGP. */ static gpg_error_t get_protocol (cjson_t json, gpgme_protocol_t *r_protocol) { cjson_t j_item; *r_protocol = GPGME_PROTOCOL_OpenPGP; j_item = cJSON_GetObjectItem (json, "protocol"); if (!j_item) ; else if (!cjson_is_string (j_item)) return gpg_error (GPG_ERR_INV_VALUE); else if (!strcmp(j_item->valuestring, "openpgp")) ; else if (!strcmp(j_item->valuestring, "cms")) *r_protocol = GPGME_PROTOCOL_CMS; else return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); return 0; } /* Get the chunksize from JSON and store it at R_CHUNKSIZE. */ static gpg_error_t get_chunksize (cjson_t json, size_t *r_chunksize) { cjson_t j_item; *r_chunksize = DEF_REPLY_CHUNK_SIZE; j_item = cJSON_GetObjectItem (json, "chunksize"); if (!j_item) ; else if (!cjson_is_number (j_item)) return gpg_error (GPG_ERR_INV_VALUE); else if ((size_t)j_item->valueint < MIN_REPLY_CHUNK_SIZE) *r_chunksize = MIN_REPLY_CHUNK_SIZE; else if ((size_t)j_item->valueint > MAX_REPLY_CHUNK_SIZE) *r_chunksize = MAX_REPLY_CHUNK_SIZE; else *r_chunksize = (size_t)j_item->valueint; return 0; } /* Extract the keys from the array or string with the name "name" * in the JSON object. On success a string with the keys identifiers * is stored at R_KEYS. * The keys in that string are LF delimited. On failure an error code * is returned. */ static gpg_error_t get_keys (cjson_t json, const char *name, char **r_keystring) { cjson_t j_keys, j_item; int i, nkeys; char *p; size_t length; *r_keystring = NULL; j_keys = cJSON_GetObjectItem (json, name); if (!j_keys) return gpg_error (GPG_ERR_NO_KEY); if (!cjson_is_array (j_keys) && !cjson_is_string (j_keys)) return gpg_error (GPG_ERR_INV_VALUE); /* Fixme: We should better use a membuf like thing. */ length = 1; /* For the EOS. */ if (cjson_is_string (j_keys)) { nkeys = 1; length += strlen (j_keys->valuestring); if (strchr (j_keys->valuestring, '\n')) return gpg_error (GPG_ERR_INV_USER_ID); } else { nkeys = cJSON_GetArraySize (j_keys); if (!nkeys) return gpg_error (GPG_ERR_NO_KEY); for (i=0; i < nkeys; i++) { j_item = cJSON_GetArrayItem (j_keys, i); if (!j_item || !cjson_is_string (j_item)) return gpg_error (GPG_ERR_INV_VALUE); if (i) length++; /* Space for delimiter. */ length += strlen (j_item->valuestring); if (strchr (j_item->valuestring, '\n')) return gpg_error (GPG_ERR_INV_USER_ID); } } p = *r_keystring = xtrymalloc (length); if (!p) return gpg_error_from_syserror (); if (cjson_is_string (j_keys)) { strcpy (p, j_keys->valuestring); } else { for (i=0; i < nkeys; i++) { j_item = cJSON_GetArrayItem (j_keys, i); if (i) *p++ = '\n'; /* Add delimiter. */ p = stpcpy (p, j_item->valuestring); } } return 0; } /* * GPGME support functions. */ /* Helper for get_context. */ static gpgme_ctx_t _create_new_context (gpgme_protocol_t proto) { gpg_error_t err; gpgme_ctx_t ctx; err = gpgme_new (&ctx); if (err) log_fatal ("error creating GPGME context: %s\n", gpg_strerror (err)); gpgme_set_protocol (ctx, proto); gpgme_set_ctx_flag (ctx, "request-origin", "browser"); return ctx; } /* Return a context object for protocol PROTO. This is currently a * statically allocated context initialized for PROTO. Terminates * process on failure. */ static gpgme_ctx_t get_context (gpgme_protocol_t proto) { static gpgme_ctx_t ctx_openpgp, ctx_cms, ctx_conf; if (proto == GPGME_PROTOCOL_OpenPGP) { if (!ctx_openpgp) ctx_openpgp = _create_new_context (proto); return ctx_openpgp; } else if (proto == GPGME_PROTOCOL_CMS) { if (!ctx_cms) ctx_cms = _create_new_context (proto); return ctx_cms; } else if (proto == GPGME_PROTOCOL_GPGCONF) { if (!ctx_conf) ctx_conf = _create_new_context (proto); return ctx_conf; } else log_bug ("invalid protocol %d requested\n", proto); } /* Free context object retrieved by get_context. */ static void release_context (gpgme_ctx_t ctx) { /* Nothing to do right now. */ (void)ctx; } /* Create an addition context for short operations. */ static gpgme_ctx_t create_onetime_context (gpgme_protocol_t proto) { return _create_new_context (proto); } /* Release a one-time context. */ static void release_onetime_context (gpgme_ctx_t ctx) { return gpgme_release (ctx); } /* Given a Base-64 encoded string object in JSON return a gpgme data * object at R_DATA. */ static gpg_error_t data_from_base64_string (gpgme_data_t *r_data, cjson_t json) { #if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */ *r_data = NULL; return gpg_error (GPG_ERR_NOT_SUPPORTED); #else gpg_error_t err; size_t len; char *buf = NULL; gpgrt_b64state_t state = NULL; gpgme_data_t data = NULL; *r_data = NULL; /* A quick check on the JSON. */ if (!cjson_is_string (json)) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } state = gpgrt_b64dec_start (NULL); if (!state) { err = gpg_err_code_from_syserror (); goto leave; } /* Fixme: Data duplication - we should see how to snatch the memory * from the json object. */ len = strlen (json->valuestring); buf = xtrystrdup (json->valuestring); if (!buf) { err = gpg_error_from_syserror (); goto leave; } err = gpgrt_b64dec_proc (state, buf, len, &len); if (err) goto leave; err = gpgrt_b64dec_finish (state); state = NULL; if (err) goto leave; err = gpgme_data_new_from_mem (&data, buf, len, 1); if (err) goto leave; *r_data = data; data = NULL; leave: xfree (data); xfree (buf); gpgrt_b64dec_finish (state); return err; #endif } /* Create a keylist pattern array from a json keys object * in the request. Returns either a malloced NULL terminated * string array which can be used as patterns for * op_keylist_ext or NULL. */ static char ** create_keylist_patterns (cjson_t request, const char *name) { char *keystring; char *p; char *tmp; char **ret; int cnt = 1; int i = 0; if (get_keys (request, name, &keystring)) return NULL; for (p = keystring; p; p++) if (*p == '\n') cnt++; ret = xmalloc (cnt * sizeof *ret); for (p = keystring, tmp = keystring; *p; p++) { if (*p != '\n') continue; *p = '\0'; ret[i++] = xstrdup (tmp); tmp = p + 1; } /* The last key is not newline delimted. */ ret[i++] = *tmp ? xstrdup (tmp) : NULL; ret[i] = NULL; xfree (keystring); return ret; } /* Create sigsum json array */ static cjson_t sigsum_to_json (gpgme_sigsum_t summary) { cjson_t result = xjson_CreateObject (); cjson_t sigsum_array = xjson_CreateArray (); if ( (summary & GPGME_SIGSUM_VALID )) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("valid")); if ( (summary & GPGME_SIGSUM_GREEN )) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("green")); if ( (summary & GPGME_SIGSUM_RED )) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("red")); if ( (summary & GPGME_SIGSUM_KEY_REVOKED)) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("revoked")); if ( (summary & GPGME_SIGSUM_KEY_EXPIRED)) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("key-expired")); if ( (summary & GPGME_SIGSUM_SIG_EXPIRED)) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("sig-expired")); if ( (summary & GPGME_SIGSUM_KEY_MISSING)) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("key-missing")); if ( (summary & GPGME_SIGSUM_CRL_MISSING)) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("crl-missing")); if ( (summary & GPGME_SIGSUM_CRL_TOO_OLD)) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("crl-too-old")); if ( (summary & GPGME_SIGSUM_BAD_POLICY )) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("bad-policy")); if ( (summary & GPGME_SIGSUM_SYS_ERROR )) cJSON_AddItemToArray (sigsum_array, cJSON_CreateString ("sys-error")); /* The signature summary as string array. */ xjson_AddItemToObject (result, "sigsum", sigsum_array); /* Bools for the same. */ xjson_AddBoolToObject (result, "valid", (summary & GPGME_SIGSUM_VALID )); xjson_AddBoolToObject (result, "green", (summary & GPGME_SIGSUM_GREEN )); xjson_AddBoolToObject (result, "red", (summary & GPGME_SIGSUM_RED )); xjson_AddBoolToObject (result, "revoked", (summary & GPGME_SIGSUM_KEY_REVOKED)); xjson_AddBoolToObject (result, "key-expired", (summary & GPGME_SIGSUM_KEY_EXPIRED)); xjson_AddBoolToObject (result, "sig-expired", (summary & GPGME_SIGSUM_SIG_EXPIRED)); xjson_AddBoolToObject (result, "key-missing", (summary & GPGME_SIGSUM_KEY_MISSING)); xjson_AddBoolToObject (result, "crl-missing", (summary & GPGME_SIGSUM_CRL_MISSING)); xjson_AddBoolToObject (result, "crl-too-old", (summary & GPGME_SIGSUM_CRL_TOO_OLD)); xjson_AddBoolToObject (result, "bad-policy", (summary & GPGME_SIGSUM_BAD_POLICY )); xjson_AddBoolToObject (result, "sys-error", (summary & GPGME_SIGSUM_SYS_ERROR )); return result; } /* Helper for summary formatting */ static const char * validity_to_string (gpgme_validity_t val) { switch (val) { case GPGME_VALIDITY_UNDEFINED:return "undefined"; case GPGME_VALIDITY_NEVER: return "never"; case GPGME_VALIDITY_MARGINAL: return "marginal"; case GPGME_VALIDITY_FULL: return "full"; case GPGME_VALIDITY_ULTIMATE: return "ultimate"; case GPGME_VALIDITY_UNKNOWN: default: return "unknown"; } } static const char * protocol_to_string (gpgme_protocol_t proto) { switch (proto) { 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"; default: return "unknown"; } } /* Create a sig_notation json object */ static cjson_t sig_notation_to_json (gpgme_sig_notation_t not) { cjson_t result = xjson_CreateObject (); xjson_AddBoolToObject (result, "human_readable", not->human_readable); xjson_AddBoolToObject (result, "critical", not->critical); xjson_AddStringToObject0 (result, "name", not->name); xjson_AddStringToObject0 (result, "value", not->value); xjson_AddNumberToObject (result, "flags", not->flags); return result; } /* Create a key_sig json object */ static cjson_t key_sig_to_json (gpgme_key_sig_t sig) { cjson_t result = xjson_CreateObject (); xjson_AddBoolToObject (result, "revoked", sig->revoked); xjson_AddBoolToObject (result, "expired", sig->expired); xjson_AddBoolToObject (result, "invalid", sig->invalid); xjson_AddBoolToObject (result, "exportable", sig->exportable); xjson_AddStringToObject0 (result, "pubkey_algo_name", gpgme_pubkey_algo_name (sig->pubkey_algo)); xjson_AddStringToObject0 (result, "keyid", sig->keyid); xjson_AddStringToObject0 (result, "status", gpgme_strerror (sig->status)); xjson_AddStringToObject0 (result, "name", sig->name); xjson_AddStringToObject0 (result, "email", sig->email); xjson_AddStringToObject0 (result, "comment", sig->comment); xjson_AddNumberToObject (result, "pubkey_algo", sig->pubkey_algo); xjson_AddNumberToObject (result, "timestamp", sig->timestamp); xjson_AddNumberToObject (result, "expires", sig->expires); xjson_AddNumberToObject (result, "status_code", sig->status); xjson_AddNumberToObject (result, "sig_class", sig->sig_class); if (sig->notations) { gpgme_sig_notation_t not; cjson_t array = xjson_CreateArray (); for (not = sig->notations; not; not = not->next) cJSON_AddItemToArray (array, sig_notation_to_json (not)); xjson_AddItemToObject (result, "notations", array); } return result; } /* Create a tofu info object */ static cjson_t tofu_to_json (gpgme_tofu_info_t tofu) { cjson_t result = xjson_CreateObject (); xjson_AddStringToObject0 (result, "description", tofu->description); xjson_AddNumberToObject (result, "validity", tofu->validity); xjson_AddNumberToObject (result, "policy", tofu->policy); xjson_AddNumberToObject (result, "signcount", tofu->signcount); xjson_AddNumberToObject (result, "encrcount", tofu->encrcount); xjson_AddNumberToObject (result, "signfirst", tofu->signfirst); xjson_AddNumberToObject (result, "signlast", tofu->signlast); xjson_AddNumberToObject (result, "encrfirst", tofu->encrfirst); xjson_AddNumberToObject (result, "encrlast", tofu->encrlast); return result; } /* Create a userid json object */ static cjson_t uid_to_json (gpgme_user_id_t uid) { cjson_t result = xjson_CreateObject (); xjson_AddBoolToObject (result, "revoked", uid->revoked); xjson_AddBoolToObject (result, "invalid", uid->invalid); xjson_AddStringToObject0 (result, "validity", validity_to_string (uid->validity)); xjson_AddStringToObject0 (result, "uid", uid->uid); xjson_AddStringToObject0 (result, "name", uid->name); xjson_AddStringToObject0 (result, "email", uid->email); xjson_AddStringToObject0 (result, "comment", uid->comment); xjson_AddStringToObject0 (result, "address", uid->address); xjson_AddNumberToObject (result, "origin", uid->origin); xjson_AddNumberToObject (result, "last_update", uid->last_update); /* Key sigs */ if (uid->signatures) { cjson_t sig_array = xjson_CreateArray (); gpgme_key_sig_t sig; for (sig = uid->signatures; sig; sig = sig->next) cJSON_AddItemToArray (sig_array, key_sig_to_json (sig)); xjson_AddItemToObject (result, "signatures", sig_array); } /* TOFU info */ if (uid->tofu) { gpgme_tofu_info_t tofu; cjson_t array = xjson_CreateArray (); for (tofu = uid->tofu; tofu; tofu = tofu->next) cJSON_AddItemToArray (array, tofu_to_json (tofu)); xjson_AddItemToObject (result, "tofu", array); } return result; } /* Create a subkey json object */ static cjson_t subkey_to_json (gpgme_subkey_t sub) { cjson_t result = xjson_CreateObject (); xjson_AddBoolToObject (result, "revoked", sub->revoked); xjson_AddBoolToObject (result, "expired", sub->expired); xjson_AddBoolToObject (result, "disabled", sub->disabled); xjson_AddBoolToObject (result, "invalid", sub->invalid); xjson_AddBoolToObject (result, "can_encrypt", sub->can_encrypt); xjson_AddBoolToObject (result, "can_sign", sub->can_sign); xjson_AddBoolToObject (result, "can_certify", sub->can_certify); xjson_AddBoolToObject (result, "can_authenticate", sub->can_authenticate); xjson_AddBoolToObject (result, "secret", sub->secret); xjson_AddBoolToObject (result, "is_qualified", sub->is_qualified); xjson_AddBoolToObject (result, "is_cardkey", sub->is_cardkey); xjson_AddBoolToObject (result, "is_de_vs", sub->is_de_vs); xjson_AddStringToObject0 (result, "pubkey_algo_name", gpgme_pubkey_algo_name (sub->pubkey_algo)); xjson_AddStringToObject0 (result, "pubkey_algo_string", gpgme_pubkey_algo_string (sub)); xjson_AddStringToObject0 (result, "keyid", sub->keyid); xjson_AddStringToObject0 (result, "card_number", sub->card_number); xjson_AddStringToObject0 (result, "curve", sub->curve); xjson_AddStringToObject0 (result, "keygrip", sub->keygrip); xjson_AddNumberToObject (result, "pubkey_algo", sub->pubkey_algo); xjson_AddNumberToObject (result, "length", sub->length); xjson_AddNumberToObject (result, "timestamp", sub->timestamp); xjson_AddNumberToObject (result, "expires", sub->expires); return result; } /* Create a key json object */ static cjson_t key_to_json (gpgme_key_t key) { cjson_t result = xjson_CreateObject (); xjson_AddBoolToObject (result, "revoked", key->revoked); xjson_AddBoolToObject (result, "expired", key->expired); xjson_AddBoolToObject (result, "disabled", key->disabled); xjson_AddBoolToObject (result, "invalid", key->invalid); xjson_AddBoolToObject (result, "can_encrypt", key->can_encrypt); xjson_AddBoolToObject (result, "can_sign", key->can_sign); xjson_AddBoolToObject (result, "can_certify", key->can_certify); xjson_AddBoolToObject (result, "can_authenticate", key->can_authenticate); xjson_AddBoolToObject (result, "secret", key->secret); xjson_AddBoolToObject (result, "is_qualified", key->is_qualified); xjson_AddStringToObject0 (result, "protocol", protocol_to_string (key->protocol)); xjson_AddStringToObject0 (result, "issuer_serial", key->issuer_serial); xjson_AddStringToObject0 (result, "issuer_name", key->issuer_name); xjson_AddStringToObject0 (result, "fingerprint", key->fpr); xjson_AddStringToObject0 (result, "chain_id", key->chain_id); xjson_AddStringToObject0 (result, "owner_trust", validity_to_string (key->owner_trust)); xjson_AddNumberToObject (result, "origin", key->origin); xjson_AddNumberToObject (result, "last_update", key->last_update); /* Add subkeys */ if (key->subkeys) { cjson_t subkey_array = xjson_CreateArray (); gpgme_subkey_t sub; for (sub = key->subkeys; sub; sub = sub->next) cJSON_AddItemToArray (subkey_array, subkey_to_json (sub)); xjson_AddItemToObject (result, "subkeys", subkey_array); } /* User Ids */ if (key->uids) { cjson_t uid_array = xjson_CreateArray (); gpgme_user_id_t uid; for (uid = key->uids; uid; uid = uid->next) cJSON_AddItemToArray (uid_array, uid_to_json (uid)); xjson_AddItemToObject (result, "userids", uid_array); } return result; } /* Create a signature json object */ static cjson_t signature_to_json (gpgme_signature_t sig) { cjson_t result = xjson_CreateObject (); xjson_AddItemToObject (result, "summary", sigsum_to_json (sig->summary)); xjson_AddBoolToObject (result, "wrong_key_usage", sig->wrong_key_usage); xjson_AddBoolToObject (result, "chain_model", sig->chain_model); xjson_AddBoolToObject (result, "is_de_vs", sig->is_de_vs); xjson_AddStringToObject0 (result, "status_string", gpgme_strerror (sig->status)); xjson_AddStringToObject0 (result, "fingerprint", sig->fpr); xjson_AddStringToObject0 (result, "validity_string", validity_to_string (sig->validity)); xjson_AddStringToObject0 (result, "pubkey_algo_name", gpgme_pubkey_algo_name (sig->pubkey_algo)); xjson_AddStringToObject0 (result, "hash_algo_name", gpgme_hash_algo_name (sig->hash_algo)); xjson_AddStringToObject0 (result, "pka_address", sig->pka_address); xjson_AddNumberToObject (result, "status_code", sig->status); xjson_AddNumberToObject (result, "timestamp", sig->timestamp); xjson_AddNumberToObject (result, "exp_timestamp", sig->exp_timestamp); xjson_AddNumberToObject (result, "pka_trust", sig->pka_trust); xjson_AddNumberToObject (result, "validity", sig->validity); xjson_AddNumberToObject (result, "validity_reason", sig->validity_reason); if (sig->notations) { gpgme_sig_notation_t not; cjson_t array = xjson_CreateArray (); for (not = sig->notations; not; not = not->next) cJSON_AddItemToArray (array, sig_notation_to_json (not)); xjson_AddItemToObject (result, "notations", array); } return result; } /* Create a JSON object from a gpgme_verify result */ static cjson_t verify_result_to_json (gpgme_verify_result_t verify_result) { cjson_t result = xjson_CreateObject (); xjson_AddStringToObject0 (result, "file_name", verify_result->file_name); xjson_AddBoolToObject (result, "is_mime", verify_result->is_mime); if (verify_result->signatures) { cjson_t array = xjson_CreateArray (); gpgme_signature_t sig; for (sig = verify_result->signatures; sig; sig = sig->next) cJSON_AddItemToArray (array, signature_to_json (sig)); xjson_AddItemToObject (result, "signatures", array); } return result; } /* Create a JSON object from an engine_info */ static cjson_t engine_info_to_json (gpgme_engine_info_t info) { cjson_t result = xjson_CreateObject (); xjson_AddStringToObject0 (result, "protocol", protocol_to_string (info->protocol)); xjson_AddStringToObject0 (result, "fname", info->file_name); xjson_AddStringToObject0 (result, "version", info->version); xjson_AddStringToObject0 (result, "req_version", info->req_version); xjson_AddStringToObject0 (result, "homedir", info->home_dir ? info->home_dir : "default"); return result; } /* Create a JSON object from an import_status */ static cjson_t import_status_to_json (gpgme_import_status_t sts) { cjson_t result = xjson_CreateObject (); xjson_AddStringToObject0 (result, "fingerprint", sts->fpr); xjson_AddStringToObject0 (result, "error_string", gpgme_strerror (sts->result)); xjson_AddNumberToObject (result, "status", sts->status); return result; } /* Create a JSON object from an import result */ static cjson_t import_result_to_json (gpgme_import_result_t imp) { cjson_t result = xjson_CreateObject (); xjson_AddNumberToObject (result, "considered", imp->considered); xjson_AddNumberToObject (result, "no_user_id", imp->no_user_id); xjson_AddNumberToObject (result, "imported", imp->imported); xjson_AddNumberToObject (result, "imported_rsa", imp->imported_rsa); xjson_AddNumberToObject (result, "unchanged", imp->unchanged); xjson_AddNumberToObject (result, "new_user_ids", imp->new_user_ids); xjson_AddNumberToObject (result, "new_sub_keys", imp->new_sub_keys); xjson_AddNumberToObject (result, "new_signatures", imp->new_signatures); xjson_AddNumberToObject (result, "new_revocations", imp->new_revocations); xjson_AddNumberToObject (result, "secret_read", imp->secret_read); xjson_AddNumberToObject (result, "secret_imported", imp->secret_imported); xjson_AddNumberToObject (result, "secret_unchanged", imp->secret_unchanged); xjson_AddNumberToObject (result, "skipped_new_keys", imp->skipped_new_keys); xjson_AddNumberToObject (result, "not_imported", imp->not_imported); xjson_AddNumberToObject (result, "skipped_v3_keys", imp->skipped_v3_keys); if (imp->imports) { cjson_t array = xjson_CreateArray (); gpgme_import_status_t status; for (status = imp->imports; status; status = status->next) cJSON_AddItemToArray (array, import_status_to_json (status)); xjson_AddItemToObject (result, "imports", array); } return result; } /* Create a JSON object from a gpgconf arg */ static cjson_t conf_arg_to_json (gpgme_conf_arg_t arg, gpgme_conf_type_t type) { cjson_t result = xjson_CreateObject (); int is_none = 0; switch (type) { case GPGME_CONF_STRING: case GPGME_CONF_PATHNAME: case GPGME_CONF_LDAP_SERVER: case GPGME_CONF_KEY_FPR: case GPGME_CONF_PUB_KEY: case GPGME_CONF_SEC_KEY: case GPGME_CONF_ALIAS_LIST: xjson_AddStringToObject0 (result, "string", arg->value.string); break; case GPGME_CONF_UINT32: xjson_AddNumberToObject (result, "number", arg->value.uint32); break; case GPGME_CONF_INT32: xjson_AddNumberToObject (result, "number", arg->value.int32); break; case GPGME_CONF_NONE: default: is_none = 1; break; } xjson_AddBoolToObject (result, "is_none", is_none); return result; } /* Create a JSON object from a gpgconf option */ static cjson_t conf_opt_to_json (gpgme_conf_opt_t opt) { cjson_t result = xjson_CreateObject (); xjson_AddStringToObject0 (result, "name", opt->name); xjson_AddStringToObject0 (result, "description", opt->description); xjson_AddStringToObject0 (result, "argname", opt->argname); xjson_AddStringToObject0 (result, "default_description", opt->default_description); xjson_AddStringToObject0 (result, "no_arg_description", opt->no_arg_description); xjson_AddNumberToObject (result, "flags", opt->flags); xjson_AddNumberToObject (result, "level", opt->level); xjson_AddNumberToObject (result, "type", opt->type); xjson_AddNumberToObject (result, "alt_type", opt->alt_type); if (opt->default_value) { cjson_t array = xjson_CreateArray (); gpgme_conf_arg_t arg; for (arg = opt->default_value; arg; arg = arg->next) cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type)); xjson_AddItemToObject (result, "default_value", array); } if (opt->no_arg_value) { cjson_t array = xjson_CreateArray (); gpgme_conf_arg_t arg; for (arg = opt->no_arg_value; arg; arg = arg->next) cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type)); xjson_AddItemToObject (result, "no_arg_value", array); } if (opt->value) { cjson_t array = xjson_CreateArray (); gpgme_conf_arg_t arg; for (arg = opt->value; arg; arg = arg->next) cJSON_AddItemToArray (array, conf_arg_to_json (arg, opt->alt_type)); xjson_AddItemToObject (result, "value", array); } return result; } /* Create a JSON object from a gpgconf component*/ static cjson_t conf_comp_to_json (gpgme_conf_comp_t cmp) { cjson_t result = xjson_CreateObject (); xjson_AddStringToObject0 (result, "name", cmp->name); xjson_AddStringToObject0 (result, "description", cmp->description); xjson_AddStringToObject0 (result, "program_name", cmp->program_name); if (cmp->options) { cjson_t array = xjson_CreateArray (); gpgme_conf_opt_t opt; for (opt = cmp->options; opt; opt = opt->next) cJSON_AddItemToArray (array, conf_opt_to_json (opt)); xjson_AddItemToObject (result, "options", array); } return result; } /* Create a gpgme_data from json string data named "name" * in the request. Takes the base64 option into account. * * Adds an error to the "result" on error. */ static gpg_error_t get_string_data (cjson_t request, cjson_t result, const char *name, gpgme_data_t *r_data) { gpgme_error_t err; int opt_base64; cjson_t j_data; if ((err = get_boolean_flag (request, "base64", 0, &opt_base64))) return err; /* Get the data. Note that INPUT is a shallow data object with the * storage hold in REQUEST. */ j_data = cJSON_GetObjectItem (request, name); if (!j_data) { return gpg_error (GPG_ERR_NO_DATA); } if (!cjson_is_string (j_data)) { return gpg_error (GPG_ERR_INV_VALUE); } if (opt_base64) { err = data_from_base64_string (r_data, j_data); if (err) { gpg_error_object (result, err, "Error decoding Base-64 encoded '%s': %s", name, gpg_strerror (err)); return err; } } else { err = gpgme_data_new_from_mem (r_data, j_data->valuestring, strlen (j_data->valuestring), 0); if (err) { gpg_error_object (result, err, "Error getting '%s': %s", name, gpg_strerror (err)); return err; } } return 0; } - -/* - * Implementation of the commands. - */ - - -/* Create a "data" object and the "type", "base64" and "more" flags +/* Create a "data" object and the "type" and "base64" flags * from DATA and append them to RESULT. Ownership of DATA is * transferred to this function. TYPE must be a fixed string. - * CHUNKSIZE is the chunksize requested from the caller. If BASE64 is - * -1 the need for base64 encoding is determined by the content of - * DATA, all other values are taken as true or false. Note that - * op_getmore has similar code but works on PENDING_DATA which is set - * here. */ + * If BASE64 is -1 the need for base64 encoding is determined + * by the content of DATA, all other values are taken as true + * or false. */ static gpg_error_t -make_data_object (cjson_t result, gpgme_data_t data, size_t chunksize, +make_data_object (cjson_t result, gpgme_data_t data, const char *type, int base64) { gpg_error_t err; char *buffer; const char *s; size_t buflen, n; - int c; if (!base64 || base64 == -1) /* Make sure that we really have a string. */ gpgme_data_write (data, "", 1); buffer = gpgme_data_release_and_get_mem (data, &buflen); data = NULL; if (!buffer) { err = gpg_error_from_syserror (); goto leave; } if (base64 == -1) { base64 = 0; if (!buflen) log_fatal ("Appended Nul byte got lost\n"); /* Figure out if there is any Nul octet in the buffer. In that * case we need to Base-64 the buffer. Due to problems with the * browser's Javascript we use Base-64 also in case an UTF-8 * character is in the buffer. This is because the chunking may * split an UTF-8 characters and JS can't handle this. */ for (s=buffer, n=0; n < buflen -1; s++, n++) if (!*s || (*s & 0x80)) { buflen--; /* Adjust for the extra nul byte. */ base64 = 1; break; } } - /* Adjust the chunksize if we need to do base64 conversion. */ - if (base64) - chunksize = (chunksize / 4) * 3; - xjson_AddStringToObject (result, "type", type); xjson_AddBoolToObject (result, "base64", base64); - if (buflen > chunksize) - { - xjson_AddBoolToObject (result, "more", 1); + if (base64) + err = add_base64_to_object (result, "data", buffer, buflen); + else + err = cjson_AddStringToObject (result, "data", buffer); - c = buffer[chunksize]; - buffer[chunksize] = 0; - if (base64) - err = add_base64_to_object (result, "data", buffer, chunksize); - else - err = cjson_AddStringToObject (result, "data", buffer); - buffer[chunksize] = c; - if (err) - goto leave; + leave: + gpgme_free (buffer); + return err; +} - pending_data.buffer = buffer; - buffer = NULL; - pending_data.length = buflen; - pending_data.written = chunksize; - pending_data.type = type; - pending_data.base64 = base64; - } + +/* Encode and chunk response. + * + * If neccessary this base64 encodes and chunks the repsonse + * for getmore so that we always return valid json independent + * of the chunksize. + * + * A chunked repsonse contains the base64 encoded chunk + * as a string and a boolean if there is still more data + * available for getmore like: + * { + * chunk: "SGVsbG8gV29ybGQK" + * more: true + * } + * + * Chunking is only done if the response is larger then the + * chunksize. + * + * caller has to xfree the return value. + */ +static char * +encode_and_chunk (cjson_t request, cjson_t response) +{ + char *data; + gpg_error_t err = 0; + size_t chunksize; + char *getmore_request = NULL; + + if (opt_interactive) + data = cJSON_Print (response); else + data = cJSON_PrintUnformatted (response); + + if (!data) + goto leave; + + if ((err = get_chunksize (request, &chunksize))) + goto leave; + + if (!chunksize) + goto leave; + + pending_data.buffer = data; + /* Data should already be encoded so that it does not + contain 0.*/ + pending_data.length = strlen (data); + pending_data.written = 0; + + if (gpgrt_asprintf (&getmore_request, + "{ \"op\":\"getmore\", \"chunksize\": %i }", + (int) chunksize) == -1) { - if (base64) - err = add_base64_to_object (result, "data", buffer, buflen); - else - err = cjson_AddStringToObject (result, "data", buffer); + err = gpg_error_from_syserror (); + goto leave; } - leave: - gpgme_free (buffer); - return err; + data = process_request (getmore_request); + +leave: + xfree (getmore_request); + + if (err) + { + cjson_t err_obj = gpg_error_object (NULL, err, + "Encode and chunk failed: %s", + gpgme_strerror (err)); + if (opt_interactive) + return cJSON_Print (err_obj); + return cJSON_PrintUnformatted (err_obj); + } + + return data; } +/* + * Implementation of the commands. + */ static const char hlp_encrypt[] = "op: \"encrypt\"\n" "keys: Array of strings with the fingerprints or user-ids\n" " of the keys to encrypt the data. For a single key\n" " a String may be used instead of an array.\n" "data: Input data. \n" "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" "signing_keys: Similar to the keys parameter for added signing.\n" " (openpgp only)" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" "mime: Indicate that data is a MIME object.\n" "armor: Request output in armored format.\n" "always-trust: Request --always-trust option.\n" "no-encrypt-to: Do not use a default recipient.\n" "no-compress: Do not compress the plaintext first.\n" "throw-keyids: Request the --throw-keyids option.\n" "want-address: Require that the keys include a mail address.\n" "wrap: Assume the input is an OpenPGP message.\n" "\n" "Response on success:\n" "type: \"ciphertext\"\n" "data: Unless armor mode is used a Base64 encoded binary\n" " ciphertext. In armor mode a string with an armored\n" " OpenPGP or a PEM message.\n" - "base64: Boolean indicating whether data is base64 encoded.\n" - "more: Optional boolean indicating that \"getmore\" is required."; + "base64: Boolean indicating whether data is base64 encoded."; static gpg_error_t op_encrypt (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; char **signing_patterns = NULL; - size_t chunksize; int opt_mime; char *keystring = NULL; gpgme_data_t input = NULL; gpgme_data_t output = NULL; int abool; gpgme_encrypt_flags_t encrypt_flags = 0; gpgme_ctx_t keylist_ctx = NULL; gpgme_key_t key = NULL; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; if ((err = get_boolean_flag (request, "mime", 0, &opt_mime))) goto leave; if ((err = get_boolean_flag (request, "armor", 0, &abool))) goto leave; gpgme_set_armor (ctx, abool); if ((err = get_boolean_flag (request, "always-trust", 0, &abool))) goto leave; if (abool) encrypt_flags |= GPGME_ENCRYPT_ALWAYS_TRUST; if ((err = get_boolean_flag (request, "no-encrypt-to", 0,&abool))) goto leave; if (abool) encrypt_flags |= GPGME_ENCRYPT_NO_ENCRYPT_TO; if ((err = get_boolean_flag (request, "no-compress", 0, &abool))) goto leave; if (abool) encrypt_flags |= GPGME_ENCRYPT_NO_COMPRESS; if ((err = get_boolean_flag (request, "throw-keyids", 0, &abool))) goto leave; if (abool) encrypt_flags |= GPGME_ENCRYPT_THROW_KEYIDS; if ((err = get_boolean_flag (request, "wrap", 0, &abool))) goto leave; if (abool) encrypt_flags |= GPGME_ENCRYPT_WRAP; if ((err = get_boolean_flag (request, "want-address", 0, &abool))) goto leave; if (abool) encrypt_flags |= GPGME_ENCRYPT_WANT_ADDRESS; /* Get the keys. */ err = get_keys (request, "keys", &keystring); if (err) { /* Provide a custom error response. */ gpg_error_object (result, err, "Error getting keys: %s", gpg_strerror (err)); goto leave; } /* Do we have signing keys ? */ signing_patterns = create_keylist_patterns (request, "signing_keys"); if (signing_patterns) { keylist_ctx = create_onetime_context (protocol); gpgme_set_keylist_mode (keylist_ctx, GPGME_KEYLIST_MODE_LOCAL); err = gpgme_op_keylist_ext_start (keylist_ctx, (const char **) signing_patterns, 1, 0); if (err) { gpg_error_object (result, err, "Error listing keys: %s", gpg_strerror (err)); goto leave; } while (!(err = gpgme_op_keylist_next (keylist_ctx, &key))) { if ((err = gpgme_signers_add (ctx, key))) { gpg_error_object (result, err, "Error adding signer: %s", gpg_strerror (err)); goto leave; } gpgme_key_unref (key); key = NULL; } release_onetime_context (keylist_ctx); keylist_ctx = NULL; } if ((err = get_string_data (request, result, "data", &input))) goto leave; if (opt_mime) gpgme_data_set_encoding (input, GPGME_DATA_ENCODING_MIME); /* Create an output data object. */ err = gpgme_data_new (&output); if (err) { gpg_error_object (result, err, "Error creating output data object: %s", gpg_strerror (err)); goto leave; } /* Encrypt. */ if (!signing_patterns) { err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags, input, output); } else { err = gpgme_op_encrypt_sign_ext (ctx, NULL, keystring, encrypt_flags, input, output); } /* encrypt_result = gpgme_op_encrypt_result (ctx); */ if (err) { gpg_error_object (result, err, "Encryption failed: %s", gpg_strerror (err)); goto leave; } gpgme_data_release (input); input = NULL; /* We need to base64 if armoring has not been requested. */ - err = make_data_object (result, output, chunksize, + err = make_data_object (result, output, "ciphertext", !gpgme_get_armor (ctx)); output = NULL; leave: xfree_array (signing_patterns); xfree (keystring); release_onetime_context (keylist_ctx); gpgme_key_unref (key); gpgme_signers_clear (ctx); release_context (ctx); gpgme_data_release (input); gpgme_data_release (output); return err; } static const char hlp_decrypt[] = "op: \"decrypt\"\n" "data: The encrypted data.\n" "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" "\n" "Response on success:\n" "type: \"plaintext\"\n" "data: The decrypted data. This may be base64 encoded.\n" "base64: Boolean indicating whether data is base64 encoded.\n" "mime: A Boolean indicating whether the data is a MIME object.\n" "info: An object with verification information. (gpgme_verify_result_t)\n" " file_name: Optional string of the plaintext file name.\n" " is_mime: Boolean that is true if the messages claims it is MIME.\n" " signatures: Array of signatures\n" " summary: Object containing summary information.\n" " Boolean values: (Check gpgme_sigsum_t doc for meaning)\n" " valid\n" " green\n" " red\n" " revoked\n" " key-expired\n" " sig-expired\n" " key-missing\n" " crl-missing\n" " crl-too-old\n" " bad-policy\n" " sys-error\n" " sigsum: Array of strings representing the sigsum.\n" " Boolean values:\n" " wrong_key_usage: Key should not have been used for signing.\n" " chain_model: Validity has been verified using the chain model.\n" " is_de_vs: signature is in compliance to the de-vs mode.\n" " String values:\n" " status_string: The status code as localized gpg-error string\n" " fingerprint: The fingerprint of the signing key.\n" " validity_string: The validity as string.\n" " pubkey_algo_name: gpgme_pubkey_algo_name of used algo.\n" " hash_algo_name: gpgme_hash_algo_name of used hash algo\n" " pka_address: The mailbox from the PKA information.\n" " Number values:\n" " status_code: The status as a number. (gpg_error_t)\n" " timestamp: Signature creation time. (secs since epoch)\n" " exp_timestamp: Signature expiration or 0. (secs since epoch)\n" " pka_trust: PKA status: 0 = not available, 1 = bad, 2 = okay, 3 = RFU.\n" " validity: validity as number (gpgme_validity_t)\n" " validity_reason: (gpg_error_t)\n" " Array values:\n" " notations: Notation data and policy urls (gpgme_sig_notation_t)\n" " Boolean values:\n" " human_readable\n" " critical\n" " String values:\n" " name\n" " value\n" " Number values:\n" - " flags\n" - "more: Optional boolean indicating that \"getmore\" is required."; + " flags\n"; static gpg_error_t op_decrypt (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; gpgme_data_t input = NULL; gpgme_data_t output = NULL; gpgme_decrypt_result_t decrypt_result; gpgme_verify_result_t verify_result; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; if ((err = get_string_data (request, result, "data", &input))) goto leave; /* Create an output data object. */ err = gpgme_data_new (&output); if (err) { gpg_error_object (result, err, "Error creating output data object: %s", gpg_strerror (err)); goto leave; } /* Decrypt. */ err = gpgme_op_decrypt_ext (ctx, GPGME_DECRYPT_VERIFY, input, output); decrypt_result = gpgme_op_decrypt_result (ctx); if (err) { gpg_error_object (result, err, "Decryption failed: %s", gpg_strerror (err)); goto leave; } gpgme_data_release (input); input = NULL; if (decrypt_result->is_mime) xjson_AddBoolToObject (result, "mime", 1); verify_result = gpgme_op_verify_result (ctx); if (verify_result && verify_result->signatures) { xjson_AddItemToObject (result, "info", verify_result_to_json (verify_result)); } - err = make_data_object (result, output, chunksize, "plaintext", -1); + err = make_data_object (result, output, "plaintext", -1); output = NULL; if (err) { gpg_error_object (result, err, "Plaintext output failed: %s", gpg_strerror (err)); goto leave; } leave: release_context (ctx); gpgme_data_release (input); gpgme_data_release (output); return err; } static const char hlp_sign[] = "op: \"sign\"\n" "keys: Array of strings with the fingerprints of the signing key.\n" " For a single key a String may be used instead of an array.\n" "data: Input data. \n" "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" "sender: The mail address of the sender.\n" "mode: A string with the signing mode can be:\n" " detached (default)\n" " opaque\n" " clearsign\n" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" "armor: Request output in armored format.\n" "\n" "Response on success:\n" "type: \"signature\"\n" "data: Unless armor mode is used a Base64 encoded binary\n" " signature. In armor mode a string with an armored\n" " OpenPGP or a PEM message.\n" - "base64: Boolean indicating whether data is base64 encoded.\n" - "more: Optional boolean indicating that \"getmore\" is required."; + "base64: Boolean indicating whether data is base64 encoded.\n"; static gpg_error_t op_sign (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; char **patterns = NULL; gpgme_data_t input = NULL; gpgme_data_t output = NULL; int abool; cjson_t j_tmp; gpgme_sig_mode_t mode = GPGME_SIG_MODE_DETACH; gpgme_ctx_t keylist_ctx = NULL; gpgme_key_t key = NULL; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; if ((err = get_boolean_flag (request, "armor", 0, &abool))) goto leave; gpgme_set_armor (ctx, abool); j_tmp = cJSON_GetObjectItem (request, "mode"); if (j_tmp && cjson_is_string (j_tmp)) { if (!strcmp (j_tmp->valuestring, "opaque")) { mode = GPGME_SIG_MODE_NORMAL; } else if (!strcmp (j_tmp->valuestring, "clearsign")) { mode = GPGME_SIG_MODE_CLEAR; } } j_tmp = cJSON_GetObjectItem (request, "sender"); if (j_tmp && cjson_is_string (j_tmp)) { gpgme_set_sender (ctx, j_tmp->valuestring); } patterns = create_keylist_patterns (request, "keys"); if (!patterns) { gpg_error_object (result, err, "Error getting keys: %s", gpg_strerror (gpg_error (GPG_ERR_NO_KEY))); goto leave; } /* Do a keylisting and add the keys */ keylist_ctx = create_onetime_context (protocol); gpgme_set_keylist_mode (keylist_ctx, GPGME_KEYLIST_MODE_LOCAL); err = gpgme_op_keylist_ext_start (keylist_ctx, (const char **) patterns, 1, 0); if (err) { gpg_error_object (result, err, "Error listing keys: %s", gpg_strerror (err)); goto leave; } while (!(err = gpgme_op_keylist_next (keylist_ctx, &key))) { if ((err = gpgme_signers_add (ctx, key))) { gpg_error_object (result, err, "Error adding signer: %s", gpg_strerror (err)); goto leave; } gpgme_key_unref (key); key = NULL; } if ((err = get_string_data (request, result, "data", &input))) goto leave; /* Create an output data object. */ err = gpgme_data_new (&output); if (err) { gpg_error_object (result, err, "Error creating output data object: %s", gpg_strerror (err)); goto leave; } /* Sign. */ err = gpgme_op_sign (ctx, input, output, mode); if (err) { gpg_error_object (result, err, "Signing failed: %s", gpg_strerror (err)); goto leave; } gpgme_data_release (input); input = NULL; /* We need to base64 if armoring has not been requested. */ - err = make_data_object (result, output, chunksize, + err = make_data_object (result, output, "signature", !gpgme_get_armor (ctx)); output = NULL; leave: xfree_array (patterns); gpgme_signers_clear (ctx); gpgme_key_unref (key); release_onetime_context (keylist_ctx); release_context (ctx); gpgme_data_release (input); gpgme_data_release (output); return err; } static const char hlp_verify[] = "op: \"verify\"\n" "data: The data to verify.\n" "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" "signature: A detached signature. If missing opaque is assumed.\n" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" "\n" "Response on success:\n" "type: \"plaintext\"\n" "data: The verified data. This may be base64 encoded.\n" "base64: Boolean indicating whether data is base64 encoded.\n" "info: An object with verification information (gpgme_verify_result_t).\n" " file_name: Optional string of the plaintext file name.\n" " is_mime: Boolean that is true if the messages claims it is MIME.\n" " signatures: Array of signatures\n" " summary: Object containing summary information.\n" " Boolean values: (Check gpgme_sigsum_t doc for meaning)\n" " valid\n" " green\n" " red\n" " revoked\n" " key-expired\n" " sig-expired\n" " key-missing\n" " crl-missing\n" " crl-too-old\n" " bad-policy\n" " sys-error\n" " sigsum: Array of strings representing the sigsum.\n" " Boolean values:\n" " wrong_key_usage: Key should not have been used for signing.\n" " chain_model: Validity has been verified using the chain model.\n" " is_de_vs: signature is in compliance to the de-vs mode.\n" " String values:\n" " status_string: The status code as localized gpg-error string\n" " fingerprint: The fingerprint of the signing key.\n" " validity_string: The validity as string.\n" " pubkey_algo_name: gpgme_pubkey_algo_name of used algo.\n" " hash_algo_name: gpgme_hash_algo_name of used hash algo\n" " pka_address: The mailbox from the PKA information.\n" " Number values:\n" " status_code: The status as a number. (gpg_error_t)\n" " timestamp: Signature creation time. (secs since epoch)\n" " exp_timestamp: Signature expiration or 0. (secs since epoch)\n" " pka_trust: PKA status: 0 = not available, 1 = bad, 2 = okay, 3 = RFU.\n" " validity: validity as number (gpgme_validity_t)\n" " validity_reason: (gpg_error_t)\n" " Array values:\n" " notations: Notation data and policy urls (gpgme_sig_notation_t)\n" " Boolean values:\n" " human_readable\n" " critical\n" " String values:\n" " name\n" " value\n" " Number values:\n" - " flags\n" - "more: Optional boolean indicating that \"getmore\" is required."; + " flags\n"; static gpg_error_t op_verify (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; gpgme_data_t input = NULL; gpgme_data_t signature = NULL; gpgme_data_t output = NULL; gpgme_verify_result_t verify_result; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; if ((err = get_string_data (request, result, "data", &input))) goto leave; err = get_string_data (request, result, "signature", &signature); /* Signature data is optional otherwise we expect opaque or clearsigned. */ if (err && err != gpg_error (GPG_ERR_NO_DATA)) goto leave; /* Create an output data object. */ err = gpgme_data_new (&output); if (err) { gpg_error_object (result, err, "Error creating output data object: %s", gpg_strerror (err)); goto leave; } /* Verify. */ if (signature) { err = gpgme_op_verify (ctx, signature, input, output); } else { err = gpgme_op_verify (ctx, input, 0, output); } if (err) { gpg_error_object (result, err, "Verify failed: %s", gpg_strerror (err)); goto leave; } gpgme_data_release (input); input = NULL; gpgme_data_release (signature); signature = NULL; verify_result = gpgme_op_verify_result (ctx); if (verify_result && verify_result->signatures) { xjson_AddItemToObject (result, "info", verify_result_to_json (verify_result)); } - err = make_data_object (result, output, chunksize, "plaintext", -1); + err = make_data_object (result, output, "plaintext", -1); output = NULL; if (err) { gpg_error_object (result, err, "Plaintext output failed: %s", gpg_strerror (err)); goto leave; } leave: release_context (ctx); gpgme_data_release (input); gpgme_data_release (output); gpgme_data_release (signature); return err; } static const char hlp_version[] = "op: \"version\"\n" "\n" "Response on success:\n" "gpgme: The GPGME Version.\n" "info: dump of engine info. containing:\n" " protocol: The protocol.\n" " fname: The file name.\n" " version: The version.\n" " req_ver: The required version.\n" " homedir: The homedir of the engine or \"default\".\n"; static gpg_error_t op_version (cjson_t request, cjson_t result) { gpg_error_t err = 0; gpgme_engine_info_t ei = NULL; cjson_t infos = xjson_CreateArray (); (void)request; if (!cJSON_AddStringToObject (result, "gpgme", gpgme_check_version (NULL))) { cJSON_Delete (infos); return gpg_error_from_syserror (); } if ((err = gpgme_get_engine_info (&ei))) { cJSON_Delete (infos); return err; } for (; ei; ei = ei->next) cJSON_AddItemToArray (infos, engine_info_to_json (ei)); if (!cJSON_AddItemToObject (result, "info", infos)) { err = gpg_error_from_syserror (); cJSON_Delete (infos); return err; } return 0; } static const char hlp_keylist[] = "op: \"keylist\"\n" "\n" "Optional parameters:\n" "keys: Array of strings or fingerprints to lookup\n" " For a single key a String may be used instead of an array.\n" " default lists all keys.\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" "\n" "Optional boolean flags (default is false):\n" "secret: List secret keys.\n" "extern: Add KEYLIST_MODE_EXTERN.\n" "local: Add KEYLIST_MODE_LOCAL. (default mode).\n" "sigs: Add KEYLIST_MODE_SIGS.\n" "notations: Add KEYLIST_MODE_SIG_NOTATIONS.\n" "tofu: Add KEYLIST_MODE_WITH_TOFU.\n" "ephemeral: Add KEYLIST_MODE_EPHEMERAL.\n" "validate: Add KEYLIST_MODE_VALIDATE.\n" "\n" "Response on success:\n" "keys: Array of keys.\n" " Boolean values:\n" " revoked\n" " expired\n" " disabled\n" " invalid\n" " can_encrypt\n" " can_sign\n" " can_certify\n" " can_authenticate\n" " secret\n" " is_qualified\n" " String values:\n" " protocol\n" " issuer_serial (CMS Only)\n" " issuer_name (CMS Only)\n" " chain_id (CMS Only)\n" " owner_trust (OpenPGP only)\n" " fingerprint\n" " Number values:\n" " last_update\n" " origin\n" " Array values:\n" " subkeys\n" " Boolean values:\n" " revoked\n" " expired\n" " disabled\n" " invalid\n" " can_encrypt\n" " can_sign\n" " can_certify\n" " can_authenticate\n" " secret\n" " is_qualified\n" " is_cardkey\n" " is_de_vs\n" " String values:\n" " pubkey_algo_name\n" " pubkey_algo_string\n" " keyid\n" " card_number\n" " curve\n" " keygrip\n" " Number values:\n" " pubkey_algo\n" " length\n" " timestamp\n" " expires\n" " userids\n" " Boolean values:\n" " revoked\n" " invalid\n" " String values:\n" " validity\n" " uid\n" " name\n" " email\n" " comment\n" " address\n" " Number values:\n" " origin\n" " last_update\n" " Array values:\n" " signatures\n" " Boolean values:\n" " revoked\n" " expired\n" " invalid\n" " exportable\n" " String values:\n" " pubkey_algo_name\n" " keyid\n" " status\n" " uid\n" " name\n" " email\n" " comment\n" " Number values:\n" " pubkey_algo\n" " timestamp\n" " expires\n" " status_code\n" " sig_class\n" " Array values:\n" " notations\n" " Boolean values:\n" " human_readable\n" " critical\n" " String values:\n" " name\n" " value\n" " Number values:\n" " flags\n" " tofu\n" " String values:\n" " description\n" " Number values:\n" " validity\n" " policy\n" " signcount\n" " encrcount\n" " signfirst\n" " signlast\n" " encrfirst\n" - " encrlast\n" - "more: Optional boolean indicating that \"getmore\" is required.\n" - " (not implemented)"; + " encrlast\n"; static gpg_error_t op_keylist (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; char **patterns = NULL; int abool; gpgme_keylist_mode_t mode = 0; gpgme_key_t key = NULL; cjson_t keyarray = xjson_CreateArray (); if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; /* Handle the various keylist mode bools. */ if ((err = get_boolean_flag (request, "secret", 0, &abool))) goto leave; if (abool) mode |= GPGME_KEYLIST_MODE_WITH_SECRET; if ((err = get_boolean_flag (request, "extern", 0, &abool))) goto leave; if (abool) mode |= GPGME_KEYLIST_MODE_EXTERN; if ((err = get_boolean_flag (request, "local", 0, &abool))) goto leave; if (abool) mode |= GPGME_KEYLIST_MODE_LOCAL; if ((err = get_boolean_flag (request, "sigs", 0, &abool))) goto leave; if (abool) mode |= GPGME_KEYLIST_MODE_SIGS; if ((err = get_boolean_flag (request, "notations", 0, &abool))) goto leave; if (abool) mode |= GPGME_KEYLIST_MODE_SIG_NOTATIONS; if ((err = get_boolean_flag (request, "tofu", 0, &abool))) goto leave; if (abool) mode |= GPGME_KEYLIST_MODE_WITH_TOFU; if ((err = get_boolean_flag (request, "ephemeral", 0, &abool))) goto leave; if (abool) mode |= GPGME_KEYLIST_MODE_EPHEMERAL; if ((err = get_boolean_flag (request, "validate", 0, &abool))) goto leave; if (abool) mode |= GPGME_KEYLIST_MODE_VALIDATE; if (!mode) { /* default to local */ mode = GPGME_KEYLIST_MODE_LOCAL; } /* Get the keys. */ patterns = create_keylist_patterns (request, "keys"); /* Do a keylisting and add the keys */ gpgme_set_keylist_mode (ctx, mode); err = gpgme_op_keylist_ext_start (ctx, (const char **) patterns, (mode & GPGME_KEYLIST_MODE_WITH_SECRET), 0); if (err) { gpg_error_object (result, err, "Error listing keys: %s", gpg_strerror (err)); goto leave; } while (!(err = gpgme_op_keylist_next (ctx, &key))) { cJSON_AddItemToArray (keyarray, key_to_json (key)); gpgme_key_unref (key); } err = 0; if (!cJSON_AddItemToObject (result, "keys", keyarray)) { err = gpg_error_from_syserror (); goto leave; } leave: xfree_array (patterns); if (err) { cJSON_Delete (keyarray); } return err; } static const char hlp_import[] = "op: \"import\"\n" "data: The data to import.\n" "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" "\n" "Optional boolean flags (default is false):\n" "base64: Input data is base64 encoded.\n" "\n" "Response on success:\n" "result: The import result.\n" " Number values:\n" " considered\n" " no_user_id\n" " imported\n" " imported_rsa\n" " unchanged\n" " new_user_ids\n" " new_sub_keys\n" " new_signatures\n" " new_revocations\n" " secret_read\n" " secret_imported\n" " secret_unchanged\n" " skipped_new_keys\n" " not_imported\n" " skipped_v3_keys\n" " Array values:\n" " imports: List of keys for which an import was attempted\n" " String values:\n" " fingerprint\n" " error_string\n" " Number values:\n" " error_code\n" " status\n"; static gpg_error_t op_import (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_data_t input = NULL; gpgme_import_result_t import_result; gpgme_protocol_t protocol; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); if ((err = get_string_data (request, result, "data", &input))) goto leave; /* Import. */ err = gpgme_op_import (ctx, input); import_result = gpgme_op_import_result (ctx); if (err) { gpg_error_object (result, err, "Import failed: %s", gpg_strerror (err)); goto leave; } gpgme_data_release (input); input = NULL; xjson_AddItemToObject (result, "result", import_result_to_json (import_result)); leave: release_context (ctx); gpgme_data_release (input); return err; } static const char hlp_export[] = "op: \"export\"\n" "\n" "Optional parameters:\n" "keys: Array of strings or fingerprints to lookup\n" " For a single key a String may be used instead of an array.\n" " default exports all keys.\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" - "chunksize: Max number of bytes in the resulting \"data\".\n" "\n" "Optional boolean flags (default is false):\n" "armor: Request output in armored format.\n" "extern: Add EXPORT_MODE_EXTERN.\n" "minimal: Add EXPORT_MODE_MINIMAL.\n" "raw: Add EXPORT_MODE_RAW.\n" "pkcs12: Add EXPORT_MODE_PKCS12.\n" "\n" "Response on success:\n" "type: \"keys\"\n" "data: Unless armor mode is used a Base64 encoded binary.\n" " In armor mode a string with an armored\n" " OpenPGP or a PEM / PKCS12 key.\n" - "base64: Boolean indicating whether data is base64 encoded.\n" - "more: Optional boolean indicating that \"getmore\" is required."; + "base64: Boolean indicating whether data is base64 encoded.\n"; static gpg_error_t op_export (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_protocol_t protocol; - size_t chunksize; char **patterns = NULL; int abool; gpgme_export_mode_t mode = 0; gpgme_data_t output = NULL; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); - if ((err = get_chunksize (request, &chunksize))) - goto leave; if ((err = get_boolean_flag (request, "armor", 0, &abool))) goto leave; gpgme_set_armor (ctx, abool); /* Handle the various export mode bools. */ if ((err = get_boolean_flag (request, "secret", 0, &abool))) goto leave; if (abool) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } if ((err = get_boolean_flag (request, "extern", 0, &abool))) goto leave; if (abool) mode |= GPGME_EXPORT_MODE_EXTERN; if ((err = get_boolean_flag (request, "minimal", 0, &abool))) goto leave; if (abool) mode |= GPGME_EXPORT_MODE_MINIMAL; if ((err = get_boolean_flag (request, "raw", 0, &abool))) goto leave; if (abool) mode |= GPGME_EXPORT_MODE_RAW; if ((err = get_boolean_flag (request, "pkcs12", 0, &abool))) goto leave; if (abool) mode |= GPGME_EXPORT_MODE_PKCS12; /* Get the export patterns. */ patterns = create_keylist_patterns (request, "keys"); /* Create an output data object. */ err = gpgme_data_new (&output); if (err) { gpg_error_object (result, err, "Error creating output data object: %s", gpg_strerror (err)); goto leave; } err = gpgme_op_export_ext (ctx, (const char **) patterns, mode, output); if (err) { gpg_error_object (result, err, "Error exporting keys: %s", gpg_strerror (err)); goto leave; } /* We need to base64 if armoring has not been requested. */ - err = make_data_object (result, output, chunksize, + err = make_data_object (result, output, "keys", !gpgme_get_armor (ctx)); output = NULL; leave: xfree_array (patterns); release_context (ctx); gpgme_data_release (output); return err; } static const char hlp_delete[] = "op: \"delete\"\n" "key: Fingerprint of the key to delete.\n" "\n" "Optional parameters:\n" "protocol: Either \"openpgp\" (default) or \"cms\".\n" "\n" "Response on success:\n" "success: Boolean true.\n"; static gpg_error_t op_delete (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_ctx_t keylist_ctx = NULL; gpgme_protocol_t protocol; gpgme_key_t key; int secret = 0; cjson_t j_key = NULL; if ((err = get_protocol (request, &protocol))) goto leave; ctx = get_context (protocol); keylist_ctx = get_context (protocol); if ((err = get_boolean_flag (request, "secret", 0, &secret))) goto leave; if (secret) { err = gpg_error (GPG_ERR_FORBIDDEN); goto leave; } j_key = cJSON_GetObjectItem (request, "key"); if (!j_key) { err = gpg_error (GPG_ERR_NO_KEY); goto leave; } if (!cjson_is_string (j_key)) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } /* Get the key */ if ((err = gpgme_get_key (keylist_ctx, j_key->valuestring, &key, 0))) { gpg_error_object (result, err, "Error fetching key for delete: %s", gpg_strerror (err)); goto leave; } err = gpgme_op_delete (ctx, key, 0); if (err) { gpg_error_object (result, err, "Error deleting key: %s", gpg_strerror (err)); goto leave; } xjson_AddBoolToObject (result, "success", 1); leave: gpgme_key_unref (key); release_context (ctx); release_context (keylist_ctx); return err; } static const char hlp_config_opt[] = "op: \"config_opt\"\n" "component: The component of the option.\n" "option: The name of the option.\n" "\n" "Response on success:\n" "\n" "option: Information about the option.\n" " String values:\n" " name: The name of the option\n" " description: Localized description of the opt.\n" " argname: Thhe argument name e.g. --verbose\n" " default_description\n" " no_arg_description\n" " Number values:\n" " flags: Flags for this option.\n" " level: the level of the description. See gpgme_conf_level_t.\n" " type: The type of the option. See gpgme_conf_type_t.\n" " alt_type: Alternate type of the option. See gpgme_conf_type_t\n" " Arg type values: (see desc. below)\n" " default_value: Array of the default value.\n" " no_arg_value: Array of the value if it is not set.\n" " value: Array for the current value if the option is set.\n" "\n" "If the response is empty the option was not found\n" ""; static gpg_error_t op_config_opt (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_conf_comp_t conf = NULL; gpgme_conf_comp_t comp = NULL; cjson_t j_tmp; char *comp_name = NULL; char *opt_name = NULL; ctx = get_context (GPGME_PROTOCOL_GPGCONF); j_tmp = cJSON_GetObjectItem (request, "component"); if (!j_tmp || !cjson_is_string (j_tmp)) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } comp_name = j_tmp->valuestring; j_tmp = cJSON_GetObjectItem (request, "option"); if (!j_tmp || !cjson_is_string (j_tmp)) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } opt_name = j_tmp->valuestring; /* Load the config */ err = gpgme_op_conf_load (ctx, &conf); if (err) { goto leave; } comp = conf; for (comp = conf; comp; comp = comp->next) { gpgme_conf_opt_t opt = NULL; int found = 0; if (!comp->name || strcmp (comp->name, comp_name)) { /* Skip components if a single one is specified */ continue; } for (opt = comp->options; opt; opt = opt->next) { if (!opt->name || strcmp (opt->name, opt_name)) { /* Skip components if a single one is specified */ continue; } xjson_AddItemToObject (result, "option", conf_opt_to_json (opt)); found = 1; break; } if (found) break; } leave: gpgme_conf_release (conf); release_context (ctx); return err; } static const char hlp_config[] = "op: \"config\"\n" "\n" "Optional parameters:\n" "component: Component of entries to list.\n" " Default: all\n" "\n" "Response on success:\n" " components: Array of the component program configs.\n" " name: The component name.\n" " description: Description of the component.\n" " program_name: The absolute path to the program.\n" " options: Array of config options\n" " String values:\n" " name: The name of the option\n" " description: Localized description of the opt.\n" " argname: Thhe argument name e.g. --verbose\n" " default_description\n" " no_arg_description\n" " Number values:\n" " flags: Flags for this option.\n" " level: the level of the description. See gpgme_conf_level_t.\n" " type: The type of the option. See gpgme_conf_type_t.\n" " alt_type: Alternate type of the option. See gpgme_conf_type_t\n" " Arg type values: (see desc. below)\n" " default_value: Array of the default value.\n" " no_arg_value: Array of the value if it is not set.\n" " value: Array for the current value if the option is set.\n" "\n" "Conf type values are an array of values that are either\n" "of type number named \"number\" or of type string,\n" "named \"string\".\n" "If the type is none the bool value is_none is true.\n" ""; static gpg_error_t op_config (cjson_t request, cjson_t result) { gpg_error_t err; gpgme_ctx_t ctx = NULL; gpgme_conf_comp_t conf = NULL; gpgme_conf_comp_t comp = NULL; cjson_t j_tmp; char *comp_name = NULL; cjson_t j_comps = xjson_CreateArray (); ctx = get_context (GPGME_PROTOCOL_GPGCONF); j_tmp = cJSON_GetObjectItem (request, "component"); if (j_tmp && cjson_is_string (j_tmp)) { comp_name = j_tmp->valuestring; } else if (j_tmp && !cjson_is_string (j_tmp)) { err = gpg_error (GPG_ERR_INV_VALUE); goto leave; } /* Load the config */ err = gpgme_op_conf_load (ctx, &conf); if (err) { goto leave; } comp = conf; for (comp = conf; comp; comp = comp->next) { if (comp_name && comp->name && strcmp (comp->name, comp_name)) { /* Skip components if a single one is specified */ continue; } cJSON_AddItemToArray (j_comps, conf_comp_to_json (comp)); } xjson_AddItemToObject (result, "components", j_comps); leave: gpgme_conf_release (conf); release_context (ctx); return err; } static const char hlp_getmore[] = "op: \"getmore\"\n" "\n" - "Optional parameters:\n" - "chunksize: Max number of bytes in the \"data\" object.\n" - "\n" "Response on success:\n" - "type: Type of the pending data\n" - "data: The next chunk of data\n" - "base64: Boolean indicating whether data is base64 encoded\n" - "more: Optional boolean requesting another \"getmore\"."; + "response: base64 encoded json response.\n" + "more: Another getmore is required.\n" + "base64: boolean if the response is base64 encoded.\n"; static gpg_error_t op_getmore (cjson_t request, cjson_t result) { gpg_error_t err; int c; size_t n; size_t chunksize; if ((err = get_chunksize (request, &chunksize))) goto leave; - /* Adjust the chunksize if we need to do base64 conversion. */ - if (pending_data.base64) - chunksize = (chunksize / 4) * 3; + /* For the meta data we need 41 bytes: + {"more":true,"base64":true,"response":""} */ + chunksize -= 41; + + /* Adjust the chunksize for the base64 conversion. */ + chunksize = (chunksize / 4) * 3; /* Do we have anything pending? */ if (!pending_data.buffer) { err = gpg_error (GPG_ERR_NO_DATA); gpg_error_object (result, err, "Operation not possible: %s", gpg_strerror (err)); goto leave; } - xjson_AddStringToObject (result, "type", pending_data.type); - xjson_AddBoolToObject (result, "base64", pending_data.base64); + /* We currently always use base64 encoding for simplicity. */ + xjson_AddBoolToObject (result, "base64", 1); if (pending_data.written >= pending_data.length) { /* EOF reached. This should not happen but we return an empty * string once in case of client errors. */ gpgme_free (pending_data.buffer); pending_data.buffer = NULL; xjson_AddBoolToObject (result, "more", 0); - err = cjson_AddStringToObject (result, "data", ""); + err = cjson_AddStringToObject (result, "response", ""); } else { n = pending_data.length - pending_data.written; if (n > chunksize) { n = chunksize; xjson_AddBoolToObject (result, "more", 1); } else xjson_AddBoolToObject (result, "more", 0); c = pending_data.buffer[pending_data.written + n]; pending_data.buffer[pending_data.written + n] = 0; - if (pending_data.base64) - err = add_base64_to_object (result, "data", - (pending_data.buffer - + pending_data.written), n); - else - err = cjson_AddStringToObject (result, "data", - (pending_data.buffer - + pending_data.written)); + err = add_base64_to_object (result, "response", + (pending_data.buffer + + pending_data.written), n); pending_data.buffer[pending_data.written + n] = c; if (!err) { pending_data.written += n; if (pending_data.written >= pending_data.length) { - gpgme_free (pending_data.buffer); + xfree (pending_data.buffer); pending_data.buffer = NULL; } } } leave: return err; } static const char hlp_help[] = "The tool expects a JSON object with the request and responds with\n" "another JSON object. Even on error a JSON object is returned. The\n" "property \"op\" is mandatory and its string value selects the\n" "operation; if the property \"help\" with the value \"true\" exists, the\n" "operation is not performned but a string with the documentation\n" "returned. To list all operations it is allowed to leave out \"op\" in\n" "help mode. Supported values for \"op\" are:\n\n" " config Read configuration values.\n" " decrypt Decrypt data.\n" " delete Delete a key.\n" " encrypt Encrypt data.\n" " export Export keys.\n" " import Import data.\n" " keylist List keys.\n" " sign Sign data.\n" " verify Verify data.\n" " version Get engine information.\n" - " getmore Retrieve remaining data.\n" - " help Help overview."; + " getmore Retrieve remaining data if chunksize was used.\n" + " help Help overview.\n" + "\n" + "If the data needs to be transferred in smaller chunks the\n" + "property \"chunksize\" with an integer value can be added.\n" + "When \"chunksize\" is set the response (including json) will\n" + "not be larger then \"chunksize\" but might be smaller.\n" + "The chunked result will be transferred in base64 encoded chunks\n" + "using the \"getmore\" operation. See help getmore for more info."; static gpg_error_t op_help (cjson_t request, cjson_t result) { cjson_t j_tmp; char *buffer = NULL; const char *msg; j_tmp = cJSON_GetObjectItem (request, "interactive_help"); if (opt_interactive && j_tmp && cjson_is_string (j_tmp)) msg = buffer = xstrconcat (hlp_help, "\n", j_tmp->valuestring, NULL); else msg = hlp_help; xjson_AddStringToObject (result, "type", "help"); xjson_AddStringToObject (result, "msg", msg); xfree (buffer); return 0; } /* * Dispatcher */ /* Process a request and return the response. The response is a newly * allocated string or NULL in case of an error. */ static char * process_request (const char *request) { static struct { const char *op; gpg_error_t (*handler)(cjson_t request, cjson_t result); const char * const helpstr; } optbl[] = { { "config", op_config, hlp_config }, { "config_opt", op_config_opt, hlp_config_opt }, { "encrypt", op_encrypt, hlp_encrypt }, { "export", op_export, hlp_export }, { "decrypt", op_decrypt, hlp_decrypt }, { "delete", op_delete, hlp_delete }, { "keylist", op_keylist, hlp_keylist }, { "import", op_import, hlp_import }, { "sign", op_sign, hlp_sign }, { "verify", op_verify, hlp_verify }, { "version", op_version, hlp_version }, { "getmore", op_getmore, hlp_getmore }, { "help", op_help, hlp_help }, { NULL } }; size_t erroff; cjson_t json; cjson_t j_tmp, j_op; cjson_t response; int helpmode; + int is_getmore = 0; const char *op; char *res; int idx; response = xjson_CreateObject (); json = cJSON_Parse (request, &erroff); if (!json) { log_string (GPGRT_LOGLVL_INFO, request); log_info ("invalid JSON object at offset %zu\n", erroff); error_object (response, "invalid JSON object at offset %zu\n", erroff); goto leave; } j_tmp = cJSON_GetObjectItem (json, "help"); helpmode = (j_tmp && cjson_is_true (j_tmp)); j_op = cJSON_GetObjectItem (json, "op"); if (!j_op || !cjson_is_string (j_op)) { if (!helpmode) { error_object (response, "Property \"op\" missing"); goto leave; } op = "help"; /* Help summary. */ } else op = j_op->valuestring; for (idx=0; optbl[idx].op; idx++) if (!strcmp (op, optbl[idx].op)) break; if (optbl[idx].op) { if (helpmode && strcmp (op, "help")) { xjson_AddStringToObject (response, "type", "help"); xjson_AddStringToObject (response, "op", op); xjson_AddStringToObject (response, "msg", optbl[idx].helpstr); } else { gpg_error_t err; - + is_getmore = optbl[idx].handler == op_getmore; /* If this is not the "getmore" command and we have any * pending data release that data. */ if (pending_data.buffer && optbl[idx].handler != op_getmore) { gpgme_free (pending_data.buffer); pending_data.buffer = NULL; } err = optbl[idx].handler (json, response); if (err) { if (!(j_tmp = cJSON_GetObjectItem (response, "type")) || !cjson_is_string (j_tmp) || strcmp (j_tmp->valuestring, "error")) { /* No error type response - provide a generic one. */ gpg_error_object (response, err, "Operation failed: %s", gpg_strerror (err)); } xjson_AddStringToObject (response, "op", op); } } } else /* Operation not supported. */ { error_object (response, "Unknown operation '%s'", op); xjson_AddStringToObject (response, "op", op); } leave: - cJSON_Delete (json); - if (opt_interactive) - res = cJSON_Print (response); + if (is_getmore) + { + /* For getmore we bypass the encode_and_chunk. */ + if (opt_interactive) + res = cJSON_Print (response); + else + res = cJSON_PrintUnformatted (response); + } else - res = cJSON_PrintUnformatted (response); + res = encode_and_chunk (json, response); if (!res) log_error ("Printing JSON data failed\n"); + + cJSON_Delete (json); cJSON_Delete (response); return res; } /* * Driver code */ static char * get_file (const char *fname) { gpg_error_t err; estream_t fp; struct stat st; char *buf; size_t buflen; fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); return NULL; } if (fstat (es_fileno(fp), &st)) { err = gpg_error_from_syserror (); log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err)); es_fclose (fp); return NULL; } buflen = st.st_size; buf = xmalloc (buflen+1); if (es_fread (buf, buflen, 1, fp) != 1) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); es_fclose (fp); xfree (buf); return NULL; } buf[buflen] = 0; es_fclose (fp); return buf; } /* Return a malloced line or NULL on EOF. Terminate on read * error. */ static char * get_line (void) { char *line = NULL; size_t linesize = 0; gpg_error_t err; size_t maxlength = 2048; int n; const char *s; char *p; again: n = es_read_line (es_stdin, &line, &linesize, &maxlength); if (n < 0) { err = gpg_error_from_syserror (); log_error ("error reading line: %s\n", gpg_strerror (err)); exit (1); } if (!n) { xfree (line); line = NULL; return NULL; /* EOF */ } if (!maxlength) { log_info ("line too long - skipped\n"); goto again; } if (memchr (line, 0, n)) log_info ("warning: line shortened due to embedded Nul character\n"); if (line[n-1] == '\n') line[n-1] = 0; /* Trim leading spaces. */ for (s=line; spacep (s); s++) ; if (s != line) { for (p=line; *s;) *p++ = *s++; *p = 0; n = p - line; } return line; } /* Process meta commands used with the standard REPL. */ static char * process_meta_commands (const char *request) { char *result = NULL; while (spacep (request)) request++; if (!strncmp (request, "help", 4) && (spacep (request+4) || !request[4])) { if (request[4]) { char *buf = xstrconcat ("{ \"help\":true, \"op\":\"", request+5, "\" }", NULL); result = process_request (buf); xfree (buf); } else result = process_request ("{ \"op\": \"help\"," " \"interactive_help\": " "\"\\nMeta commands:\\n" " ,read FNAME Process data from FILE\\n" " ,help CMD Print help for a command\\n" " ,quit Terminate process\"" "}"); } else if (!strncmp (request, "quit", 4) && (spacep (request+4) || !request[4])) exit (0); else if (!strncmp (request, "read", 4) && (spacep (request+4) || !request[4])) { if (!request[4]) log_info ("usage: ,read FILENAME\n"); else { char *buffer = get_file (request + 5); if (buffer) { result = process_request (buffer); xfree (buffer); } } } else log_info ("invalid meta command\n"); return result; } /* If STRING has a help response, return the MSG property in a human * readable format. */ static char * get_help_msg (const char *string) { cjson_t json, j_type, j_msg; const char *msg; char *buffer = NULL; char *p; json = cJSON_Parse (string, NULL); if (json) { j_type = cJSON_GetObjectItem (json, "type"); if (j_type && cjson_is_string (j_type) && !strcmp (j_type->valuestring, "help")) { j_msg = cJSON_GetObjectItem (json, "msg"); if (j_msg || cjson_is_string (j_msg)) { msg = j_msg->valuestring; buffer = malloc (strlen (msg)+1); if (buffer) { for (p=buffer; *msg; msg++) { if (*msg == '\\' && msg[1] == '\n') *p++ = '\n'; else *p++ = *msg; } *p = 0; } } } cJSON_Delete (json); } return buffer; } /* An interactive standard REPL. */ static void interactive_repl (void) { char *line = NULL; char *request = NULL; char *response = NULL; char *p; int first; es_setvbuf (es_stdin, NULL, _IONBF, 0); #if GPGRT_VERSION_NUMBER >= 0x011d00 /* 1.29 */ es_fprintf (es_stderr, "%s %s ready (enter \",help\" for help)\n", gpgrt_strusage (11), gpgrt_strusage (13)); #endif do { es_fputs ("> ", es_stderr); es_fflush (es_stderr); es_fflush (es_stdout); xfree (line); line = get_line (); es_fflush (es_stderr); es_fflush (es_stdout); first = !request; if (line && *line) { if (!request) request = xstrdup (line); else request = xstrconcat (request, "\n", line, NULL); } if (!line) es_fputs ("\n", es_stderr); if (!line || !*line || (first && *request == ',')) { /* Process the input. */ xfree (response); response = NULL; if (request && *request == ',') { response = process_meta_commands (request+1); } else if (request) { response = process_request (request); } xfree (request); request = NULL; if (response) { if (opt_interactive) { char *msg = get_help_msg (response); if (msg) { xfree (response); response = msg; } } es_fputs ("===> ", es_stderr); es_fflush (es_stderr); for (p=response; *p; p++) { if (*p == '\n') { es_fflush (es_stdout); es_fputs ("\n===> ", es_stderr); es_fflush (es_stderr); } else es_putc (*p, es_stdout); } es_fflush (es_stdout); es_fputs ("\n", es_stderr); } } } while (line); xfree (request); xfree (response); xfree (line); } /* Read and process a single request. */ static void read_and_process_single_request (void) { char *line = NULL; char *request = NULL; char *response = NULL; size_t n; for (;;) { xfree (line); line = get_line (); if (line && *line) request = (request? xstrconcat (request, "\n", line, NULL) /**/ : xstrdup (line)); if (!line) { if (request) { xfree (response); response = process_request (request); if (response) { es_fputs (response, es_stdout); if ((n = strlen (response)) && response[n-1] != '\n') es_fputc ('\n', es_stdout); } es_fflush (es_stdout); } break; } } xfree (response); xfree (request); xfree (line); } /* The Native Messaging processing loop. */ static void native_messaging_repl (void) { gpg_error_t err; uint32_t nrequest, nresponse; char *request = NULL; char *response = NULL; size_t n; /* Due to the length octets we need to switch the I/O stream into * binary mode. */ es_set_binary (es_stdin); es_set_binary (es_stdout); es_setbuf (es_stdin, NULL); /* stdin needs to be unbuffered! */ for (;;) { /* Read length. Note that the protocol uses native endianess. * Is it allowed to call such a thing a well thought out * protocol? */ if (es_read (es_stdin, &nrequest, sizeof nrequest, &n)) { err = gpg_error_from_syserror (); log_error ("error reading request header: %s\n", gpg_strerror (err)); break; } if (!n) break; /* EOF */ if (n != sizeof nrequest) { log_error ("error reading request header: short read\n"); break; } if (nrequest > MAX_REQUEST_SIZE) { log_error ("error reading request: request too long (%zu MiB)\n", (size_t)nrequest / (1024*1024)); /* Fixme: Shall we read the request to the bit bucket and * return an error reponse or just return an error reponse * and terminate? Needs some testing. */ break; } /* Read request. */ request = xtrymalloc (nrequest); if (!request) { err = gpg_error_from_syserror (); log_error ("error reading request: Not enough memory for %zu MiB)\n", (size_t)nrequest / (1024*1024)); /* FIXME: See comment above. */ break; } if (es_read (es_stdin, request, nrequest, &n)) { err = gpg_error_from_syserror (); log_error ("error reading request: %s\n", gpg_strerror (err)); break; } if (n != nrequest) { /* That is a protocol violation. */ xfree (response); response = error_object_string ("Invalid request:" " short read (%zu of %zu bytes)\n", n, (size_t)nrequest); } else /* Process request */ { if (opt_debug) log_debug ("request='%s'\n", request); xfree (response); response = process_request (request); if (opt_debug) log_debug ("response='%s'\n", response); } nresponse = strlen (response); /* Write response */ if (es_write (es_stdout, &nresponse, sizeof nresponse, &n)) { err = gpg_error_from_syserror (); log_error ("error writing request header: %s\n", gpg_strerror (err)); break; } if (n != sizeof nrequest) { log_error ("error writing request header: short write\n"); break; } if (es_write (es_stdout, response, nresponse, &n)) { err = gpg_error_from_syserror (); log_error ("error writing request: %s\n", gpg_strerror (err)); break; } if (n != nresponse) { log_error ("error writing request: short write\n"); break; } if (es_fflush (es_stdout) || es_ferror (es_stdout)) { err = gpg_error_from_syserror (); log_error ("error writing request: %s\n", gpg_strerror (err)); break; } } xfree (response); xfree (request); } static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "LGPL-2.1-or-later"; break; case 11: p = "gpgme-json"; break; case 13: p = PACKAGE_VERSION; break; case 14: p = "Copyright (C) 2018 g10 Code GmbH"; break; case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break; case 1: case 40: p = "Usage: gpgme-json [OPTIONS]"; break; case 41: p = "Native messaging based GPGME operations.\n"; break; case 42: p = "1"; /* Flag print 40 as part of 41. */ break; default: p = NULL; break; } return p; } int main (int argc, char *argv[]) { #if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */ fprintf (stderr, "WARNING: Old libgpg-error - using limited mode\n"); native_messaging_repl (); #else /* This is a modern libgp-error. */ enum { CMD_DEFAULT = 0, CMD_INTERACTIVE = 'i', CMD_SINGLE = 's', CMD_LIBVERSION = 501, } cmd = CMD_DEFAULT; enum { OPT_DEBUG = 600 }; static gpgrt_opt_t opts[] = { ARGPARSE_c (CMD_INTERACTIVE, "interactive", "Interactive REPL"), ARGPARSE_c (CMD_SINGLE, "single", "Single request mode"), ARGPARSE_c (CMD_LIBVERSION, "lib-version", "Show library version"), ARGPARSE_s_n(OPT_DEBUG, "debug", "Flyswatter"), ARGPARSE_end() }; gpgrt_argparse_t pargs = { &argc, &argv}; gpgrt_set_strusage (my_strusage); #ifdef HAVE_SETLOCALE setlocale (LC_ALL, ""); #endif gpgme_check_version (NULL); #ifdef LC_CTYPE gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL)); #endif #ifdef LC_MESSAGES gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL)); #endif while (gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case CMD_INTERACTIVE: opt_interactive = 1; /* Fall trough. */ case CMD_SINGLE: case CMD_LIBVERSION: cmd = pargs.r_opt; break; case OPT_DEBUG: opt_debug = 1; break; default: pargs.err = ARGPARSE_PRINT_WARNING; break; } } gpgrt_argparse (NULL, &pargs, NULL); if (!opt_debug) { const char *s = getenv ("GPGME_JSON_DEBUG"); if (s && atoi (s) > 0) opt_debug = 1; } if (opt_debug) { const char *home = getenv ("HOME"); char *file = xstrconcat ("socket://", home? home:"/tmp", "/.gnupg/S.gpgme-json.log", NULL); log_set_file (file); xfree (file); } if (opt_debug) { int i; for (i=0; argv[i]; i++) log_debug ("argv[%d]='%s'\n", i, argv[i]); } switch (cmd) { case CMD_DEFAULT: native_messaging_repl (); break; case CMD_SINGLE: read_and_process_single_request (); break; case CMD_INTERACTIVE: interactive_repl (); break; case CMD_LIBVERSION: printf ("Version from header: %s (0x%06x)\n", GPGME_VERSION, GPGME_VERSION_NUMBER); printf ("Version from binary: %s\n", gpgme_check_version (NULL)); printf ("Copyright blurb ...:%s\n", gpgme_check_version ("\x01\x01")); break; } if (opt_debug) log_debug ("ready"); #endif /* This is a modern libgp-error. */ return 0; } #endif /* libgpg-error >= 1.28 */