/* This file is part of Anastasis Copyright (C) 2020, 2021, 2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see */ /** * @file restclient/anastasis_api_truth_solve.c * @brief Implementation of the POST /truth/$TID/solve request * @author Christian Grothoff * @author Dennis Neufeld * @author Dominik Meister */ #include "platform.h" #include #include #include /* just for HTTP status codes */ #include "anastasis_service.h" #include "anastasis_api_curl_defaults.h" #include #include #include /** * @brief A Contract Operation Handle */ struct ANASTASIS_TruthSolveOperation { /** * The url for this request, including parameters. */ char *url; /** * Handle for the request. */ struct GNUNET_CURL_Job *job; /** * Function to call with the result. */ ANASTASIS_TruthSolveCallback cb; /** * Closure for @a cb. */ void *cb_cls; /** * Context for #TEH_curl_easy_post(). Keeps the data that must * persist for Curl to make the upload. */ struct TALER_CURL_PostContext ctx; /** * Payment URI we received from the service, or NULL. */ char *pay_uri; /** * Content type of the body. */ char *content_type; }; void ANASTASIS_truth_solve_cancel ( struct ANASTASIS_TruthSolveOperation *tso) { if (NULL != tso->job) { GNUNET_CURL_job_cancel (tso->job); tso->job = NULL; } GNUNET_free (tso->pay_uri); GNUNET_free (tso->url); GNUNET_free (tso->content_type); TALER_curl_easy_post_finished (&tso->ctx); GNUNET_free (tso); } /** * Process POST /truth/$TID/solve response * * @param cls our `struct ANASTASIS_TruthSolveOperation *` * @param response_code the HTTP status * @param data the body of the response * @param data_size number of bytes in @a data */ static void handle_truth_solve_finished (void *cls, long response_code, const void *data, size_t data_size) { struct ANASTASIS_TruthSolveOperation *tso = cls; struct ANASTASIS_TruthSolveReply tsr = { .http_status = response_code }; tso->job = NULL; switch (response_code) { case 0: /* Hard error */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Backend didn't even return from POST /truth/$TID/solve\n"); tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; case MHD_HTTP_OK: if (sizeof (tsr.details.success.eks) != data_size) { GNUNET_break_op (0); tsr.http_status = 0; tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } /* Success, call callback with all details! */ memcpy (&tsr.details.success.eks, data, data_size); break; case MHD_HTTP_BAD_REQUEST: /* This should never happen, either us or the anastasis server is buggy (or API version conflict); just pass JSON reply to the application */ GNUNET_break (0); tsr.ec = TALER_JSON_get_error_code2 (data, data_size); break; case MHD_HTTP_PAYMENT_REQUIRED: { struct TALER_MERCHANT_PayUriData pd; if ( (NULL == tso->pay_uri) || (GNUNET_OK != TALER_MERCHANT_parse_pay_uri (tso->pay_uri, &pd)) ) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to parse `%s'\n", tso->pay_uri); tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } if (GNUNET_OK != GNUNET_STRINGS_string_to_data ( pd.order_id, strlen (pd.order_id), &tsr.details.payment_required.ps, sizeof (tsr.details.payment_required.ps))) { GNUNET_break (0); tsr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; TALER_MERCHANT_parse_pay_uri_free (&pd); break; } tsr.details.payment_required.pd = &pd; tsr.details.payment_required.payment_request = tso->pay_uri; tso->cb (tso->cb_cls, &tsr); TALER_MERCHANT_parse_pay_uri_free (&pd); ANASTASIS_truth_solve_cancel (tso); return; } break; case MHD_HTTP_FORBIDDEN: tsr.ec = TALER_JSON_get_error_code2 (data, data_size); break; case MHD_HTTP_NOT_FOUND: tsr.ec = TALER_JSON_get_error_code2 (data, data_size); break; case MHD_HTTP_REQUEST_TIMEOUT: tsr.ec = TALER_JSON_get_error_code2 (data, data_size); break; case MHD_HTTP_TOO_MANY_REQUESTS: { json_t *reply; reply = json_loadb (data, data_size, JSON_REJECT_DUPLICATES, NULL); if (NULL == reply) { GNUNET_break_op (0); tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; break; } { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_uint32 ( "request_limit", &tsr.details.too_many_requests.request_limit), GNUNET_JSON_spec_relative_time ( "request_frequency", &tsr.details.too_many_requests.request_frequency), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (reply, spec, NULL, NULL)) { GNUNET_break_op (0); tsr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; json_decref (reply); break; } json_decref (reply); break; } } case MHD_HTTP_INTERNAL_SERVER_ERROR: /* Server had an internal issue; we should retry, but this API leaves this to the application */ tsr.ec = TALER_JSON_get_error_code2 (data, data_size); break; case MHD_HTTP_BAD_GATEWAY: tsr.ec = TALER_JSON_get_error_code2 (data, data_size); break; default: /* unexpected response code */ tsr.ec = TALER_JSON_get_error_code2 (data, data_size); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unexpected response code %u/%d to POST /truth/$TID/solve\n", (unsigned int) response_code, (int) tsr.ec); break; } tso->cb (tso->cb_cls, &tsr); ANASTASIS_truth_solve_cancel (tso); } /** * Patch value in @a val, replacing new line with '\0'. * * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in. */ static void patch_value (char *val) { size_t len; /* found location URI we care about! */ len = strlen (val); while ( (len > 0) && ( ('\n' == val[len - 1]) || ('\r' == val[len - 1]) ) ) { len--; val[len] = '\0'; } } /** * Handle HTTP header received by curl. * * @param buffer one line of HTTP header data * @param size size of an item * @param nitems number of items passed * @param userdata our `struct ANASTASIS_StorePolicyOperation *` * @return `size * nitems` */ static size_t handle_header (char *buffer, size_t size, size_t nitems, void *userdata) { struct ANASTASIS_TruthSolveOperation *tso = userdata; size_t total = size * nitems; char *ndup; const char *hdr_type; char *hdr_val; char *sp; ndup = GNUNET_strndup (buffer, total); hdr_type = strtok_r (ndup, ":", &sp); if (NULL == hdr_type) { GNUNET_free (ndup); return total; } hdr_val = strtok_r (NULL, "", &sp); if (NULL == hdr_val) { GNUNET_free (ndup); return total; } if (' ' == *hdr_val) hdr_val++; if (0 == strcasecmp (hdr_type, ANASTASIS_HTTP_HEADER_TALER)) { /* found payment URI we care about! */ GNUNET_free (tso->pay_uri); tso->pay_uri = GNUNET_strdup (hdr_val); patch_value (tso->pay_uri); } if (0 == strcasecmp (hdr_type, MHD_HTTP_HEADER_CONTENT_TYPE)) { /* found location URI we care about! */ GNUNET_free (tso->content_type); tso->content_type = GNUNET_strdup (hdr_val); patch_value (tso->content_type); } GNUNET_free (ndup); return total; } struct ANASTASIS_TruthSolveOperation * ANASTASIS_truth_solve ( struct GNUNET_CURL_Context *ctx, const char *backend_url, const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, const struct ANASTASIS_PaymentSecretP *payment_secret, struct GNUNET_TIME_Relative timeout, const struct GNUNET_HashCode *hashed_answer, ANASTASIS_TruthSolveCallback cb, void *cb_cls) { struct ANASTASIS_TruthSolveOperation *tso; CURL *eh; char *path; unsigned long long tms; json_t *body; body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("truth_decryption_key", truth_key), GNUNET_JSON_pack_data_auto ("h_response", hashed_answer), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_data_auto ("payment_secret", payment_secret))); GNUNET_assert (NULL != body); tms = (unsigned long long) (timeout.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); tso = GNUNET_new (struct ANASTASIS_TruthSolveOperation); tso->cb = cb; tso->cb_cls = cb_cls; { char *uuid_str; uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid, sizeof (*truth_uuid)); GNUNET_asprintf (&path, "truth/%s/solve", uuid_str); GNUNET_free (uuid_str); } { char timeout_ms[32]; GNUNET_snprintf (timeout_ms, sizeof (timeout_ms), "%llu", tms); tso->url = TALER_url_join (backend_url, path, "timeout_ms", (! GNUNET_TIME_relative_is_zero (timeout)) ? timeout_ms : NULL, NULL); } GNUNET_free (path); eh = ANASTASIS_curl_easy_get_ (tso->url); if ( (NULL == eh) || (GNUNET_OK != TALER_curl_easy_post (&tso->ctx, eh, body)) ) { GNUNET_break (0); if (NULL != eh) curl_easy_cleanup (eh); json_decref (body); GNUNET_free (tso->url); GNUNET_free (tso); return NULL; } json_decref (body); if (0 != tms) GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_TIMEOUT_MS, (long) (tms + 5000))); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HEADERFUNCTION, &handle_header)); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_HEADERDATA, tso)); tso->job = GNUNET_CURL_job_add_raw (ctx, eh, tso->ctx.headers, &handle_truth_solve_finished, tso); return tso; } /* end of anastasis_api_truth_solve.c */