Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2026 Taler Systems SA
4 :
5 : TALER 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 2.1, or (at your option) any later version.
8 :
9 : TALER 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 Lesser General Public License for more details.
12 :
13 : You should have received a copy of the GNU Lesser General Public License along with
14 : TALER; see the file COPYING.LGPL. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file merchant_api_get-config-new.c
19 : * @brief Implementation of the GET /config request
20 : * @author Christian Grothoff
21 : */
22 : #include "taler/platform.h"
23 : #include <curl/curl.h>
24 : #include <jansson.h>
25 : #include <microhttpd.h> /* just for HTTP status codes */
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_curl_lib.h>
28 : #include <taler/merchant/get-config.h>
29 : #include "merchant_api_curl_defaults.h"
30 : #include <taler/taler_json_lib.h>
31 : #include <taler/taler_signatures.h>
32 :
33 : /**
34 : * Which version of the Taler protocol is implemented
35 : * by this library? Used to determine compatibility.
36 : */
37 : #define MERCHANT_PROTOCOL_CURRENT 27
38 :
39 : /**
40 : * How many configs are we backwards-compatible with?
41 : */
42 : #define MERCHANT_PROTOCOL_AGE 3
43 :
44 : /**
45 : * How many exchanges do we allow at most per merchant?
46 : */
47 : #define MAX_EXCHANGES 1024
48 :
49 : /**
50 : * How many currency specs do we allow at most per merchant?
51 : */
52 : #define MAX_CURRENCIES 1024
53 :
54 :
55 : /**
56 : * Handle for a GET /config operation.
57 : */
58 : struct TALER_MERCHANT_GetConfigHandle
59 : {
60 : /**
61 : * Base URL of the merchant backend.
62 : */
63 : char *base_url;
64 :
65 : /**
66 : * The full URL for this request.
67 : */
68 : char *url;
69 :
70 : /**
71 : * Handle for the request.
72 : */
73 : struct GNUNET_CURL_Job *job;
74 :
75 : /**
76 : * Function to call with the result.
77 : */
78 : TALER_MERCHANT_GetConfigCallback cb;
79 :
80 : /**
81 : * Closure for @a cb.
82 : */
83 : TALER_MERCHANT_GET_CONFIG_RESULT_CLOSURE *cb_cls;
84 :
85 : /**
86 : * Reference to the execution context.
87 : */
88 : struct GNUNET_CURL_Context *ctx;
89 : };
90 :
91 :
92 : /**
93 : * Function called when we're done processing the
94 : * HTTP GET /config request.
95 : *
96 : * @param cls the `struct TALER_MERCHANT_GetConfigHandle`
97 : * @param response_code HTTP response code, 0 on error
98 : * @param response response body, NULL if not in JSON
99 : */
100 : static void
101 0 : handle_get_config_finished (void *cls,
102 : long response_code,
103 : const void *response)
104 : {
105 0 : struct TALER_MERCHANT_GetConfigHandle *gch = cls;
106 0 : const json_t *json = response;
107 0 : struct TALER_MERCHANT_GetConfigResponse cr = {
108 0 : .hr.http_status = (unsigned int) response_code,
109 : .hr.reply = json
110 : };
111 :
112 0 : gch->job = NULL;
113 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
114 : "Got /config response with status code %u\n",
115 : (unsigned int) response_code);
116 0 : switch (response_code)
117 : {
118 0 : case MHD_HTTP_OK:
119 : {
120 : const json_t *jcs;
121 0 : const json_t *exchanges = NULL;
122 0 : struct TALER_MERCHANT_GetConfigExchangeInfo *eci = NULL;
123 0 : unsigned int num_eci = 0;
124 : unsigned int nspec;
125 : struct TALER_JSON_ProtocolVersion pv;
126 : struct GNUNET_JSON_Specification spec[] = {
127 0 : GNUNET_JSON_spec_object_const ("currencies",
128 : &jcs),
129 0 : GNUNET_JSON_spec_array_const ("exchanges",
130 : &exchanges),
131 0 : GNUNET_JSON_spec_string ("currency",
132 : &cr.details.ok.ci.currency),
133 0 : TALER_JSON_spec_version ("version",
134 : &pv),
135 0 : GNUNET_JSON_spec_string ("version",
136 : &cr.details.ok.ci.version),
137 0 : GNUNET_JSON_spec_mark_optional (
138 : GNUNET_JSON_spec_string ("implementation",
139 : &cr.details.ok.implementation),
140 : NULL),
141 0 : GNUNET_JSON_spec_bool ("have_self_provisioning",
142 : &cr.details.ok.have_self_provisioning),
143 0 : GNUNET_JSON_spec_bool ("have_donau",
144 : &cr.details.ok.have_donau),
145 0 : GNUNET_JSON_spec_string ("payment_target_types",
146 : &cr.details.ok.payment_target_types),
147 0 : GNUNET_JSON_spec_mark_optional (
148 : GNUNET_JSON_spec_string ("payment_target_regex",
149 : &cr.details.ok.payment_target_regex),
150 : NULL),
151 0 : GNUNET_JSON_spec_mark_optional (
152 : GNUNET_JSON_spec_array_const ("mandatory_tan_channels",
153 : &cr.details.ok.mandatory_tan_channels),
154 : NULL),
155 0 : GNUNET_JSON_spec_relative_time ("default_pay_delay",
156 : &cr.details.ok.default_pay_delay),
157 0 : GNUNET_JSON_spec_relative_time ("default_refund_delay",
158 : &cr.details.ok.default_refund_delay),
159 0 : GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
160 : &cr.details.ok.default_wire_transfer_delay),
161 0 : GNUNET_JSON_spec_string ("default_persona",
162 : &cr.details.ok.default_persona),
163 0 : GNUNET_JSON_spec_mark_optional (
164 : GNUNET_JSON_spec_array_const ("report_generators",
165 : &cr.details.ok.report_generators),
166 : NULL),
167 0 : GNUNET_JSON_spec_mark_optional (
168 : GNUNET_JSON_spec_string ("phone_regex",
169 : &cr.details.ok.phone_regex),
170 : NULL),
171 0 : GNUNET_JSON_spec_mark_optional (
172 : GNUNET_JSON_spec_object_const ("spa_options",
173 : &cr.details.ok.spa_options),
174 : NULL),
175 0 : GNUNET_JSON_spec_end ()
176 : };
177 :
178 : cr.details.ok.compat
179 0 : = TALER_MERCHANT_GET_CONFIG_VC_PROTOCOL_ERROR;
180 0 : if (GNUNET_OK !=
181 0 : GNUNET_JSON_parse (json,
182 : spec,
183 : NULL, NULL))
184 : {
185 0 : GNUNET_break_op (0);
186 0 : cr.hr.http_status = 0;
187 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
188 0 : break;
189 : }
190 0 : cr.details.ok.compat = TALER_MERCHANT_GET_CONFIG_VC_MATCH;
191 0 : if (MERCHANT_PROTOCOL_CURRENT < pv.current)
192 : {
193 0 : cr.details.ok.compat |= TALER_MERCHANT_GET_CONFIG_VC_NEWER;
194 0 : if (MERCHANT_PROTOCOL_CURRENT < pv.current - pv.age)
195 0 : cr.details.ok.compat
196 0 : |= TALER_MERCHANT_GET_CONFIG_VC_INCOMPATIBLE;
197 : }
198 0 : if (MERCHANT_PROTOCOL_CURRENT > pv.current)
199 : {
200 0 : cr.details.ok.compat |= TALER_MERCHANT_GET_CONFIG_VC_OLDER;
201 0 : if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > pv.current)
202 0 : cr.details.ok.compat
203 0 : |= TALER_MERCHANT_GET_CONFIG_VC_INCOMPATIBLE;
204 : }
205 :
206 0 : nspec = (unsigned int) json_object_size (jcs);
207 0 : if ( (nspec > MAX_CURRENCIES) ||
208 0 : (json_object_size (jcs) != (size_t) nspec) )
209 : {
210 0 : GNUNET_break_op (0);
211 0 : cr.hr.http_status = 0;
212 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
213 0 : break;
214 : }
215 0 : if (NULL != exchanges)
216 : {
217 0 : num_eci = (unsigned int) json_array_size (exchanges);
218 0 : if ( (num_eci > MAX_EXCHANGES) ||
219 0 : (json_array_size (exchanges) != (size_t) num_eci) )
220 : {
221 0 : GNUNET_break_op (0);
222 0 : cr.hr.http_status = 0;
223 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
224 0 : break;
225 : }
226 0 : eci = GNUNET_new_array (num_eci,
227 : struct TALER_MERCHANT_GetConfigExchangeInfo);
228 0 : for (unsigned int i = 0; i<num_eci; i++)
229 : {
230 0 : struct TALER_MERCHANT_GetConfigExchangeInfo *ei = &eci[i];
231 0 : const json_t *ej = json_array_get (exchanges,
232 : i);
233 : struct GNUNET_JSON_Specification ispec[] = {
234 0 : GNUNET_JSON_spec_string ("currency",
235 : &ei->currency),
236 0 : GNUNET_JSON_spec_string ("base_url",
237 : &ei->base_url),
238 0 : GNUNET_JSON_spec_fixed_auto ("master_pub",
239 : &ei->master_pub),
240 0 : GNUNET_JSON_spec_end ()
241 : };
242 :
243 0 : if (GNUNET_OK !=
244 0 : GNUNET_JSON_parse (ej,
245 : ispec,
246 : NULL, NULL))
247 : {
248 0 : GNUNET_break_op (0);
249 0 : cr.hr.http_status = 0;
250 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
251 0 : GNUNET_free (eci);
252 0 : break;
253 : }
254 : }
255 : }
256 : {
257 : struct TALER_CurrencySpecification *cspecs;
258 0 : unsigned int off = 0;
259 : json_t *obj;
260 : const char *curr;
261 :
262 0 : cspecs = GNUNET_new_array (nspec,
263 : struct TALER_CurrencySpecification);
264 0 : cr.details.ok.num_cspecs = nspec;
265 0 : cr.details.ok.cspecs = cspecs;
266 0 : cr.details.ok.num_exchanges = (unsigned int) num_eci;
267 0 : cr.details.ok.exchanges = eci;
268 0 : json_object_foreach ((json_t *) jcs, curr, obj)
269 : {
270 0 : struct TALER_CurrencySpecification *cs = &cspecs[off++];
271 : struct GNUNET_JSON_Specification cspec[] = {
272 0 : TALER_JSON_spec_currency_specification (curr,
273 : curr,
274 : cs),
275 0 : GNUNET_JSON_spec_end ()
276 : };
277 :
278 0 : if (GNUNET_OK !=
279 0 : GNUNET_JSON_parse (jcs,
280 : cspec,
281 : NULL, NULL))
282 : {
283 0 : GNUNET_break_op (0);
284 0 : cr.hr.http_status = 0;
285 0 : cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
286 0 : GNUNET_free (eci);
287 0 : TALER_CONFIG_free_currencies (off - 1,
288 : cspecs);
289 0 : break;
290 : }
291 : }
292 0 : gch->cb (gch->cb_cls,
293 : &cr);
294 0 : GNUNET_free (eci);
295 0 : TALER_CONFIG_free_currencies (nspec,
296 : cspecs);
297 : }
298 0 : TALER_MERCHANT_get_config_cancel (gch);
299 0 : return;
300 : }
301 0 : default:
302 0 : cr.hr.ec = TALER_JSON_get_error_code (json);
303 0 : cr.hr.hint = TALER_JSON_get_error_hint (json);
304 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
305 : "Unexpected response code %u/%d\n",
306 : (unsigned int) response_code,
307 : (int) cr.hr.ec);
308 0 : break;
309 : }
310 0 : gch->cb (gch->cb_cls,
311 : &cr);
312 0 : TALER_MERCHANT_get_config_cancel (gch);
313 : }
314 :
315 :
316 : struct TALER_MERCHANT_GetConfigHandle *
317 0 : TALER_MERCHANT_get_config_create (
318 : struct GNUNET_CURL_Context *ctx,
319 : const char *url)
320 : {
321 : struct TALER_MERCHANT_GetConfigHandle *gch;
322 :
323 0 : gch = GNUNET_new (struct TALER_MERCHANT_GetConfigHandle);
324 0 : gch->ctx = ctx;
325 0 : gch->base_url = GNUNET_strdup (url);
326 0 : return gch;
327 : }
328 :
329 :
330 : enum TALER_ErrorCode
331 0 : TALER_MERCHANT_get_config_start (
332 : struct TALER_MERCHANT_GetConfigHandle *gch,
333 : TALER_MERCHANT_GetConfigCallback cb,
334 : TALER_MERCHANT_GET_CONFIG_RESULT_CLOSURE *cb_cls)
335 : {
336 : CURL *eh;
337 :
338 0 : gch->cb = cb;
339 0 : gch->cb_cls = cb_cls;
340 0 : gch->url = TALER_url_join (gch->base_url,
341 : "config",
342 : NULL);
343 0 : if (NULL == gch->url)
344 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
345 0 : eh = TALER_MERCHANT_curl_easy_get_ (gch->url);
346 0 : if (NULL == eh)
347 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
348 0 : gch->job = GNUNET_CURL_job_add (gch->ctx,
349 : eh,
350 : &handle_get_config_finished,
351 : gch);
352 0 : if (NULL == gch->job)
353 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
354 0 : return TALER_EC_NONE;
355 : }
356 :
357 :
358 : void
359 0 : TALER_MERCHANT_get_config_cancel (
360 : struct TALER_MERCHANT_GetConfigHandle *gch)
361 : {
362 0 : if (NULL != gch->job)
363 : {
364 0 : GNUNET_CURL_job_cancel (gch->job);
365 0 : gch->job = NULL;
366 : }
367 0 : GNUNET_free (gch->url);
368 0 : GNUNET_free (gch->base_url);
369 0 : GNUNET_free (gch);
370 0 : }
371 :
372 :
373 : /* end of merchant_api_get-config-new.c */
|