/* This file is part of TALER Copyright (C) 2020 Taler Systems SA TALER 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. TALER 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 TALER; see the file COPYING. If not, see */ /** * @file merchant-tools/taler-merchant-setup-reserve.c * @brief Create reserve for tipping * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler_merchant_service.h" /** * How often do we try before giving up? */ #define MAX_TRIES 30 /** * Return value from main(). */ static int global_ret; /** * Initial amount the reserve will be filled with. */ static struct TALER_Amount initial_amount; /** * Base URL of the merchant (with instance) to create the reserve for. */ static char *merchant_base_url; /** * Base URL of the exchange to create the reserve at. */ static char *exchange_base_url; /** * Wire method to use. */ static char *wire_method; /** * Operation handle. */ static struct TALER_MERCHANT_PostReservesHandle *prh; /** * Our context for making HTTP requests. */ static struct GNUNET_CURL_Context *ctx; /** * Reschedule context. */ static struct GNUNET_CURL_RescheduleContext *rc; /** * Username and password to use for client authentication * (optional). */ static char *userpass; /** * Type of the client's TLS certificate (optional). */ static char *certtype; /** * File with the client's TLS certificate (optional). */ static char *certfile; /** * File with the client's TLS private key (optional). */ static char *keyfile; /** * This value goes in the Authorization:-header. */ static char *apikey; /** * Passphrase to decrypt client's TLS private key file (optional). */ static char *keypass; /** * How often have we tried? */ static unsigned int tries; /** * Task to do the main work. */ static struct GNUNET_SCHEDULER_Task *task; /** * Shutdown task (invoked when the process is being terminated) * * @param cls NULL */ static void do_shutdown (void *cls) { if (NULL != task) { GNUNET_SCHEDULER_cancel (task); task = NULL; } if (NULL != ctx) { GNUNET_CURL_fini (ctx); ctx = NULL; } if (NULL != rc) { GNUNET_CURL_gnunet_rc_destroy (rc); rc = NULL; } if (NULL != prh) { TALER_MERCHANT_reserves_post_cancel (prh); prh = NULL; } } /** * Function that makes the call to setup the reserve. * * @param cls NULL */ static void do_request (void *cls); /** * Callbacks of this type are used to work the result of submitting a * POST /reserves request to a merchant * * @param cls closure * @param prr response details */ static void result_cb (void *cls, const struct TALER_MERCHANT_PostReservesResponse *prr) { (void) cls; prh = NULL; switch (prr->hr.http_status) { case MHD_HTTP_OK: { char res_str[sizeof (prr->details.ok.reserve_pub) * 2 + 1]; GNUNET_STRINGS_data_to_string (&prr->details.ok.reserve_pub, sizeof (prr->details.ok.reserve_pub), res_str, sizeof (res_str)); for (unsigned int i = 0; idetails.ok.accounts_len; i++) { const struct TALER_EXCHANGE_WireAccount *wa = &prr->details.ok.accounts[i]; const char *payto_uri = wa->payto_uri; bool skip = false; for (unsigned int j = 0; jcredit_restrictions_length; j++) if (TALER_EXCHANGE_AR_DENY == wa->credit_restrictions[j].type) skip = true; if (skip) continue; if (NULL != strchr (payto_uri, '?')) fprintf (stdout, "%s&message=%s\n", payto_uri, res_str); else fprintf (stdout, "%s?message=%s\n", payto_uri, res_str); if (NULL != wa->conversion_url) fprintf (stdout, "\tConversion needed: %s\n", wa->conversion_url); for (unsigned int j = 0; jcredit_restrictions_length; j++) { const struct TALER_EXCHANGE_AccountRestriction *cr = &wa->credit_restrictions[j]; switch (cr->type) { case TALER_EXCHANGE_AR_INVALID: GNUNET_assert (0); break; case TALER_EXCHANGE_AR_DENY: GNUNET_assert (0); break; case TALER_EXCHANGE_AR_REGEX: fprintf (stdout, "\tCredit restriction: %s (%s)\n", cr->details.regex.human_hint, cr->details.regex.posix_egrep); break; } } } } break; case MHD_HTTP_CONFLICT: fprintf (stderr, "Conflict trying to setup reserve: %u/%d\nHint: %s\n", prr->hr.http_status, (int) prr->hr.ec, prr->hr.hint); global_ret = 1; break; case MHD_HTTP_INTERNAL_SERVER_ERROR: case MHD_HTTP_BAD_GATEWAY: tries++; if (tries < MAX_TRIES) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Merchant failed, will try again.\n"); task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &do_request, NULL); return; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Merchant failed too often (%u/%d), giving up\n", prr->hr.http_status, prr->hr.ec); global_ret = 1; break; default: fprintf (stderr, "Unexpected backend failure: %u/%d\nHint: %s\n", prr->hr.http_status, (int) prr->hr.ec, prr->hr.hint); global_ret = 1; break; } GNUNET_SCHEDULER_shutdown (); } /** * Main function that will be run. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param config configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *config) { /* setup HTTP client event loop */ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &rc); rc = GNUNET_CURL_gnunet_rc_create (ctx); if (NULL != userpass) GNUNET_CURL_set_userpass (ctx, userpass); if (NULL != keyfile) GNUNET_CURL_set_tlscert (ctx, certtype, certfile, keyfile, keypass); if (NULL != apikey) { char *auth_header; GNUNET_asprintf (&auth_header, "%s: %s", MHD_HTTP_HEADER_AUTHORIZATION, apikey); if (GNUNET_OK != GNUNET_CURL_append_header (ctx, auth_header)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed so set %s header, trying without\n", MHD_HTTP_HEADER_AUTHORIZATION); } GNUNET_free (auth_header); } /* setup termination logic */ GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); task = GNUNET_SCHEDULER_add_now (&do_request, NULL); } static void do_request (void *cls) { (void) cls; task = NULL; /* run actual (async) operation */ prh = TALER_MERCHANT_reserves_post (ctx, merchant_base_url, &initial_amount, exchange_base_url, wire_method, &result_cb, NULL); if (NULL == prh) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to begin operation with merchant backend!\n"); global_ret = 2; GNUNET_SCHEDULER_shutdown (); return; } } /** * The main function for setting up reserves for tipping. * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, 1 on error */ int main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_mandatory ( TALER_getopt_get_amount ('a', "amount", "VALUE", "amount to be transferred into the reserve", &initial_amount)), GNUNET_GETOPT_option_string ('A', "auth", "USERNAME:PASSWORD", "use the given USERNAME and PASSWORD for client authentication", &userpass), GNUNET_GETOPT_option_string ('C', "cert", "CERTFILE", "name of the TLS client certificate file", &certfile), GNUNET_GETOPT_option_mandatory ( GNUNET_GETOPT_option_string ('e', "exchange-url", "URL", "base URL of the exchange to create the reserve at", &exchange_base_url)), GNUNET_GETOPT_option_string ('k', "key", "KEYFILE", "file with the private TLS key for TLS client authentication", &keyfile), GNUNET_GETOPT_option_mandatory ( GNUNET_GETOPT_option_string ('m', "merchant-url", "URL", "base URL of the merchant backend's REST API", &merchant_base_url)), GNUNET_GETOPT_option_string ('p', "pass", "KEYFILEPASSPHRASE", "passphrase needed to decrypt the TLS client private key file", &keypass), GNUNET_GETOPT_option_string ('K', "apikey", "APIKEY", "API key to use in the HTTP request", &apikey), GNUNET_GETOPT_option_string ('t', "type", "CERTTYPE", "type of the TLS client certificate, defaults to PEM if not specified", &certtype), GNUNET_GETOPT_option_version (PACKAGE_VERSION "-" VCS_VERSION), GNUNET_GETOPT_option_mandatory ( GNUNET_GETOPT_option_string ('w', "wire-method", "METHOD", "wire method to use for the wire transfer (i.e. IBAN)", &wire_method)), GNUNET_GETOPT_OPTION_END }; enum GNUNET_GenericReturnValue ret; /* force linker to link against libtalerutil; if we do not do this, the linker may "optimize" libtalerutil away and skip #TALER_OS_init(), which we do need */ (void) TALER_project_data_default (); ret = GNUNET_PROGRAM_run ( argc, argv, "taler-merchant-setup-reserve", gettext_noop ("Setup reserve for tipping"), options, &run, NULL); if (GNUNET_SYSERR == ret) return 3; if (GNUNET_NO == ret) return 0; return global_ret; } /* end of taler-merchant-setup-reserve.c */