aboutsummaryrefslogtreecommitdiff
path: root/src/reducer/anastasis_api_redux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/reducer/anastasis_api_redux.c')
-rw-r--r--src/reducer/anastasis_api_redux.c1730
1 files changed, 1730 insertions, 0 deletions
diff --git a/src/reducer/anastasis_api_redux.c b/src/reducer/anastasis_api_redux.c
new file mode 100644
index 0000000..eb7e362
--- /dev/null
+++ b/src/reducer/anastasis_api_redux.c
@@ -0,0 +1,1730 @@
1/*
2 This file is part of Anastasis
3 Copyright (C) 2020, 2021 Taler Systems SA
4
5 Anastasis is free software; you can redistribute it and/or modify it under the
6 terms of the GNU Lesser General Public License as published by the Free Software
7 Foundation; either version 3, or (at your option) any later version.
8
9 Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along with
14 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
15*/
16/**
17 * @file lib/anastasis_api_redux.c
18 * @brief anastasis reducer api
19 * @author Christian Grothoff
20 * @author Dominik Meister
21 * @author Dennis Neufeld
22 */
23#include <platform.h>
24#include <jansson.h>
25#include "anastasis_redux.h"
26#include "anastasis_error_codes.h"
27#include <taler/taler_json_lib.h>
28#include "anastasis_api_redux.h"
29#include <dlfcn.h>
30
31
32/**
33 * How long do we wait at most for a /config reply from an Anastasis provider.
34 * 60s is very generous, given the tiny bandwidth required, even for the most
35 * remote locations.
36 */
37#define CONFIG_GENERIC_TIMEOUT GNUNET_TIME_UNIT_MINUTES
38
39
40#define GENERATE_STRING(STRING) #STRING,
41static const char *generic_strings[] = {
42 ANASTASIS_GENERIC_STATES (GENERATE_STRING)
43};
44#undef GENERATE_STRING
45
46
47/**
48 * #ANASTASIS_REDUX_add_provider_to_state_ waiting for the
49 * configuration request to complete or fail.
50 */
51struct ConfigReduxWaiting
52{
53 /**
54 * Kept in a DLL.
55 */
56 struct ConfigReduxWaiting *prev;
57
58 /**
59 * Kept in a DLL.
60 */
61 struct ConfigReduxWaiting *next;
62
63 /**
64 * Associated redux action.
65 */
66 struct ANASTASIS_ReduxAction ra;
67
68 /**
69 * Config request we are waiting for.
70 */
71 struct ConfigRequest *cr;
72
73 /**
74 * State we are processing.
75 */
76 json_t *state;
77
78 /**
79 * Function to call with updated @e state.
80 */
81 ANASTASIS_ActionCallback cb;
82
83 /**
84 * Closure for @e cb.
85 */
86 void *cb_cls;
87
88};
89
90
91/**
92 * Anastasis authorization method configuration
93 */
94struct AuthorizationMethodConfig
95{
96 /**
97 * Type of the method, i.e. "question".
98 */
99 char *type;
100
101 /**
102 * Fee charged for accessing key share using this method.
103 */
104 struct TALER_Amount usage_fee;
105};
106
107
108/**
109 * State for a "get config" operation.
110 */
111struct ConfigRequest
112{
113
114 /**
115 * Kept in a DLL, given that we may have multiple backends.
116 */
117 struct ConfigRequest *next;
118
119 /**
120 * Kept in a DLL, given that we may have multiple backends.
121 */
122 struct ConfigRequest *prev;
123
124 /**
125 * Head of DLL of REDUX operations waiting for an answer.
126 */
127 struct ConfigReduxWaiting *w_head;
128
129 /**
130 * Tail of DLL of REDUX operations waiting for an answer.
131 */
132 struct ConfigReduxWaiting *w_tail;
133
134 /**
135 * Obtained status code.
136 */
137 unsigned int http_status;
138
139 /**
140 * The /config GET operation handle.
141 */
142 struct ANASTASIS_ConfigOperation *co;
143
144 /**
145 * URL of the anastasis backend.
146 */
147 char *url;
148
149 /**
150 * Business name of the anastasis backend.
151 */
152 char *business_name;
153
154 /**
155 * currency used by the anastasis backend.
156 */
157 char *currency;
158
159 /**
160 * Array of authorization methods supported by the server.
161 */
162 struct AuthorizationMethodConfig *methods;
163
164 /**
165 * Length of the @e methods array.
166 */
167 unsigned int methods_length;
168
169 /**
170 * Maximum size of an upload in megabytes.
171 */
172 uint32_t storage_limit_in_megabytes;
173
174 /**
175 * Annual fee for an account / policy upload.
176 */
177 struct TALER_Amount annual_fee;
178
179 /**
180 * Fee for a truth upload.
181 */
182 struct TALER_Amount truth_upload_fee;
183
184 /**
185 * Maximum legal liability for data loss covered by the
186 * provider.
187 */
188 struct TALER_Amount liability_limit;
189
190 /**
191 * Server salt.
192 */
193 struct ANASTASIS_CRYPTO_ProviderSaltP salt;
194
195 /**
196 * Task to timeout /config requests.
197 */
198 struct GNUNET_SCHEDULER_Task *tt;
199
200 /**
201 * Status of the /config request.
202 */
203 enum TALER_ErrorCode ec;
204};
205
206
207/**
208 * Reducer API's CURL context handle.
209 */
210struct GNUNET_CURL_Context *ANASTASIS_REDUX_ctx_;
211
212/**
213 * JSON containing country specific identity attributes to ask the user for.
214 */
215static json_t *redux_id_attr;
216
217/**
218 * Head of DLL of Anastasis backend configuration requests.
219 */
220static struct ConfigRequest *cr_head;
221
222/**
223 * Tail of DLL of Anastasis backend configuration requests.
224 */
225static struct ConfigRequest *cr_tail;
226
227/**
228 * JSON containing country specific information.
229 */
230static json_t *redux_countries;
231
232/**
233 * List of Anastasis providers.
234 */
235static json_t *provider_list;
236
237
238/**
239 * Extract the mode of a state from json
240 *
241 * @param state the state to operate on
242 * @return "backup_state" or "recovery_state"
243 */
244static const char *
245get_state_mode (const json_t *state)
246{
247 if (json_object_get (state, "backup_state"))
248 return "backup_state";
249 if (json_object_get (state, "recovery_state"))
250 return "recovery_state";
251 GNUNET_assert (0);
252 return NULL;
253}
254
255
256enum ANASTASIS_GenericState
257ANASTASIS_generic_state_from_string_ (const char *state_string)
258{
259 for (enum ANASTASIS_GenericState i = 0;
260 i < sizeof (generic_strings) / sizeof(*generic_strings);
261 i++)
262 if (0 == strcmp (state_string,
263 generic_strings[i]))
264 return i;
265 return ANASTASIS_GENERIC_STATE_ERROR;
266}
267
268
269const char *
270ANASTASIS_generic_state_to_string_ (enum ANASTASIS_GenericState gs)
271{
272 if ( (gs < 0) ||
273 (gs >= sizeof (generic_strings) / sizeof(*generic_strings)) )
274 {
275 GNUNET_break_op (0);
276 return NULL;
277 }
278 return generic_strings[gs];
279}
280
281
282void
283ANASTASIS_redux_fail_ (ANASTASIS_ActionCallback cb,
284 void *cb_cls,
285 enum TALER_ErrorCode ec,
286 const char *detail)
287{
288 json_t *estate;
289
290 estate = json_pack ("{s:s?, s:I, s:s}",
291 "detail", detail,
292 "code", (json_int_t) ec,
293 "hint", TALER_ErrorCode_get_hint (ec));
294 cb (cb_cls,
295 ec,
296 estate);
297 json_decref (estate);
298}
299
300
301/**
302 * Transition the @a state to @a gs.
303 *
304 * @param[in,out] state to transition
305 * @param gs state to transition to
306 */
307static void
308redux_transition (json_t *state,
309 enum ANASTASIS_GenericState gs)
310{
311 const char *s_mode = get_state_mode (state);
312
313 GNUNET_assert (0 ==
314 json_object_set_new (
315 state,
316 s_mode,
317 json_string (
318 ANASTASIS_generic_state_to_string_ (gs))));
319
320}
321
322
323void
324ANASTASIS_redux_init (struct GNUNET_CURL_Context *ctx)
325{
326 ANASTASIS_REDUX_ctx_ = ctx;
327}
328
329
330/**
331 * Function to free a #ConfigRequest, an async operation.
332 *
333 * @param cr state for a "get config" operation
334 */
335static void
336free_config_request (struct ConfigRequest *cr)
337{
338 GNUNET_assert (NULL == cr->w_head);
339 if (NULL != cr->co)
340 ANASTASIS_config_cancel (cr->co);
341 if (NULL != cr->tt)
342 GNUNET_SCHEDULER_cancel (cr->tt);
343 GNUNET_free (cr->currency);
344 GNUNET_free (cr->url);
345 GNUNET_free (cr->business_name);
346 for (unsigned int i = 0; i<cr->methods_length; i++)
347 GNUNET_free (cr->methods[i].type);
348 GNUNET_free (cr->methods);
349 GNUNET_free (cr);
350}
351
352
353void
354ANASTASIS_redux_done ()
355{
356 struct ConfigRequest *cr;
357
358 while (NULL != (cr = cr_head))
359 {
360 GNUNET_CONTAINER_DLL_remove (cr_head,
361 cr_tail,
362 cr);
363 free_config_request (cr);
364 }
365 ANASTASIS_REDUX_ctx_ = NULL;
366 if (NULL != redux_countries)
367 {
368 json_decref (redux_countries);
369 redux_countries = NULL;
370 }
371 if (NULL != redux_id_attr)
372 {
373 json_decref (redux_id_attr);
374 redux_id_attr = NULL;
375 }
376 if (NULL != provider_list)
377 {
378 json_decref (provider_list);
379 provider_list = NULL;
380 }
381}
382
383
384const json_t *
385ANASTASIS_redux_countries_init_ (void)
386{
387 char *dn;
388 json_error_t error;
389
390 if (NULL != redux_countries)
391 return redux_countries;
392
393 {
394 char *path;
395
396 path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
397 if (NULL == path)
398 {
399 GNUNET_break (0);
400 return NULL;
401 }
402 GNUNET_asprintf (&dn,
403 "%s/redux.countries.json",
404 path);
405 GNUNET_free (path);
406 }
407 redux_countries = json_load_file (dn,
408 JSON_COMPACT,
409 &error);
410 if (NULL == redux_countries)
411 {
412 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
413 "Failed to parse `%s': %s at %d:%d (%d)\n",
414 dn,
415 error.text,
416 error.line,
417 error.column,
418 error.position);
419 GNUNET_free (dn);
420 return NULL;
421 }
422 GNUNET_free (dn);
423 return redux_countries;
424}
425
426
427/**
428 * Abort waiting for /config reply.
429 *
430 * @param cls a `struct ConfigReduxWaiting` handle.
431 */
432static void
433abort_provider_config_cb (void *cls)
434{
435 struct ConfigReduxWaiting *w = cls;
436 struct ConfigRequest *cr = w->cr;
437
438 GNUNET_CONTAINER_DLL_remove (cr->w_head,
439 cr->w_tail,
440 w);
441 json_decref (w->state);
442 GNUNET_free (w);
443}
444
445
446/**
447 * Notify anyone waiting on @a cr that the request is done
448 * (successful or failed).
449 *
450 * @param[in,out] cr request that completed
451 */
452static void
453notify_waiting (struct ConfigRequest *cr)
454{
455 struct ConfigReduxWaiting *w;
456
457 while (NULL != (w = cr->w_head))
458 {
459 json_t *provider_list;
460 json_t *prov;
461
462 if (NULL == (provider_list = json_object_get (w->state,
463 "authentication_providers")))
464 {
465 GNUNET_assert (0 ==
466 json_object_set_new (w->state,
467 "authentication_providers",
468 provider_list = json_object ()));
469 }
470 provider_list = json_object_get (w->state,
471 "authentication_providers");
472 GNUNET_assert (NULL != provider_list);
473
474 if (TALER_EC_NONE != cr->ec)
475 {
476 prov = json_pack ("{s:I, s:I}",
477 "error_code",
478 (json_int_t) cr->ec,
479 "http_status",
480 (json_int_t) cr->http_status);
481 }
482 else
483 {
484 json_t *methods_list;
485
486 methods_list = json_array ();
487 GNUNET_assert (NULL != methods_list);
488 for (unsigned int i = 0; i<cr->methods_length; i++)
489 {
490 struct AuthorizationMethodConfig *method = &cr->methods[i];
491 json_t *mj = json_pack ("{s:s, s:o}",
492 "type",
493 method->type,
494 "usage_fee",
495 TALER_JSON_from_amount (&method->usage_fee));
496
497 GNUNET_assert (NULL != mj);
498 GNUNET_assert (0 ==
499 json_array_append_new (methods_list,
500 mj));
501 }
502 prov = json_pack ("{s:o, s:o, s:o, s:o, s:s,"
503 " s:s, s:I, s:o, s:I}",
504 "methods",
505 methods_list,
506 "annual_fee",
507 TALER_JSON_from_amount (&cr->annual_fee),
508 "truth_upload_fee",
509 TALER_JSON_from_amount (&cr->truth_upload_fee),
510 "liability_limit",
511 TALER_JSON_from_amount (&cr->liability_limit),
512 "currency",
513 cr->currency,
514 /* 6 */
515 "business_name",
516 cr->business_name,
517 "storage_limit_in_megabytes",
518 (json_int_t) cr->storage_limit_in_megabytes,
519 "salt",
520 GNUNET_JSON_from_data_auto (&cr->salt),
521 "http_status",
522 (json_int_t) cr->http_status);
523 }
524 GNUNET_assert (0 ==
525 json_object_set_new (provider_list,
526 cr->url,
527 prov));
528 w->cb (w->cb_cls,
529 cr->ec,
530 w->state);
531 abort_provider_config_cb (w);
532 }
533
534}
535
536
537/**
538 * Function called with the results of a #ANASTASIS_get_config().
539 *
540 * @param cls closure
541 * @param http_status HTTP status of the request
542 * @param acfg anastasis configuration
543 */
544static void
545config_cb (void *cls,
546 unsigned int http_status,
547 const struct ANASTASIS_Config *acfg)
548{
549 struct ConfigRequest *cr = cls;
550
551 cr->co = NULL;
552 GNUNET_SCHEDULER_cancel (cr->tt);
553 cr->tt = NULL;
554 cr->http_status = http_status;
555 if (MHD_HTTP_OK != http_status)
556 cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED;
557 if ( (MHD_HTTP_OK == http_status) &&
558 (NULL == acfg) )
559 {
560 cr->http_status = MHD_HTTP_NOT_FOUND;
561 cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_CONFIG_FAILED;
562 }
563 else if (NULL != acfg)
564 {
565 if (0 == acfg->storage_limit_in_megabytes)
566 {
567 cr->http_status = 0;
568 cr->ec = TALER_EC_ANASTASIS_REDUCER_PROVIDER_INVALID_CONFIG;
569 }
570 else
571 {
572 cr->currency = GNUNET_strdup (acfg->currency);
573 cr->business_name = GNUNET_strdup (acfg->business_name);
574 cr->methods = GNUNET_new_array (acfg->methods_length,
575 struct AuthorizationMethodConfig);
576 for (unsigned int i = 0; i<acfg->methods_length; i++)
577 {
578 cr->methods[i].type = GNUNET_strdup (acfg->methods[i].type);
579 cr->methods[i].usage_fee = acfg->methods[i].usage_fee;
580 }
581 cr->methods_length = acfg->methods_length;
582 cr->storage_limit_in_megabytes = acfg->storage_limit_in_megabytes;
583 cr->annual_fee = acfg->annual_fee;
584 cr->truth_upload_fee = acfg->truth_upload_fee;
585 cr->liability_limit = acfg->liability_limit;
586 cr->salt = acfg->salt;
587 }
588 }
589 notify_waiting (cr);
590}
591
592
593/**
594 * Aborts a "get config" after timeout.
595 *
596 * @param cls closure for a "get config" request
597 */
598static void
599config_request_timeout (void *cls)
600{
601 struct ConfigRequest *cr = cls;
602
603 cr->tt = NULL;
604 ANASTASIS_config_cancel (cr->co);
605 cr->co = NULL;
606 cr->http_status = 0;
607 cr->ec = TALER_EC_GENERIC_TIMEOUT;
608 notify_waiting (cr);
609}
610
611
612/**
613 * Schedule job to obtain Anastasis provider configuration at @a url.
614 *
615 * @param url base URL of Anastasis provider
616 * @return check config handle
617 */
618static struct ConfigRequest *
619check_config (const char *url)
620{
621 struct ConfigRequest *cr;
622
623 for (cr = cr_head; NULL != cr; cr = cr->next)
624 {
625 if (0 != strcmp (url,
626 cr->url))
627 continue;
628 if (NULL != cr->co)
629 return cr; /* already on it */
630 break;
631 }
632 if (NULL == cr)
633 {
634 cr = GNUNET_new (struct ConfigRequest);
635 cr->url = GNUNET_strdup (url);
636 GNUNET_CONTAINER_DLL_insert (cr_head,
637 cr_tail,
638 cr);
639 }
640 cr->co = ANASTASIS_get_config (ANASTASIS_REDUX_ctx_,
641 cr->url,
642 &config_cb,
643 cr);
644 if (NULL == cr->co)
645 {
646 GNUNET_break (0);
647 return NULL;
648 }
649 else
650 {
651 cr->tt = GNUNET_SCHEDULER_add_delayed (CONFIG_GENERIC_TIMEOUT,
652 &config_request_timeout,
653 cr);
654 }
655 return cr;
656}
657
658
659/**
660 * Begin asynchronous check for provider configurations.
661 *
662 * @param currencies the currencies to initiate the provider checks for
663 * @param[in,out] state to set provider list for
664 * @return #TALER_EC_NONE on success
665 */
666static enum TALER_ErrorCode
667begin_provider_config_check (const json_t *currencies,
668 json_t *state)
669{
670 if (NULL == provider_list)
671 {
672 json_error_t error;
673 char *dn;
674 char *path;
675
676 path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
677 if (NULL == path)
678 {
679 GNUNET_break (0);
680 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
681 }
682 GNUNET_asprintf (&dn,
683 "%s/provider-list.json",
684 path);
685 GNUNET_free (path);
686 provider_list = json_load_file (dn,
687 JSON_COMPACT,
688 &error);
689 if (NULL == provider_list)
690 {
691 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
692 "Failed to parse `%s': %s at %d:%d (%d)\n",
693 dn,
694 error.text,
695 error.line,
696 error.column,
697 error.position);
698 GNUNET_free (dn);
699 return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
700 }
701 GNUNET_free (dn);
702 }
703
704 {
705 size_t index;
706 json_t *provider;
707 const json_t *provider_arr = json_object_get (provider_list,
708 "anastasis_provider");
709 json_t *pl;
710
711 pl = json_object ();
712 json_array_foreach (provider_arr, index, provider)
713 {
714 const char *url;
715 const char *cur;
716 struct GNUNET_JSON_Specification spec[] = {
717 GNUNET_JSON_spec_string ("url",
718 &url),
719 GNUNET_JSON_spec_string ("currency",
720 &cur),
721 GNUNET_JSON_spec_end ()
722 };
723
724 if (GNUNET_OK !=
725 GNUNET_JSON_parse (provider,
726 spec,
727 NULL, NULL))
728 {
729 GNUNET_break (0);
730 json_decref (pl);
731 return TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED;
732 }
733
734 {
735 bool found = false;
736 json_t *cu;
737 size_t off;
738
739 json_array_foreach (currencies, off, cu)
740 {
741 const char *currency;
742
743 currency = json_string_value (cu);
744 if (NULL == currency)
745 {
746 json_decref (pl);
747 return TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID;
748 }
749 found = (0 == strcasecmp (currency,
750 cur));
751 }
752 if (! found)
753 continue;
754 }
755 GNUNET_assert (0 ==
756 json_object_set_new (pl,
757 url,
758 json_object ()));
759 check_config (url);
760 }
761 GNUNET_assert (0 ==
762 json_object_set_new (state,
763 "authentication_providers",
764 pl));
765 }
766 return TALER_EC_NONE;
767}
768
769
770/**
771 * Function to validate an input by regular expression ("validation-regex").
772 *
773 * @param input text to validate
774 * @param regexp regular expression to validate
775 * @return true if validation passed, else false
776 */
777static bool
778validate_regex (const char *input,
779 const char *regexp)
780{
781 regex_t regex;
782
783 if (0 != regcomp (&regex,
784 regexp,
785 REG_EXTENDED))
786 {
787 GNUNET_break (0);
788 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
789 "Failed to compile regular expression `%s'.",
790 regexp);
791 return true;
792 }
793 /* check if input has correct form */
794 if (0 != regexec (&regex,
795 input,
796 0,
797 NULL,
798 0))
799 {
800 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
801 "Input `%s' does not match regex `%s'\n",
802 input,
803 regexp);
804 regfree (&regex);
805 return false;
806 }
807 regfree (&regex);
808 return true;
809}
810
811
812/**
813 * Function to load json containing country specific identity
814 * attributes. Uses a single-slot cache to avoid loading
815 * exactly the same attributes twice.
816 *
817 * @param country_code country code (e.g. "de")
818 * @return NULL on error
819 */
820static const json_t *
821redux_id_attr_init (const char *country_code)
822{
823 static char redux_id_cc[3];
824 char *dn;
825 json_error_t error;
826
827 if (0 == strcmp (country_code,
828 redux_id_cc))
829 return redux_id_attr;
830
831 if (NULL != redux_id_attr)
832 {
833 json_decref (redux_id_attr);
834 redux_id_attr = NULL;
835 }
836 {
837 char *path;
838
839 path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
840 if (NULL == path)
841 {
842 GNUNET_break (0);
843 return NULL;
844 }
845 GNUNET_asprintf (&dn,
846 "%s/redux.%s.json",
847 path,
848 country_code);
849 GNUNET_free (path);
850 }
851 redux_id_attr = json_load_file (dn,
852 JSON_COMPACT,
853 &error);
854 if (NULL == redux_id_attr)
855 {
856 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
857 "Failed to parse `%s': %s at %d:%d (%d)\n",
858 dn,
859 error.text,
860 error.line,
861 error.column,
862 error.position);
863 GNUNET_free (dn);
864 return NULL;
865 }
866 GNUNET_free (dn);
867 strncpy (redux_id_cc,
868 country_code,
869 sizeof (redux_id_cc));
870 redux_id_cc[2] = '\0';
871 return redux_id_attr;
872}
873
874
875/**
876 * DispatchHandler/Callback function which is called for a
877 * "select_continent" action.
878 *
879 * @param state state to operate on
880 * @param arguments arguments to use for operation on state
881 * @param cb callback to call during/after operation
882 * @param cb_cls callback closure
883 * @return NULL
884 */
885static struct ANASTASIS_ReduxAction *
886select_continent (json_t *state,
887 const json_t *arguments,
888 ANASTASIS_ActionCallback cb,
889 void *cb_cls)
890{
891 const json_t *redux_countries = ANASTASIS_redux_countries_init_ ();
892 const json_t *root = json_object_get (redux_countries,
893 "countries");
894 const json_t *continent;
895 json_t *countries;
896
897 if (NULL == root)
898 {
899 ANASTASIS_redux_fail_ (cb,
900 cb_cls,
901 TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
902 "'countries' missing");
903 return NULL;
904 }
905 if (NULL == arguments)
906 {
907 ANASTASIS_redux_fail_ (cb,
908 cb_cls,
909 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
910 "arguments missing");
911 return NULL;
912 }
913 continent = json_object_get (arguments,
914 "continent");
915 if (NULL == continent)
916 {
917 ANASTASIS_redux_fail_ (cb,
918 cb_cls,
919 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
920 "'continent' missing");
921 return NULL;
922 }
923 countries = json_array ();
924 GNUNET_assert (NULL != countries);
925 {
926 size_t index;
927 const json_t *country;
928 bool found = false;
929
930 json_array_foreach (root, index, country)
931 {
932 json_t *temp_continent = json_object_get (country,
933 "continent");
934 if (1 == json_equal (continent,
935 temp_continent))
936 {
937 GNUNET_assert (0 ==
938 json_array_append (countries,
939 (json_t *) country));
940 found = true;
941 }
942 }
943 if (! found)
944 {
945 ANASTASIS_redux_fail_ (cb,
946 cb_cls,
947 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
948 "'continent' unknown");
949 return NULL;
950 }
951 }
952 redux_transition (state,
953 ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING);
954 GNUNET_assert (0 ==
955 json_object_set (state,
956 "selected_continent",
957 (json_t *) continent));
958 GNUNET_assert (0 ==
959 json_object_set_new (state,
960 "countries",
961 countries));
962 cb (cb_cls,
963 TALER_EC_NONE,
964 state);
965 return NULL;
966}
967
968
969/**
970 * DispatchHandler/Callback function which is called for a
971 * "select_country" action.
972 *
973 * @param state state to operate on
974 * @param arguments arguments to use for operation on state
975 * @param cb callback to call during/after operation
976 * @param cb_cls callback closure
977 * @return #ANASTASIS_ReduxAction
978 */
979static struct ANASTASIS_ReduxAction *
980select_country (json_t *state,
981 const json_t *arguments,
982 ANASTASIS_ActionCallback cb,
983 void *cb_cls)
984{
985 const json_t *required_attrs;
986 const json_t *country_code;
987 const json_t *currencies;
988 const json_t *redux_id_attr;
989
990 if (NULL == arguments)
991 {
992 ANASTASIS_redux_fail_ (cb,
993 cb_cls,
994 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
995 "arguments missing");
996 return NULL;
997 }
998 country_code = json_object_get (arguments,
999 "country_code");
1000 if (NULL == country_code)
1001 {
1002 ANASTASIS_redux_fail_ (cb,
1003 cb_cls,
1004 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
1005 "'country_code' missing");
1006 return NULL;
1007 }
1008
1009 {
1010 json_t *countries = json_object_get (state,
1011 "countries");
1012 size_t index;
1013 json_t *country;
1014 bool found = false;
1015
1016 json_array_foreach (countries, index, country)
1017 {
1018 json_t *cc = json_object_get (country,
1019 "code");
1020 if (1 == json_equal (country_code,
1021 cc))
1022 {
1023 found = true;
1024 break;
1025 }
1026 }
1027 if (! found)
1028 {
1029 ANASTASIS_redux_fail_ (cb,
1030 cb_cls,
1031 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
1032 "specified country not on selected continent");
1033 return NULL;
1034 }
1035 }
1036
1037 currencies = json_object_get (arguments,
1038 "currencies");
1039 if ( (NULL == currencies) ||
1040 (! json_is_array (currencies)) )
1041 {
1042 ANASTASIS_redux_fail_ (cb,
1043 cb_cls,
1044 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
1045 "'currencies' missing");
1046 return NULL;
1047 }
1048 /* We now have an idea of the currency, begin fetching
1049 provider /configs (we likely need them later) */
1050 {
1051 enum TALER_ErrorCode ec;
1052
1053 ec = begin_provider_config_check (currencies,
1054 state);
1055 if (TALER_EC_NONE != ec)
1056 {
1057 GNUNET_break (0);
1058 ANASTASIS_redux_fail_ (cb,
1059 cb_cls,
1060 ec,
1061 NULL);
1062 return NULL;
1063 }
1064 }
1065 redux_id_attr = redux_id_attr_init (json_string_value (country_code));
1066 if (NULL == redux_id_attr)
1067 {
1068 GNUNET_break (0);
1069 ANASTASIS_redux_fail_ (cb,
1070 cb_cls,
1071 TALER_EC_ANASTASIS_REDUCER_RESOURCE_MISSING,
1072 json_string_value (country_code));
1073 return NULL;
1074 }
1075 required_attrs = json_object_get (redux_id_attr,
1076 "required_attributes");
1077 if (NULL == required_attrs)
1078 {
1079 ANASTASIS_redux_fail_ (cb,
1080 cb_cls,
1081 TALER_EC_ANASTASIS_REDUCER_RESOURCE_MALFORMED,
1082 "'required_attributes' missing");
1083 return NULL;
1084 }
1085 redux_transition (state,
1086 ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING);
1087 GNUNET_assert (0 ==
1088 json_object_set (state,
1089 "selected_country",
1090 (json_t *) country_code));
1091 GNUNET_assert (0 ==
1092 json_object_set (state,
1093 "currencies",
1094 (json_t *) currencies));
1095 GNUNET_assert (0 ==
1096 json_object_set (state,
1097 "required_attributes",
1098 (json_t *) required_attrs));
1099 cb (cb_cls,
1100 TALER_EC_NONE,
1101 state);
1102 return NULL;
1103}
1104
1105
1106/**
1107 * DispatchHandler/Callback function which is called for a
1108 * "unselect_continent" action.
1109 *
1110 * @param state state to operate on
1111 * @param arguments arguments to use for operation on state
1112 * @param cb callback to call during/after operation
1113 * @param cb_cls callback closure
1114 * @return NULL
1115 */
1116static struct ANASTASIS_ReduxAction *
1117unselect_continent (json_t *state,
1118 const json_t *arguments,
1119 ANASTASIS_ActionCallback cb,
1120 void *cb_cls)
1121{
1122 redux_transition (state,
1123 ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING);
1124 cb (cb_cls,
1125 TALER_EC_NONE,
1126 state);
1127 return NULL;
1128}
1129
1130
1131struct ANASTASIS_ReduxAction *
1132ANASTASIS_REDUX_add_provider_to_state_ (const char *url,
1133 json_t *state,
1134 ANASTASIS_ActionCallback cb,
1135 void *cb_cls)
1136{
1137 struct ConfigRequest *cr;
1138 struct ConfigReduxWaiting *w;
1139
1140 cr = check_config (url);
1141 w = GNUNET_new (struct ConfigReduxWaiting);
1142 w->cr = cr;
1143 w->state = json_incref (state);
1144 w->cb = cb;
1145 w->cb_cls = cb_cls;
1146 w->ra.cleanup = &abort_provider_config_cb;
1147 w->ra.cleanup_cls = w;
1148 GNUNET_CONTAINER_DLL_insert (cr->w_head,
1149 cr->w_tail,
1150 w);
1151 if (NULL == cr->co)
1152 {
1153 notify_waiting (cr);
1154 return NULL;
1155 }
1156 return &w->ra;
1157}
1158
1159
1160/**
1161 * DispatchHandler/Callback function which is called for a
1162 * "enter_user_attributes" action.
1163 * Returns an #ANASTASIS_ReduxAction if operation is async.
1164 *
1165 * @param state state to operate on
1166 * @param arguments arguments to use for operation on state
1167 * @param cb callback to call during/after operation
1168 * @param cb_cls callback closure
1169 * @return NULL
1170 */
1171static struct ANASTASIS_ReduxAction *
1172enter_user_attributes (json_t *state,
1173 const json_t *arguments,
1174 ANASTASIS_ActionCallback cb,
1175 void *cb_cls)
1176{
1177 const json_t *attributes;
1178 const json_t *required_attributes;
1179
1180 if (NULL == arguments)
1181 {
1182 ANASTASIS_redux_fail_ (cb,
1183 cb_cls,
1184 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
1185 "arguments missing");
1186 return NULL;
1187 }
1188 attributes = json_object_get (arguments,
1189 "identity_attributes");
1190 if (NULL == attributes)
1191 {
1192 ANASTASIS_redux_fail_ (cb,
1193 cb_cls,
1194 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
1195 "'identity_attributes' missing");
1196 return NULL;
1197 }
1198 GNUNET_assert (0 ==
1199 json_object_set (state,
1200 "identity_attributes",
1201 (json_t *) attributes));
1202
1203 /* Verify required attributes are present and well-formed */
1204 required_attributes = json_object_get (state,
1205 "required_attributes");
1206 if ( (NULL == required_attributes) ||
1207 (! json_is_array (required_attributes)) )
1208 {
1209 ANASTASIS_redux_fail_ (cb,
1210 cb_cls,
1211 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
1212 "'required_attributes' must be an array");
1213 return NULL;
1214 }
1215 {
1216 size_t index;
1217 json_t *required_attribute;
1218
1219 json_array_foreach (required_attributes, index, required_attribute)
1220 {
1221 const char *name;
1222 const char *attribute_value;
1223 const char *regexp = NULL;
1224 const char *reglog = NULL;
1225 int optional = false;
1226 struct GNUNET_JSON_Specification spec[] = {
1227 GNUNET_JSON_spec_string ("name",
1228 &name),
1229 GNUNET_JSON_spec_mark_optional (
1230 GNUNET_JSON_spec_string ("validation-regex",
1231 &regexp)),
1232 GNUNET_JSON_spec_mark_optional (
1233 GNUNET_JSON_spec_string ("validation-logic",
1234 &reglog)),
1235 GNUNET_JSON_spec_mark_optional (
1236 GNUNET_JSON_spec_boolean ("optional",
1237 &optional)),
1238 GNUNET_JSON_spec_end ()
1239 };
1240
1241 if (GNUNET_OK !=
1242 GNUNET_JSON_parse (required_attribute,
1243 spec,
1244 NULL, NULL))
1245 {
1246 GNUNET_break (0);
1247 ANASTASIS_redux_fail_ (cb,
1248 cb_cls,
1249 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
1250 "'required_attributes' lacks required fields");
1251 return NULL;
1252 }
1253 attribute_value = json_string_value (json_object_get (attributes,
1254 name));
1255 if (NULL == attribute_value)
1256 {
1257 if (optional)
1258 continue;
1259 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1260 "Request is missing required attribute `%s'\n",
1261 name);
1262 ANASTASIS_redux_fail_ (cb,
1263 cb_cls,
1264 TALER_EC_GENERIC_PARAMETER_MISSING,
1265 name);
1266 return NULL;
1267 }
1268 if ( (NULL != regexp) &&
1269 (! validate_regex (attribute_value,
1270 regexp)) )
1271 {
1272 ANASTASIS_redux_fail_ (cb,
1273 cb_cls,
1274 TALER_EC_ANASTASIS_REDUCER_INPUT_REGEX_FAILED,
1275 name);
1276 return NULL;
1277 }
1278
1279 if (NULL != reglog)
1280 {
1281 bool (*regfun)(const char *);
1282
1283 regfun = dlsym (RTLD_DEFAULT,
1284 reglog);
1285 if (NULL == regfun)
1286 {
1287 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1288 "Custom validation function `%s' is not available: %s\n",
1289 reglog,
1290 dlerror ());
1291 }
1292 else if (! regfun (attribute_value))
1293 {
1294 ANASTASIS_redux_fail_ (cb,
1295 cb_cls,
1296 TALER_EC_ANASTASIS_REDUCER_INPUT_VALIDATION_FAILED,
1297 name);
1298 return NULL;
1299 }
1300 }
1301 } /* end for all attributes loop */
1302 } /* end for all attributes scope */
1303
1304 /* Transition based on mode */
1305 {
1306 const char *s_mode = get_state_mode (state);
1307
1308 if (0 == strcmp (s_mode,
1309 "backup_state"))
1310 {
1311 GNUNET_assert (0 ==
1312 json_object_set_new (
1313 state,
1314 "backup_state",
1315 json_string (
1316 ANASTASIS_backup_state_to_string_ (
1317 ANASTASIS_BACKUP_STATE_AUTHENTICATIONS_EDITING))));
1318 return ANASTASIS_REDUX_backup_begin_ (state,
1319 arguments,
1320 cb,
1321 cb_cls);
1322 }
1323 else
1324 {
1325 GNUNET_assert (0 ==
1326 json_object_set_new (
1327 state,
1328 "recovery_state",
1329 json_string (
1330 ANASTASIS_recovery_state_to_string_ (
1331 ANASTASIS_RECOVERY_STATE_CHALLENGE_SELECTING))));
1332 return ANASTASIS_REDUX_recovery_challenge_begin_ (state,
1333 arguments,
1334 cb,
1335 cb_cls);
1336 }
1337 }
1338}
1339
1340
1341/**
1342 * DispatchHandler/Callback function which is called for a
1343 * "add_provider" action. Adds another Anastasis provider
1344 * to the list of available providers for storing information.
1345 *
1346 * @param state state to operate on
1347 * @param arguments arguments with a provider URL to add
1348 * @param cb callback to call during/after operation
1349 * @param cb_cls callback closure
1350 */
1351static struct ANASTASIS_ReduxAction *
1352add_provider (json_t *state,
1353 const json_t *arguments,
1354 ANASTASIS_ActionCallback cb,
1355 void *cb_cls)
1356{
1357 if (ANASTASIS_add_provider_ (state,
1358 arguments,
1359 cb,
1360 cb_cls))
1361 return NULL;
1362 cb (cb_cls,
1363 TALER_EC_NONE,
1364 state);
1365 return NULL;
1366}
1367
1368
1369bool
1370ANASTASIS_add_provider_ (json_t *state,
1371 const json_t *arguments,
1372 ANASTASIS_ActionCallback cb,
1373 void *cb_cls)
1374{
1375 json_t *urls;
1376 json_t *tlist;
1377
1378 tlist = json_object_get (state,
1379 "authentication_providers");
1380 if (NULL == tlist)
1381 {
1382 tlist = json_object ();
1383 GNUNET_assert (NULL != tlist);
1384 GNUNET_assert (0 ==
1385 json_object_set_new (state,
1386 "authentication_providers",
1387 tlist));
1388 }
1389 if (NULL == arguments)
1390 {
1391 ANASTASIS_redux_fail_ (cb,
1392 cb_cls,
1393 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
1394 "arguments missing");
1395 return true;
1396 }
1397 urls = json_object_get (arguments,
1398 "urls");
1399 if (NULL == urls)
1400 {
1401 ANASTASIS_redux_fail_ (cb,
1402 cb_cls,
1403 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
1404 "'urls' missing");
1405 return true;
1406 }
1407 {
1408 size_t index;
1409 json_t *url;
1410
1411 json_array_foreach (urls, index, url)
1412 {
1413 const char *url_str = json_string_value (url);
1414
1415 if (NULL == url_str)
1416 {
1417 ANASTASIS_redux_fail_ (cb,
1418 cb_cls,
1419 TALER_EC_ANASTASIS_REDUCER_INPUT_INVALID,
1420 "'urls' must be strings");
1421 return true;
1422 }
1423 GNUNET_assert (0 ==
1424 json_object_set_new (tlist,
1425 url_str,
1426 json_object ()));
1427 }
1428 }
1429 return false;
1430}
1431
1432
1433struct ANASTASIS_ReduxAction *
1434ANASTASIS_back_generic_decrement_ (json_t *state,
1435 const json_t *arguments,
1436 ANASTASIS_ActionCallback cb,
1437 void *cb_cls)
1438{
1439 const char *s_mode = get_state_mode (state);
1440 const char *state_string = json_string_value (json_object_get (state,
1441 s_mode));
1442
1443 (void) arguments;
1444 GNUNET_assert (NULL != state_string);
1445 if (0 == strcmp ("backup_state",
1446 s_mode))
1447 {
1448 enum ANASTASIS_BackupState state_index;
1449
1450 state_index = ANASTASIS_backup_state_from_string_ (state_string);
1451 GNUNET_assert (state_index > 0);
1452 state_index = state_index - 1;
1453
1454 GNUNET_assert (0 ==
1455 json_object_set_new (
1456 state,
1457 s_mode,
1458 json_string (
1459 ANASTASIS_backup_state_to_string_ (state_index))));
1460 }
1461 else
1462 {
1463 enum ANASTASIS_RecoveryState state_index;
1464
1465 state_index = ANASTASIS_recovery_state_from_string_ (state_string);
1466 GNUNET_assert (state_index > 0);
1467 state_index = state_index - 1;
1468 GNUNET_assert (0 ==
1469 json_object_set_new (
1470 state,
1471 s_mode,
1472 json_string (
1473 ANASTASIS_recovery_state_to_string_ (state_index))));
1474 }
1475 cb (cb_cls,
1476 TALER_EC_NONE,
1477 state);
1478 return NULL;
1479}
1480
1481
1482/**
1483 * Callback function which is called by the reducer in dependence of
1484 * given state and action.
1485 *
1486 * @param state the previous state to operate on
1487 * @param arguments the arguments needed by operation to operate on state
1488 * @param cb Callback function which returns the new state
1489 * @param cb_cls closure for @a cb
1490 * @return handle to cancel async actions, NULL if @a cb was already called
1491 */
1492typedef struct ANASTASIS_ReduxAction *
1493(*DispatchHandler)(json_t *state,
1494 const json_t *arguments,
1495 ANASTASIS_ActionCallback cb,
1496 void *cb_cls);
1497
1498
1499struct ANASTASIS_ReduxAction *
1500ANASTASIS_redux_action (const json_t *state,
1501 const char *action,
1502 const json_t *arguments,
1503 ANASTASIS_ActionCallback cb,
1504 void *cb_cls)
1505{
1506 struct Dispatcher
1507 {
1508 enum ANASTASIS_GenericState redux_state;
1509 const char *redux_action;
1510 DispatchHandler fun;
1511 } dispatchers[] = {
1512 {
1513 ANASTASIS_GENERIC_STATE_CONTINENT_SELECTING,
1514 "select_continent",
1515 &select_continent
1516 },
1517 {
1518 ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
1519 "unselect_continent",
1520 &unselect_continent
1521 },
1522 {
1523 ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
1524 "select_country",
1525 &select_country
1526 },
1527 {
1528 ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
1529 "select_continent",
1530 &select_continent
1531 },
1532 {
1533 ANASTASIS_GENERIC_STATE_COUNTRY_SELECTING,
1534 "unselect_continent",
1535 &unselect_continent
1536 },
1537 {
1538 ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
1539 "enter_user_attributes",
1540 &enter_user_attributes
1541 },
1542 {
1543 ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
1544 "add_provider",
1545 &add_provider
1546 },
1547 {
1548 ANASTASIS_GENERIC_STATE_USER_ATTRIBUTES_COLLECTING,
1549 "back",
1550 &ANASTASIS_back_generic_decrement_
1551 },
1552 { ANASTASIS_GENERIC_STATE_ERROR, NULL, NULL }
1553 };
1554 bool recovery_mode = false;
1555 const char *s = json_string_value (json_object_get (state,
1556 "backup_state"));
1557 enum ANASTASIS_GenericState gs;
1558
1559 if (NULL == s)
1560 {
1561 s = json_string_value (json_object_get (state,
1562 "recovery_state"));
1563 if (NULL == s)
1564 {
1565 GNUNET_break_op (0);
1566 cb (cb_cls,
1567 TALER_EC_ANASTASIS_REDUCER_STATE_INVALID,
1568 NULL);
1569 return NULL;
1570 }
1571 recovery_mode = true;
1572 }
1573 gs = ANASTASIS_generic_state_from_string_ (s);
1574 {
1575 json_t *new_state;
1576 struct ANASTASIS_ReduxAction *ret;
1577
1578 new_state = json_deep_copy (state);
1579 GNUNET_assert (NULL != new_state);
1580 if (gs != ANASTASIS_GENERIC_STATE_ERROR)
1581 {
1582 for (unsigned int i = 0; NULL != dispatchers[i].fun; i++)
1583 {
1584 if ( (gs == dispatchers[i].redux_state) &&
1585 (0 == strcmp (action,
1586 dispatchers[i].redux_action)) )
1587 {
1588 ret = dispatchers[i].fun (new_state,
1589 arguments,
1590 cb,
1591 cb_cls);
1592 json_decref (new_state);
1593 return ret;
1594 }
1595 }
1596 }
1597 if (recovery_mode)
1598 {
1599 ret = ANASTASIS_recovery_action_ (new_state,
1600 action,
1601 arguments,
1602 cb,
1603 cb_cls);
1604 }
1605 else
1606 {
1607 ret = ANASTASIS_backup_action_ (new_state,
1608 action,
1609 arguments,
1610 cb,
1611 cb_cls);
1612 }
1613 json_decref (new_state);
1614 return ret;
1615 }
1616}
1617
1618
1619void
1620ANASTASIS_redux_action_cancel (struct ANASTASIS_ReduxAction *ra)
1621{
1622 ra->cleanup (ra->cleanup_cls);
1623}
1624
1625
1626json_t *
1627ANASTASIS_REDUX_load_continents_ ()
1628{
1629 const json_t *countries;
1630 json_t *continents;
1631 const json_t *redux_countries = ANASTASIS_redux_countries_init_ ();
1632
1633 if (NULL == redux_countries)
1634 {
1635 GNUNET_break (0);
1636 return NULL;
1637 }
1638 countries = json_object_get (redux_countries,
1639 "countries");
1640 if (NULL == countries)
1641 {
1642 GNUNET_break (0);
1643 return NULL;
1644 }
1645 continents = json_array ();
1646 GNUNET_assert (NULL != continents);
1647
1648 {
1649 json_t *country;
1650 size_t index;
1651
1652 json_array_foreach (countries, index, country)
1653 {
1654 json_t *ex = NULL;
1655 const json_t *continent;
1656
1657 continent = json_object_get (country,
1658 "continent");
1659 if ( (NULL == continent) ||
1660 (! json_is_string (continent)) )
1661 {
1662 GNUNET_break (0);
1663 continue;
1664 }
1665 {
1666 size_t inner_index;
1667 json_t *inner_continent;
1668
1669 json_array_foreach (continents, inner_index, inner_continent)
1670 {
1671 const json_t *name;
1672
1673 name = json_object_get (inner_continent,
1674 "name");
1675 if (1 == json_equal (continent,
1676 name))
1677 {
1678 ex = inner_continent;
1679 break;
1680 }
1681 }
1682 }
1683 if (NULL == ex)
1684 {
1685 ex = json_pack ("{s:O}",
1686 "name",
1687 continent);
1688 GNUNET_assert (0 ==
1689 json_array_append_new (continents,
1690 ex));
1691 }
1692
1693 {
1694 json_t *i18n_continent;
1695 json_t *name_ex;
1696
1697 i18n_continent = json_object_get (country,
1698 "continent_i18n");
1699 name_ex = json_object_get (ex,
1700 "name_i18n");
1701 if (NULL != i18n_continent)
1702 {
1703 const char *lang;
1704 json_t *trans;
1705
1706 json_object_foreach (i18n_continent, lang, trans)
1707 {
1708 if (NULL == name_ex)
1709 {
1710 name_ex = json_object ();
1711 json_object_set_new (ex,
1712 "name_i18n",
1713 name_ex);
1714 }
1715 if (NULL == json_object_get (name_ex,
1716 lang))
1717 {
1718 json_object_set (name_ex,
1719 lang,
1720 trans);
1721 }
1722 }
1723 }
1724 }
1725 }
1726 }
1727 return json_pack ("{s:o}",
1728 "continents",
1729 continents);
1730}