/*
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 */