merchant_api_get-config.c (12076B)
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 src/lib/merchant_api_get-config.c 19 * @brief Implementation of the GET /config request 20 * @author Christian Grothoff 21 */ 22 #include "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 32 38 39 /** 40 * How many configs are we backwards-compatible with? 41 */ 42 #define MERCHANT_PROTOCOL_AGE 8 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 handle_get_config_finished (void *cls, 102 long response_code, 103 const void *response) 104 { 105 struct TALER_MERCHANT_GetConfigHandle *gch = cls; 106 const json_t *json = response; 107 struct TALER_MERCHANT_GetConfigResponse cr = { 108 .hr.http_status = (unsigned int) response_code, 109 .hr.reply = json 110 }; 111 112 gch->job = NULL; 113 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 114 "Got /config response with status code %u\n", 115 (unsigned int) response_code); 116 switch (response_code) 117 { 118 case MHD_HTTP_OK: 119 { 120 const json_t *jcs; 121 const json_t *exchanges = NULL; 122 struct TALER_MERCHANT_GetConfigExchangeInfo *eci = NULL; 123 unsigned int num_eci = 0; 124 unsigned int nspec; 125 struct TALER_JSON_ProtocolVersion pv; 126 struct GNUNET_JSON_Specification spec[] = { 127 GNUNET_JSON_spec_object_const ("currencies", 128 &jcs), 129 GNUNET_JSON_spec_array_const ("exchanges", 130 &exchanges), 131 GNUNET_JSON_spec_string ("currency", 132 &cr.details.ok.ci.currency), 133 TALER_JSON_spec_version ("version", 134 &pv), 135 GNUNET_JSON_spec_string ("version", 136 &cr.details.ok.ci.version), 137 GNUNET_JSON_spec_mark_optional ( 138 GNUNET_JSON_spec_string ("implementation", 139 &cr.details.ok.implementation), 140 NULL), 141 GNUNET_JSON_spec_bool ("have_self_provisioning", 142 &cr.details.ok.have_self_provisioning), 143 GNUNET_JSON_spec_bool ("have_donau", 144 &cr.details.ok.have_donau), 145 GNUNET_JSON_spec_string ("payment_target_types", 146 &cr.details.ok.payment_target_types), 147 GNUNET_JSON_spec_mark_optional ( 148 GNUNET_JSON_spec_string ("payment_target_regex", 149 &cr.details.ok.payment_target_regex), 150 NULL), 151 GNUNET_JSON_spec_mark_optional ( 152 GNUNET_JSON_spec_array_const ("mandatory_tan_channels", 153 &cr.details.ok.mandatory_tan_channels), 154 NULL), 155 GNUNET_JSON_spec_relative_time ("default_pay_delay", 156 &cr.details.ok.default_pay_delay), 157 GNUNET_JSON_spec_relative_time ("default_refund_delay", 158 &cr.details.ok.default_refund_delay), 159 GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay", 160 &cr.details.ok.default_wire_transfer_delay), 161 GNUNET_JSON_spec_string ("default_persona", 162 &cr.details.ok.default_persona), 163 GNUNET_JSON_spec_mark_optional ( 164 GNUNET_JSON_spec_array_const ("report_generators", 165 &cr.details.ok.report_generators), 166 NULL), 167 GNUNET_JSON_spec_mark_optional ( 168 GNUNET_JSON_spec_string ("phone_regex", 169 &cr.details.ok.phone_regex), 170 NULL), 171 GNUNET_JSON_spec_mark_optional ( 172 GNUNET_JSON_spec_object_const ("spa_options", 173 &cr.details.ok.spa_options), 174 NULL), 175 GNUNET_JSON_spec_end () 176 }; 177 178 cr.details.ok.compat 179 = TALER_MERCHANT_GET_CONFIG_VC_PROTOCOL_ERROR; 180 if (GNUNET_OK != 181 GNUNET_JSON_parse (json, 182 spec, 183 NULL, NULL)) 184 { 185 GNUNET_break_op (0); 186 cr.hr.http_status = 0; 187 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 188 break; 189 } 190 cr.details.ok.compat = TALER_MERCHANT_GET_CONFIG_VC_MATCH; 191 if (MERCHANT_PROTOCOL_CURRENT < pv.current) 192 { 193 cr.details.ok.compat |= TALER_MERCHANT_GET_CONFIG_VC_NEWER; 194 if (MERCHANT_PROTOCOL_CURRENT < pv.current - pv.age) 195 cr.details.ok.compat 196 |= TALER_MERCHANT_GET_CONFIG_VC_INCOMPATIBLE; 197 } 198 if (MERCHANT_PROTOCOL_CURRENT > pv.current) 199 { 200 cr.details.ok.compat |= TALER_MERCHANT_GET_CONFIG_VC_OLDER; 201 if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > pv.current) 202 cr.details.ok.compat 203 |= TALER_MERCHANT_GET_CONFIG_VC_INCOMPATIBLE; 204 } 205 206 nspec = (unsigned int) json_object_size (jcs); 207 if ( (nspec > MAX_CURRENCIES) || 208 (json_object_size (jcs) != (size_t) nspec) ) 209 { 210 GNUNET_break_op (0); 211 cr.hr.http_status = 0; 212 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 213 break; 214 } 215 if (NULL != exchanges) 216 { 217 num_eci = (unsigned int) json_array_size (exchanges); 218 if ( (num_eci > MAX_EXCHANGES) || 219 (json_array_size (exchanges) != (size_t) num_eci) ) 220 { 221 GNUNET_break_op (0); 222 cr.hr.http_status = 0; 223 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 224 break; 225 } 226 eci = GNUNET_new_array (num_eci, 227 struct TALER_MERCHANT_GetConfigExchangeInfo); 228 for (unsigned int i = 0; i<num_eci; i++) 229 { 230 struct TALER_MERCHANT_GetConfigExchangeInfo *ei = &eci[i]; 231 const json_t *ej = json_array_get (exchanges, 232 i); 233 struct GNUNET_JSON_Specification ispec[] = { 234 GNUNET_JSON_spec_string ("currency", 235 &ei->currency), 236 GNUNET_JSON_spec_string ("base_url", 237 &ei->base_url), 238 GNUNET_JSON_spec_fixed_auto ("master_pub", 239 &ei->master_pub), 240 GNUNET_JSON_spec_end () 241 }; 242 243 if (GNUNET_OK != 244 GNUNET_JSON_parse (ej, 245 ispec, 246 NULL, NULL)) 247 { 248 GNUNET_break_op (0); 249 cr.hr.http_status = 0; 250 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 251 GNUNET_free (eci); 252 goto end_outer; 253 } 254 } 255 } 256 { 257 struct TALER_CurrencySpecification *cspecs; 258 unsigned int off = 0; 259 json_t *obj; 260 const char *curr; 261 262 cspecs = GNUNET_new_array (nspec, 263 struct TALER_CurrencySpecification); 264 cr.details.ok.num_cspecs = nspec; 265 cr.details.ok.cspecs = cspecs; 266 cr.details.ok.num_exchanges = (unsigned int) num_eci; 267 cr.details.ok.exchanges = eci; 268 json_object_foreach ((json_t *) jcs, curr, obj) 269 { 270 struct TALER_CurrencySpecification *cs = &cspecs[off++]; 271 struct GNUNET_JSON_Specification cspec[] = { 272 TALER_JSON_spec_currency_specification (curr, 273 curr, 274 cs), 275 GNUNET_JSON_spec_end () 276 }; 277 278 if (GNUNET_OK != 279 GNUNET_JSON_parse (jcs, 280 cspec, 281 NULL, NULL)) 282 { 283 GNUNET_break_op (0); 284 cr.hr.http_status = 0; 285 cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 286 nspec = off - 1; /* see TALER_CONFIG_free_currencies() below */ 287 break; 288 } 289 } 290 gch->cb (gch->cb_cls, 291 &cr); 292 GNUNET_free (eci); 293 TALER_CONFIG_free_currencies (nspec, 294 cspecs); 295 } 296 TALER_MERCHANT_get_config_cancel (gch); 297 return; 298 } 299 default: 300 cr.hr.ec = TALER_JSON_get_error_code (json); 301 cr.hr.hint = TALER_JSON_get_error_hint (json); 302 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 303 "Unexpected response code %u/%d\n", 304 (unsigned int) response_code, 305 (int) cr.hr.ec); 306 break; 307 } 308 end_outer: 309 gch->cb (gch->cb_cls, 310 &cr); 311 TALER_MERCHANT_get_config_cancel (gch); 312 } 313 314 315 struct TALER_MERCHANT_GetConfigHandle * 316 TALER_MERCHANT_get_config_create ( 317 struct GNUNET_CURL_Context *ctx, 318 const char *url) 319 { 320 struct TALER_MERCHANT_GetConfigHandle *gch; 321 322 gch = GNUNET_new (struct TALER_MERCHANT_GetConfigHandle); 323 gch->ctx = ctx; 324 gch->base_url = GNUNET_strdup (url); 325 return gch; 326 } 327 328 329 enum TALER_ErrorCode 330 TALER_MERCHANT_get_config_start ( 331 struct TALER_MERCHANT_GetConfigHandle *gch, 332 TALER_MERCHANT_GetConfigCallback cb, 333 TALER_MERCHANT_GET_CONFIG_RESULT_CLOSURE *cb_cls) 334 { 335 CURL *eh; 336 337 gch->cb = cb; 338 gch->cb_cls = cb_cls; 339 gch->url = TALER_url_join (gch->base_url, 340 "config", 341 NULL); 342 if (NULL == gch->url) 343 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 344 eh = TALER_MERCHANT_curl_easy_get_ (gch->url); 345 if (NULL == eh) 346 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 347 gch->job = GNUNET_CURL_job_add (gch->ctx, 348 eh, 349 &handle_get_config_finished, 350 gch); 351 if (NULL == gch->job) 352 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 353 return TALER_EC_NONE; 354 } 355 356 357 void 358 TALER_MERCHANT_get_config_cancel ( 359 struct TALER_MERCHANT_GetConfigHandle *gch) 360 { 361 if (NULL != gch->job) 362 { 363 GNUNET_CURL_job_cancel (gch->job); 364 gch->job = NULL; 365 } 366 GNUNET_free (gch->url); 367 GNUNET_free (gch->base_url); 368 GNUNET_free (gch); 369 } 370 371 372 /* end of merchant_api_get-config-new.c */