diff options
Diffstat (limited to 'src/restclient')
-rw-r--r-- | src/restclient/Makefile.am | 10 | ||||
-rw-r--r-- | src/restclient/anastasis_api_config.c | 134 | ||||
-rw-r--r-- | src/restclient/anastasis_api_curl_defaults.c | 19 | ||||
-rw-r--r-- | src/restclient/anastasis_api_curl_defaults.h | 6 | ||||
-rw-r--r-- | src/restclient/anastasis_api_keyshare_lookup.c | 67 | ||||
-rw-r--r-- | src/restclient/anastasis_api_policy_lookup.c | 29 | ||||
-rw-r--r-- | src/restclient/anastasis_api_policy_meta_lookup.c | 272 | ||||
-rw-r--r-- | src/restclient/anastasis_api_policy_store.c | 53 | ||||
-rw-r--r-- | src/restclient/anastasis_api_truth_challenge.c | 456 | ||||
-rw-r--r-- | src/restclient/anastasis_api_truth_solve.c | 437 | ||||
-rw-r--r-- | src/restclient/anastasis_api_truth_store.c | 18 |
11 files changed, 1367 insertions, 134 deletions
diff --git a/src/restclient/Makefile.am b/src/restclient/Makefile.am index 075d3a7..1a4d83c 100644 --- a/src/restclient/Makefile.am +++ b/src/restclient/Makefile.am @@ -17,9 +17,11 @@ libanastasisrest_la_LDFLAGS = \ libanastasisrest_la_SOURCES = \ anastasis_api_config.c \ anastasis_api_policy_store.c \ - anastasis_api_truth_store.c \ anastasis_api_policy_lookup.c \ - anastasis_api_keyshare_lookup.c \ + anastasis_api_policy_meta_lookup.c \ + anastasis_api_truth_challenge.c \ + anastasis_api_truth_solve.c \ + anastasis_api_truth_store.c \ anastasis_api_curl_defaults.c anastasis_api_curl_defaults.h libanastasisrest_la_LIBADD = \ -lgnunetcurl \ @@ -27,9 +29,10 @@ libanastasisrest_la_LIBADD = \ -lgnunetutil \ -ljansson \ -ltalerjson \ - -ltalerutil \ + -ltalercurl \ -ltalermerchant \ -ltalerjson \ + -ltalerutil \ $(XLIB) if HAVE_LIBCURL @@ -39,4 +42,3 @@ if HAVE_LIBGNURL libanastasisrest_la_LIBADD += -lgnurl endif endif - diff --git a/src/restclient/anastasis_api_config.c b/src/restclient/anastasis_api_config.c index acb0967..aee0357 100644 --- a/src/restclient/anastasis_api_config.c +++ b/src/restclient/anastasis_api_config.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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 Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -86,6 +86,10 @@ handle_config_finished (void *cls, { struct ANASTASIS_ConfigOperation *co = cls; const json_t *json = response; + struct ANASTASIS_Config acfg = { + .http_status = response_code, + .response = json + }; co->job = NULL; switch (response_code) @@ -99,29 +103,29 @@ handle_config_finished (void *cls, case MHD_HTTP_OK: { const char *name; - struct ANASTASIS_Config acfg; - json_t *methods; + const json_t *methods; + struct TALER_JSON_ProtocolVersion pv; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("name", &name), GNUNET_JSON_spec_string ("business_name", - &acfg.business_name), + &acfg.details.ok.business_name), GNUNET_JSON_spec_string ("version", - &acfg.version), - GNUNET_JSON_spec_string ("currency", - &acfg.currency), - GNUNET_JSON_spec_json ("methods", - &methods), + &acfg.details.ok.version), + TALER_JSON_spec_version ("version", + &pv), + GNUNET_JSON_spec_array_const ("methods", + &methods), GNUNET_JSON_spec_uint32 ("storage_limit_in_megabytes", - &acfg.storage_limit_in_megabytes), + &acfg.details.ok.storage_limit_in_megabytes), TALER_JSON_spec_amount_any ("annual_fee", - &acfg.annual_fee), + &acfg.details.ok.annual_fee), TALER_JSON_spec_amount_any ("truth_upload_fee", - &acfg.truth_upload_fee), + &acfg.details.ok.truth_upload_fee), TALER_JSON_spec_amount_any ("liability_limit", - &acfg.liability_limit), - GNUNET_JSON_spec_fixed_auto ("server_salt", - &acfg.salt), + &acfg.details.ok.liability_limit), + GNUNET_JSON_spec_fixed_auto ("provider_salt", + &acfg.details.ok.provider_salt), GNUNET_JSON_spec_end () }; @@ -131,80 +135,54 @@ handle_config_finished (void *cls, NULL, NULL)) { GNUNET_break_op (0); - response_code = 0; + json_dumpf (json, + stderr, + JSON_INDENT (2)); + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } if (0 != strcmp (name, "anastasis")) { GNUNET_JSON_parse_free (spec); - response_code = 0; + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } + if ( (ANASTASIS_PROTOCOL_CURRENT < pv.current) && + (ANASTASIS_PROTOCOL_CURRENT < pv.current - pv.age) ) { - unsigned int age; - unsigned int revision; - unsigned int current; - char dummy; - - if (3 != sscanf (acfg.version, - "%u:%u:%u%c", - ¤t, - &revision, - &age, - &dummy)) - { - GNUNET_break_op (0); - response_code = 0; - GNUNET_JSON_parse_free (spec); - break; - } - if ( (ANASTASIS_PROTOCOL_CURRENT < current) && - (ANASTASIS_PROTOCOL_CURRENT < current - age) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Provider protocol version too new\n"); - response_code = 0; - GNUNET_JSON_parse_free (spec); - break; - } - if ( (ANASTASIS_PROTOCOL_CURRENT > current) && - (ANASTASIS_PROTOCOL_CURRENT - ANASTASIS_PROTOCOL_AGE > current) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Provider protocol version too old\n"); - GNUNET_break_op (0); - response_code = 0; - GNUNET_JSON_parse_free (spec); - break; - } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Provider protocol version too new\n"); + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_VERSION_MALFORMED; + break; } - if ( (GNUNET_OK != - TALER_amount_cmp_currency (&acfg.liability_limit, - &acfg.annual_fee)) || - (0 != - strcasecmp (acfg.currency, - acfg.annual_fee.currency)) ) + if ( (ANASTASIS_PROTOCOL_CURRENT > pv.current) && + (ANASTASIS_PROTOCOL_CURRENT - ANASTASIS_PROTOCOL_AGE > pv.current) ) { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Provider protocol version too old\n"); GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - response_code = 0; + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_VERSION_MALFORMED; break; } - - if (! json_is_array (methods)) + acfg.details.ok.methods_length = (unsigned int) json_array_size (methods); + if (((size_t) acfg.details.ok.methods_length) != + json_array_size (methods)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - response_code = 0; + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED; break; } - acfg.methods_length = json_array_size (methods); { - struct ANASTASIS_AuthorizationMethodConfig mcfg[GNUNET_NZL ( - acfg.methods_length)]; + struct ANASTASIS_AuthorizationMethodConfig mcfg[ + GNUNET_NZL (acfg.details.ok.methods_length)]; - for (unsigned int i = 0; i<acfg.methods_length; i++) + for (unsigned int i = 0; i<acfg.details.ok.methods_length; i++) { struct ANASTASIS_AuthorizationMethodConfig *m = &mcfg[i]; struct GNUNET_JSON_Specification spec[] = { @@ -222,16 +200,17 @@ handle_config_finished (void *cls, NULL, NULL)) ) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - response_code = 0; + acfg.http_status = 0; + acfg.ec = TALER_EC_GENERIC_REPLY_MALFORMED; goto end; } } - acfg.methods = mcfg; + acfg.details.ok.methods = mcfg; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Good backend found at `%s'\n", + co->url); co->cb (co->cb_cls, - MHD_HTTP_OK, &acfg); - GNUNET_JSON_parse_free (spec); ANASTASIS_config_cancel (co); return; } @@ -257,8 +236,7 @@ handle_config_finished (void *cls, } end: co->cb (co->cb_cls, - response_code, - NULL); + &acfg); ANASTASIS_config_cancel (co); } diff --git a/src/restclient/anastasis_api_curl_defaults.c b/src/restclient/anastasis_api_curl_defaults.c index b777bae..f64347b 100644 --- a/src/restclient/anastasis_api_curl_defaults.c +++ b/src/restclient/anastasis_api_curl_defaults.c @@ -3,14 +3,14 @@ Copyright (C) 2014-2019 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software + 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 Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ @@ -20,6 +20,7 @@ * @author Florian Dold */ #include "platform.h" +#include <taler/taler_curl_lib.h> #include "anastasis_api_curl_defaults.h" CURL * @@ -34,13 +35,17 @@ ANASTASIS_curl_easy_get_ (const char *url) curl_easy_setopt (eh, CURLOPT_URL, url)); - GNUNET_assert (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_FOLLOWLOCATION, - 1L)); + TALER_curl_set_secure_redirect_policy (eh, + url); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, CURLOPT_TCP_FASTOPEN, 1L)); + /* Enable compression (using whatever curl likes), see + https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html */ + GNUNET_break (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_ACCEPT_ENCODING, + "")); return eh; } diff --git a/src/restclient/anastasis_api_curl_defaults.h b/src/restclient/anastasis_api_curl_defaults.h index 4d990af..948b931 100644 --- a/src/restclient/anastasis_api_curl_defaults.h +++ b/src/restclient/anastasis_api_curl_defaults.h @@ -3,14 +3,14 @@ Copyright (C) 2014-2019 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software + 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 Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ diff --git a/src/restclient/anastasis_api_keyshare_lookup.c b/src/restclient/anastasis_api_keyshare_lookup.c index 50e0d67..4840a7e 100644 --- a/src/restclient/anastasis_api_keyshare_lookup.c +++ b/src/restclient/anastasis_api_keyshare_lookup.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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 Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -228,7 +228,6 @@ handle_keyshare_lookup_finished (void *cls, kdd.status = ANASTASIS_KSD_REDIRECT_FOR_AUTHENTICATION; kdd.details.redirect_url = kslo->location; break; - case MHD_HTTP_ALREADY_REPORTED: case MHD_HTTP_FORBIDDEN: /* Nothing really to verify, authentication required/failed */ kdd.status = ANASTASIS_KSD_INVALID_ANSWER; @@ -245,19 +244,57 @@ handle_keyshare_lookup_finished (void *cls, /* Nothing really to verify */ kdd.status = ANASTASIS_KSD_AUTHENTICATION_TIMEOUT; break; - case MHD_HTTP_GONE: - /* Nothing really to verify */ - kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN; - break; - case MHD_HTTP_EXPECTATION_FAILED: + case MHD_HTTP_CONFLICT: /* Nothing really to verify */ kdd.status = ANASTASIS_KSD_CLIENT_FAILURE; - kdd.details.server_failure.http_status = MHD_HTTP_EXPECTATION_FAILED; + kdd.details.server_failure.http_status = MHD_HTTP_CONFLICT; kdd.details.server_failure.ec = TALER_JSON_get_error_code2 (data, data_size); break; + case MHD_HTTP_GONE: + /* Nothing really to verify */ + kdd.status = ANASTASIS_KSD_TRUTH_UNKNOWN; + break; case MHD_HTTP_TOO_MANY_REQUESTS: kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED; + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint32 ( + "request_limit", + &kdd.details.rate_limit_exceeded.request_limit), + GNUNET_JSON_spec_relative_time ( + "request_frequency", + &kdd.details.rate_limit_exceeded.request_frequency), + GNUNET_JSON_spec_end () + }; + json_t *reply; + + reply = json_loadb (data, + data_size, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == reply) + { + GNUNET_break_op (0); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + kdd.details.server_failure.http_status = response_code; + break; + } + if (GNUNET_OK != + GNUNET_JSON_parse (reply, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + kdd.status = ANASTASIS_KSD_SERVER_ERROR; + kdd.details.server_failure.ec = TALER_JSON_get_error_code (reply); + kdd.details.server_failure.http_status = response_code; + 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 @@ -411,7 +448,7 @@ ANASTASIS_keyshare_lookup ( char *val; char *hdr; - /* Set Truth-Decryption-Key header */ + /* Set Anastasis-Truth-Decryption-Key header */ val = GNUNET_STRINGS_data_to_string_alloc (truth_key, sizeof (*truth_key)); GNUNET_asprintf (&hdr, @@ -487,8 +524,8 @@ ANASTASIS_keyshare_lookup ( answer_s, "timeout_ms", (0 != timeout.rel_value_us) - ? timeout_ms - : NULL, + ? timeout_ms + : NULL, NULL); GNUNET_free (answer_s); } @@ -500,8 +537,8 @@ ANASTASIS_keyshare_lookup ( path, "timeout_ms", (0 != timeout.rel_value_us) - ? timeout_ms - : NULL, + ? timeout_ms + : NULL, NULL); } } diff --git a/src/restclient/anastasis_api_policy_lookup.c b/src/restclient/anastasis_api_policy_lookup.c index e21ed58..b3132ef 100644 --- a/src/restclient/anastasis_api_policy_lookup.c +++ b/src/restclient/anastasis_api_policy_lookup.c @@ -3,16 +3,16 @@ Copyright (C) 2014-2019 Anastasis SARL ANASTASIS is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as + it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2.1, 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 Lesser General Public License for more details. + GNU General Public License for more details. - You should have received a copy of the GNU Lesser General Public + You should have received a copy of the GNU General Public License along with ANASTASIS; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/> */ @@ -106,6 +106,9 @@ handle_policy_lookup_finished (void *cls, size_t data_size) { struct ANASTASIS_PolicyLookupOperation *plo = cls; + struct ANASTASIS_DownloadDetails dd = { + .http_status = response_code + }; plo->job = NULL; switch (response_code) @@ -117,7 +120,6 @@ handle_policy_lookup_finished (void *cls, break; case MHD_HTTP_OK: { - struct ANASTASIS_DownloadDetails dd; struct ANASTASIS_UploadSignaturePS usp = { .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD), .purpose.size = htonl (sizeof (usp)), @@ -133,18 +135,17 @@ handle_policy_lookup_finished (void *cls, &plo->account_pub.pub)) { GNUNET_break_op (0); - response_code = 0; + dd.http_status = 0; + dd.ec = -1; // FIXME: needs new code in Gana! break; } /* Success, call callback with all details! */ - memset (&dd, 0, sizeof (dd)); - dd.sig = plo->account_sig; - dd.curr_policy_hash = usp.new_recovery_data_hash; - dd.policy = data; - dd.policy_size = data_size; - dd.version = plo->version; + dd.details.ok.sig = plo->account_sig; + dd.details.ok.curr_policy_hash = usp.new_recovery_data_hash; + dd.details.ok.policy = data; + dd.details.ok.policy_size = data_size; + dd.details.ok.version = plo->version; plo->cb (plo->cb_cls, - response_code, &dd); plo->cb = NULL; ANASTASIS_policy_lookup_cancel (plo); @@ -167,12 +168,10 @@ handle_policy_lookup_finished (void *cls, "Unexpected response code %u\n", (unsigned int) response_code); GNUNET_break (0); - response_code = 0; break; } plo->cb (plo->cb_cls, - response_code, - NULL); + &dd); plo->cb = NULL; ANASTASIS_policy_lookup_cancel (plo); } diff --git a/src/restclient/anastasis_api_policy_meta_lookup.c b/src/restclient/anastasis_api_policy_meta_lookup.c new file mode 100644 index 0000000..cf381fd --- /dev/null +++ b/src/restclient/anastasis_api_policy_meta_lookup.c @@ -0,0 +1,272 @@ +/* + This file is part of ANASTASIS + Copyright (C) 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 2.1, + 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.LGPL. If not, + see <http://www.gnu.org/licenses/> +*/ + +/** + * @file restclient/anastasis_api_policy_meta_lookup.c + * @brief Implementation of the /policy/$POL/meta GET request + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <gnunet/gnunet_json_lib.h> +#include <taler/taler_signatures.h> + + +/** + * @brief A Meta Operation Handle + */ +struct ANASTASIS_PolicyMetaLookupOperation +{ + + /** + * 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_PolicyMetaLookupCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Reference to the execution context. + */ + struct GNUNET_CURL_Context *ctx; + + /** + * Public key of the account we are downloading from. + */ + struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub; + + /** + * Maximum version to fetch. + */ + uint32_t max_version; + +}; + + +/** + * Process GET /policy/$POL/meta response + * + * @param cls our `struct ANASTASIS_PolicyMetaLookupOperation *` + * @param response_code HTTP status + * @param data response body, a `json_t *`, NULL on error + */ +static void +handle_policy_meta_lookup_finished (void *cls, + long response_code, + const void *response) +{ + struct ANASTASIS_PolicyMetaLookupOperation *plo = cls; + const json_t *json = response; + struct ANASTASIS_MetaDownloadDetails mdd = { + .http_status = response_code, + .response = json + }; + + plo->job = NULL; + switch (response_code) + { + case 0: + /* Hard error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend didn't even return from GET /policy\n"); + break; + case MHD_HTTP_OK: + { + size_t mlen = json_object_size (json); + + /* put a cap, as we will stack-allocate below and the + current service LIMITs the result to 1000 anyway; + could theoretically be increased in the future, but + then we should not put this onto the stack anymore... */ + if (mlen > 10000) + { + GNUNET_break (0); + response_code = 0; + break; + } + { + struct ANASTASIS_MetaDataEntry metas[GNUNET_NZL (mlen)]; + void *md[GNUNET_NZL (mlen)]; + size_t off = 0; + const char *label; + const json_t *val; + + memset (md, + 0, + sizeof (md)); + mdd.details.ok.metas = metas; + mdd.details.ok.metas_length = mlen; + json_object_foreach ((json_t *) json, + label, + val) + { + unsigned int ver; + char dummy; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_varsize ("meta", + &md[off], + &metas[off].meta_data_size), + GNUNET_JSON_spec_timestamp ("upload_time", + &metas[off].server_time), + GNUNET_JSON_spec_end () + }; + + if (1 != sscanf (label, + "%u%c", + &ver, + &dummy)) + { + GNUNET_break (0); + mdd.http_status = 0; + mdd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + GNUNET_JSON_parse (val, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + mdd.http_status = 0; + mdd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + metas[off].version = (uint32_t) ver; + metas[off].meta_data = md[off]; + off++; + } + if (off < mlen) + { + GNUNET_break (0); + mdd.http_status = 0; + mdd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + for (size_t i = 0; i<off; i++) + GNUNET_free (md[i]); + break; + } + plo->cb (plo->cb_cls, + &mdd); + for (size_t i = 0; i<off; i++) + GNUNET_free (md[i]); + plo->cb = NULL; + } + ANASTASIS_policy_meta_lookup_cancel (plo); + return; + } + 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 */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + GNUNET_break (0); + break; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "HTTP status for policy meta lookup is %u\n", + (unsigned int) response_code); + plo->cb (plo->cb_cls, + &mdd); + plo->cb = NULL; + ANASTASIS_policy_meta_lookup_cancel (plo); +} + + +struct ANASTASIS_PolicyMetaLookupOperation * +ANASTASIS_policy_meta_lookup ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub, + uint32_t max_version, + ANASTASIS_PolicyMetaLookupCallback cb, + void *cb_cls) +{ + struct ANASTASIS_PolicyMetaLookupOperation *plo; + CURL *eh; + char *path; + + // FIXME: pass 'max_version' in CURL request! + GNUNET_assert (NULL != cb); + plo = GNUNET_new (struct ANASTASIS_PolicyMetaLookupOperation); + plo->account_pub = *anastasis_pub; + { + char *acc_pub_str; + + acc_pub_str = GNUNET_STRINGS_data_to_string_alloc (anastasis_pub, + sizeof (*anastasis_pub)); + GNUNET_asprintf (&path, + "policy/%s/meta", + acc_pub_str); + GNUNET_free (acc_pub_str); + } + plo->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + eh = ANASTASIS_curl_easy_get_ (plo->url); + GNUNET_assert (NULL != eh); + plo->cb = cb; + plo->cb_cls = cb_cls; + plo->job = GNUNET_CURL_job_add (ctx, + eh, + &handle_policy_meta_lookup_finished, + plo); + return plo; +} + + +void +ANASTASIS_policy_meta_lookup_cancel ( + struct ANASTASIS_PolicyMetaLookupOperation *plo) +{ + if (NULL != plo->job) + { + GNUNET_CURL_job_cancel (plo->job); + plo->job = NULL; + } + GNUNET_free (plo->url); + GNUNET_free (plo); +} diff --git a/src/restclient/anastasis_api_policy_store.c b/src/restclient/anastasis_api_policy_store.c index 5d44094..3afee7d 100644 --- a/src/restclient/anastasis_api_policy_store.c +++ b/src/restclient/anastasis_api_policy_store.c @@ -1,18 +1,18 @@ /* This file is part of ANASTASIS - Copyright (C) 2014-2021 Anastasis SARL + Copyright (C) 2014-2022 Anastasis SARL ANASTASIS is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as + it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2.1, 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 Lesser General Public License for more details. + GNU General Public License for more details. - You should have received a copy of the GNU Lesser General Public + You should have received a copy of the GNU General Public License along with ANASTASIS; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/> */ @@ -165,8 +165,7 @@ handle_policy_store_finished (void *cls, ud.us = ANASTASIS_US_SUCCESS; ud.details.success.curr_backup_hash = &pso->new_upload_hash; ud.details.success.policy_expiration - = GNUNET_TIME_absolute_add ( - GNUNET_TIME_UNIT_ZERO_ABS, + = GNUNET_TIME_relative_to_timestamp ( GNUNET_TIME_relative_multiply ( GNUNET_TIME_UNIT_SECONDS, expiration)); @@ -212,6 +211,10 @@ handle_policy_store_finished (void *cls, ud.us = ANASTASIS_US_PAYMENT_REQUIRED; ud.details.payment.payment_request = pso->pay_uri; break; + case MHD_HTTP_REQUEST_TIMEOUT: + ud.us = ANASTASIS_US_CLIENT_ERROR; + ud.ec = TALER_EC_ANASTASIS_PAYMENT_GENERIC_TIMEOUT; + break; case MHD_HTTP_PAYLOAD_TOO_LARGE: ud.us = ANASTASIS_US_CLIENT_ERROR; ud.ec = TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT; @@ -227,6 +230,11 @@ handle_policy_store_finished (void *cls, data_size); ud.us = ANASTASIS_US_SERVER_ERROR; break; + case MHD_HTTP_BAD_GATEWAY: + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + ud.us = ANASTASIS_US_SERVER_ERROR; + break; default: ud.ec = TALER_JSON_get_error_code2 (data, data_size); @@ -348,6 +356,8 @@ ANASTASIS_policy_store ( const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *anastasis_priv, const void *recovery_data, size_t recovery_data_size, + const void *recovery_meta_data, + size_t recovery_meta_data_size, uint32_t payment_years_requested, const struct ANASTASIS_PaymentSecretP *payment_secret, struct GNUNET_TIME_Relative payment_timeout, @@ -364,6 +374,11 @@ ANASTASIS_policy_store ( .purpose.size = htonl (sizeof (usp)) }; + if (NULL == recovery_meta_data) + { + GNUNET_break (0); + return NULL; + } tms = (unsigned long long) (payment_timeout.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); GNUNET_CRYPTO_hash (recovery_data, @@ -402,7 +417,7 @@ ANASTASIS_policy_store ( val = GNUNET_STRINGS_data_to_string_alloc (&usp.new_recovery_data_hash, sizeof (struct GNUNET_HashCode)); GNUNET_asprintf (&hdr, - "%s: %s", + "%s: \"%s\"", MHD_HTTP_HEADER_IF_NONE_MATCH, val); GNUNET_free (val); @@ -417,6 +432,30 @@ ANASTASIS_policy_store ( } job_headers = ext; + /* Setup meta-data header */ + { + char *meta_val; + + meta_val = GNUNET_STRINGS_data_to_string_alloc ( + recovery_meta_data, + recovery_meta_data_size); + GNUNET_asprintf (&hdr, + "%s: %s", + ANASTASIS_HTTP_HEADER_POLICY_META_DATA, + meta_val); + GNUNET_free (meta_val); + ext = curl_slist_append (job_headers, + hdr); + GNUNET_free (hdr); + if (NULL == ext) + { + GNUNET_break (0); + curl_slist_free_all (job_headers); + return NULL; + } + job_headers = ext; + } + /* Setup Payment-Identifier header */ if (NULL != payment_secret) { diff --git a/src/restclient/anastasis_api_truth_challenge.c b/src/restclient/anastasis_api_truth_challenge.c new file mode 100644 index 0000000..7a39db5 --- /dev/null +++ b/src/restclient/anastasis_api_truth_challenge.c @@ -0,0 +1,456 @@ +/* + 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file restclient/anastasis_api_truth_challenge.c + * @brief Implementation of the POST /truth/$TID/challenge request on the client-side + * @author Christian Grothoff + * @author Dennis Neufeld + * @author Dominik Meister + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <taler/taler_curl_lib.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * @brief A Contract Operation Handle + */ +struct ANASTASIS_TruthChallengeOperation +{ + /** + * The url for this request. + */ + char *url; + + /** + * Handle for the request. + */ + struct GNUNET_CURL_Job *job; + + /** + * Function to call with the result. + */ + ANASTASIS_TruthChallengeCallback 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_challenge_cancel ( + struct ANASTASIS_TruthChallengeOperation *tco) +{ + if (NULL != tco->job) + { + GNUNET_CURL_job_cancel (tco->job); + tco->job = NULL; + } + GNUNET_free (tco->pay_uri); + GNUNET_free (tco->url); + GNUNET_free (tco->content_type); + TALER_curl_easy_post_finished (&tco->ctx); + GNUNET_free (tco); +} + + +/** + * Process POST /truth/$TID/challenge response + * + * @param cls our `struct ANASTASIS_TruthChallengeOperation *` + * @param response_code the HTTP status + * @param response parsed JSON result, NULL one rrro + */ +static void +handle_truth_challenge_finished (void *cls, + long response_code, + const void *response) +{ + struct ANASTASIS_TruthChallengeOperation *tco = cls; + const json_t *j = response; + struct ANASTASIS_TruthChallengeDetails tcd = { + .http_status = response_code, + .response = j + }; + + tco->job = NULL; + switch (response_code) + { + case 0: + /* Hard error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Backend didn't even return from POST /truth/$TID/challenge\n"); + tcd.ec = TALER_EC_GENERIC_INVALID_RESPONSE; + break; + case MHD_HTTP_OK: + { + const char *ct; + const char *tan_hint = NULL; + const char *filename = NULL; + const json_t *wire_details = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ( + "challenge_type", + &ct), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("tan_address_hint", + &tan_hint), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("filename", + &filename), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_object_const ("wire_details", + &wire_details), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (j, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + tcd.http_status = 0; + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (0 == strcmp (ct, + "TAN_SENT")) + { + tcd.details.success.cs = ANASTASIS_CS_TAN_SENT; + tcd.details.success.details.tan_address_hint = tan_hint; + break; + } + if (0 == strcmp (ct, + "TAN_ALREADY_SENT")) + { + tcd.details.success.cs = ANASTASIS_CS_TAN_ALREADY_SENT; + break; + } + if ( (0 == strcmp (ct, + "FILE_WRITTEN")) && + (NULL != filename) ) + { + tcd.details.success.cs = ANASTASIS_CS_FILE_WRITTEN; + tcd.details.success.details.challenge_filename = filename; + break; + } + if ( (0 == strcmp (ct, + "IBAN_WIRE")) && + (NULL != wire_details) ) + { + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ( + "credit_iban", + &tcd.details.success.details.wire_funds.target_iban), + GNUNET_JSON_spec_uint64 ( + "answer_code", + &tcd.details.success.details.wire_funds.answer_code), + GNUNET_JSON_spec_string ( + "business_name", + &tcd.details.success.details.wire_funds.target_business_name), + GNUNET_JSON_spec_string ( + "wire_transfer_subject", + &tcd.details.success.details.wire_funds.wire_transfer_subject), + TALER_JSON_spec_amount_any ("challenge_amount", + &tcd.details.success.details.wire_funds. + amount), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (wire_details, + ispec, + NULL, NULL)) + { + GNUNET_break_op (0); + tcd.http_status = 0; + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + tcd.details.success.cs = ANASTASIS_CS_WIRE_FUNDS; + tco->cb (tco->cb_cls, + &tcd); + ANASTASIS_truth_challenge_cancel (tco); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected challenge type `%s'\n", + ct); + json_dumpf (j, + stderr, + JSON_INDENT (2)); + tcd.http_status = 0; + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + 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); + tcd.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + { + struct TALER_MERCHANT_PayUriData pd; + + if ( (NULL == tco->pay_uri) || + (GNUNET_OK != + TALER_MERCHANT_parse_pay_uri (tco->pay_uri, + &pd)) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse `%s'\n", + tco->pay_uri); + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + break; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data ( + pd.order_id, + strlen (pd.order_id), + &tcd.details.payment_required.ps, + sizeof (tcd.details.payment_required.ps))) + { + GNUNET_break (0); + tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; + TALER_MERCHANT_parse_pay_uri_free (&pd); + break; + } + tcd.details.payment_required.pd = &pd; + tcd.details.payment_required.payment_request = tco->pay_uri; + tco->cb (tco->cb_cls, + &tcd); + TALER_MERCHANT_parse_pay_uri_free (&pd); + ANASTASIS_truth_challenge_cancel (tco); + return; + } + break; + case MHD_HTTP_FORBIDDEN: + /* Nothing really to verify, authentication required/failed */ + tcd.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify */ + tcd.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + tcd.ec = TALER_JSON_get_error_code (j); + break; + case MHD_HTTP_BAD_GATEWAY: + tcd.ec = TALER_JSON_get_error_code (j); + break; + default: + /* unexpected response code */ + tcd.ec = TALER_JSON_get_error_code (j); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u/%d to POST /truth/$TID/challenge\n", + (unsigned int) response_code, + (int) tcd.ec); + GNUNET_break (0); + break; + } + tco->cb (tco->cb_cls, + &tcd); + ANASTASIS_truth_challenge_cancel (tco); +} + + +/** + * 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_TruthChallengeOperation *tco = 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 (tco->pay_uri); + tco->pay_uri = GNUNET_strdup (hdr_val); + patch_value (tco->pay_uri); + } + if (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_CONTENT_TYPE)) + { + /* found location URI we care about! */ + GNUNET_free (tco->content_type); + tco->content_type = GNUNET_strdup (hdr_val); + patch_value (tco->content_type); + } + GNUNET_free (ndup); + return total; +} + + +struct ANASTASIS_TruthChallengeOperation * +ANASTASIS_truth_challenge ( + 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, + ANASTASIS_TruthChallengeCallback cb, + void *cb_cls) +{ + struct ANASTASIS_TruthChallengeOperation *tco; + CURL *eh; + json_t *body; + + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto ("truth_decryption_key", + truth_key), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_data_auto ("payment_secret", + payment_secret))); + GNUNET_assert (NULL != body); + tco = GNUNET_new (struct ANASTASIS_TruthChallengeOperation); + tco->cb = cb; + tco->cb_cls = cb_cls; + { + char *path; + char *uuid_str; + + uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid, + sizeof (*truth_uuid)); + GNUNET_asprintf (&path, + "truth/%s/challenge", + uuid_str); + GNUNET_free (uuid_str); + tco->url = TALER_url_join (backend_url, + path, + NULL); + GNUNET_free (path); + } + eh = ANASTASIS_curl_easy_get_ (tco->url); + if ( (NULL == eh) || + (GNUNET_OK != + TALER_curl_easy_post (&tco->ctx, + eh, + body)) ) + { + GNUNET_break (0); + if (NULL != eh) + curl_easy_cleanup (eh); + json_decref (body); + GNUNET_free (tco->url); + GNUNET_free (tco); + return NULL; + } + json_decref (body); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERFUNCTION, + &handle_header)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + tco)); + tco->job = GNUNET_CURL_job_add2 (ctx, + eh, + tco->ctx.headers, + &handle_truth_challenge_finished, + tco); + return tco; +} + + +/* end of anastasis_api_truth_challenge.c */ diff --git a/src/restclient/anastasis_api_truth_solve.c b/src/restclient/anastasis_api_truth_solve.c new file mode 100644 index 0000000..9002a63 --- /dev/null +++ b/src/restclient/anastasis_api_truth_solve.c @@ -0,0 +1,437 @@ +/* + 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 <http://www.gnu.org/licenses/> +*/ +/** + * @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 <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include "anastasis_service.h" +#include "anastasis_api_curl_defaults.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_curl_lib.h> +#include <taler/taler_merchant_service.h> + + +/** + * @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 */ diff --git a/src/restclient/anastasis_api_truth_store.c b/src/restclient/anastasis_api_truth_store.c index 74b9238..855ad5a 100644 --- a/src/restclient/anastasis_api_truth_store.c +++ b/src/restclient/anastasis_api_truth_store.c @@ -3,14 +3,14 @@ Copyright (C) 2020, 2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the - terms of the GNU Lesser General Public License as published by the Free Software + 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 Affero General Public License for more details. + A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU Affero General Public License along with + You should have received a copy of the GNU General Public License along with Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> */ /** @@ -169,10 +169,18 @@ handle_truth_store_finished (void *cls, ud.ec = TALER_JSON_get_error_code2 (data, data_size); break; + case MHD_HTTP_BAD_GATEWAY: + ud.ec = TALER_JSON_get_error_code2 (data, + data_size); + break; default: - GNUNET_break (0); ud.ec = TALER_JSON_get_error_code2 (data, data_size); + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected HTTP status code %u/%d\n", + (unsigned int) response_code, + ud.ec); break; } tso->cb (tso->cb_cls, @@ -296,7 +304,7 @@ ANASTASIS_truth_store ( json_t *truth_data; truth_data = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("keyshare_data", + GNUNET_JSON_pack_data_auto ("key_share_data", encrypted_keyshare), GNUNET_JSON_pack_string ("type", type), |