json: Add framework for the gpgme-json tool
[gpgme.git] / src / gpgme-json.c
1 /* gpgme-json.c - JSON based interface to gpgme (server)
2  * Copyright (C) 2018 g10 Code GmbH
3  *
4  * This file is part of GPGME.
5  *
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.
10  *
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.
15  *
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+
19  */
20
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.  The used data format is
24  * similar to the API of openpgpjs.
25  */
26
27 #include <config.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <stdarg.h>
32 #ifdef HAVE_LOCALE_H
33 #include <locale.h>
34 #endif
35 #include <stdint.h>
36
37 #define GPGRT_ENABLE_ES_MACROS 1
38 #define GPGRT_ENABLE_LOG_MACROS 1
39 #include "gpgme.h"
40 #include "argparse.h"
41 #include "cJSON.h"
42
43
44 /* We don't allow a request with more than 64 MiB.  */
45 #define MAX_REQUEST_SIZE (64 * 1024 * 1024)
46
47
48 static void xoutofcore (const char *type) GPGRT_ATTR_NORETURN;
49 static cjson_t error_object_v (const char *message,
50                               va_list arg_ptr) GPGRT_ATTR_PRINTF(1,0);
51 static cjson_t error_object (const char *message,
52                             ...) GPGRT_ATTR_PRINTF(1,2);
53 static char *error_object_string (const char *message,
54                                   ...) GPGRT_ATTR_PRINTF(1,2);
55
56
57 /* True if interactive mode is active.  */
58 static int opt_interactive;
59
60
61
62 /*
63  * Helper functions and macros
64  */
65
66 #define xtrymalloc(a)  gpgrt_malloc ((a))
67 #define xstrdup(a) ({                           \
68       char *_r = gpgrt_strdup ((a));            \
69       if (!_r)                                  \
70         xoutofcore ("strdup");                  \
71       _r; })
72 #define xstrconcat(a, ...) ({                           \
73       char *_r = gpgrt_strconcat ((a), __VA_ARGS__);    \
74       if (!_r)                                          \
75         xoutofcore ("strconcat");                       \
76       _r; })
77 #define xfree(a) gpgrt_free ((a))
78
79 #define spacep(p)   (*(p) == ' ' || *(p) == '\t')
80
81
82 static void
83 xoutofcore (const char *type)
84 {
85   gpg_error_t err = gpg_error_from_syserror ();
86   log_error ("%s failed: %s\n", type, gpg_strerror (err));
87   exit (2);
88 }
89
90
91 /* Call cJSON_CreateObject but terminate in case of an error.  */
92 static cjson_t
93 xjson_CreateObject (void)
94 {
95   cjson_t json = cJSON_CreateObject ();
96   if (!json)
97     xoutofcore ("cJSON_CreateObject");
98   return json;
99 }
100
101
102 /* Wrapper around cJSON_AddStringToObject which returns an gpg-error
103  * code instead of the NULL or the object.  */
104 static gpg_error_t
105 cjson_AddStringToObject (cjson_t object, const char *name, const char *string)
106 {
107   if (!cJSON_AddStringToObject (object, name, string))
108     return gpg_error_from_syserror ();
109   return 0;
110 }
111
112
113 /* Same as cjson_AddStringToObject but prints an error message and
114  * terminates. the process.  */
115 static void
116 xjson_AddStringToObject (cjson_t object, const char *name, const char *string)
117 {
118   if (!cJSON_AddStringToObject (object, name, string))
119     xoutofcore ("cJSON_AddStringToObject");
120 }
121
122
123 /* Create a JSON error object.  */
124 static cjson_t
125 error_object_v (const char *message, va_list arg_ptr)
126 {
127   cjson_t response;
128   char *msg;
129
130   msg = gpgrt_vbsprintf (message, arg_ptr);
131   if (!msg)
132     xoutofcore ("error_object");
133
134   response = xjson_CreateObject ();
135   xjson_AddStringToObject (response, "type", "error");
136   xjson_AddStringToObject (response, "msg", msg);
137
138   xfree (msg);
139   return response;
140 }
141
142
143 /* Call cJSON_Print but terminate in case of an error.  */
144 static char *
145 xjson_Print (cjson_t object)
146 {
147   char *buf;
148   buf = cJSON_Print (object);
149   if (!buf)
150     xoutofcore ("cJSON_Print");
151   return buf;
152 }
153
154
155 static cjson_t
156 error_object (const char *message, ...)
157 {
158   cjson_t response;
159   va_list arg_ptr;
160
161   va_start (arg_ptr, message);
162   response = error_object_v (message, arg_ptr);
163   va_end (arg_ptr);
164   return response;
165 }
166
167
168 static char *
169 error_object_string (const char *message, ...)
170 {
171   cjson_t response;
172   va_list arg_ptr;
173   char *msg;
174
175   va_start (arg_ptr, message);
176   response = error_object_v (message, arg_ptr);
177   va_end (arg_ptr);
178
179   msg = xjson_Print (response);
180   cJSON_Delete (response);
181   return msg;
182 }
183
184
185 \f
186 /*
187  * Implementaion of the commands.
188  */
189
190
191 static const char hlp_encrypt[] =
192   "op:     \"encrypt\"\n"
193   "keys:   Array of strings with the fingerprints or user-ids\n"
194   "        of the keys to encrypt the data.  For a single key\n"
195   "        a String may be used instead of an array.\n"
196   "data:   Base64 encoded input data.\n"
197   "\n"
198   "Optional parameters:\n"
199   "protocol:      Either \"openpgp\" (default) or \"cms\".\n"
200   "\n"
201   "Optional boolean flags (default is false):\n"
202   "armor:         Request output in armored format.\n"
203   "always-trust:  Request --always-trust option.\n"
204   "no-encrypt-to: Do not use a default recipient.\n"
205   "no-compress:   Do not compress the plaintext first.\n"
206   "throw-keyids:  Request the --throw-keyids option.\n"
207   "wrap:          Assume the input is an OpenPGP message.\n"
208   "\n"
209   "Response on success:\n"
210   "type:   \"ciphertext\"\n"
211   "data:   Unless armor mode is used a Base64 encoded binary\n"
212   "        ciphertext.  In armor mode a string with an armored\n"
213   "        OpenPGP or a PEM message.\n"
214   "base64: Boolean indicating whether data is base64 encoded.";
215 static gpg_error_t
216 op_encrypt (cjson_t request, cjson_t *r_result)
217 {
218
219
220   return 0;
221 }
222
223
224
225 static const char hlp_help[] =
226   "The tool expects a JSON object with the request and responds with\n"
227   "another JSON object.  Even on error a JSON object is returned.  The\n"
228   "property \"op\" is mandatory and its string value selects the\n"
229   "operation; if the property \"help\" with the value \"true\" exists, the\n"
230   "operation is not performned but a string with the documentation\n"
231   "returned.  To list all operations it is allowed to leave out \"op\" in\n"
232   "help mode.  Supported values for \"op\" are:\n\n"
233   "  encrypt     Encrypt data.\n"
234   "  help        Help overview.";
235 static gpg_error_t
236 op_help (cjson_t request, cjson_t *r_result)
237 {
238   gpg_error_t err = 0;
239   cjson_t result = NULL;
240   cjson_t j_tmp;
241   char *buffer = NULL;
242   const char *msg;
243
244   j_tmp = cJSON_GetObjectItem (request, "interactive_help");
245   if (opt_interactive && j_tmp && cjson_is_string (j_tmp))
246     msg = buffer = xstrconcat (hlp_help, "\n", j_tmp->valuestring, NULL);
247   else
248     msg = hlp_help;
249
250   result = cJSON_CreateObject ();
251   if (!result)
252     err = gpg_error_from_syserror ();
253   if (!err)
254     err = cjson_AddStringToObject (result, "type", "help");
255   if (!err)
256     err = cjson_AddStringToObject (result, "msg", msg);
257
258   xfree (buffer);
259   if (err)
260     xfree (result);
261   else
262     *r_result = result;
263   return err;
264 }
265
266
267
268 /* Process a request and return the response.  The response is a newly
269  * allocated staring or NULL in case of an error.  */
270 static char *
271 process_request (const char *request)
272 {
273   static struct {
274     const char *op;
275     gpg_error_t (*handler)(cjson_t request, cjson_t *r_result);
276     const char * const helpstr;
277   } optbl[] = {
278     { "encrypt", op_encrypt, hlp_encrypt },
279
280
281     { "help",    op_help,    hlp_help },
282     { NULL }
283   };
284   gpg_error_t err = 0;
285   size_t erroff;
286   cjson_t json;
287   cjson_t j_tmp, j_op;
288   cjson_t response = NULL;
289   int helpmode;
290   const char *op;
291   char *res;
292   int idx;
293
294   json = cJSON_Parse (request, &erroff);
295   if (!json)
296     {
297       log_string (GPGRT_LOGLVL_INFO, request);
298       log_info ("invalid JSON object at offset %zu\n", erroff);
299       response = error_object ("invalid JSON object at offset %zu\n", erroff);
300       goto leave;
301     }
302
303   j_tmp = cJSON_GetObjectItem (json, "help");
304   helpmode = (j_tmp && cjson_is_true (j_tmp));
305
306   j_op = cJSON_GetObjectItem (json, "op");
307   if (!j_op || !cjson_is_string (j_op))
308     {
309       if (!helpmode)
310         {
311           response = error_object ("Property \"op\" missing");
312           goto leave;
313         }
314       op = "help";  /* Help summary.  */
315     }
316   else
317     op = j_op->valuestring;
318
319   for (idx=0; optbl[idx].op; idx++)
320     if (!strcmp (op, optbl[idx].op))
321       break;
322   if (optbl[idx].op)
323     {
324       if (helpmode && strcmp (op, "help"))
325         {
326           response = cJSON_CreateObject ();
327           if (!response)
328             err = gpg_error_from_syserror ();
329           if (!err)
330             err = cjson_AddStringToObject (response, "type", "help");
331           if (!err)
332             err = cjson_AddStringToObject (response, "op", op);
333           if (!err)
334             err = cjson_AddStringToObject (response, "msg", optbl[idx].helpstr);
335         }
336       else
337         err = optbl[idx].handler (json, &response);
338     }
339   else  /* Operation not supported.  */
340     {
341       response = error_object ("Unknown operation '%s'", op);
342       err = cjson_AddStringToObject (response, "op", op);
343     }
344
345  leave:
346   cJSON_Delete (json);
347   json = NULL;
348   if (err)
349     log_error ("failed to create the response: %s\n", gpg_strerror (err));
350   if (response)
351     {
352       res = cJSON_Print (response);
353       if (!res)
354         log_error ("Printing JSON data failed\n");
355       cJSON_Delete (response);
356     }
357   else
358     res = NULL;
359
360   return res;
361 }
362
363
364 \f
365 /*
366  *  Driver code
367  */
368
369 /* Return a malloced line or NULL on EOF.  Terminate on read
370  * error.  */
371 static char *
372 get_line (void)
373 {
374   char *line = NULL;
375   size_t linesize = 0;
376   gpg_error_t err;
377   size_t maxlength = 2048;
378   int n;
379   const char *s;
380   char *p;
381
382  again:
383   n = es_read_line (es_stdin, &line, &linesize, &maxlength);
384   if (n < 0)
385     {
386       err = gpg_error_from_syserror ();
387       log_error ("error reading line: %s\n", gpg_strerror (err));
388       exit (1);
389     }
390   if (!n)
391     {
392       xfree (line);
393       line = NULL;
394       return NULL;  /* EOF */
395     }
396   if (!maxlength)
397     {
398       log_info ("line too long - skipped\n");
399       goto again;
400     }
401   if (memchr (line, 0, n))
402     log_info ("warning: line shortened due to embedded Nul character\n");
403
404   if (line[n-1] == '\n')
405     line[n-1] = 0;
406
407   /* Trim leading spaces.  */
408   for (s=line; spacep (s); s++)
409     ;
410   if (s != line)
411     {
412       for (p=line; *s;)
413         *p++ = *s++;
414       *p = 0;
415       n = p - line;
416     }
417
418   return line;
419 }
420
421
422 /* Process meta commands used with the standard REPL.  */
423 static char *
424 process_meta_commands (const char *request)
425 {
426   char *result = NULL;
427
428   while (spacep (request))
429     request++;
430
431   if (!strncmp (request, "help", 4) && (spacep (request+4) || !request[4]))
432     result = process_request ("{ \"op\": \"help\","
433                               " \"interactive_help\": "
434                               "\"\\nMeta commands:\\n"
435                               "  ,help       This help\\n"
436                               "  ,quit       Terminate process\""
437                               "}");
438   else if (!strncmp (request, "quit", 4) && (spacep (request+4) || !request[4]))
439     exit (0);
440   else
441     log_info ("invalid meta command\n");
442
443   return result;
444 }
445
446
447 /* If STRING has a help response, return the MSG property in a human
448  * readable format.  */
449 static char *
450 get_help_msg (const char *string)
451 {
452   cjson_t json, j_type, j_msg;
453   const char *msg;
454   char *buffer = NULL;
455   char *p;
456
457   json = cJSON_Parse (string, NULL);
458   if (json)
459     {
460       j_type = cJSON_GetObjectItem (json, "type");
461       if (j_type && cjson_is_string (j_type)
462           && !strcmp (j_type->valuestring, "help"))
463         {
464           j_msg = cJSON_GetObjectItem (json, "msg");
465           if (j_msg || cjson_is_string (j_msg))
466             {
467               msg = j_msg->valuestring;
468               buffer = malloc (strlen (msg)+1);
469               if (buffer)
470                 {
471                   for (p=buffer; *msg; msg++)
472                     {
473                       if (*msg == '\\' && msg[1] == '\n')
474                         *p++ = '\n';
475                       else
476                         *p++ = *msg;
477                     }
478                   *p = 0;
479                 }
480             }
481         }
482       cJSON_Delete (json);
483     }
484   return buffer;
485 }
486
487
488 /* An interactive standard REPL.  */
489 static void
490 interactive_repl (void)
491 {
492   char *line = NULL;
493   char *request = NULL;
494   char *response = NULL;
495   char *p;
496   int first;
497
498   es_setvbuf (es_stdin, NULL, _IONBF, 0);
499   es_fprintf (es_stderr, "%s %s ready (enter \",help\" for help)\n",
500               strusage (11), strusage (13));
501   do
502     {
503       es_fputs ("> ", es_stderr);
504       es_fflush (es_stderr);
505       es_fflush (es_stdout);
506       xfree (line);
507       line = get_line ();
508       es_fflush (es_stderr);
509       es_fflush (es_stdout);
510
511       first = !request;
512       if (line && *line)
513         {
514           if (!request)
515             request = xstrdup (line);
516           else
517             request = xstrconcat (request, "\n", line, NULL);
518         }
519
520       if (!line)
521         es_fputs ("\n", es_stderr);
522
523       if (!line || !*line || (first && *request == ','))
524         {
525           /* Process the input.  */
526           xfree (response);
527           response = NULL;
528           if (request && *request == ',')
529             {
530               response = process_meta_commands (request+1);
531             }
532           else if (request)
533             {
534               response = process_request (request);
535             }
536           xfree (request);
537           request = NULL;
538
539           if (response)
540             {
541               if (opt_interactive)
542                 {
543                   char *msg = get_help_msg (response);
544                   if (msg)
545                     {
546                       xfree (response);
547                       response = msg;
548                     }
549                 }
550
551               es_fputs ("===> ", es_stderr);
552               es_fflush (es_stderr);
553               for (p=response; *p; p++)
554                 {
555                   if (*p == '\n')
556                     {
557                       es_fflush (es_stdout);
558                       es_fputs ("\n===> ", es_stderr);
559                       es_fflush (es_stderr);
560                     }
561                   else
562                     es_putc (*p, es_stdout);
563                 }
564               es_fflush (es_stdout);
565               es_fputs ("\n", es_stderr);
566             }
567         }
568     }
569   while (line);
570
571   xfree (request);
572   xfree (response);
573   xfree (line);
574 }
575
576
577 /* Read and process  asingle request.  */
578 static void
579 read_and_process_single_request (void)
580 {
581   char *line = NULL;
582   char *request = NULL;
583   char *response = NULL;
584   size_t n;
585
586   for (;;)
587     {
588       xfree (line);
589       line = get_line ();
590       if (line && *line)
591         request = (request? xstrconcat (request, "\n", line, NULL)
592                    /**/   : xstrdup (line));
593       if (!line)
594         {
595           if (request)
596             {
597               xfree (response);
598               response = process_request (request);
599               if (response)
600                 {
601                   es_fputs (response, es_stdout);
602                   if ((n = strlen (response)) && response[n-1] != '\n')
603                     es_fputc ('\n', es_stdout);
604                 }
605               es_fflush (es_stdout);
606             }
607           break;
608         }
609     }
610
611   xfree (response);
612   xfree (request);
613   xfree (line);
614 }
615
616
617 /* The Native Messaging processing loop.  */
618 static void
619 native_messaging_repl (void)
620 {
621   gpg_error_t err;
622   uint32_t nrequest, nresponse;
623   char *request = NULL;
624   char *response = NULL;
625   size_t n;
626
627   /* Due to the length octets we need to switch the I/O stream into
628    * binary mode.  */
629   es_set_binary (es_stdin);
630   es_set_binary (es_stdout);
631
632   for (;;)
633     {
634       /* Read length.  Note that the protocol uses native endianess.
635        * Is it allowed to call such a thing a well thought out
636        * protocol?  */
637       if (es_read (es_stdin, &nrequest, sizeof nrequest, &n))
638         {
639           err = gpg_error_from_syserror ();
640           log_error ("error reading request header: %s\n", gpg_strerror (err));
641           break;
642         }
643       if (!n)
644         break;  /* EOF */
645       if (n != sizeof nrequest)
646         {
647           log_error ("error reading request header: short read\n");
648           break;
649         }
650       if (nrequest > MAX_REQUEST_SIZE)
651         {
652           log_error ("error reading request: request too long (%zu MiB)\n",
653                      (size_t)nrequest / (1024*1024));
654           /* Fixme: Shall we read the request t the bit bucket and
655            * return an error reponse or just return an error reponse
656            * and terminate?  Needs some testing.  */
657           break;
658         }
659
660       /* Read request.  */
661       request = xtrymalloc (nrequest);
662       if (!request)
663         {
664           err = gpg_error_from_syserror ();
665           log_error ("error reading request: Not enough memory for %zu MiB)\n",
666                      (size_t)nrequest / (1024*1024));
667           /* FIXME: See comment above.  */
668           break;
669         }
670       if (es_read (es_stdin, request, nrequest, &n))
671         {
672           err = gpg_error_from_syserror ();
673           log_error ("error reading request: %s\n", gpg_strerror (err));
674           break;
675         }
676       if (n != nrequest)
677         {
678           /* That is a protocol violation.  */
679           xfree (response);
680           response = error_object_string ("Invalid request:"
681                                           " short read (%zu of %zu bytes)\n",
682                                           n, (size_t)nrequest);
683         }
684       else /* Process request  */
685         {
686           xfree (response);
687           response = process_request (request);
688         }
689       nresponse = strlen (response);
690
691       /* Write response */
692       if (es_write (es_stdout, &nresponse, sizeof nresponse, &n))
693         {
694           err = gpg_error_from_syserror ();
695           log_error ("error writing request header: %s\n", gpg_strerror (err));
696           break;
697         }
698       if (n != sizeof nrequest)
699         {
700           log_error ("error writing request header: short write\n");
701           break;
702         }
703       if (es_write (es_stdout, response, nresponse, &n))
704         {
705           err = gpg_error_from_syserror ();
706           log_error ("error writing request: %s\n", gpg_strerror (err));
707           break;
708         }
709       if (n != nresponse)
710         {
711           log_error ("error writing request: short write\n");
712           break;
713         }
714       if (es_fflush (es_stdout) || es_ferror (es_stdout))
715         {
716           err = gpg_error_from_syserror ();
717           log_error ("error writing request: %s\n", gpg_strerror (err));
718           break;
719         }
720     }
721
722   xfree (response);
723   xfree (request);
724 }
725
726
727 \f
728 static const char *
729 my_strusage( int level )
730 {
731   const char *p;
732
733   switch (level)
734     {
735     case 11: p = "gpgme-json"; break;
736     case 13: p = PACKAGE_VERSION; break;
737     case 14: p = "Copyright (C) 2018 g10 Code GmbH"; break;
738     case 19: p = "Please report bugs to <" PACKAGE_BUGREPORT ">.\n"; break;
739     case 1:
740     case 40:
741       p = "Usage: gpgme-json [OPTIONS]";
742       break;
743     case 41:
744       p = "Native messaging based GPGME operations.\n";
745       break;
746     case 42:
747       p = "1"; /* Flag print 40 as part of 41. */
748       break;
749     default: p = NULL; break;
750     }
751   return p;
752 }
753
754
755 int
756 main (int argc, char *argv[])
757 {
758   enum { CMD_DEFAULT     = 0,
759          CMD_INTERACTIVE = 'i',
760          CMD_SINGLE      = 's',
761          CMD_LIBVERSION  = 501
762   } cmd = CMD_DEFAULT;
763   static ARGPARSE_OPTS opts[] = {
764     ARGPARSE_c  (CMD_INTERACTIVE, "interactive", "Interactive REPL"),
765     ARGPARSE_c  (CMD_SINGLE,      "single",      "Single request mode"),
766     ARGPARSE_c  (CMD_LIBVERSION,  "lib-version", "Show library version"),
767     ARGPARSE_end()
768   };
769   ARGPARSE_ARGS pargs = { &argc, &argv, 0 };
770
771   set_strusage (my_strusage);
772
773 #ifdef HAVE_SETLOCALE
774   setlocale (LC_ALL, "");
775 #endif
776   gpgme_check_version (NULL);
777 #ifdef LC_CTYPE
778   gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
779 #endif
780 #ifdef LC_MESSAGES
781   gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL));
782 #endif
783
784   while (arg_parse  (&pargs, opts))
785     {
786       switch (pargs.r_opt)
787         {
788         case CMD_INTERACTIVE:
789           opt_interactive = 1;
790           /* Fall trough.  */
791         case CMD_SINGLE:
792         case CMD_LIBVERSION:
793           cmd = pargs.r_opt;
794           break;
795
796         default:
797           pargs.err = ARGPARSE_PRINT_WARNING;
798           break;
799         }
800     }
801
802   switch (cmd)
803     {
804     case CMD_DEFAULT:
805       native_messaging_repl ();
806       break;
807
808     case CMD_SINGLE:
809       read_and_process_single_request ();
810       break;
811
812     case CMD_INTERACTIVE:
813       interactive_repl ();
814       break;
815
816     case CMD_LIBVERSION:
817       printf ("Version from header: %s (0x%06x)\n",
818               GPGME_VERSION, GPGME_VERSION_NUMBER);
819       printf ("Version from binary: %s\n", gpgme_check_version (NULL));
820       printf ("Copyright blurb ...:%s\n", gpgme_check_version ("\x01\x01"));
821       break;
822     }
823
824   return 0;
825 }