anastasis

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

anastasis_api_truth_solve.c (12355B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2020, 2021, 2022 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_truth_solve.c
     18  * @brief Implementation of the POST /truth/$TID/solve request
     19  * @author Christian Grothoff
     20  * @author Dennis Neufeld
     21  * @author Dominik Meister
     22  */
     23 #include "platform.h"
     24 #include <curl/curl.h>
     25 #include <jansson.h>
     26 #include <microhttpd.h> /* just for HTTP status codes */
     27 #include "anastasis_service.h"
     28 #include "anastasis_api_curl_defaults.h"
     29 #include <taler/taler_json_lib.h>
     30 #include <taler/taler_curl_lib.h>
     31 #include <taler/taler_merchant_service.h>
     32 
     33 
     34 /**
     35  * @brief A Contract Operation Handle
     36  */
     37 struct ANASTASIS_TruthSolveOperation
     38 {
     39   /**
     40    * The url for this request, including parameters.
     41    */
     42   char *url;
     43 
     44   /**
     45    * Handle for the request.
     46    */
     47   struct GNUNET_CURL_Job *job;
     48 
     49   /**
     50    * Function to call with the result.
     51    */
     52   ANASTASIS_TruthSolveCallback cb;
     53 
     54   /**
     55    * Closure for @a cb.
     56    */
     57   void *cb_cls;
     58 
     59   /**
     60    * Context for #TEH_curl_easy_post(). Keeps the data that must
     61    * persist for Curl to make the upload.
     62    */
     63   struct TALER_CURL_PostContext ctx;
     64 
     65   /**
     66    * Payment URI we received from the service, or NULL.
     67    */
     68   char *pay_uri;
     69 
     70   /**
     71    * Content type of the body.
     72    */
     73   char *content_type;
     74 };
     75 
     76 
     77 void
     78 ANASTASIS_truth_solve_cancel (
     79   struct ANASTASIS_TruthSolveOperation *tso)
     80 {
     81   if (NULL != tso->job)
     82   {
     83     GNUNET_CURL_job_cancel (tso->job);
     84     tso->job = NULL;
     85   }
     86   GNUNET_free (tso->pay_uri);
     87   GNUNET_free (tso->url);
     88   GNUNET_free (tso->content_type);
     89   TALER_curl_easy_post_finished (&tso->ctx);
     90   GNUNET_free (tso);
     91 }
     92 
     93 
     94 /**
     95  * Process POST /truth/$TID/solve response
     96  *
     97  * @param cls our `struct ANASTASIS_TruthSolveOperation *`
     98  * @param response_code the HTTP status
     99  * @param data the body of the response
    100  * @param data_size number of bytes in @a data
    101  */
    102 static void
    103 handle_truth_solve_finished (void *cls,
    104                              long response_code,
    105                              const void *data,
    106                              size_t data_size)
    107 {
    108   struct ANASTASIS_TruthSolveOperation *tso = cls;
    109   struct ANASTASIS_TruthSolveReply tsr = {
    110     .http_status = response_code
    111   };
    112 
    113   tso->job = NULL;
    114   switch (response_code)
    115   {
    116   case 0:
    117     /* Hard error */
    118     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    119                 "Backend didn't even return from POST /truth/$TID/solve\n");
    120     tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    121     break;
    122   case MHD_HTTP_OK:
    123     if (sizeof (tsr.details.success.eks) != data_size)
    124     {
    125       GNUNET_break_op (0);
    126       tsr.http_status = 0;
    127       tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    128       break;
    129     }
    130     /* Success, call callback with all details! */
    131     memcpy (&tsr.details.success.eks,
    132             data,
    133             data_size);
    134     break;
    135   case MHD_HTTP_BAD_REQUEST:
    136     /* This should never happen, either us or the anastasis server is buggy
    137        (or API version conflict); just pass JSON reply to the application */
    138     GNUNET_break (0);
    139     tsr.ec = TALER_JSON_get_error_code2 (data,
    140                                          data_size);
    141     break;
    142   case MHD_HTTP_PAYMENT_REQUIRED:
    143     {
    144       struct TALER_MERCHANT_PayUriData pd;
    145 
    146       if ( (NULL == tso->pay_uri) ||
    147            (GNUNET_OK !=
    148             TALER_MERCHANT_parse_pay_uri (tso->pay_uri,
    149                                           &pd)) )
    150       {
    151         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    152                     "Failed to parse `%s'\n",
    153                     tso->pay_uri);
    154         tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    155         break;
    156       }
    157       if (GNUNET_OK !=
    158           GNUNET_STRINGS_string_to_data (
    159             pd.order_id,
    160             strlen (pd.order_id),
    161             &tsr.details.payment_required.ps,
    162             sizeof (tsr.details.payment_required.ps)))
    163       {
    164         GNUNET_break (0);
    165         tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
    166         TALER_MERCHANT_parse_pay_uri_free (&pd);
    167         break;
    168       }
    169       tsr.details.payment_required.pd = &pd;
    170       tsr.details.payment_required.payment_request = tso->pay_uri;
    171       tso->cb (tso->cb_cls,
    172                &tsr);
    173       TALER_MERCHANT_parse_pay_uri_free (&pd);
    174       ANASTASIS_truth_solve_cancel (tso);
    175       return;
    176     }
    177     break;
    178   case MHD_HTTP_FORBIDDEN:
    179     tsr.ec = TALER_JSON_get_error_code2 (data,
    180                                          data_size);
    181     break;
    182   case MHD_HTTP_NOT_FOUND:
    183     tsr.ec = TALER_JSON_get_error_code2 (data,
    184                                          data_size);
    185     break;
    186   case MHD_HTTP_REQUEST_TIMEOUT:
    187     tsr.ec = TALER_JSON_get_error_code2 (data,
    188                                          data_size);
    189     break;
    190   case MHD_HTTP_TOO_MANY_REQUESTS:
    191     {
    192       json_t *reply;
    193 
    194       reply = json_loadb (data,
    195                           data_size,
    196                           JSON_REJECT_DUPLICATES,
    197                           NULL);
    198       if (NULL == reply)
    199       {
    200         GNUNET_break_op (0);
    201         tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    202         break;
    203       }
    204 
    205       {
    206         struct GNUNET_JSON_Specification spec[] = {
    207           GNUNET_JSON_spec_uint32 (
    208             "request_limit",
    209             &tsr.details.too_many_requests.request_limit),
    210           GNUNET_JSON_spec_relative_time (
    211             "request_frequency",
    212             &tsr.details.too_many_requests.request_frequency),
    213           GNUNET_JSON_spec_end ()
    214         };
    215         if (GNUNET_OK !=
    216             GNUNET_JSON_parse (reply,
    217                                spec,
    218                                NULL, NULL))
    219         {
    220           GNUNET_break_op (0);
    221           tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    222           json_decref (reply);
    223           break;
    224         }
    225         json_decref (reply);
    226         break;
    227       }
    228     }
    229   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    230     /* Server had an internal issue; we should retry, but this API
    231        leaves this to the application */
    232     tsr.ec = TALER_JSON_get_error_code2 (data,
    233                                          data_size);
    234     break;
    235   case MHD_HTTP_BAD_GATEWAY:
    236     tsr.ec = TALER_JSON_get_error_code2 (data,
    237                                          data_size);
    238     break;
    239   default:
    240     /* unexpected response code */
    241     tsr.ec = TALER_JSON_get_error_code2 (data,
    242                                          data_size);
    243     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    244                 "Unexpected response code %u/%d to POST /truth/$TID/solve\n",
    245                 (unsigned int) response_code,
    246                 (int) tsr.ec);
    247     break;
    248   }
    249   tso->cb (tso->cb_cls,
    250            &tsr);
    251   ANASTASIS_truth_solve_cancel (tso);
    252 }
    253 
    254 
    255 /**
    256  * Patch value in @a val, replacing new line with '\0'.
    257  *
    258  * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in.
    259  */
    260 static void
    261 patch_value (char *val)
    262 {
    263   size_t len;
    264 
    265   /* found location URI we care about! */
    266   len = strlen (val);
    267   while ( (len > 0) &&
    268           ( ('\n' == val[len - 1]) ||
    269             ('\r' == val[len - 1]) ) )
    270   {
    271     len--;
    272     val[len] = '\0';
    273   }
    274 }
    275 
    276 
    277 /**
    278  * Handle HTTP header received by curl.
    279  *
    280  * @param buffer one line of HTTP header data
    281  * @param size size of an item
    282  * @param nitems number of items passed
    283  * @param userdata our `struct ANASTASIS_StorePolicyOperation *`
    284  * @return `size * nitems`
    285  */
    286 static size_t
    287 handle_header (char *buffer,
    288                size_t size,
    289                size_t nitems,
    290                void *userdata)
    291 {
    292   struct ANASTASIS_TruthSolveOperation *tso = userdata;
    293   size_t total = size * nitems;
    294   char *ndup;
    295   const char *hdr_type;
    296   char *hdr_val;
    297   char *sp;
    298 
    299   ndup = GNUNET_strndup (buffer,
    300                          total);
    301   hdr_type = strtok_r (ndup,
    302                        ":",
    303                        &sp);
    304   if (NULL == hdr_type)
    305   {
    306     GNUNET_free (ndup);
    307     return total;
    308   }
    309   hdr_val = strtok_r (NULL,
    310                       "",
    311                       &sp);
    312   if (NULL == hdr_val)
    313   {
    314     GNUNET_free (ndup);
    315     return total;
    316   }
    317   if (' ' == *hdr_val)
    318     hdr_val++;
    319   if (0 == strcasecmp (hdr_type,
    320                        ANASTASIS_HTTP_HEADER_TALER))
    321   {
    322     /* found payment URI we care about! */
    323     GNUNET_free (tso->pay_uri);
    324     tso->pay_uri = GNUNET_strdup (hdr_val);
    325     patch_value (tso->pay_uri);
    326   }
    327   if (0 == strcasecmp (hdr_type,
    328                        MHD_HTTP_HEADER_CONTENT_TYPE))
    329   {
    330     /* found location URI we care about! */
    331     GNUNET_free (tso->content_type);
    332     tso->content_type = GNUNET_strdup (hdr_val);
    333     patch_value (tso->content_type);
    334   }
    335   GNUNET_free (ndup);
    336   return total;
    337 }
    338 
    339 
    340 struct ANASTASIS_TruthSolveOperation *
    341 ANASTASIS_truth_solve (
    342   struct GNUNET_CURL_Context *ctx,
    343   const char *backend_url,
    344   const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
    345   const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key,
    346   const struct ANASTASIS_PaymentSecretP *payment_secret,
    347   struct GNUNET_TIME_Relative timeout,
    348   const struct GNUNET_HashCode *hashed_answer,
    349   ANASTASIS_TruthSolveCallback cb,
    350   void *cb_cls)
    351 {
    352   struct ANASTASIS_TruthSolveOperation *tso;
    353   CURL *eh;
    354   char *path;
    355   unsigned long long tms;
    356   json_t *body;
    357 
    358   body = GNUNET_JSON_PACK (
    359     GNUNET_JSON_pack_data_auto ("truth_decryption_key",
    360                                 truth_key),
    361     GNUNET_JSON_pack_data_auto ("h_response",
    362                                 hashed_answer),
    363     GNUNET_JSON_pack_allow_null (
    364       GNUNET_JSON_pack_data_auto ("payment_secret",
    365                                   payment_secret)));
    366   GNUNET_assert (NULL != body);
    367 
    368   tms = (unsigned long long) (timeout.rel_value_us
    369                               / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
    370   tso = GNUNET_new (struct ANASTASIS_TruthSolveOperation);
    371   tso->cb = cb;
    372   tso->cb_cls = cb_cls;
    373   {
    374     char *uuid_str;
    375 
    376     uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid,
    377                                                     sizeof (*truth_uuid));
    378     GNUNET_asprintf (&path,
    379                      "truth/%s/solve",
    380                      uuid_str);
    381     GNUNET_free (uuid_str);
    382   }
    383   {
    384     char timeout_ms[32];
    385 
    386     GNUNET_snprintf (timeout_ms,
    387                      sizeof (timeout_ms),
    388                      "%llu",
    389                      tms);
    390     tso->url = TALER_url_join (backend_url,
    391                                path,
    392                                "timeout_ms",
    393                                (! GNUNET_TIME_relative_is_zero (timeout))
    394                                ? timeout_ms
    395                                : NULL,
    396                                NULL);
    397   }
    398   GNUNET_free (path);
    399   eh = ANASTASIS_curl_easy_get_ (tso->url);
    400   if ( (NULL == eh) ||
    401        (GNUNET_OK !=
    402         TALER_curl_easy_post (&tso->ctx,
    403                               eh,
    404                               body)) )
    405   {
    406     GNUNET_break (0);
    407     if (NULL != eh)
    408       curl_easy_cleanup (eh);
    409     json_decref (body);
    410     GNUNET_free (tso->url);
    411     GNUNET_free (tso);
    412     return NULL;
    413   }
    414   json_decref (body);
    415   if (0 != tms)
    416     GNUNET_assert (CURLE_OK ==
    417                    curl_easy_setopt (eh,
    418                                      CURLOPT_TIMEOUT_MS,
    419                                      (long) (tms + 5000)));
    420   GNUNET_assert (CURLE_OK ==
    421                  curl_easy_setopt (eh,
    422                                    CURLOPT_HEADERFUNCTION,
    423                                    &handle_header));
    424   GNUNET_assert (CURLE_OK ==
    425                  curl_easy_setopt (eh,
    426                                    CURLOPT_HEADERDATA,
    427                                    tso));
    428   tso->job = GNUNET_CURL_job_add_raw (ctx,
    429                                       eh,
    430                                       tso->ctx.headers,
    431                                       &handle_truth_solve_finished,
    432                                       tso);
    433   return tso;
    434 }
    435 
    436 
    437 /* end of anastasis_api_truth_solve.c */