/*
This file is part of Anastasis
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
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.
You should have received a copy of the GNU Affero General Public License along with
Anastasis; see the file COPYING.GPL. If not, see
*/
/**
* @file testing/testing_cmd_challenge_answer.c
* @brief command to execute the anastasis recovery service
* @author Christian Grothoff
* @author Dennis Neufeld
* @author Dominik Meister
*/
#include "platform.h"
#include "anastasis_testing_lib.h"
#include
#include
#include
/**
* State for a "challenge answer" CMD.
*/
struct ChallengeState
{
/**
* The interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
/**
* Reference to the challenge we are solving
*/
struct ANASTASIS_Challenge *c;
/**
* Answer to the challenge we are solving
*/
const char *answer;
/**
* Reference to the recovery process
*/
const char *challenge_ref;
/**
* Reference to the payment
*/
const char *payment_ref;
/**
* "taler://pay/" URL we got back, if any. Otherwise NULL.
*/
char *payment_uri;
/**
* Order ID extracted from @e payment_uri, or NULL.
*/
char *order_id;
/**
* Payment order ID we are to provide in the request.
*/
struct ANASTASIS_PaymentSecretP payment_order_req;
/**
* Expected status code.
*/
enum ANASTASIS_ChallengeStatus expected_cs;
/**
* Index of the challenge we are solving
*/
unsigned int challenge_index;
/**
* 0 for no plugin needed 1 for plugin needed to authenticate
*/
unsigned int mode;
/**
* code we read in the file generated by the plugin
*/
char code[22];
};
static void
challenge_answer_cb (void *af_cls,
const struct ANASTASIS_ChallengeStartResponse *csr)
{
struct ChallengeState *cs = af_cls;
cs->c = NULL;
if (csr->cs != cs->expected_cs)
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Expected status %u, got %u\n",
cs->expected_cs,
csr->cs);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
switch (csr->cs)
{
case ANASTASIS_CHALLENGE_STATUS_SOLVED:
break;
case ANASTASIS_CHALLENGE_STATUS_INSTRUCTIONS:
{
FILE *file;
char *fn;
if (0 == strcasecmp (csr->details.open_challenge.content_type,
"application/json"))
{
const char *filename;
json_t *in;
in = json_loadb (csr->details.open_challenge.body,
csr->details.open_challenge.body_size,
JSON_REJECT_DUPLICATES,
NULL);
if (NULL == in)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
filename = json_string_value (json_object_get (in,
"filename"));
if (NULL == filename)
{
GNUNET_break (0);
json_decref (in);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
fn = GNUNET_strdup (filename);
json_decref (in);
}
else
{
fn = GNUNET_strndup (csr->details.open_challenge.body,
csr->details.open_challenge.body_size);
}
file = fopen (fn,
"r");
if (NULL == file)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
"open",
fn);
GNUNET_free (fn);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
if (0 == fscanf (file,
"%21s",
cs->code))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
"fscanf",
fn);
TALER_TESTING_interpreter_fail (cs->is);
fclose (file);
GNUNET_free (fn);
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Read challenge answer `%s' from file `%s'\n",
cs->code,
fn);
TALER_TESTING_interpreter_next (cs->is);
GNUNET_break (0 == fclose (file));
GNUNET_free (fn);
return;
}
case ANASTASIS_CHALLENGE_STATUS_PAYMENT_REQUIRED:
if (0 != strncmp (csr->details.payment_required.taler_pay_uri,
"taler+http://pay/",
strlen ("taler+http://pay/")))
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Invalid payment URI `%s'\n",
csr->details.payment_required.taler_pay_uri);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
cs->payment_uri = GNUNET_strdup (
csr->details.payment_required.taler_pay_uri);
{
struct TALER_MERCHANT_PayUriData pud;
if (GNUNET_OK !=
TALER_MERCHANT_parse_pay_uri (cs->payment_uri,
&pud))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
cs->order_id = GNUNET_strdup (pud.order_id);
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (cs->order_id,
strlen (cs->order_id),
&cs->payment_order_req,
sizeof (cs->payment_order_req)))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
TALER_MERCHANT_parse_pay_uri_free (&pud);
}
TALER_TESTING_interpreter_next (cs->is);
return;
case ANASTASIS_CHALLENGE_STATUS_TRUTH_UNKNOWN:
break;
case ANASTASIS_CHALLENGE_STATUS_REDIRECT_FOR_AUTHENTICATION:
break;
case ANASTASIS_CHALLENGE_STATUS_SERVER_FAILURE:
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
case ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED:
break;
case ANASTASIS_CHALLENGE_STATUS_AUTH_TIMEOUT:
break;
case ANASTASIS_CHALLENGE_STATUS_EXTERNAL_INSTRUCTIONS:
break;
}
TALER_TESTING_interpreter_next (cs->is);
}
/**
* Run a "recover secret" CMD.
*
* @param cls closure.
* @param cmd command currently being run.
* @param is interpreter state.
*/
static void
challenge_answer_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
struct ChallengeState *cs = cls;
const struct ANASTASIS_Challenge *c;
const struct ANASTASIS_PaymentSecretP *ps;
cs->is = is;
if (NULL != cs->challenge_ref)
{
const struct TALER_TESTING_Command *ref;
ref = TALER_TESTING_interpreter_lookup_command (
is,
cs->challenge_ref);
if (NULL == ref)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
if (GNUNET_OK !=
ANASTASIS_TESTING_get_trait_challenge (ref,
cs->challenge_index,
&c))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
}
if (NULL != cs->payment_ref)
{
const struct TALER_TESTING_Command *ref;
ref = TALER_TESTING_interpreter_lookup_command (is,
cs->payment_ref);
if (NULL == ref)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
if (GNUNET_OK !=
ANASTASIS_TESTING_get_trait_payment_secret (ref,
0,
&ps))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
}
else
{
ps = NULL;
}
cs->c = (struct ANASTASIS_Challenge *) c;
if (1 == cs->mode)
{
const struct TALER_TESTING_Command *ref;
const char *answer;
unsigned long long code;
char dummy;
ref = TALER_TESTING_interpreter_lookup_command (is,
cs->answer);
if (NULL == ref)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
if (GNUNET_OK !=
ANASTASIS_TESTING_get_trait_code (ref,
0,
&answer))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
if (1 !=
sscanf (answer,
"%llu%c",
&code,
&dummy))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
if (GNUNET_OK !=
ANASTASIS_challenge_answer2 (cs->c,
ps,
GNUNET_TIME_UNIT_ZERO,
code,
&challenge_answer_cb,
cs))
{
GNUNET_break (0);
cs->c = NULL;
TALER_TESTING_interpreter_fail (cs->is);
return;
}
}
else
{
if (GNUNET_OK !=
ANASTASIS_challenge_answer (cs->c,
ps,
GNUNET_TIME_UNIT_ZERO,
cs->answer,
&challenge_answer_cb,
cs))
{
GNUNET_break (0);
cs->c = NULL;
TALER_TESTING_interpreter_fail (cs->is);
return;
}
}
}
/**
* Run a "recover secret" CMD.
*
* @param cls closure.
* @param cmd command currently being run.
* @param is interpreter state.
*/
static void
challenge_start_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
struct ChallengeState *cs = cls;
const struct ANASTASIS_Challenge *c;
const struct TALER_TESTING_Command *ref;
const struct ANASTASIS_PaymentSecretP *ps;
cs->is = is;
ref = TALER_TESTING_interpreter_lookup_command (
is,
cs->challenge_ref);
if (NULL == ref)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
if (GNUNET_OK !=
ANASTASIS_TESTING_get_trait_challenge (ref,
cs->challenge_index,
&c))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
if (NULL != cs->payment_ref)
{
const struct TALER_TESTING_Command *ref;
ref = TALER_TESTING_interpreter_lookup_command (is,
cs->payment_ref);
if (NULL == ref)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
if (GNUNET_OK !=
ANASTASIS_TESTING_get_trait_payment_secret (ref,
0,
&ps))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
}
else
{
ps = NULL;
}
if (GNUNET_OK !=
ANASTASIS_challenge_start ((struct ANASTASIS_Challenge *) c,
ps,
GNUNET_TIME_UNIT_ZERO,
NULL,
&challenge_answer_cb,
cs))
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (cs->is);
return;
}
}
/**
* Free the state of a "recover secret" CMD, and possibly
* cancel it if it did not complete.
*
* @param cls closure.
* @param cmd command being freed.
*/
static void
challenge_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct ChallengeState *cs = cls;
if (NULL != cs->c)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Command '%s' did not complete (challenge answer)\n",
cmd->label);
ANASTASIS_challenge_abort (cs->c);
cs->c = NULL;
}
GNUNET_free (cs->payment_uri);
GNUNET_free (cs->order_id);
GNUNET_free (cs);
}
/**
* Offer internal data to other commands.
*
* @param cls closure
* @param[out] ret result (could be anything)
* @param trait name of the trait
* @param index index number of the object to extract.
* @return #GNUNET_OK on success
*/
static int
challenge_create_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct ChallengeState *cs = cls;
struct TALER_TESTING_Trait traits[] = {
ANASTASIS_TESTING_make_trait_code (0,
cs->code),
ANASTASIS_TESTING_make_trait_payment_secret (0,
&cs->payment_order_req),
TALER_TESTING_make_trait_url (TALER_TESTING_UT_TALER_URL,
cs->payment_uri),
TALER_TESTING_make_trait_order_id (0,
cs->order_id),
TALER_TESTING_trait_end ()
};
return TALER_TESTING_get_trait (traits,
ret,
trait,
index);
}
struct TALER_TESTING_Command
ANASTASIS_TESTING_cmd_challenge_start (
const char *label,
const char *payment_ref,
const char *challenge_ref,
unsigned int challenge_index,
enum ANASTASIS_ChallengeStatus expected_cs)
{
struct ChallengeState *cs;
cs = GNUNET_new (struct ChallengeState);
cs->expected_cs = expected_cs;
cs->challenge_ref = challenge_ref;
cs->payment_ref = payment_ref;
cs->challenge_index = challenge_index;
{
struct TALER_TESTING_Command cmd = {
.cls = cs,
.label = label,
.run = &challenge_start_run,
.cleanup = &challenge_cleanup,
.traits = &challenge_create_traits
};
return cmd;
}
}
struct TALER_TESTING_Command
ANASTASIS_TESTING_cmd_challenge_answer (
const char *label,
const char *payment_ref,
const char *challenge_ref,
unsigned int challenge_index,
const char *answer,
unsigned int mode,
enum ANASTASIS_ChallengeStatus expected_cs)
{
struct ChallengeState *cs;
cs = GNUNET_new (struct ChallengeState);
cs->expected_cs = expected_cs;
cs->challenge_ref = challenge_ref;
cs->payment_ref = payment_ref;
cs->answer = answer;
cs->challenge_index = challenge_index;
cs->mode = mode;
{
struct TALER_TESTING_Command cmd = {
.cls = cs,
.label = label,
.run = &challenge_answer_run,
.cleanup = &challenge_cleanup,
.traits = &challenge_create_traits
};
return cmd;
}
}
/* end of testing_cmd_challenge_answer.c */