merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_private-post-donau-instance.c (10059B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024, 2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 /**
     17  * @file taler-merchant-httpd_private-post-donau-instance.c
     18  * @brief implementation of POST /donau
     19  * @author Bohdan Potuzhnyi
     20  * @author Vlada Svirsh
     21  * @author Christian Grothoff
     22  */
     23 #include "platform.h"
     24 #include <jansson.h>
     25 #include "donau/donau_service.h"
     26 #include <taler/taler_json_lib.h>
     27 #include <taler/taler_dbevents.h>
     28 #include "taler_merchant_service.h"
     29 #include "taler-merchant-httpd_private-post-donau-instance.h"
     30 
     31 /**
     32  * Context for the POST /donau request handler.
     33  */
     34 struct PostDonauCtx
     35 {
     36   /**
     37    * Stored in a DLL.
     38    */
     39   struct PostDonauCtx *next;
     40 
     41   /**
     42    * Stored in a DLL.
     43    */
     44   struct PostDonauCtx *prev;
     45 
     46   /**
     47    * Connection to the MHD server
     48    */
     49   struct MHD_Connection *connection;
     50 
     51   /**
     52    * Context of the request handler.
     53    */
     54   struct TMH_HandlerContext *hc;
     55 
     56   /**
     57    * URL of the DONAU service
     58    * to which the charity belongs.
     59    */
     60   const char *donau_url;
     61 
     62   /**
     63    * ID of the charity in the DONAU service.
     64    */
     65   uint64_t charity_id;
     66 
     67   /**
     68    * Handle returned by DONAU_charities_get(); needed to cancel on
     69    * connection abort, etc.
     70    */
     71   struct DONAU_CharityGetHandle *get_handle;
     72 
     73   /**
     74    * Response to return.
     75    */
     76   struct MHD_Response *response;
     77 
     78   /**
     79    * HTTP status for @e response.
     80    */
     81   unsigned int http_status;
     82 
     83   /**
     84    * #GNUNET_YES if we are suspended,
     85    * #GNUNET_NO if not,
     86    * #GNUNET_SYSERR on shutdown
     87    */
     88   enum GNUNET_GenericReturnValue suspended;
     89 };
     90 
     91 
     92 /**
     93  * Head of active pay context DLL.
     94  */
     95 static struct PostDonauCtx *pdc_head;
     96 
     97 /**
     98  * Tail of active pay context DLL.
     99  */
    100 static struct PostDonauCtx *pdc_tail;
    101 
    102 
    103 void
    104 TMH_force_post_donau_resume ()
    105 {
    106   for (struct PostDonauCtx *pdc = pdc_head;
    107        NULL != pdc;
    108        pdc = pdc->next)
    109   {
    110     if (GNUNET_YES == pdc->suspended)
    111     {
    112       pdc->suspended = GNUNET_SYSERR;
    113       MHD_resume_connection (pdc->connection);
    114     }
    115   }
    116 }
    117 
    118 
    119 /**
    120  * Callback for DONAU_charities_get() to handle the response.
    121  *
    122  * @param cls closure with PostDonauCtx
    123  * @param gcr response from Donau
    124  */
    125 static void
    126 donau_charity_get_cb (void *cls,
    127                       const struct DONAU_GetCharityResponse *gcr)
    128 {
    129   struct PostDonauCtx *pdc = cls;
    130   enum GNUNET_DB_QueryStatus qs;
    131 
    132   pdc->get_handle = NULL;
    133   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    134               "Processing DONAU charity get response");
    135   /* Anything but 200 => propagate Donau’s response. */
    136   if (MHD_HTTP_OK != gcr->hr.http_status)
    137   {
    138     pdc->http_status = MHD_HTTP_BAD_GATEWAY;
    139     pdc->response = TALER_MHD_MAKE_JSON_PACK (
    140       TALER_MHD_PACK_EC (gcr->hr.ec),
    141       GNUNET_JSON_pack_uint64 ("donau_http_status",
    142                                gcr->hr.http_status));
    143     pdc->suspended = GNUNET_NO;
    144     MHD_resume_connection (pdc->connection);
    145     TALER_MHD_daemon_trigger ();
    146     return;
    147   }
    148 
    149   if (0 !=
    150       GNUNET_memcmp (&gcr->details.ok.charity.charity_pub.eddsa_pub,
    151                      &pdc->hc->instance->merchant_pub.eddsa_pub))
    152   {
    153     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    154                 "Charity key at donau does not match our merchant key\n");
    155     pdc->http_status = MHD_HTTP_CONFLICT;
    156     pdc->response = TALER_MHD_make_error (
    157       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    158       "charity_pub != merchant_pub");
    159     MHD_resume_connection (pdc->connection);
    160     TALER_MHD_daemon_trigger ();
    161     return;
    162   }
    163 
    164   qs = TMH_db->insert_donau_instance (TMH_db->cls,
    165                                       pdc->donau_url,
    166                                       &gcr->details.ok.charity,
    167                                       pdc->charity_id);
    168   switch (qs)
    169   {
    170   case GNUNET_DB_STATUS_HARD_ERROR:
    171     GNUNET_break (0);
    172     pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    173     pdc->response = TALER_MHD_make_error (
    174       TALER_EC_GENERIC_DB_STORE_FAILED,
    175       "insert_donau_instance");
    176     break;
    177   case GNUNET_DB_STATUS_SOFT_ERROR:
    178     GNUNET_break (0);
    179     pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    180     pdc->response = TALER_MHD_make_error (
    181       TALER_EC_GENERIC_DB_STORE_FAILED,
    182       "insert_donau_instance");
    183     break;
    184   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    185     /* presumably idempotent + concurrent, no need to notify, but still respond */
    186     pdc->http_status = MHD_HTTP_NO_CONTENT;
    187     pdc->response = MHD_create_response_from_buffer_static (0,
    188                                                             NULL);
    189     TALER_MHD_add_global_headers (pdc->response,
    190                                   false);
    191     break;
    192   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    193     {
    194       struct GNUNET_DB_EventHeaderP es = {
    195         .size = htons (sizeof (es)),
    196         .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
    197       };
    198 
    199       TMH_db->event_notify (TMH_db->cls,
    200                             &es,
    201                             pdc->donau_url,
    202                             strlen (pdc->donau_url) + 1);
    203       pdc->http_status = MHD_HTTP_NO_CONTENT;
    204       pdc->response = MHD_create_response_from_buffer_static (0,
    205                                                               NULL);
    206       TALER_MHD_add_global_headers (pdc->response,
    207                                     false);
    208       break;
    209     }
    210   }
    211   pdc->suspended = GNUNET_NO;
    212   MHD_resume_connection (pdc->connection);
    213   TALER_MHD_daemon_trigger ();
    214 }
    215 
    216 
    217 /**
    218  * Cleanup function for the PostDonauCtx.
    219  *
    220  * @param cls closure with PostDonauCtx
    221  */
    222 static void
    223 post_donau_cleanup (void *cls)
    224 {
    225   struct PostDonauCtx *pdc = cls;
    226 
    227   if (pdc->get_handle)
    228   {
    229     DONAU_charity_get_cancel (pdc->get_handle);
    230     pdc->get_handle = NULL;
    231   }
    232   GNUNET_CONTAINER_DLL_remove (pdc_head,
    233                                pdc_tail,
    234                                pdc);
    235   GNUNET_free (pdc);
    236 }
    237 
    238 
    239 /**
    240  * Handle a POST "/donau" request.
    241  *
    242  * @param rh context of the handler
    243  * @param connection the MHD connection to handle
    244  * @param[in,out] hc context with further information about the request
    245  * @return MHD result code
    246  */
    247 MHD_RESULT
    248 TMH_private_post_donau_instance (const struct TMH_RequestHandler *rh,
    249                                  struct MHD_Connection *connection,
    250                                  struct TMH_HandlerContext *hc)
    251 {
    252   struct PostDonauCtx *pdc = hc->ctx;
    253 
    254   if (NULL == pdc)
    255   {
    256     enum GNUNET_DB_QueryStatus qs;
    257 
    258     pdc = GNUNET_new (struct PostDonauCtx);
    259     pdc->connection = connection;
    260     pdc->hc = hc;
    261     hc->ctx = pdc;
    262     hc->cc = &post_donau_cleanup;
    263     GNUNET_CONTAINER_DLL_insert (pdc_head,
    264                                  pdc_tail,
    265                                  pdc);
    266     {
    267       struct GNUNET_JSON_Specification spec[] = {
    268         GNUNET_JSON_spec_string ("donau_url",
    269                                  &pdc->donau_url),
    270         GNUNET_JSON_spec_uint64 ("charity_id",
    271                                  &pdc->charity_id),
    272         GNUNET_JSON_spec_end ()
    273       };
    274 
    275       if (GNUNET_OK !=
    276           TALER_MHD_parse_json_data (connection,
    277                                      hc->request_body,
    278                                      spec))
    279       {
    280         GNUNET_break_op (0);
    281         return MHD_NO;
    282       }
    283     }
    284     qs = TMH_db->check_donau_instance (TMH_db->cls,
    285                                        &hc->instance->merchant_pub,
    286                                        pdc->donau_url,
    287                                        pdc->charity_id);
    288     switch (qs)
    289     {
    290     case GNUNET_DB_STATUS_HARD_ERROR:
    291     case GNUNET_DB_STATUS_SOFT_ERROR:
    292       GNUNET_break (0);
    293       pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    294       return TALER_MHD_reply_with_error (
    295         connection,
    296         MHD_HTTP_INTERNAL_SERVER_ERROR,
    297         TALER_EC_GENERIC_DB_FETCH_FAILED,
    298         "check_donau_instance");
    299       break;
    300     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    301       pdc->http_status = MHD_HTTP_NO_CONTENT;
    302       pdc->response = MHD_create_response_from_buffer_static (0,
    303                                                               NULL);
    304       TALER_MHD_add_global_headers (pdc->response,
    305                                     false);
    306       goto respond;
    307     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    308       /* normal case, continue below */
    309       break;
    310     }
    311 
    312     {
    313       struct DONAU_CharityPrivateKeyP cp;
    314 
    315       /* Merchant private key IS our charity private key */
    316       cp.eddsa_priv = hc->instance->merchant_priv.eddsa_priv;
    317       pdc->get_handle =
    318         DONAU_charity_get (TMH_curl_ctx,
    319                            pdc->donau_url,
    320                            pdc->charity_id,
    321                            &cp,
    322                            &donau_charity_get_cb,
    323                            pdc);
    324     }
    325     if (NULL == pdc->get_handle)
    326     {
    327       GNUNET_break (0);
    328       GNUNET_free (pdc);
    329       return TALER_MHD_reply_with_error (connection,
    330                                          MHD_HTTP_SERVICE_UNAVAILABLE,
    331                                          TALER_EC_GENERIC_ALLOCATION_FAILURE,
    332                                          "Failed to initiate Donau lookup");
    333     }
    334     pdc->suspended = GNUNET_YES;
    335     MHD_suspend_connection (connection);
    336     return MHD_YES;
    337   }
    338 respond:
    339   if (NULL != pdc->response)
    340   {
    341     MHD_RESULT res;
    342 
    343     GNUNET_break (GNUNET_NO == pdc->suspended);
    344     res = MHD_queue_response (pdc->connection,
    345                               pdc->http_status,
    346                               pdc->response);
    347     MHD_destroy_response (pdc->response);
    348     return res;
    349   }
    350   GNUNET_break (GNUNET_SYSERR == pdc->suspended);
    351   return MHD_NO;
    352 }