1 /* gpgme-json.c - JSON based interface to gpgme (server)
2 * Copyright (C) 2018 g10 Code GmbH
4 * This file is part of GPGME.
6 * GPGME is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as
8 * published by the Free Software Foundation; either version 2.1 of
9 * the License, or (at your option) any later version.
11 * GPGME is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, see <http://www.gnu.org/licenses/>.
18 * SPDX-License-Identifier: LGPL-2.1+
21 /* This is tool implements the Native Messaging protocol of web
22 * browsers and provides the server part of it. A Javascript based
23 * client can be found in lang/javascript.
37 #define GPGRT_ENABLE_ES_MACROS 1
38 #define GPGRT_ENABLE_LOG_MACROS 1
39 #define GPGRT_ENABLE_ARGPARSE_MACROS 1
44 #if GPGRT_VERSION_NUMBER < 0x011c00 /* 1.28 */
45 int main (void){fputs ("Build with Libgpg-error >= 1.28!\n", stderr);return 1;}
46 #else /* libgpg-error >= 1.28 */
48 /* We don't allow a request with more than 64 MiB. */
49 #define MAX_REQUEST_SIZE (64 * 1024 * 1024)
51 /* Minimal, default and maximum chunk size for returned data. The
52 * first chunk is returned directly. If the "more" flag is also
53 * returned, a "getmore" command needs to be used to get the next
54 * chunk. Right now this value covers just the value of the "data"
55 * element; so to cover for the other returned objects this values
56 * needs to be lower than the maximum allowed size of the browser. */
57 #define MIN_REPLY_CHUNK_SIZE 512
58 #define DEF_REPLY_CHUNK_SIZE (512 * 1024)
59 #define MAX_REPLY_CHUNK_SIZE (10 * 1024 * 1024)
62 static void xoutofcore (const char *type) GPGRT_ATTR_NORETURN;
63 static cjson_t error_object_v (cjson_t json, const char *message,
64 va_list arg_ptr) GPGRT_ATTR_PRINTF(2,0);
65 static cjson_t error_object (cjson_t json, const char *message,
66 ...) GPGRT_ATTR_PRINTF(2,3);
67 static char *error_object_string (const char *message,
68 ...) GPGRT_ATTR_PRINTF(1,2);
71 /* True if interactive mode is active. */
72 static int opt_interactive;
73 /* True is debug mode is active. */
76 /* Pending data to be returned by a getmore command. */
79 char *buffer; /* Malloced data or NULL if not used. */
80 size_t length; /* Length of that data. */
81 size_t written; /* # of already written bytes from BUFFER. */
82 const char *type;/* The "type" of the data. */
83 int base64; /* The "base64" flag of the data. */
88 * Helper functions and macros
91 #define xtrymalloc(a) gpgrt_malloc ((a))
92 #define xtrystrdup(a) gpgrt_strdup ((a))
93 #define xmalloc(a) ({ \
94 void *_r = gpgrt_malloc ((a)); \
96 xoutofcore ("malloc"); \
98 #define xcalloc(a,b) ({ \
99 void *_r = gpgrt_calloc ((a), (b)); \
101 xoutofcore ("calloc"); \
103 #define xstrdup(a) ({ \
104 char *_r = gpgrt_strdup ((a)); \
106 xoutofcore ("strdup"); \
108 #define xstrconcat(a, ...) ({ \
109 char *_r = gpgrt_strconcat ((a), __VA_ARGS__); \
111 xoutofcore ("strconcat"); \
113 #define xfree(a) gpgrt_free ((a))
115 #define spacep(p) (*(p) == ' ' || *(p) == '\t')
118 static GPGRT_INLINE char *
119 _my_stpcpy (char *a, const char *b)
126 #define stpcpy(a,b) _my_stpcpy ((a), (b))
127 #endif /*!HAVE_STPCPY*/
132 xoutofcore (const char *type)
134 gpg_error_t err = gpg_error_from_syserror ();
135 log_error ("%s failed: %s\n", type, gpg_strerror (err));
140 /* Call cJSON_CreateObject but terminate in case of an error. */
142 xjson_CreateObject (void)
144 cjson_t json = cJSON_CreateObject ();
146 xoutofcore ("cJSON_CreateObject");
151 /* Wrapper around cJSON_AddStringToObject which returns an gpg-error
152 * code instead of the NULL or the new object. */
154 cjson_AddStringToObject (cjson_t object, const char *name, const char *string)
156 if (!cJSON_AddStringToObject (object, name, string))
157 return gpg_error_from_syserror ();
162 /* Same as cjson_AddStringToObject but prints an error message and
163 * terminates the process. */
165 xjson_AddStringToObject (cjson_t object, const char *name, const char *string)
167 if (!cJSON_AddStringToObject (object, name, string))
168 xoutofcore ("cJSON_AddStringToObject");
172 /* Wrapper around cJSON_AddBoolToObject which terminates the process
173 * in case of an error. */
175 xjson_AddBoolToObject (cjson_t object, const char *name, int abool)
177 if (!cJSON_AddBoolToObject (object, name, abool))
178 xoutofcore ("cJSON_AddStringToObject");
182 /* This is similar to cJSON_AddStringToObject but takes (DATA,
183 * DATALEN) and adds it under NAME as a base 64 encoded string to
186 add_base64_to_object (cjson_t object, const char *name,
187 const void *data, size_t datalen)
189 #if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */
190 return gpg_error (GPG_ERR_NOT_SUPPORTED);
194 gpgrt_b64state_t state = NULL;
195 cjson_t j_str = NULL;
198 fp = es_fopenmem (0, "rwb");
201 err = gpg_err_code_from_syserror ();
204 state = gpgrt_b64enc_start (fp, "");
207 err = gpg_err_code_from_syserror ();
211 err = gpgrt_b64enc_write (state, data, datalen);
215 err = gpgrt_b64enc_finish (state);
221 if (es_fclose_snatch (fp, &buffer, NULL))
224 err = gpg_error_from_syserror ();
229 j_str = cJSON_CreateStringConvey (buffer);
232 err = gpg_error_from_syserror ();
237 if (!cJSON_AddItemToObject (object, name, j_str))
239 err = gpg_error_from_syserror ();
240 cJSON_Delete (j_str);
248 cJSON_Delete (j_str);
249 gpgrt_b64enc_finish (state);
256 /* Create a JSON error object. If JSON is not NULL the error message
257 * is appended to that object. An existing "type" item will be replaced. */
259 error_object_v (cjson_t json, const char *message, va_list arg_ptr)
261 cjson_t response, j_tmp;
264 msg = gpgrt_vbsprintf (message, arg_ptr);
266 xoutofcore ("error_object");
268 response = json? json : xjson_CreateObject ();
270 if (!(j_tmp = cJSON_GetObjectItem (response, "type")))
271 xjson_AddStringToObject (response, "type", "error");
272 else /* Replace existing "type". */
274 j_tmp = cJSON_CreateString ("error");
276 xoutofcore ("cJSON_CreateString");
277 cJSON_ReplaceItemInObject (response, "type", j_tmp);
279 xjson_AddStringToObject (response, "msg", msg);
286 /* Call cJSON_Print but terminate in case of an error. */
288 xjson_Print (cjson_t object)
291 buf = cJSON_Print (object);
293 xoutofcore ("cJSON_Print");
299 error_object (cjson_t json, const char *message, ...)
304 va_start (arg_ptr, message);
305 response = error_object_v (json, message, arg_ptr);
312 error_object_string (const char *message, ...)
318 va_start (arg_ptr, message);
319 response = error_object_v (NULL, message, arg_ptr);
322 msg = xjson_Print (response);
323 cJSON_Delete (response);
328 /* Get the boolean property NAME from the JSON object and store true
329 * or valse at R_VALUE. If the name is unknown the value of DEF_VALUE
330 * is returned. If the type of the value is not boolean,
331 * GPG_ERR_INV_VALUE is returned and R_VALUE set to DEF_VALUE. */
333 get_boolean_flag (cjson_t json, const char *name, int def_value, int *r_value)
337 j_item = cJSON_GetObjectItem (json, name);
339 *r_value = def_value;
340 else if (cjson_is_true (j_item))
342 else if (cjson_is_false (j_item))
346 *r_value = def_value;
347 return gpg_error (GPG_ERR_INV_VALUE);
354 /* Get the boolean property PROTOCOL from the JSON object and store
355 * its value at R_PROTOCOL. The default is OpenPGP. */
357 get_protocol (cjson_t json, gpgme_protocol_t *r_protocol)
361 *r_protocol = GPGME_PROTOCOL_OpenPGP;
362 j_item = cJSON_GetObjectItem (json, "protocol");
365 else if (!cjson_is_string (j_item))
366 return gpg_error (GPG_ERR_INV_VALUE);
367 else if (!strcmp(j_item->valuestring, "openpgp"))
369 else if (!strcmp(j_item->valuestring, "cms"))
370 *r_protocol = GPGME_PROTOCOL_CMS;
372 return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
378 /* Get the chunksize from JSON and store it at R_CHUNKSIZE. */
380 get_chunksize (cjson_t json, size_t *r_chunksize)
384 *r_chunksize = DEF_REPLY_CHUNK_SIZE;
385 j_item = cJSON_GetObjectItem (json, "chunksize");
388 else if (!cjson_is_number (j_item))
389 return gpg_error (GPG_ERR_INV_VALUE);
390 else if ((size_t)j_item->valueint < MIN_REPLY_CHUNK_SIZE)
391 *r_chunksize = MIN_REPLY_CHUNK_SIZE;
392 else if ((size_t)j_item->valueint > MAX_REPLY_CHUNK_SIZE)
393 *r_chunksize = MAX_REPLY_CHUNK_SIZE;
395 *r_chunksize = (size_t)j_item->valueint;
401 /* Extract the keys from the "keys" array in the JSON object. On
402 * success a string with the keys identifiers is stored at R_KEYS.
403 * The keys in that string are LF delimited. On failure an error code
406 get_keys (cjson_t json, char **r_keystring)
408 cjson_t j_keys, j_item;
415 j_keys = cJSON_GetObjectItem (json, "keys");
417 return gpg_error (GPG_ERR_NO_KEY);
418 if (!cjson_is_array (j_keys) && !cjson_is_string (j_keys))
419 return gpg_error (GPG_ERR_INV_VALUE);
421 /* Fixme: We should better use a membuf like thing. */
422 length = 1; /* For the EOS. */
423 if (cjson_is_string (j_keys))
426 length += strlen (j_keys->valuestring);
427 if (strchr (j_keys->valuestring, '\n'))
428 return gpg_error (GPG_ERR_INV_USER_ID);
432 nkeys = cJSON_GetArraySize (j_keys);
434 return gpg_error (GPG_ERR_NO_KEY);
435 for (i=0; i < nkeys; i++)
437 j_item = cJSON_GetArrayItem (j_keys, i);
438 if (!j_item || !cjson_is_string (j_item))
439 return gpg_error (GPG_ERR_INV_VALUE);
441 length++; /* Space for delimiter. */
442 length += strlen (j_item->valuestring);
443 if (strchr (j_item->valuestring, '\n'))
444 return gpg_error (GPG_ERR_INV_USER_ID);
448 p = *r_keystring = xtrymalloc (length);
450 return gpg_error_from_syserror ();
452 if (cjson_is_string (j_keys))
454 strcpy (p, j_keys->valuestring);
458 for (i=0; i < nkeys; i++)
460 j_item = cJSON_GetArrayItem (j_keys, i);
462 *p++ = '\n'; /* Add delimiter. */
463 p = stpcpy (p, j_item->valuestring);
473 * GPGME support functions.
476 /* Helper for get_context. */
478 _create_new_context (gpgme_protocol_t proto)
483 err = gpgme_new (&ctx);
485 log_fatal ("error creating GPGME context: %s\n", gpg_strerror (err));
486 gpgme_set_protocol (ctx, proto);
487 gpgme_set_ctx_flag (ctx, "request-origin", "browser");
492 /* Return a context object for protocol PROTO. This is currently a
493 * statuically allocated context initialized for PROTO. Termnates
494 * process on failure. */
496 get_context (gpgme_protocol_t proto)
498 static gpgme_ctx_t ctx_openpgp, ctx_cms;
500 if (proto == GPGME_PROTOCOL_OpenPGP)
503 ctx_openpgp = _create_new_context (proto);
506 else if (proto == GPGME_PROTOCOL_CMS)
509 ctx_cms = _create_new_context (proto);
513 log_bug ("invalid protocol %d requested\n", proto);
518 /* Free context object retrieved by get_context. */
520 release_context (gpgme_ctx_t ctx)
522 /* Nothing to do right now. */
528 /* Given a Base-64 encoded string object in JSON return a gpgme data
529 * object at R_DATA. */
531 data_from_base64_string (gpgme_data_t *r_data, cjson_t json)
533 #if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */
535 return gpg_error (GPG_ERR_NOT_SUPPORTED);
540 gpgrt_b64state_t state = NULL;
541 gpgme_data_t data = NULL;
545 /* A quick check on the JSON. */
546 if (!cjson_is_string (json))
548 err = gpg_error (GPG_ERR_INV_VALUE);
552 state = gpgrt_b64dec_start (NULL);
555 err = gpg_err_code_from_syserror ();
559 /* Fixme: Data duplication - we should see how to snatch the memory
560 * from the json object. */
561 len = strlen (json->valuestring);
562 buf = xtrystrdup (json->valuestring);
565 err = gpg_error_from_syserror ();
569 err = gpgrt_b64dec_proc (state, buf, len, &len);
573 err = gpgrt_b64dec_finish (state);
578 err = gpgme_data_new_from_mem (&data, buf, len, 1);
587 gpgrt_b64dec_finish (state);
595 * Implementation of the commands.
599 /* Create a "data" object and the "type", "base64" and "more" flags
600 * from DATA and append them to RESULT. Ownership if DATA is
601 * transferred to this function. TYPE must be a fixed string.
602 * CHUNKSIZE is the chunksize requested from the caller. If BASE64 is
603 * -1 the need for base64 encoding is determined by the content of
604 * DATA, all other values are take as rtue or false. Note that
605 * op_getmore has similar code but works on PENDING_DATA which is set
608 make_data_object (cjson_t result, gpgme_data_t data, size_t chunksize,
609 const char *type, int base64)
616 if (!base64 || base64 == -1) /* Make sure that we really have a string. */
617 gpgme_data_write (data, "", 1);
619 buffer = gpgme_data_release_and_get_mem (data, &buflen);
623 err = gpg_error_from_syserror ();
631 log_fatal ("Appended Nul byte got lost\n");
632 if (memchr (buffer, 0, buflen-1))
634 buflen--; /* Adjust for the extra nul byte. */
637 /* Fixme: We might want to do more advanced heuristics than to
638 * only look for a Nul. */
641 /* Adjust the chunksize if we need to do base64 conversion. */
643 chunksize = (chunksize / 4) * 3;
645 xjson_AddStringToObject (result, "type", type);
646 xjson_AddBoolToObject (result, "base64", base64);
648 if (buflen > chunksize)
650 xjson_AddBoolToObject (result, "more", 1);
652 c = buffer[chunksize];
653 buffer[chunksize] = 0;
655 err = add_base64_to_object (result, "data", buffer, chunksize);
657 err = cjson_AddStringToObject (result, "data", buffer);
658 buffer[chunksize] = c;
662 pending_data.buffer = buffer;
664 pending_data.length = buflen;
665 pending_data.written = chunksize;
666 pending_data.type = type;
667 pending_data.base64 = base64;
672 err = add_base64_to_object (result, "data", buffer, buflen);
674 err = cjson_AddStringToObject (result, "data", buffer);
684 static const char hlp_encrypt[] =
686 "keys: Array of strings with the fingerprints or user-ids\n"
687 " of the keys to encrypt the data. For a single key\n"
688 " a String may be used instead of an array.\n"
689 "data: Input data. \n"
691 "Optional parameters:\n"
692 "protocol: Either \"openpgp\" (default) or \"cms\".\n"
693 "chunksize: Max number of bytes in the resulting \"data\".\n"
695 "Optional boolean flags (default is false):\n"
696 "base64: Input data is base64 encoded.\n"
697 "mime: Indicate that data is a MIME object.\n"
698 "armor: Request output in armored format.\n"
699 "always-trust: Request --always-trust option.\n"
700 "no-encrypt-to: Do not use a default recipient.\n"
701 "no-compress: Do not compress the plaintext first.\n"
702 "throw-keyids: Request the --throw-keyids option.\n"
703 "want-address: Require that the keys include a mail address.\n"
704 "wrap: Assume the input is an OpenPGP message.\n"
706 "Response on success:\n"
707 "type: \"ciphertext\"\n"
708 "data: Unless armor mode is used a Base64 encoded binary\n"
709 " ciphertext. In armor mode a string with an armored\n"
710 " OpenPGP or a PEM message.\n"
711 "base64: Boolean indicating whether data is base64 encoded.\n"
712 "more: Optional boolean indicating that \"getmore\" is required.";
714 op_encrypt (cjson_t request, cjson_t result)
717 gpgme_ctx_t ctx = NULL;
718 gpgme_protocol_t protocol;
722 char *keystring = NULL;
724 gpgme_data_t input = NULL;
725 gpgme_data_t output = NULL;
727 gpgme_encrypt_flags_t encrypt_flags = 0;
729 if ((err = get_protocol (request, &protocol)))
731 ctx = get_context (protocol);
732 if ((err = get_chunksize (request, &chunksize)))
735 if ((err = get_boolean_flag (request, "base64", 0, &opt_base64)))
737 if ((err = get_boolean_flag (request, "mime", 0, &opt_mime)))
740 if ((err = get_boolean_flag (request, "armor", 0, &abool)))
742 gpgme_set_armor (ctx, abool);
743 if ((err = get_boolean_flag (request, "always-trust", 0, &abool)))
746 encrypt_flags |= GPGME_ENCRYPT_ALWAYS_TRUST;
747 if ((err = get_boolean_flag (request, "no-encrypt-to", 0,&abool)))
750 encrypt_flags |= GPGME_ENCRYPT_NO_ENCRYPT_TO;
751 if ((err = get_boolean_flag (request, "no-compress", 0, &abool)))
754 encrypt_flags |= GPGME_ENCRYPT_NO_COMPRESS;
755 if ((err = get_boolean_flag (request, "throw-keyids", 0, &abool)))
758 encrypt_flags |= GPGME_ENCRYPT_THROW_KEYIDS;
759 if ((err = get_boolean_flag (request, "wrap", 0, &abool)))
762 encrypt_flags |= GPGME_ENCRYPT_WRAP;
763 if ((err = get_boolean_flag (request, "want-address", 0, &abool)))
766 encrypt_flags |= GPGME_ENCRYPT_WANT_ADDRESS;
770 err = get_keys (request, &keystring);
773 /* Provide a custom error response. */
774 error_object (result, "Error getting keys: %s", gpg_strerror (err));
778 /* Get the data. Note that INPUT is a shallow data object with the
779 * storage hold in REQUEST. */
780 j_input = cJSON_GetObjectItem (request, "data");
783 err = gpg_error (GPG_ERR_NO_DATA);
786 if (!cjson_is_string (j_input))
788 err = gpg_error (GPG_ERR_INV_VALUE);
793 err = data_from_base64_string (&input, j_input);
796 error_object (result, "Error decoding Base-64 encoded 'data': %s",
803 err = gpgme_data_new_from_mem (&input, j_input->valuestring,
804 strlen (j_input->valuestring), 0);
807 error_object (result, "Error getting 'data': %s", gpg_strerror (err));
812 gpgme_data_set_encoding (input, GPGME_DATA_ENCODING_MIME);
815 /* Create an output data object. */
816 err = gpgme_data_new (&output);
819 error_object (result, "Error creating output data object: %s",
825 err = gpgme_op_encrypt_ext (ctx, NULL, keystring, encrypt_flags,
827 /* encrypt_result = gpgme_op_encrypt_result (ctx); */
830 error_object (result, "Encryption failed: %s", gpg_strerror (err));
833 gpgme_data_release (input);
836 /* We need to base64 if armoring has not been requested. */
837 err = make_data_object (result, output, chunksize,
838 "ciphertext", !gpgme_get_armor (ctx));
843 release_context (ctx);
844 gpgme_data_release (input);
845 gpgme_data_release (output);
851 static const char hlp_decrypt[] =
853 "data: The encrypted data.\n"
855 "Optional parameters:\n"
856 "protocol: Either \"openpgp\" (default) or \"cms\".\n"
857 "chunksize: Max number of bytes in the resulting \"data\".\n"
859 "Optional boolean flags (default is false):\n"
860 "base64: Input data is base64 encoded.\n"
862 "Response on success:\n"
863 "type: \"plaintext\"\n"
864 "data: The decrypted data. This may be base64 encoded.\n"
865 "base64: Boolean indicating whether data is base64 encoded.\n"
866 "mime: A Boolean indicating whether the data is a MIME object.\n"
867 "info: An optional object with extra information.\n"
868 "more: Optional boolean indicating that \"getmore\" is required.";
870 op_decrypt (cjson_t request, cjson_t result)
873 gpgme_ctx_t ctx = NULL;
874 gpgme_protocol_t protocol;
878 gpgme_data_t input = NULL;
879 gpgme_data_t output = NULL;
880 gpgme_decrypt_result_t decrypt_result;
882 if ((err = get_protocol (request, &protocol)))
884 ctx = get_context (protocol);
885 if ((err = get_chunksize (request, &chunksize)))
888 if ((err = get_boolean_flag (request, "base64", 0, &opt_base64)))
891 /* Get the data. Note that INPUT is a shallow data object with the
892 * storage hold in REQUEST. */
893 j_input = cJSON_GetObjectItem (request, "data");
896 err = gpg_error (GPG_ERR_NO_DATA);
899 if (!cjson_is_string (j_input))
901 err = gpg_error (GPG_ERR_INV_VALUE);
906 err = data_from_base64_string (&input, j_input);
909 error_object (result, "Error decoding Base-64 encoded 'data': %s",
916 err = gpgme_data_new_from_mem (&input, j_input->valuestring,
917 strlen (j_input->valuestring), 0);
920 error_object (result, "Error getting 'data': %s", gpg_strerror (err));
925 /* Create an output data object. */
926 err = gpgme_data_new (&output);
929 error_object (result, "Error creating output data object: %s",
935 err = gpgme_op_decrypt_ext (ctx, GPGME_DECRYPT_VERIFY,
937 decrypt_result = gpgme_op_decrypt_result (ctx);
940 error_object (result, "Decryption failed: %s", gpg_strerror (err));
943 gpgme_data_release (input);
946 if (decrypt_result->is_mime)
947 xjson_AddBoolToObject (result, "mime", 1);
949 err = make_data_object (result, output, chunksize, "plaintext", -1);
953 release_context (ctx);
954 gpgme_data_release (input);
955 gpgme_data_release (output);
961 static const char hlp_getmore[] =
964 "Optional parameters:\n"
965 "chunksize: Max number of bytes in the \"data\" object.\n"
967 "Response on success:\n"
968 "type: Type of the pending data\n"
969 "data: The next chunk of data\n"
970 "base64: Boolean indicating whether data is base64 encoded\n"
971 "more: Optional boolean requesting another \"getmore\".";
973 op_getmore (cjson_t request, cjson_t result)
980 if ((err = get_chunksize (request, &chunksize)))
983 /* Adjust the chunksize if we need to do base64 conversion. */
984 if (pending_data.base64)
985 chunksize = (chunksize / 4) * 3;
987 /* Do we have anything pending? */
988 if (!pending_data.buffer)
990 err = gpg_error (GPG_ERR_NO_DATA);
991 error_object (result, "Operation not possible: %s", gpg_strerror (err));
995 xjson_AddStringToObject (result, "type", pending_data.type);
996 xjson_AddBoolToObject (result, "base64", pending_data.base64);
998 if (pending_data.written >= pending_data.length)
1000 /* EOF reached. This should not happen but we return an empty
1001 * string once in case of client errors. */
1002 gpgme_free (pending_data.buffer);
1003 pending_data.buffer = NULL;
1004 xjson_AddBoolToObject (result, "more", 0);
1005 err = cjson_AddStringToObject (result, "data", "");
1009 n = pending_data.length - pending_data.written;
1013 xjson_AddBoolToObject (result, "more", 1);
1016 xjson_AddBoolToObject (result, "more", 0);
1018 c = pending_data.buffer[pending_data.written + n];
1019 pending_data.buffer[pending_data.written + n] = 0;
1020 if (pending_data.base64)
1021 err = add_base64_to_object (result, "data",
1022 (pending_data.buffer
1023 + pending_data.written), n);
1025 err = cjson_AddStringToObject (result, "data",
1026 (pending_data.buffer
1027 + pending_data.written));
1028 pending_data.buffer[pending_data.written + n] = c;
1031 pending_data.written += n;
1032 if (pending_data.written >= pending_data.length)
1034 gpgme_free (pending_data.buffer);
1035 pending_data.buffer = NULL;
1046 static const char hlp_help[] =
1047 "The tool expects a JSON object with the request and responds with\n"
1048 "another JSON object. Even on error a JSON object is returned. The\n"
1049 "property \"op\" is mandatory and its string value selects the\n"
1050 "operation; if the property \"help\" with the value \"true\" exists, the\n"
1051 "operation is not performned but a string with the documentation\n"
1052 "returned. To list all operations it is allowed to leave out \"op\" in\n"
1053 "help mode. Supported values for \"op\" are:\n\n"
1054 " encrypt Encrypt data.\n"
1055 " getmore Retrieve remaining data.\n"
1056 " help Help overview.";
1058 op_help (cjson_t request, cjson_t result)
1061 char *buffer = NULL;
1064 j_tmp = cJSON_GetObjectItem (request, "interactive_help");
1065 if (opt_interactive && j_tmp && cjson_is_string (j_tmp))
1066 msg = buffer = xstrconcat (hlp_help, "\n", j_tmp->valuestring, NULL);
1070 xjson_AddStringToObject (result, "type", "help");
1071 xjson_AddStringToObject (result, "msg", msg);
1083 /* Process a request and return the response. The response is a newly
1084 * allocated string or NULL in case of an error. */
1086 process_request (const char *request)
1090 gpg_error_t (*handler)(cjson_t request, cjson_t result);
1091 const char * const helpstr;
1093 { "encrypt", op_encrypt, hlp_encrypt },
1094 { "decrypt", op_decrypt, hlp_decrypt },
1095 { "getmore", op_getmore, hlp_getmore },
1096 { "help", op_help, hlp_help },
1101 cjson_t j_tmp, j_op;
1108 response = xjson_CreateObject ();
1110 json = cJSON_Parse (request, &erroff);
1113 log_string (GPGRT_LOGLVL_INFO, request);
1114 log_info ("invalid JSON object at offset %zu\n", erroff);
1115 error_object (response, "invalid JSON object at offset %zu\n", erroff);
1119 j_tmp = cJSON_GetObjectItem (json, "help");
1120 helpmode = (j_tmp && cjson_is_true (j_tmp));
1122 j_op = cJSON_GetObjectItem (json, "op");
1123 if (!j_op || !cjson_is_string (j_op))
1127 error_object (response, "Property \"op\" missing");
1130 op = "help"; /* Help summary. */
1133 op = j_op->valuestring;
1135 for (idx=0; optbl[idx].op; idx++)
1136 if (!strcmp (op, optbl[idx].op))
1140 if (helpmode && strcmp (op, "help"))
1142 xjson_AddStringToObject (response, "type", "help");
1143 xjson_AddStringToObject (response, "op", op);
1144 xjson_AddStringToObject (response, "msg", optbl[idx].helpstr);
1150 /* If this is not the "getmore" command and we have any
1151 * pending data release that data. */
1152 if (pending_data.buffer && optbl[idx].handler != op_getmore)
1154 gpgme_free (pending_data.buffer);
1155 pending_data.buffer = NULL;
1158 err = optbl[idx].handler (json, response);
1161 if (!(j_tmp = cJSON_GetObjectItem (response, "type"))
1162 || !cjson_is_string (j_tmp)
1163 || strcmp (j_tmp->valuestring, "error"))
1165 /* No error type response - provide a generic one. */
1166 error_object (response, "Operation failed: %s",
1167 gpg_strerror (err));
1170 xjson_AddStringToObject (response, "op", op);
1174 else /* Operation not supported. */
1176 error_object (response, "Unknown operation '%s'", op);
1177 xjson_AddStringToObject (response, "op", op);
1181 cJSON_Delete (json);
1182 if (opt_interactive)
1183 res = cJSON_Print (response);
1185 res = cJSON_PrintUnformatted (response);
1187 log_error ("Printing JSON data failed\n");
1188 cJSON_Delete (response);
1199 get_file (const char *fname)
1207 fp = es_fopen (fname, "r");
1210 err = gpg_error_from_syserror ();
1211 log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
1215 if (fstat (es_fileno(fp), &st))
1217 err = gpg_error_from_syserror ();
1218 log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
1223 buflen = st.st_size;
1224 buf = xmalloc (buflen+1);
1225 if (es_fread (buf, buflen, 1, fp) != 1)
1227 err = gpg_error_from_syserror ();
1228 log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
1240 /* Return a malloced line or NULL on EOF. Terminate on read
1246 size_t linesize = 0;
1248 size_t maxlength = 2048;
1254 n = es_read_line (es_stdin, &line, &linesize, &maxlength);
1257 err = gpg_error_from_syserror ();
1258 log_error ("error reading line: %s\n", gpg_strerror (err));
1265 return NULL; /* EOF */
1269 log_info ("line too long - skipped\n");
1272 if (memchr (line, 0, n))
1273 log_info ("warning: line shortened due to embedded Nul character\n");
1275 if (line[n-1] == '\n')
1278 /* Trim leading spaces. */
1279 for (s=line; spacep (s); s++)
1293 /* Process meta commands used with the standard REPL. */
1295 process_meta_commands (const char *request)
1297 char *result = NULL;
1299 while (spacep (request))
1302 if (!strncmp (request, "help", 4) && (spacep (request+4) || !request[4]))
1306 char *buf = xstrconcat ("{ \"help\":true, \"op\":\"", request+5,
1308 result = process_request (buf);
1312 result = process_request ("{ \"op\": \"help\","
1313 " \"interactive_help\": "
1314 "\"\\nMeta commands:\\n"
1315 " ,read FNAME Process data from FILE\\n"
1316 " ,help CMD Print help for a command\\n"
1317 " ,quit Terminate process\""
1320 else if (!strncmp (request, "quit", 4) && (spacep (request+4) || !request[4]))
1322 else if (!strncmp (request, "read", 4) && (spacep (request+4) || !request[4]))
1325 log_info ("usage: ,read FILENAME\n");
1328 char *buffer = get_file (request + 5);
1331 result = process_request (buffer);
1337 log_info ("invalid meta command\n");
1343 /* If STRING has a help response, return the MSG property in a human
1344 * readable format. */
1346 get_help_msg (const char *string)
1348 cjson_t json, j_type, j_msg;
1350 char *buffer = NULL;
1353 json = cJSON_Parse (string, NULL);
1356 j_type = cJSON_GetObjectItem (json, "type");
1357 if (j_type && cjson_is_string (j_type)
1358 && !strcmp (j_type->valuestring, "help"))
1360 j_msg = cJSON_GetObjectItem (json, "msg");
1361 if (j_msg || cjson_is_string (j_msg))
1363 msg = j_msg->valuestring;
1364 buffer = malloc (strlen (msg)+1);
1367 for (p=buffer; *msg; msg++)
1369 if (*msg == '\\' && msg[1] == '\n')
1378 cJSON_Delete (json);
1384 /* An interactive standard REPL. */
1386 interactive_repl (void)
1389 char *request = NULL;
1390 char *response = NULL;
1394 es_setvbuf (es_stdin, NULL, _IONBF, 0);
1395 #if GPGRT_VERSION_NUMBER >= 0x011d00 /* 1.29 */
1396 es_fprintf (es_stderr, "%s %s ready (enter \",help\" for help)\n",
1397 gpgrt_strusage (11), gpgrt_strusage (13));
1401 es_fputs ("> ", es_stderr);
1402 es_fflush (es_stderr);
1403 es_fflush (es_stdout);
1406 es_fflush (es_stderr);
1407 es_fflush (es_stdout);
1413 request = xstrdup (line);
1415 request = xstrconcat (request, "\n", line, NULL);
1419 es_fputs ("\n", es_stderr);
1421 if (!line || !*line || (first && *request == ','))
1423 /* Process the input. */
1426 if (request && *request == ',')
1428 response = process_meta_commands (request+1);
1432 response = process_request (request);
1439 if (opt_interactive)
1441 char *msg = get_help_msg (response);
1449 es_fputs ("===> ", es_stderr);
1450 es_fflush (es_stderr);
1451 for (p=response; *p; p++)
1455 es_fflush (es_stdout);
1456 es_fputs ("\n===> ", es_stderr);
1457 es_fflush (es_stderr);
1460 es_putc (*p, es_stdout);
1462 es_fflush (es_stdout);
1463 es_fputs ("\n", es_stderr);
1475 /* Read and process a single request. */
1477 read_and_process_single_request (void)
1480 char *request = NULL;
1481 char *response = NULL;
1489 request = (request? xstrconcat (request, "\n", line, NULL)
1490 /**/ : xstrdup (line));
1496 response = process_request (request);
1499 es_fputs (response, es_stdout);
1500 if ((n = strlen (response)) && response[n-1] != '\n')
1501 es_fputc ('\n', es_stdout);
1503 es_fflush (es_stdout);
1515 /* The Native Messaging processing loop. */
1517 native_messaging_repl (void)
1520 uint32_t nrequest, nresponse;
1521 char *request = NULL;
1522 char *response = NULL;
1525 /* Due to the length octets we need to switch the I/O stream into
1527 es_set_binary (es_stdin);
1528 es_set_binary (es_stdout);
1529 es_setbuf (es_stdin, NULL); /* stdin needs to be unbuffered! */
1533 /* Read length. Note that the protocol uses native endianess.
1534 * Is it allowed to call such a thing a well thought out
1536 if (es_read (es_stdin, &nrequest, sizeof nrequest, &n))
1538 err = gpg_error_from_syserror ();
1539 log_error ("error reading request header: %s\n", gpg_strerror (err));
1544 if (n != sizeof nrequest)
1546 log_error ("error reading request header: short read\n");
1549 if (nrequest > MAX_REQUEST_SIZE)
1551 log_error ("error reading request: request too long (%zu MiB)\n",
1552 (size_t)nrequest / (1024*1024));
1553 /* Fixme: Shall we read the request to the bit bucket and
1554 * return an error reponse or just return an error reponse
1555 * and terminate? Needs some testing. */
1560 request = xtrymalloc (nrequest);
1563 err = gpg_error_from_syserror ();
1564 log_error ("error reading request: Not enough memory for %zu MiB)\n",
1565 (size_t)nrequest / (1024*1024));
1566 /* FIXME: See comment above. */
1569 if (es_read (es_stdin, request, nrequest, &n))
1571 err = gpg_error_from_syserror ();
1572 log_error ("error reading request: %s\n", gpg_strerror (err));
1577 /* That is a protocol violation. */
1579 response = error_object_string ("Invalid request:"
1580 " short read (%zu of %zu bytes)\n",
1581 n, (size_t)nrequest);
1583 else /* Process request */
1586 log_debug ("request='%s'\n", request);
1588 response = process_request (request);
1590 log_debug ("response='%s'\n", response);
1592 nresponse = strlen (response);
1594 /* Write response */
1595 if (es_write (es_stdout, &nresponse, sizeof nresponse, &n))
1597 err = gpg_error_from_syserror ();
1598 log_error ("error writing request header: %s\n", gpg_strerror (err));
1601 if (n != sizeof nrequest)
1603 log_error ("error writing request header: short write\n");
1606 if (es_write (es_stdout, response, nresponse, &n))
1608 err = gpg_error_from_syserror ();
1609 log_error ("error writing request: %s\n", gpg_strerror (err));
1614 log_error ("error writing request: short write\n");
1617 if (es_fflush (es_stdout) || es_ferror (es_stdout))
1619 err = gpg_error_from_syserror ();
1620 log_error ("error writing request: %s\n", gpg_strerror (err));
1632 my_strusage( int level )
1638 case 9: p = "LGPL-2.1-or-later"; break;
1639 case 11: p = "gpgme-json"; break;
1640 case 13: p = PACKAGE_VERSION; break;
1641 case 14: p = "Copyright (C) 2018 g10 Code GmbH"; break;
1642 case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break;
1645 p = "Usage: gpgme-json [OPTIONS]";
1648 p = "Native messaging based GPGME operations.\n";
1651 p = "1"; /* Flag print 40 as part of 41. */
1653 default: p = NULL; break;
1659 main (int argc, char *argv[])
1661 #if GPGRT_VERSION_NUMBER < 0x011d00 /* 1.29 */
1663 fprintf (stderr, "WARNING: Old libgpg-error - using limited mode\n");
1664 native_messaging_repl ();
1666 #else /* This is a modern libgp-error. */
1668 enum { CMD_DEFAULT = 0,
1669 CMD_INTERACTIVE = 'i',
1671 CMD_LIBVERSION = 501,
1672 } cmd = CMD_DEFAULT;
1677 static gpgrt_opt_t opts[] = {
1678 ARGPARSE_c (CMD_INTERACTIVE, "interactive", "Interactive REPL"),
1679 ARGPARSE_c (CMD_SINGLE, "single", "Single request mode"),
1680 ARGPARSE_c (CMD_LIBVERSION, "lib-version", "Show library version"),
1681 ARGPARSE_s_n(OPT_DEBUG, "debug", "Flyswatter"),
1685 gpgrt_argparse_t pargs = { &argc, &argv};
1687 gpgrt_set_strusage (my_strusage);
1689 #ifdef HAVE_SETLOCALE
1690 setlocale (LC_ALL, "");
1692 gpgme_check_version (NULL);
1694 gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
1697 gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL));
1700 while (gpgrt_argparse (NULL, &pargs, opts))
1702 switch (pargs.r_opt)
1704 case CMD_INTERACTIVE:
1705 opt_interactive = 1;
1708 case CMD_LIBVERSION:
1712 case OPT_DEBUG: opt_debug = 1; break;
1715 pargs.err = ARGPARSE_PRINT_WARNING;
1719 gpgrt_argparse (NULL, &pargs, NULL);
1723 const char *s = getenv ("GPGME_JSON_DEBUG");
1724 if (s && atoi (s) > 0)
1730 const char *home = getenv ("HOME");
1731 char *file = xstrconcat ("socket://",
1733 "/.gnupg/S.gpgme-json.log", NULL);
1734 log_set_file (file);
1740 for (i=0; argv[i]; i++)
1741 log_debug ("argv[%d]='%s'\n", i, argv[i]);
1747 native_messaging_repl ();
1751 read_and_process_single_request ();
1754 case CMD_INTERACTIVE:
1755 interactive_repl ();
1758 case CMD_LIBVERSION:
1759 printf ("Version from header: %s (0x%06x)\n",
1760 GPGME_VERSION, GPGME_VERSION_NUMBER);
1761 printf ("Version from binary: %s\n", gpgme_check_version (NULL));
1762 printf ("Copyright blurb ...:%s\n", gpgme_check_version ("\x01\x01"));
1767 log_debug ("ready");
1769 #endif /* This is a modern libgp-error. */
1772 #endif /* libgpg-error >= 1.28 */