paivana

HTTP paywall reverse proxy
Log | Files | Refs | README | LICENSE

commit ba6d5c2a38a29e928e0878c2b8130b761040a0d0
parent e90465d04d6ef2d32a7ea0d920a2cb8cbfdc7d4a
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun, 19 Apr 2026 23:13:16 +0200

more work on Paivana: add pay handler

Diffstat:
Msrc/backend/Makefile.am | 8++++++--
Msrc/backend/paivana-httpd.c | 380+++++++++++++++++++++++++++++++++----------------------------------------------
Msrc/backend/paivana-httpd.h | 12+++++++++++-
Asrc/backend/paivana-httpd_cookie.c | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/paivana-httpd_cookie.h | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/paivana-httpd_pay.c | 335+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/paivana-httpd_pay.h | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backend/paivana-httpd_reverse.c | 4++--
8 files changed, 784 insertions(+), 225 deletions(-)

diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am @@ -11,14 +11,18 @@ bin_PROGRAMS = \ paivana_httpd_SOURCES = \ paivana-httpd.c \ + paivana-httpd_cookie.c paivana-httpd_cookie.h \ paivana-httpd_reverse.c paivana-httpd_reverse.h \ + paivana-httpd_pay.c paivana-httpd_pay.h \ paivana_pd.c paivana_pd.h paivana_httpd_LDADD = \ $(LIBGCRYPT_LIBS) \ + -ltalermerchant \ -ltalermhd \ + -lgnunetjson \ + -lgnunetcurl \ + -lgnunetutil \ -lmicrohttpd \ -lcurl \ -ljansson \ - -lgnunetutil \ - -lgnunetjson \ -lz diff --git a/src/backend/paivana-httpd.c b/src/backend/paivana-httpd.c @@ -29,8 +29,11 @@ #include "platform.h" #include <curl/curl.h> #include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_curl_lib.h> #include <taler/taler_mhd_lib.h> #include "paivana-httpd.h" +#include "paivana-httpd_cookie.h" +#include "paivana-httpd_pay.h" #include "paivana-httpd_reverse.h" #include "paivana_pd.h" @@ -39,18 +42,51 @@ struct RequestContext { + + /** + * HTTP connection to the client. + */ + struct MHD_Connection *connection; + /** * Handle for request forwarding as reverse proxy. */ struct HttpRequest *hr; /** + * Handle for processing actual payment. + */ + struct PayRequest *hp; + + /** + * Full request URL. + */ + char *url; + + /** + * True if this is a POST to the .well-known/paivana endpoint. + */ + bool is_paivana; + + /** * We are past the paywall, forward to client. */ bool do_forward; }; +char *PH_target_server_base_url; + +char *PH_merchant_base_url; + +struct GNUNET_CURL_Context *PH_ctx; + +/** + * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule(). + */ +struct GNUNET_CURL_RescheduleContext *ctx_rc; + + /** * Set to true if we started a daemon. */ @@ -76,142 +112,6 @@ static int no_check; */ static int global_ret; -char *target_server_base_url; - -/** - * Merchant backend base URL. - */ -static char *merchant_base_url; - -/** - * Merchant backend access token. - */ -static char *merchant_access_token; - -/** - * Secret for the cookie generation. - */ -static struct GNUNET_HashCode paivana_secret; - - -/* ********************* Paivana Cookie handling ****************** */ - -/** - * Compute access cookie hash for the given @a expiration and @a ca. - * - * @param expiration expiration time of the cookie - * @param ca_len number of bytes in @a ca - * @param ca client address - * @param[out] c set to the cookie hash - */ -static void -compute_cookie_hash (struct GNUNET_TIME_Timestamp expiration, - size_t ca_len, - const void *ca, - struct GNUNET_HashCode *c) -{ - struct GNUNET_TIME_AbsoluteNBO e; - - e = GNUNET_TIME_absolute_hton (expiration.abs_time); - GNUNET_assert (GNUNET_YES == - GNUNET_CRYPTO_hkdf_gnunet ( - c, /* result */ - sizeof (c), - &e, /* salt */ - sizeof (e), - &paivana_secret, /* source key material */ - sizeof (paivana_secret), - GNUNET_CRYPTO_kdf_arg (ca, - ca_len))); -} - - -/** - * Check if the given cookie currently grants access. - * - * @param cookie the cookie - * @param ca_len number of bytes in @a ca - * @param ca client address - * @return true if the cookie is OK - */ -static bool -check_cookie (const char *cookie, - size_t ca_len, - const void *ca) -{ - const char *dash; - unsigned long long u; - struct GNUNET_HashCode h; - struct GNUNET_HashCode c; - struct GNUNET_TIME_Timestamp a; - - dash = strchr (cookie, - '-'); - if (NULL == dash) - return false; - dash++; - if (1 != - sscanf (cookie, - "%llu-", - &u)) - return false; - a.abs_time.abs_value_us = u * 1000LLU * 1000LLU; - if (GNUNET_TIME_absolute_is_past (a.abs_time)) - return false; - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (dash, - strlen (dash), - &c, - sizeof (c))) - return false; - compute_cookie_hash (a, - ca_len, - ca, - &h); - return (0 == - GNUNET_memcmp (&c, - &h)); -} - - -/** - * Compute access cookie hash for the given @a expiration and @a ca. - * - * @param expiration expiration time of the cookie - * @param ca_len number of bytes in @a ca - * @param ca client address - * @param[out] c set to the cookie hash - */ -static char * -compute_cookie (struct GNUNET_TIME_Timestamp expiration, - size_t ca_len, - const void *ca) -{ - struct GNUNET_HashCode h; - char *end; - char cstr[128]; - char *res; - - compute_cookie_hash (expiration, - ca_len, - ca, - &h); - end = GNUNET_STRINGS_data_to_string (&h, - sizeof (h), - cstr, - sizeof (cstr)); - *end = '\0'; - GNUNET_asprintf ( - &res, - "%llu-%s", - (unsigned long long) (expiration.abs_time.abs_value_us / 1000LLU / 1000LLU), - cstr); - return res; -} - - -/* *************** MHD response generation ***************** */ - /** * Main MHD callback for handling requests. @@ -248,51 +148,79 @@ create_response (void *cls, void **con_cls) { struct RequestContext *rc = *con_cls; - struct HttpRequest *hr = rc->hr; + const char *cookie; (void) cls; - if (NULL == hr) + if ( (! rc->is_paivana) && + (0 == strcmp (url, + ".well-known/paivana")) && + (0 == strcasecmp (meth, + "POST")) ) { - GNUNET_break (0); - return MHD_NO; + rc->is_paivana = true; + } + if (rc->is_paivana) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client POSTed payment, checking validity\n"); + if (NULL == rc->hp) + rc->hp = PAIVANA_HTTPD_payment_create (rc->connection); + return PAIVANA_HTTPD_payment_handle (rc->hp, + upload_data, + upload_data_size); } - // FIXME: check if url is one that we reverse proxy! + // FIXME: check if url is one that we require payment for, + // if not set 'do_forward = true'. + // (also should eventually determine WHICH payment template + // we use...) - if (! rc->do_forward) + if (rc->do_forward) { - const char *cookie; - bool ok = (0 != no_check); + if (NULL == rc->hr) + rc->hr = PAIVANA_HTTPD_reverse_create (rc->connection, + rc->url); + return PAIVANA_HTTPD_reverse (rc->hr, + con, + url, + meth, + ver, + upload_data, + upload_data_size); + } - cookie = MHD_lookup_connection_value (con, - MHD_COOKIE_KIND, - "Paivana-Cookie"); - if (NULL != cookie) + cookie = MHD_lookup_connection_value (con, + MHD_COOKIE_KIND, + "Paivana-Cookie"); + if (NULL != cookie) + { + const union MHD_ConnectionInfo *ci; + const struct sockaddr *ca; + socklen_t ca_len; + bool ok; + + // FIXME: de-duplicate with logic in paivana-httpd_pay.c, + // and also support getting client address from HTTP + // headers instead (in case of reverse proxy). + ci = MHD_get_connection_info (con, + MHD_CONNECTION_INFO_CLIENT_ADDRESS); + GNUNET_assert (NULL != ci); + ca = ci->client_addr; + switch (ca->sa_family) { - const union MHD_ConnectionInfo *ci; - const struct sockaddr *ca; - socklen_t ca_len; - - ci = MHD_get_connection_info (con, - MHD_CONNECTION_INFO_CLIENT_ADDRESS); - GNUNET_assert (NULL != ci); - ca = ci->client_addr; - switch (ca->sa_family) - { - case AF_INET: - ca_len = sizeof (struct sockaddr_in); - break; - case AF_INET6: - ca_len = sizeof (struct sockaddr_in6); - break; - default: - GNUNET_break (0); - ca_len = 0; - break; - } - ok = check_cookie (cookie, - ca_len, - ca); + case AF_INET: + ca_len = sizeof (struct sockaddr_in); + break; + case AF_INET6: + ca_len = sizeof (struct sockaddr_in6); + break; + default: + GNUNET_break (0); + ca_len = 0; + break; } + ok = PAIVANA_HTTPD_check_cookie (cookie, + ca_len, + ca); if (! ok) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -301,26 +229,15 @@ create_response (void *cls, MHD_HTTP_PAYMENT_REQUIRED, paywall); } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Request ok!\n"); - rc->do_forward = true; - /* TODO: hacks for 100 continue suppression should go here! */ - return MHD_YES; } - - return PAIVANA_HTTPD_reverse (hr, - con, - url, - meth, - ver, - upload_data, - upload_data_size); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Request ok!\n"); + rc->do_forward = true; + /* TODO: hacks for 100 continue suppression should go here! */ + return MHD_YES; } -/* ************ MHD HTTP setup and event loop *************** */ - - /** * Function called when MHD decides that we * are done with a request. @@ -346,9 +263,14 @@ mhd_completed_cb (void *cls, return; if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "MHD encountered error handling request: %d\n", + "MHD encountered error handling request to %s: %d\n", + rc->url, toe); - PAIVANA_HTTPD_reverse_cleanup (rc->hr); + if (NULL != rc->hr) + PAIVANA_HTTPD_reverse_cleanup (rc->hr); + if (NULL != rc->hp) + PAIVANA_HTTPD_payment_destroy (rc->hp); + GNUNET_free (rc->url); GNUNET_free (rc); *con_cls = NULL; } @@ -373,22 +295,11 @@ mhd_log_callback (void *cls, struct MHD_Connection *connection) { struct RequestContext *rc; - const union MHD_ConnectionInfo *ci; - (void) cls; - ci = MHD_get_connection_info (connection, - MHD_CONNECTION_INFO_SOCKET_CONTEXT); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Processing %s\n", - url); - if (NULL == ci) - { - GNUNET_break (0); - return NULL; - } rc = GNUNET_new (struct RequestContext); - rc->hr = PAIVANA_HTTPD_reverse_create (connection, - url); + rc->connection = connection; + rc->url = GNUNET_strdup (url); + rc->do_forward = (1 == no_check); return rc; } @@ -408,9 +319,21 @@ do_shutdown (void *cls) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Shutting down...\n"); TALER_MHD_daemons_halt (); + PAIVANA_HTTPD_payment_shutdown (); PAIVANA_HTTPD_reverse_shutdown (); TALER_MHD_daemons_destroy (); - GNUNET_free (target_server_base_url); + GNUNET_free (PH_target_server_base_url); + GNUNET_free (PH_merchant_base_url); + if (NULL != PH_ctx) + { + GNUNET_CURL_fini (PH_ctx); + PH_ctx = NULL; + } + if (NULL != ctx_rc) + { + GNUNET_CURL_gnunet_rc_destroy (ctx_rc); + ctx_rc = NULL; + } } @@ -548,7 +471,7 @@ run (void *cls, c, "paivana", "DESTINATION_BASE_URL", - &target_server_base_url)) + &PH_target_server_base_url)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "paivana", @@ -561,7 +484,7 @@ run (void *cls, c, "paivana", "MERCHANT_BACKEND_URL", - &merchant_base_url)) + &PH_merchant_base_url)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "paivana", @@ -573,19 +496,6 @@ run (void *cls, GNUNET_CONFIGURATION_get_value_string ( c, "paivana", - "MERCHANT_ACCESS_TOKEN", - &merchant_access_token)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "paivana", - "MERCHANT_ACCESS_TOKEN"); - GNUNET_SCHEDULER_shutdown (); - return; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string ( - c, - "paivana", "SECRET", &secret)) { @@ -605,6 +515,36 @@ run (void *cls, } GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); + PH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, + &ctx_rc); + { + char *merchant_access_token; + char *auth_header; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string ( + c, + "paivana", + "MERCHANT_ACCESS_TOKEN", + &merchant_access_token)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "paivana", + "MERCHANT_ACCESS_TOKEN"); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_asprintf (&auth_header, + "%s: %s", + MHD_HTTP_HEADER_AUTHORIZATION, + merchant_access_token); + GNUNET_free (merchant_access_token); + GNUNET_assert (GNUNET_OK == + GNUNET_CURL_append_header (PH_ctx, + auth_header)); + GNUNET_free (auth_header); + } + ctx_rc = GNUNET_CURL_gnunet_rc_create (PH_ctx); ret = TALER_MHD_listen_bind (c, "paivana", diff --git a/src/backend/paivana-httpd.h b/src/backend/paivana-httpd.h @@ -40,7 +40,17 @@ * Destination to which HTTP server we forward requests to. * Of the format "http://servername:PORT" */ -extern char *target_server_base_url; +extern char *PH_target_server_base_url; + +/** + * Merchant backend base URL. + */ +extern char *PH_merchant_base_url; + +/** + * Curl context for making HTTP requests. + */ +extern struct GNUNET_CURL_Context *PH_ctx; #endif diff --git a/src/backend/paivana-httpd_cookie.c b/src/backend/paivana-httpd_cookie.c @@ -0,0 +1,137 @@ +/* + This file is part of GNU Taler + Copyright (C) 2012-2014 GNUnet e.V. + Copyright (C) 2018, 2025 Taler Systems SA + + GNU 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. + + GNU 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 GNU Taler; see the file COPYING. If not, + write to the Free Software Foundation, Inc., 51 Franklin + Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/** + * @author Martin Schanzenbach + * @author Christian Grothoff + * @author Marcello Stanisci + * @file src/backend/paivana-httpd_cookie.c + * @brief Cookie computation logic for paivana + */ +#include "platform.h" +#include <curl/curl.h> +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_mhd_lib.h> +#include "paivana-httpd_cookie.h" + + +/** + * Secret for the cookie generation. + */ +struct GNUNET_HashCode paivana_secret; + + +/** + * Compute access cookie hash for the given @a expiration and @a ca. + * + * @param expiration expiration time of the cookie + * @param ca_len number of bytes in @a ca + * @param ca client address + * @param[out] c set to the cookie hash + */ +static void +compute_cookie_hash (struct GNUNET_TIME_Timestamp expiration, + size_t ca_len, + const void *ca, + struct GNUNET_HashCode *c) +{ + struct GNUNET_TIME_AbsoluteNBO e; + + e = GNUNET_TIME_absolute_hton (expiration.abs_time); + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_hkdf_gnunet ( + c, /* result */ + sizeof (c), + &e, /* salt */ + sizeof (e), + &paivana_secret, /* source key material */ + sizeof (paivana_secret), + GNUNET_CRYPTO_kdf_arg (ca, + ca_len))); +} + + +bool +PAIVANA_HTTPD_check_cookie (const char *cookie, + size_t ca_len, + const void *ca) +{ + const char *dash; + unsigned long long u; + struct GNUNET_HashCode h; + struct GNUNET_HashCode c; + struct GNUNET_TIME_Timestamp a; + + dash = strchr (cookie, + '-'); + if (NULL == dash) + return false; + dash++; + if (1 != + sscanf (cookie, + "%llu-", + &u)) + return false; + a.abs_time.abs_value_us = u * 1000LLU * 1000LLU; + if (GNUNET_TIME_absolute_is_past (a.abs_time)) + return false; + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (dash, + strlen (dash), + &c, + sizeof (c))) + return false; + compute_cookie_hash (a, + ca_len, + ca, + &h); + return (0 == + GNUNET_memcmp (&c, + &h)); +} + + +char * +PAIVANA_HTTPD_compute_cookie (struct GNUNET_TIME_Timestamp expiration, + size_t ca_len, + const void *ca) +{ + struct GNUNET_HashCode h; + char *end; + char cstr[128]; + char *res; + + compute_cookie_hash (expiration, + ca_len, + ca, + &h); + end = GNUNET_STRINGS_data_to_string (&h, + sizeof (h), + cstr, + sizeof (cstr)); + *end = '\0'; + GNUNET_asprintf ( + &res, + "%llu-%s", + (unsigned long long) (expiration.abs_time.abs_value_us / 1000LLU / 1000LLU), + cstr); + return res; +} diff --git a/src/backend/paivana-httpd_cookie.h b/src/backend/paivana-httpd_cookie.h @@ -0,0 +1,76 @@ +/* + This file is part of GNUnet. + Copyright (C) 2026 Taler Systems SA + + Paivana 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. + + Paivana 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 Paivana; see the file COPYING. If not, + write to the Free Software Foundation, Inc., 51 Franklin + Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/** + * @author Christian Grothoff + * @file paivana-httpd_cookie.h + * + * @brief cookie generation and verification logic + */ +#ifndef PAIVANA_HTTPD_COOKIE_H +#define PAIVANA_HTTPD_COOKIE_H + +#include <gnunet/gnunet_util_lib.h> + +/** + * Secret for the cookie generation. + */ +extern struct GNUNET_HashCode paivana_secret; + + +/** + * Nonce generated client-side in Paivana protocol. + */ +struct PAIVANA_Nonce +{ + /** + * Some 128-bit value. + */ + uint32_t val[4]; +}; + + +/** + * Check if the given cookie currently grants access. + * + * @param cookie the cookie + * @param ca_len number of bytes in @a ca + * @param ca client address + * @return true if the cookie is OK + */ +bool +PAIVANA_HTTPD_check_cookie (const char *cookie, + size_t ca_len, + const void *ca); + +/** + * Compute access cookie hash for the given @a expiration and @a ca. + * + * @param expiration expiration time of the cookie + * @param ca_len number of bytes in @a ca + * @param ca client address + * @param[out] c set to the cookie hash + */ +char * +PAIVANA_HTTPD_compute_cookie (struct GNUNET_TIME_Timestamp expiration, + size_t ca_len, + const void *ca); + +#endif diff --git a/src/backend/paivana-httpd_pay.c b/src/backend/paivana-httpd_pay.c @@ -0,0 +1,335 @@ +/* + This file is part of GNUnet. + Copyright (C) 2026 Taler Systems SA + + Paivana 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. + + Paivana 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 Paivana; see the file COPYING. If not, + write to the Free Software Foundation, Inc., 51 Franklin + Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/** + * @author Christian Grothoff + * @file paivana-httpd_pay.c + * + * @brief payment processing logic + */ +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include <taler/taler_mhd_lib.h> +#include <taler/taler_error_codes.h> +#include "paivana-httpd_cookie.h" +#include "paivana-httpd_pay.h" + +struct PayRequest; +#define TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE struct PayRequest +#include "taler/merchant/get-private-orders-ORDER_ID.h" + + +/** + * Handle for processing actual payment. + */ +struct PayRequest +{ + + /** + * Kept in a DLL while suspended. + */ + struct PayRequest *next; + + /** + * Kept in a DLL while suspended. + */ + struct PayRequest *prev; + + /** + * Connection we are handling. + */ + struct MHD_Connection *connection; + + /** + * Buffer for TALER_MHD_parse_post_json. + */ + void *buffer; + + /** + * Uploaded JSON body, NULL if none yet. + */ + json_t *body; + + /** + * Handle for our request to the merchant backend. + */ + struct TALER_MERCHANT_GetPrivateOrderHandle *co; + + /** + * Response to return. + */ + struct MHD_Response *response; + + /** + * ID of the order the client claims to have paid. + */ + const char *order_id; + + /** + * Website the order is supposed to have paid for. + */ + const char *website; + + /** + * Client-side nonce. + */ + struct PAIVANA_Nonce nonce; + + /** + * + */ + struct GNUNET_TIME_Timestamp cur_time; + + /** + * HTTP status to return in combination with @e resp to the client. + */ + unsigned int response_status; + +}; + + +/** + * Head of DLL of suspended requests. + */ +static struct PayRequest *ph_head; + +/** + * Tail of DLL of suspended requests. + */ +static struct PayRequest *ph_tail; + + +void +PAIVANA_HTTPD_payment_shutdown () +{ + while (NULL != ph_head) + { + struct PayRequest *ph = ph_head; + + GNUNET_CONTAINER_DLL_remove (ph_head, + ph_tail, + ph); + MHD_resume_connection (ph->connection); + } +} + + +struct PayRequest * +PAIVANA_HTTPD_payment_create (struct MHD_Connection *connection) +{ + struct PayRequest *ph; + + ph = GNUNET_new (struct PayRequest); + ph->connection = connection; + return ph; +} + + +/** + * Handle response from the GET /private/orders/$ORDER_ID request. + * + * @param ph the payment request we are processing + * @param osr response details + */ +static void +order_status_cb (struct PayRequest *ph, + const struct TALER_MERCHANT_GetPrivateOrderResponse *osr) +{ + ph->co = NULL; + switch (osr->hr.http_status) + { + case MHD_HTTP_OK: + if (TALER_MERCHANT_OSC_PAID != osr->details.ok.status) + { + GNUNET_break_op (0); + ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_PAYMENT_MISSING, + ph->order_id); + ph->response_status = MHD_HTTP_BAD_REQUEST; + } + else + { + const union MHD_ConnectionInfo *ci; + const struct sockaddr *ca; + socklen_t ca_len; + // FIXME: relationship of expiration to ph->cur_time? + struct GNUNET_TIME_Timestamp expiration; + char *cookie; + struct MHD_Response *resp; + + // FIXME: de-duplicate with logic in paivana-httpd.c, + // and also support getting client address from HTTP + // headers instead (in case of reverse proxy). + ci = MHD_get_connection_info (ph->connection, + MHD_CONNECTION_INFO_CLIENT_ADDRESS); + GNUNET_assert (NULL != ci); + ca = ci->client_addr; + switch (ca->sa_family) + { + case AF_INET: + ca_len = sizeof (struct sockaddr_in); + break; + case AF_INET6: + ca_len = sizeof (struct sockaddr_in6); + break; + default: + GNUNET_break (0); + ca_len = 0; + break; + } + // FIXME: include website + nonce + cur_time somehow!! + // => TALER_EC_PAIVANA_WRONG_ORDER with 409! + cookie = PAIVANA_HTTPD_compute_cookie (expiration, + ca_len, + ca); + resp = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + GNUNET_assert (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_SET_COOKIE, + cookie)); + GNUNET_assert (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_LOCATION, + ph->website)); + GNUNET_free (cookie); + TALER_MHD_add_global_headers (resp, + false); + ph->response = resp; + ph->response_status = MHD_HTTP_SEE_OTHER; + } + break; + case MHD_HTTP_FORBIDDEN: + ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_BACKEND_REFUSED, + NULL); + ph->response_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + break; + case MHD_HTTP_NOT_FOUND: + ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_ORDER_UNKNOWN, + ph->order_id); + ph->response_status = MHD_HTTP_NOT_FOUND; + break; + default: + { + char code[20]; + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unexpected status code %u from backend\n", + osr->hr.http_status); + GNUNET_snprintf (code, + sizeof (code), + "%u", + osr->hr.http_status); + ph->response = TALER_MHD_make_error (TALER_EC_PAIVANA_BACKEND_ERROR, + code); + ph->response_status = MHD_HTTP_BAD_GATEWAY; + } + break; + } + GNUNET_CONTAINER_DLL_insert (ph_head, + ph_tail, + ph); + MHD_resume_connection (ph->connection); + TALER_MHD_daemon_trigger (); + +} + + +enum MHD_Result +PAIVANA_HTTPD_payment_handle (struct PayRequest *ph, + const char *upload_data, + size_t *upload_data_size) +{ + if (NULL == ph->body) + { + enum GNUNET_GenericReturnValue ret; + + ret = TALER_MHD_parse_post_json (ph->connection, + &ph->buffer, + upload_data, + upload_data_size, + &ph->body); + if (GNUNET_OK != ret) + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + if (NULL == ph->body) + return MHD_YES; + } + if (NULL != ph->response) + { + return MHD_queue_response (ph->connection, + ph->response_status, + ph->response); + } + if (NULL == ph->order_id) + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("order_id", + &ph->order_id), + GNUNET_JSON_spec_string ("website", + &ph->website), + GNUNET_JSON_spec_timestamp ("cur_time", + &ph->cur_time), + GNUNET_JSON_spec_fixed_auto ("nonce", + &ph->nonce), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue ret; + + ret = TALER_MHD_parse_json_data (ph->connection, + ph->body, + spec); + if (GNUNET_YES != ret) + return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; + } + GNUNET_assert (NULL == ph->co); + ph->co = TALER_MERCHANT_get_private_order_create (PH_ctx, + PH_merchant_base_url, + ph->order_id); + if (NULL == ph->co) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error (ph->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_PAIVANA_GET_ORDER_FAILED, + ph->order_id); + } + GNUNET_CONTAINER_DLL_insert (ph_head, + ph_tail, + ph); + MHD_suspend_connection (ph->connection); + GNUNET_assert (TALER_EC_NONE == + TALER_MERCHANT_get_private_order_start (ph->co, + &order_status_cb, + ph)); + return MHD_YES; +} + + +void +PAIVANA_HTTPD_payment_destroy (struct PayRequest *ph) +{ + TALER_MHD_parse_post_cleanup_callback (ph->buffer); + if (NULL != ph->co) + TALER_MERCHANT_get_private_order_cancel (ph->co); + if (NULL != ph->response) + MHD_destroy_response (ph->response); + json_decref (ph->body); + GNUNET_free (ph); +} diff --git a/src/backend/paivana-httpd_pay.h b/src/backend/paivana-httpd_pay.h @@ -0,0 +1,57 @@ +/* + This file is part of GNUnet. + Copyright (C) 2026 Taler Systems SA + + Paivana 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. + + Paivana 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 Paivana; see the file COPYING. If not, + write to the Free Software Foundation, Inc., 51 Franklin + Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/** + * @author Christian Grothoff + * @file paivana-httpd_pay.h + * + * @brief payment processing logic + */ +#ifndef PAIVANA_HTTPD_PAY_H +#define PAIVANA_HTTPD_PAY_H + +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "paivana-httpd.h" + +/** + * Handle for processing actual payment. + */ +struct PayRequest; + +void +PAIVANA_HTTPD_payment_shutdown (void); + + +struct PayRequest * +PAIVANA_HTTPD_payment_create (struct MHD_Connection *connection); + + +enum MHD_Result +PAIVANA_HTTPD_payment_handle (struct PayRequest *pr, + const char *upload_data, + size_t *upload_data_size); + + +void +PAIVANA_HTTPD_payment_destroy (struct PayRequest *ph); + + +#endif diff --git a/src/backend/paivana-httpd_reverse.c b/src/backend/paivana-httpd_reverse.c @@ -1057,7 +1057,7 @@ PAIVANA_HTTPD_reverse (struct HttpRequest *hr, GNUNET_asprintf (&curlurl, "%s%s", - target_server_base_url, + PH_target_server_base_url, hr->url); curl_easy_setopt (hr->curl, CURLOPT_URL, @@ -1067,7 +1067,7 @@ PAIVANA_HTTPD_reverse (struct HttpRequest *hr, curlurl); GNUNET_free (curlurl); - host_hdr = build_host_header (target_server_base_url); + host_hdr = build_host_header (PH_target_server_base_url); PAIVANA_LOG_DEBUG ("Faking the host header, %s\n", host_hdr); hr->headers = curl_slist_append (hr->headers,