1 /* t-json.c - Regression test.
2 * Copyright (C) 2018 Bundesamt für Sicherheit in der Informationstechnik
3 * Software engineering by Intevation GmbH
4 *
5 * This file is part of GPGME.
6 *
7 * GPGME is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * GPGME is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this program; if not, see <https://gnu.org/licenses/>.
19 * SPDX-License-Identifier: LGPL-2.1-or-later
20 */
21
22 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <sys/stat.h>
31
32 #include <gpgme.h>
33 #include <gpg-error.h>
34
35 #include "../gpg/t-support.h"
36 #include "../../src/cJSON.h"
37
38 /* Register tests here */
39 static const char*tests[] = { "t-config", "t-version",
40 "t-keylist", "t-keylist-secret", "t-decrypt", "t-config-opt",
41 "t-encrypt", "t-encrypt-sign", "t-sign", "t-verify",
42 "t-decrypt-verify", "t-export", "t-createkey",
43 "t-export-secret-info", "t-chunking", "t-sig-notations",
44 /* For these two the order is important
45 * as t-import imports the deleted key from t-delete */
46 "t-delete", "t-import",
47 NULL };
48
49 static int verbose = 0;
50
51
52 /* Read the next number in the version string STR and return it in
53 *NUMBER. Return a pointer to the tail of STR after parsing, or
54 *NULL if the version string was invalid. */
55 static const char *
parse_version_number(const char * str,int * number)56 parse_version_number (const char *str, int *number)
57 {
58 #define MAXVAL ((INT_MAX - 10) / 10)
59 int val = 0;
60
61 /* Leading zeros are not allowed. */
62 if (*str == '0' && isdigit(str[1]))
63 return NULL;
64
65 while (isdigit (*str) && val <= MAXVAL)
66 {
67 val *= 10;
68 val += *(str++) - '0';
69 }
70 *number = val;
71 return val > MAXVAL ? NULL : str;
72 }
73
74
75 /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for
76 example, 9.3.2) and return the components in MAJOR, MINOR and MICRO
77 as integers. The function returns the tail of the string that
78 follows the version number. This might be the empty string if there
79 is nothing following the version number, or a patchlevel. The
80 function returns NULL if the version string is not valid. */
81 static const char *
parse_version_string(const char * str,int * major,int * minor,int * micro)82 parse_version_string (const char *str, int *major, int *minor, int *micro)
83 {
84 str = parse_version_number (str, major);
85 if (!str || *str != '.')
86 return NULL;
87 str++;
88
89 str = parse_version_number (str, minor);
90 if (!str || *str != '.')
91 return NULL;
92 str++;
93
94 str = parse_version_number (str, micro);
95 if (!str)
96 return NULL;
97
98 /* A patchlevel might follow. */
99 return str;
100 }
101
102
103 /* Return true if MY_VERSION is at least REQ_VERSION, and false
104 otherwise. */
105 static int
compare_versions(const char * my_version,const char * rq_version)106 compare_versions (const char *my_version,
107 const char *rq_version)
108 {
109 int my_major, my_minor, my_micro;
110 int rq_major, rq_minor, rq_micro;
111 const char *my_plvl, *rq_plvl;
112
113 if (!rq_version)
114 return 1;
115 if (!my_version)
116 return 0;
117
118 my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro);
119 if (!my_plvl)
120 return 0;
121
122 rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro);
123 if (!rq_plvl)
124 return 0;
125
126 if (my_major > rq_major
127 || (my_major == rq_major && my_minor > rq_minor)
128 || (my_major == rq_major && my_minor == rq_minor
129 && my_micro > rq_micro)
130 || (my_major == rq_major && my_minor == rq_minor
131 && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0))
132 return 1;
133
134 return 0;
135 }
136
137 /* Return true if we have the required gpg version.
138
139 This should probably go into gpgrt or gpgme proper.
140 */
141 static int
check_gpg_version(const char * req_version)142 check_gpg_version (const char *req_version)
143 {
144 gpgme_engine_info_t engine_info;
145 init_gpgme (GPGME_PROTOCOL_OpenPGP);
146
147 fail_if_err (gpgme_get_engine_info (&engine_info));
148 for (; engine_info; engine_info = engine_info->next)
149 if (engine_info->protocol == GPGME_PROTOCOL_OpenPGP)
150 break;
151
152 test (engine_info);
153
154 return compare_versions (engine_info->version, req_version);
155 }
156
157 static char *
get_file(const char * fname)158 get_file (const char *fname)
159 {
160 gpg_error_t err;
161 gpgrt_stream_t fp;
162 struct stat st;
163 char *buf;
164 size_t buflen;
165
166 fp = gpgrt_fopen (fname, "r");
167 if (!fp)
168 {
169 err = gpg_error_from_syserror ();
170 fprintf (stderr, "Error: can't open '%s': %s\n", fname,
171 gpg_strerror (err));
172 return NULL;
173 }
174
175 if (fstat (gpgrt_fileno(fp), &st))
176 {
177 err = gpg_error_from_syserror ();
178 fprintf (stderr, "Error: can't stat '%s': %s\n", fname,
179 gpg_strerror (err));
180 gpgrt_fclose (fp);
181 return NULL;
182 }
183
184 buflen = st.st_size;
185 buf = malloc (buflen+1);
186 if (!buf)
187 {
188 fprintf (stderr, "Error: no mem\n");
189 gpgrt_fclose (fp);
190 return NULL;
191 }
192
193 if (gpgrt_fread (buf, buflen, 1, fp) != 1)
194 {
195 err = gpg_error_from_syserror ();
196 fprintf (stderr, "error reading '%s': %s\n", fname, gpg_strerror (err));
197 gpgrt_fclose (fp);
198 free (buf);
199 return NULL;
200 }
201 buf[buflen] = 0;
202 gpgrt_fclose (fp);
203
204 return buf;
205 }
206
207 /* Check that the element needle exists in hay. Returns 0 if
208 the needle was found. */
209 int
test_contains(cjson_t needle,cjson_t hay)210 test_contains (cjson_t needle, cjson_t hay)
211 {
212 cjson_t it;
213
214 if (verbose == 2)
215 fprintf (stderr, "%s: -------checking-------- "
216 "\n%s\n -------against-------- \n%s\n",
217 nonnull (needle->string), cJSON_Print (needle),
218 cJSON_Print (hay));
219
220 /* Type check. This automatically checks bool vals and NULL */
221 if (needle->type != hay->type)
222 {
223 if (verbose)
224 fprintf (stderr, "%s: type mismatch expected %i got %i\n",
225 nonnull (needle->string), needle->type,
226 hay->type);
227 return 1;
228 }
229
230 /* First the simple types */
231 if (cjson_is_number (needle))
232 {
233 if (needle->valueint != hay->valueint)
234 {
235 if (verbose)
236 fprintf (stderr, "%s: value mismatch. Expected %i got %i\n",
237 nonnull (needle->string), needle->valueint,
238 hay->valueint);
239 return 1;
240 }
241 }
242 if (cjson_is_string (needle))
243 {
244 if (strcmp (needle->valuestring, hay->valuestring) &&
245 /* Use * as a general don't care placeholder */
246 strcmp (needle->valuestring, "*"))
247 {
248 if (verbose)
249 fprintf (stderr, "%s: string mismatch Expected '%s' got '%s'\n",
250 needle->string, needle->valuestring, hay->valuestring);
251 return 1;
252 }
253 }
254
255 /* Now the complex types */
256 if (needle->child)
257 {
258 if (!hay->child)
259 {
260 fprintf (stderr, "Depth mismatch. Expected child for %s\n",
261 nonnull (needle->string));
262 }
263 if (test_contains (needle->child, hay->child))
264 {
265 int found = 0;
266 cjson_t hit;
267
268 for (hit = hay->child; hit; hit = hit->next)
269 {
270 found |= !test_contains (needle->child, hit);
271 if (found)
272 {
273 break;
274 }
275 }
276 if (!found)
277 {
278 return 1;
279 }
280 }
281 }
282
283 if (needle->prev)
284 {
285 return 0;
286 }
287
288 /* Walk elements of an array */
289 for (it = needle->next; it; it = it->next)
290 {
291 int found = 0;
292 cjson_t hit;
293
294 if (!it->string && it->child)
295 {
296 /* Try out all other anonymous children on the same level */
297 hit = hay;
298
299 /* Return to the beginning */
300 while (hit->prev)
301 {
302 hit = hit->prev;
303 }
304 for (; hit && hit->child; hit = hit->next)
305 {
306 found |= !test_contains (it->child, hit->child);
307 if (found)
308 {
309 break;
310 }
311 }
312 if (!found)
313 {
314 return 1;
315 }
316 continue;
317 }
318
319 /* Try the children in the haystack */
320 for (hit = hay; hit; hit = hit->next)
321 {
322 if (hit->string && it->string &&
323 !strcmp (hit->string, it->string))
324 {
325 found = 1;
326 if (test_contains (it, hit))
327 {
328 return 1;
329 }
330 }
331 }
332 if (!found)
333 {
334 if (verbose)
335 fprintf (stderr, "Failed to find '%s' in list\n",
336 nonnull (it->string));
337 return 1;
338 }
339 }
340 return 0;
341 }
342
343
344 int
check_response(const char * response,const char * expected)345 check_response (const char *response, const char *expected)
346 {
347 cjson_t hay;
348 cjson_t needle;
349 int rc;
350 size_t erroff;
351
352 hay = cJSON_Parse (response, &erroff);
353
354 if (!hay)
355 {
356 fprintf (stderr, "Failed to parse json at %i:\n%s\n", (int) erroff,
357 response);
358 return 1;
359 }
360 needle = cJSON_Parse (expected, &erroff);
361 if (!needle)
362 {
363 fprintf (stderr, "Failed to parse json at %i:\n%s\n", (int) erroff,
364 expected);
365 cJSON_Delete (hay);
366 return 1;
367 }
368
369 rc = test_contains (needle, hay);
370
371 cJSON_Delete (needle);
372 cJSON_Delete (hay);
373 return rc;
374 }
375
376
377 int
run_test(const char * test,const char * gpgme_json)378 run_test (const char *test, const char *gpgme_json)
379 {
380 gpgme_ctx_t ctx;
381 gpgme_data_t json_stdin = NULL;
382 gpgme_data_t json_stdout = NULL;
383 gpgme_data_t json_stderr = NULL;
384 char *test_in;
385 char *test_out;
386 const char *argv[3];
387 char *response;
388 char *expected = NULL;
389 size_t response_size;
390 int rc = 0;
391 const char *top_srcdir = getenv ("top_srcdir");
392
393 if (!top_srcdir)
394 {
395 fprintf (stderr, "Error top_srcdir environment variable not set\n");
396 exit(1);
397 }
398
399 gpgrt_asprintf (&test_in, "%s/tests/json/%s.in.json",
400 top_srcdir, test);
401 gpgrt_asprintf (&test_out, "%s/tests/json/%s.out.json",
402 top_srcdir, test);
403
404 printf ("Running %s...\n", test);
405
406 fail_if_err (gpgme_new (&ctx));
407
408 gpgme_set_protocol (ctx, GPGME_PROTOCOL_SPAWN);
409
410 fail_if_err (gpgme_data_new_from_file (&json_stdin, test_in, 1));
411 fail_if_err (gpgme_data_new (&json_stdout));
412 fail_if_err (gpgme_data_new (&json_stderr));
413
414 argv[0] = gpgme_json;
415 argv[1] = "-s";
416 argv[2] = NULL;
417
418 fail_if_err (gpgme_op_spawn (ctx, gpgme_json, argv,
419 json_stdin,
420 json_stdout,
421 json_stderr,
422 0));
423 response = gpgme_data_release_and_get_mem (json_stdout,
424 &response_size);
425 if (response_size)
426 {
427 expected = get_file (test_out);
428
429 test (expected);
430
431 rc = check_response (response, expected);
432 }
433 else
434 {
435 rc = 1;
436 }
437
438 if (!rc)
439 {
440 printf (" success\n");
441 gpgme_data_release (json_stderr);
442 }
443 else
444 {
445 char *buf;
446 size_t size;
447
448 buf = gpgme_data_release_and_get_mem (json_stderr, &size);
449 printf (" failed%s\n", response_size ? "" :
450 ", no response from gpgme-json");
451 if (size)
452 {
453 printf ("gpgme-json stderr:\n%.*s\n", (int)size, buf);
454 }
455 free (buf);
456 }
457
458 free (test_out);
459 free (test_in);
460 free (response);
461 free (expected);
462 gpgme_data_release (json_stdin);
463 gpgme_release (ctx);
464
465 return rc;
466 }
467
468 int
main(int argc,char * argv[])469 main (int argc, char *argv[])
470 {
471 const char *gpgme_json = getenv ("gpgme_json");
472 int last_argc = -1;
473 const char **test;
474
475 if (argc)
476 { argc--; argv++; }
477
478
479 while (argc && last_argc != argc )
480 {
481 last_argc = argc;
482 if (!strcmp (*argv, "--verbose"))
483 {
484 verbose++;
485 argc--; argv++;
486 }
487 }
488
489 if (!check_gpg_version ("2.1.18"))
490 {
491 /* Lets not break too much or have to test all combinations */
492 printf ("Testsuite skipped. Minimum GnuPG version (2.1.18) "
493 "not found.\n");
494 exit(0);
495 }
496
497 init_gpgme (GPGME_PROTOCOL_SPAWN);
498
499 for (test = tests; *test; test++)
500 {
501 if (run_test (*test, gpgme_json))
502 {
503 exit(1);
504 }
505 }
506 return 0;
507 }
508