anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

anastasis_api_config.c (13729B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020, 2021 Anastasis SARL
      4 
      5   Anastasis is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU 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 restclient/anastasis_api_config.c
     18  * @brief Implementation of the /config GET
     19  * @author Christian Grothoff
     20  * @author Dennis Neufeld
     21  * @author Dominik Meister
     22  */
     23 #include "platform.h"
     24 #include <curl/curl.h>
     25 #include <microhttpd.h> /* just for HTTP status codes */
     26 #include "anastasis_service.h"
     27 #include "anastasis_api_curl_defaults.h"
     28 #include <gnunet/gnunet_json_lib.h>
     29 #include <taler/taler_json_lib.h>
     30 
     31 
     32 /**
     33  * Which version of the Taler protocol is implemented
     34  * by this library?  Used to determine compatibility.
     35  *
     36  * Raised to 1 when the key derivation was fixed to use the full context
     37  * string: every key derived by a 0:x:x client differs, so backups written by
     38  * one side cannot be recovered by the other.  Hence 0 must stay outside of
     39  * the compatibility range.
     40  *
     41  * Raised to 3 for multi-currency pricing, which only *added* the plural
     42  * fee fields and "currencies" to /config.  The age therefore stays at 1:
     43  * a version 2 provider is still usable, we just synthesize the plural
     44  * fields from the scalar ones below.
     45  */
     46 #define ANASTASIS_PROTOCOL_CURRENT 3
     47 
     48 /**
     49  * How many versions are we backwards compatible with?
     50  */
     51 #define ANASTASIS_PROTOCOL_AGE 1
     52 
     53 
     54 struct ANASTASIS_ConfigOperation
     55 {
     56   /**
     57    * The url for this request.
     58    */
     59   char *url;
     60 
     61   /**
     62    * Handle for the request.
     63    */
     64   struct GNUNET_CURL_Job *job;
     65 
     66   /**
     67    * Reference to the execution context.
     68    */
     69   struct GNUNET_CURL_Context *ctx;
     70 
     71   /**
     72   * The callback to pass the backend response to
     73   */
     74   ANASTASIS_ConfigCallback cb;
     75 
     76   /**
     77    * Closure for @a cb.
     78    */
     79   void *cb_cls;
     80 
     81 };
     82 
     83 
     84 /**
     85  * Parse a successful GET /config response and hand it to @a cb.
     86  *
     87  * Kept apart from #handle_config_finished() because the price lists are
     88  * heap allocated: every exit path from here has to release them, and a
     89  * flat sequence of `break's out of a switch cannot express that.
     90  *
     91  * @param json the response to parse
     92  * @param url provider URL, for logging
     93  * @param[in,out] acfg configuration to fill in and report
     94  * @param cb function to call with the parsed configuration
     95  * @param cb_cls closure for @a cb
     96  * @return #GNUNET_OK if @a cb was invoked, #GNUNET_SYSERR if @a json was
     97  *         unusable and the caller must report the failure itself
     98  */
     99 static enum GNUNET_GenericReturnValue
    100 handle_config_ok (const json_t *json,
    101                   const char *url,
    102                   struct ANASTASIS_Config *acfg,
    103                   ANASTASIS_ConfigCallback cb,
    104                   void *cb_cls)
    105 {
    106   const char *name;
    107   const json_t *methods;
    108   const json_t *currencies = NULL;
    109   struct TALER_JSON_ProtocolVersion pv;
    110   struct ANASTASIS_AuthorizationMethodConfig *mcfg = NULL;
    111   const char **carr = NULL;
    112   unsigned int carr_len = 0;
    113   enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    114   struct GNUNET_JSON_Specification spec[] = {
    115     GNUNET_JSON_spec_string ("name",
    116                              &name),
    117     GNUNET_JSON_spec_string ("business_name",
    118                              &acfg->details.ok.business_name),
    119     GNUNET_JSON_spec_string ("version",
    120                              &acfg->details.ok.version),
    121     TALER_JSON_spec_version ("version",
    122                              &pv),
    123     GNUNET_JSON_spec_array_const ("methods",
    124                                   &methods),
    125     GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes",
    126                              &acfg->details.ok.storage_limit_in_megabytes),
    127     TALER_JSON_spec_amount_any ("annual_fee",
    128                                 &acfg->details.ok.annual_fee),
    129     TALER_JSON_spec_amount_any ("truth_upload_fee",
    130                                 &acfg->details.ok.truth_upload_fee),
    131     TALER_JSON_spec_amount_any ("liability_limit",
    132                                 &acfg->details.ok.liability_limit),
    133     /* Added in protocol version 3.  A version 2 provider omits them, and
    134        they are synthesized from the scalar fields further down. */
    135     GNUNET_JSON_spec_mark_optional (
    136       GNUNET_JSON_spec_array_const ("currencies",
    137                                     &currencies),
    138       NULL),
    139     GNUNET_JSON_spec_mark_optional (
    140       TALER_JSON_spec_amount_list ("annual_fees",
    141                                    &acfg->details.ok.annual_fees),
    142       NULL),
    143     GNUNET_JSON_spec_mark_optional (
    144       TALER_JSON_spec_amount_list ("truth_upload_fees",
    145                                    &acfg->details.ok.truth_upload_fees),
    146       NULL),
    147     GNUNET_JSON_spec_mark_optional (
    148       TALER_JSON_spec_amount_list ("liability_limits",
    149                                    &acfg->details.ok.liability_limits),
    150       NULL),
    151     GNUNET_JSON_spec_fixed_auto ("provider_salt",
    152                                  &acfg->details.ok.provider_salt),
    153     GNUNET_JSON_spec_end ()
    154   };
    155 
    156   if (GNUNET_OK !=
    157       GNUNET_JSON_parse (json,
    158                          spec,
    159                          NULL, NULL))
    160   {
    161     GNUNET_break_op (0);
    162     json_dumpf (json,
    163                 stderr,
    164                 JSON_INDENT (2));
    165     acfg->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    166     return GNUNET_SYSERR;
    167   }
    168   if (0 != strcmp (name,
    169                    "anastasis"))
    170   {
    171     GNUNET_break_op (0);
    172     acfg->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    173     goto cleanup;
    174   }
    175   if ( (ANASTASIS_PROTOCOL_CURRENT < pv.current) &&
    176        (ANASTASIS_PROTOCOL_CURRENT < pv.current - pv.age) )
    177   {
    178     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    179                 "Provider protocol version too new\n");
    180     acfg->ec = TALER_EC_GENERIC_VERSION_MALFORMED;
    181     goto cleanup;
    182   }
    183   if ( (ANASTASIS_PROTOCOL_CURRENT > pv.current) &&
    184        (ANASTASIS_PROTOCOL_CURRENT - ANASTASIS_PROTOCOL_AGE > pv.current) )
    185   {
    186     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    187                 "Provider protocol version too old\n");
    188     GNUNET_break_op (0);
    189     acfg->ec = TALER_EC_GENERIC_VERSION_MALFORMED;
    190     goto cleanup;
    191   }
    192   acfg->details.ok.methods_length = (unsigned int) json_array_size (methods);
    193   if (((size_t) acfg->details.ok.methods_length) !=
    194       json_array_size (methods))
    195   {
    196     GNUNET_break_op (0);
    197     acfg->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    198     goto cleanup;
    199   }
    200   /* Bound the arrays so a malicious provider cannot force an unbounded
    201      allocation (a ~40 MB response could otherwise yield ~10^6 elements). */
    202   if (acfg->details.ok.methods_length > 1024)
    203   {
    204     GNUNET_break_op (0);
    205     acfg->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    206     goto cleanup;
    207   }
    208 
    209   /* Currencies: given outright by a version 3 provider, otherwise the
    210      single currency the provider was already quoting everything in. */
    211   if (NULL != currencies)
    212   {
    213     size_t clen = json_array_size (currencies);
    214     size_t idx;
    215     json_t *entry;
    216 
    217     if ( (0 == clen) ||
    218          (clen > 1024) )
    219     {
    220       GNUNET_break_op (0);
    221       acfg->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    222       goto cleanup;
    223     }
    224     carr = GNUNET_new_array (clen,
    225                              const char *);
    226     json_array_foreach (currencies, idx, entry)
    227     {
    228       if (! json_is_string (entry))
    229       {
    230         GNUNET_break_op (0);
    231         acfg->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    232         goto cleanup;
    233       }
    234       carr[idx] = json_string_value (entry);
    235       carr_len = idx + 1;
    236     }
    237   }
    238   else
    239   {
    240     carr = GNUNET_new_array (1,
    241                              const char *);
    242     carr[0] = acfg->details.ok.annual_fee.currency;
    243     carr_len = 1;
    244   }
    245   acfg->details.ok.currencies = carr;
    246   acfg->details.ok.currencies_len = carr_len;
    247 
    248   /* Plural fee fields: a version 2 provider has none, so make a
    249      one-element list out of what it did send.  Everything downstream can
    250      then work with lists only. */
    251   {
    252     struct
    253     {
    254       const struct TALER_Amount *scalar;
    255       struct TALER_AmountList *list;
    256     } fees[] = {
    257       { &acfg->details.ok.annual_fee, &acfg->details.ok.annual_fees },
    258       { &acfg->details.ok.truth_upload_fee,
    259         &acfg->details.ok.truth_upload_fees },
    260       { &acfg->details.ok.liability_limit,
    261         &acfg->details.ok.liability_limits }
    262     };
    263 
    264     for (unsigned int i = 0; i<sizeof (fees) / sizeof (fees[0]); i++)
    265     {
    266       if (0 != fees[i].list->tal_len)
    267         continue;
    268       GNUNET_array_append (fees[i].list->tal,
    269                            fees[i].list->tal_len,
    270                            *fees[i].scalar);
    271     }
    272   }
    273 
    274   mcfg = GNUNET_new_array (GNUNET_NZL (acfg->details.ok.methods_length),
    275                            struct ANASTASIS_AuthorizationMethodConfig);
    276   for (unsigned int i = 0; i<acfg->details.ok.methods_length; i++)
    277   {
    278     struct ANASTASIS_AuthorizationMethodConfig *m = &mcfg[i];
    279     struct GNUNET_JSON_Specification ispec[] = {
    280       GNUNET_JSON_spec_string ("type",
    281                                &m->type),
    282       TALER_JSON_spec_amount_any ("cost",
    283                                   &m->usage_fee),
    284       GNUNET_JSON_spec_mark_optional (
    285         TALER_JSON_spec_amount_list ("costs",
    286                                      &m->usage_fees),
    287         NULL),
    288       GNUNET_JSON_spec_end ()
    289     };
    290 
    291     if (GNUNET_OK !=
    292         GNUNET_JSON_parse (json_array_get (methods,
    293                                            i),
    294                            ispec,
    295                            NULL, NULL))
    296     {
    297       GNUNET_break_op (0);
    298       acfg->ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    299       goto cleanup;
    300     }
    301     if (0 == m->usage_fees.tal_len)
    302       GNUNET_array_append (m->usage_fees.tal,
    303                            m->usage_fees.tal_len,
    304                            m->usage_fee);
    305   }
    306   acfg->details.ok.methods = mcfg;
    307   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    308               "Good backend found at `%s'\n",
    309               url);
    310   cb (cb_cls,
    311       acfg);
    312   ret = GNUNET_OK;
    313 cleanup:
    314   if (NULL != mcfg)
    315   {
    316     for (unsigned int i = 0; i<acfg->details.ok.methods_length; i++)
    317       TALER_amount_list_free (&mcfg[i].usage_fees);
    318     GNUNET_free (mcfg);
    319   }
    320   GNUNET_free (carr);
    321   GNUNET_JSON_parse_free (spec);
    322   if (GNUNET_OK != ret)
    323   {
    324     /* Everything in there points into memory just released, and the
    325        caller is about to report a failure with the same struct. */
    326     memset (&acfg->details.ok,
    327             0,
    328             sizeof (acfg->details.ok));
    329   }
    330   return ret;
    331 }
    332 
    333 
    334 /**
    335  * Function called when we're done processing the
    336  * HTTP /config request.
    337  *
    338  * @param cls the `struct ANASTASIS_ConfigOperation`
    339  * @param response_code HTTP response code, 0 on error
    340  * @param response parsed JSON result, NULL on error
    341  */
    342 static void
    343 handle_config_finished (void *cls,
    344                         long response_code,
    345                         const void *response)
    346 {
    347   struct ANASTASIS_ConfigOperation *co = cls;
    348   const json_t *json = response;
    349   struct ANASTASIS_Config acfg = {
    350     .http_status = response_code,
    351     .response = json
    352   };
    353 
    354   co->job = NULL;
    355   switch (response_code)
    356   {
    357   case 0:
    358     /* No reply received */
    359     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    360                 "Backend `%s' failed to respond to GET /config\n",
    361                 co->url);
    362     break;
    363   case MHD_HTTP_OK:
    364     if (GNUNET_OK ==
    365         handle_config_ok (json,
    366                           co->url,
    367                           &acfg,
    368                           co->cb,
    369                           co->cb_cls))
    370     {
    371       ANASTASIS_config_cancel (co);
    372       return;
    373     }
    374     acfg.http_status = 0;
    375     break;
    376   case MHD_HTTP_BAD_REQUEST:
    377     /* This should never happen, either us or the anastasis server is buggy
    378        (or API version conflict); just pass JSON reply to the application */
    379     break;
    380   case MHD_HTTP_NOT_FOUND:
    381     /* Nothing really to verify */
    382     break;
    383   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    384     /* Server had an internal issue; we should retry, but this API
    385        leaves this to the application */
    386     break;
    387   default:
    388     /* unexpected response code */
    389     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    390                 "Unexpected response code %u\n",
    391                 (unsigned int) response_code);
    392     GNUNET_break_op (0);
    393     break;
    394   }
    395   co->cb (co->cb_cls,
    396           &acfg);
    397   ANASTASIS_config_cancel (co);
    398 }
    399 
    400 
    401 struct ANASTASIS_ConfigOperation *
    402 ANASTASIS_get_config (struct GNUNET_CURL_Context *ctx,
    403                       const char *base_url,
    404                       ANASTASIS_ConfigCallback cb,
    405                       void *cb_cls)
    406 {
    407   struct ANASTASIS_ConfigOperation *co;
    408 
    409   co = GNUNET_new (struct ANASTASIS_ConfigOperation);
    410   co->url = TALER_url_join (base_url,
    411                             "config",
    412                             NULL);
    413   co->ctx = ctx;
    414   co->cb = cb;
    415   co->cb_cls = cb_cls;
    416   {
    417     CURL *eh;
    418 
    419     eh = ANASTASIS_curl_easy_get_ (co->url);
    420     co->job = GNUNET_CURL_job_add (ctx,
    421                                    eh,
    422                                    &handle_config_finished,
    423                                    co);
    424   }
    425   if (NULL == co->job)
    426   {
    427     GNUNET_free (co->url);
    428     GNUNET_free (co);
    429     return NULL;
    430   }
    431   return co;
    432 }
    433 
    434 
    435 void
    436 ANASTASIS_config_cancel (struct ANASTASIS_ConfigOperation *co)
    437 {
    438   if (NULL != co->job)
    439   {
    440     GNUNET_CURL_job_cancel (co->job);
    441     co->job = NULL;
    442   }
    443   GNUNET_free (co->url);
    444   GNUNET_free (co);
    445 }
    446 
    447 
    448 /* end of anastasis_api_config.c */