anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

anastasis-httpd.c (40965B)


      1 /*
      2   This file is part of Anastasis
      3   (C) 2020-2025 Anastasis SARL
      4 
      5   Anastasis 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   Anastasis 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 Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   Anastasis; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file backend/anastasis-httpd.c
     18  * @brief HTTP serving layer intended to provide basic backup operations
     19  * @author Christian Grothoff
     20  * @author Dennis Neufeld
     21  * @author Dominik Meister
     22  */
     23 #include "platform.h"
     24 #include "anastasis-httpd.h"
     25 #include "anastasis_util_lib.h"
     26 #include "anastasis-httpd_mhd.h"
     27 #include "anastasis_database_lib.h"
     28 #include "anastasis-httpd_policy.h"
     29 #include "anastasis-httpd_policy-meta.h"
     30 #include "anastasis-httpd_truth.h"
     31 #include "anastasis-httpd_terms.h"
     32 #include "anastasis-httpd_config.h"
     33 #include <taler/taler_json_lib.h>
     34 
     35 
     36 /**
     37  * Upload limit to the service, in megabytes.
     38  */
     39 unsigned long long int AH_upload_limit_mb;
     40 
     41 /**
     42  * Currencies this provider prices its service in, primary currency first.
     43  */
     44 char **AH_currencies;
     45 
     46 /**
     47  * Length of the #AH_currencies array.
     48  */
     49 unsigned int AH_currencies_len;
     50 
     51 /**
     52  * Annual fee for the backup account, per currency.
     53  */
     54 struct TALER_AmountList AH_annual_fees;
     55 
     56 /**
     57  * Fee for a truth upload, per currency.
     58  */
     59 struct TALER_AmountList AH_truth_upload_fees;
     60 
     61 /**
     62  * Amount of insurance, per currency.
     63  */
     64 struct TALER_AmountList AH_insurance;
     65 
     66 /**
     67  * Cost for secure question truth download, per currency.
     68  */
     69 struct TALER_AmountList AH_question_costs;
     70 
     71 /**
     72  * Our configuration.
     73  */
     74 const struct GNUNET_CONFIGURATION_Handle *AH_cfg;
     75 
     76 /**
     77  * Our Taler backend to process payments.
     78  */
     79 char *AH_backend_url;
     80 
     81 /**
     82  * Our fulfillment URL.
     83  */
     84 char *AH_fulfillment_url;
     85 
     86 /**
     87  * Our business name.
     88  */
     89 char *AH_business_name;
     90 
     91 /**
     92  * Our provider salt.
     93  */
     94 struct ANASTASIS_CRYPTO_ProviderSaltP AH_provider_salt;
     95 
     96 /**
     97  * Number of policy uploads permitted per annual fee payment.
     98  */
     99 unsigned long long AH_post_counter = 64LLU;
    100 
    101 /**
    102  * Our context for making HTTP requests.
    103  */
    104 struct GNUNET_CURL_Context *AH_ctx;
    105 
    106 /**
    107  * Should a "Connection: close" header be added to each HTTP response?
    108  */
    109 static int AH_connection_close;
    110 
    111 /**
    112  * True if we started any HTTP daemon.
    113  */
    114 static bool have_daemons;
    115 
    116 /**
    117  * Heap for processing timeouts of requests.
    118  */
    119 struct GNUNET_CONTAINER_Heap *AH_to_heap;
    120 
    121 /**
    122  * Global return code
    123  */
    124 static int global_result;
    125 
    126 /**
    127  * Reschedule context for #AH_ctx.
    128  */
    129 static struct GNUNET_CURL_RescheduleContext *rc;
    130 
    131 /**
    132  * Username and password to use for client authentication
    133  * (optional).
    134  */
    135 static char *userpass;
    136 
    137 /**
    138  * Type of the client's TLS certificate (optional).
    139  */
    140 static char *certtype;
    141 
    142 /**
    143  * File with the client's TLS certificate (optional).
    144  */
    145 static char *certfile;
    146 
    147 /**
    148  * File with the client's TLS private key (optional).
    149  */
    150 static char *keyfile;
    151 
    152 /**
    153  * This value goes in the Authorization:-header.
    154  */
    155 static char *apikey;
    156 
    157 /**
    158  * Passphrase to decrypt client's TLS private key file (optional).
    159  */
    160 static char *keypass;
    161 
    162 
    163 /**
    164  * Kick MHD to run now, to be called after MHD_resume_connection().
    165  * Basically, we need to explicitly resume MHD's event loop whenever
    166  * we made progress serving a request.  This function re-schedules
    167  * the task processing MHD's activities to run immediately.
    168  *
    169  * @param cls NULL -- FIXME: why cls?
    170  *
    171  * FIXME: maybe call directly?
    172  */
    173 void
    174 AH_trigger_daemon (void *cls)
    175 {
    176   TALER_MHD_daemon_trigger ();
    177 }
    178 
    179 
    180 /**
    181  * Kick GNUnet Curl scheduler to begin curl interactions.
    182  */
    183 void
    184 AH_trigger_curl (void)
    185 {
    186   GNUNET_CURL_gnunet_scheduler_reschedule (&rc);
    187 }
    188 
    189 
    190 /**
    191  * A client has requested the given url using the given method
    192  * (MHD_HTTP_METHOD_GET, MHD_HTTP_METHOD_PUT,
    193  * MHD_HTTP_METHOD_DELETE, MHD_HTTP_METHOD_POST, etc).  The callback
    194  * must call MHD callbacks to provide content to give back to the
    195  * client and return an HTTP status code (i.e. MHD_HTTP_OK,
    196  * MHD_HTTP_NOT_FOUND, etc.).
    197  *
    198  * @param cls argument given together with the function
    199  *        pointer when the handler was registered with MHD
    200  * @param connection MHD connection handle with further request details
    201  * @param url the requested url
    202  * @param method the HTTP method used (MHD_HTTP_METHOD_GET,
    203  *        MHD_HTTP_METHOD_PUT, etc.)
    204  * @param version the HTTP version string (i.e.
    205  *        MHD_HTTP_VERSION_1_1)
    206  * @param upload_data the data being uploaded (excluding HEADERS,
    207  *        for a POST that fits into memory and that is encoded
    208  *        with a supported encoding, the POST data will NOT be
    209  *        given in upload_data and is instead available as
    210  *        part of MHD_get_connection_values(); very large POST
    211  *        data *will* be made available incrementally in
    212  *        @a upload_data)
    213  * @param upload_data_size set initially to the size of the
    214  *        @a upload_data provided; the method must update this
    215  *        value to the number of bytes NOT processed;
    216  * @param con_cls pointer that the callback can set to some
    217  *        address and that will be preserved by MHD for future
    218  *        calls for this request; since the access handler may
    219  *        be called many times (i.e., for a PUT/POST operation
    220  *        with plenty of upload data) this allows the application
    221  *        to easily associate some request-specific state.
    222  *        If necessary, this state can be cleaned up in the
    223  *        global MHD_RequestCompletedCallback (which
    224  *        can be set with the MHD_OPTION_NOTIFY_COMPLETED).
    225  *        Initially, `*con_cls` will be NULL.
    226  * @return #MHD_YES if the connection was handled successfully,
    227  *         #MHD_NO if the socket must be closed due to a serious
    228  *         error while handling the request
    229  */
    230 static enum MHD_Result
    231 url_handler (void *cls,
    232              struct MHD_Connection *connection,
    233              const char *url,
    234              const char *method,
    235              const char *version,
    236              const char *upload_data,
    237              size_t *upload_data_size,
    238              void **con_cls)
    239 {
    240   static struct AH_RequestHandler handlers[] = {
    241     /* Landing page, tell humans to go away. */
    242     { "/", MHD_HTTP_METHOD_GET, "text/plain",
    243       "Hello, I'm Anastasis. This HTTP server is not for humans.\n", 0,
    244       &TMH_MHD_handler_static_response, MHD_HTTP_OK },
    245     { "/agpl", MHD_HTTP_METHOD_GET, "text/plain",
    246       NULL, 0,
    247       &TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND },
    248     { "/terms", MHD_HTTP_METHOD_GET, NULL,
    249       NULL, 0,
    250       &AH_handler_terms, MHD_HTTP_OK },
    251     { "/privacy", MHD_HTTP_METHOD_GET, NULL,
    252       NULL, 0,
    253       &AH_handler_privacy, MHD_HTTP_OK },
    254     { "/config", MHD_HTTP_METHOD_GET, "text/json",
    255       NULL, 0,
    256       &AH_handler_config, MHD_HTTP_OK },
    257     {NULL, NULL, NULL, NULL, 0, 0 }
    258   };
    259   static struct AH_RequestHandler h404 = {
    260     "", NULL, "text/html",
    261     "<html><title>404: not found</title></html>", 0,
    262     &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND
    263   };
    264   static struct AH_RequestHandler h405 = {
    265     "", NULL, "text/html",
    266     "<html><title>405: method not allowed</title></html>", 0,
    267     &TMH_MHD_handler_static_response, MHD_HTTP_METHOD_NOT_ALLOWED
    268   };
    269   struct TM_HandlerContext *hc = *con_cls;
    270   const char *correlation_id = NULL;
    271   bool path_matched;
    272 
    273   if (NULL == hc)
    274   {
    275     struct GNUNET_AsyncScopeId aid;
    276 
    277     GNUNET_async_scope_fresh (&aid);
    278     /* We only read the correlation ID on the first callback for every client */
    279     correlation_id = MHD_lookup_connection_value (connection,
    280                                                   MHD_HEADER_KIND,
    281                                                   "Anastasis-Correlation-Id");
    282     if ((NULL != correlation_id) &&
    283         (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)))
    284     {
    285       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    286                   "Invalid incoming correlation ID\n");
    287       correlation_id = NULL;
    288     }
    289     hc = GNUNET_new (struct TM_HandlerContext);
    290     *con_cls = hc;
    291     hc->async_scope_id = aid;
    292     hc->url = url;
    293   }
    294   if (0 == strcasecmp (method,
    295                        MHD_HTTP_METHOD_HEAD))
    296     method = MHD_HTTP_METHOD_GET; /* MHD will throw away the body */
    297 
    298   GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
    299   if (NULL != correlation_id)
    300     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    301                 "Handling request for (%s) URL '%s', correlation_id=%s\n",
    302                 method,
    303                 url,
    304                 correlation_id);
    305   else
    306     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    307                 "Handling request (%s) for URL '%s'\n",
    308                 method,
    309                 url);
    310   if (0 == strncmp (url,
    311                     "/policy/",
    312                     strlen ("/policy/")))
    313   {
    314     const char *account = url + strlen ("/policy/");
    315     const char *end = strchr (account, '/');
    316     struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub;
    317 
    318     if (GNUNET_OK !=
    319         GNUNET_STRINGS_string_to_data (
    320           account,
    321           (NULL == end)
    322           ? strlen (account)
    323           : end - account,
    324           &account_pub,
    325           sizeof (struct ANASTASIS_CRYPTO_AccountPublicKeyP)))
    326     {
    327       return TALER_MHD_reply_with_error (connection,
    328                                          MHD_HTTP_BAD_REQUEST,
    329                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    330                                          "account public key");
    331     }
    332     if ( (NULL != end) &&
    333          (0 != strcmp (end,
    334                        "/meta")) )
    335       return TMH_MHD_handler_static_response (&h404,
    336                                               connection);
    337     if (0 == strcmp (method,
    338                      MHD_HTTP_METHOD_GET))
    339     {
    340       if (NULL == end)
    341         return AH_policy_get (connection,
    342                               &account_pub);
    343       return AH_policy_meta_get (connection,
    344                                  &account_pub);
    345     }
    346     if ( (0 == strcmp (method,
    347                        MHD_HTTP_METHOD_POST)) &&
    348          (NULL == end) )
    349     {
    350       return AH_handler_policy_post (connection,
    351                                      hc,
    352                                      &account_pub,
    353                                      upload_data,
    354                                      upload_data_size);
    355     }
    356     if (0 == strcmp (method,
    357                      MHD_HTTP_METHOD_OPTIONS))
    358     {
    359       return TALER_MHD_reply_cors_preflight (connection);
    360     }
    361     return TMH_MHD_handler_static_response (&h405,
    362                                             connection);
    363   }
    364   if (0 == strncmp (url,
    365                     "/truth/",
    366                     strlen ("/truth/")))
    367   {
    368     struct ANASTASIS_CRYPTO_TruthUUIDP tu;
    369     const char *pub_key_str;
    370     const char *end;
    371     size_t len;
    372 
    373     pub_key_str = &url[strlen ("/truth/")];
    374     end = strchr (pub_key_str,
    375                   '/');
    376     if (NULL == end)
    377       len = strlen (pub_key_str);
    378     else
    379       len = end - pub_key_str;
    380     if (GNUNET_OK !=
    381         GNUNET_STRINGS_string_to_data (
    382           pub_key_str,
    383           len,
    384           &tu,
    385           sizeof(tu)))
    386     {
    387       GNUNET_break_op (0);
    388       return TALER_MHD_reply_with_error (connection,
    389                                          MHD_HTTP_BAD_REQUEST,
    390                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    391                                          "truth UUID");
    392     }
    393     if ( (NULL != end) &&
    394          (0 != strcmp (end, "/solve")) &&
    395          (0 != strcmp (end, "/challenge")) )
    396       return TMH_MHD_handler_static_response (&h404,
    397                                               connection);
    398     if (0 == strcmp (method,
    399                      MHD_HTTP_METHOD_OPTIONS))
    400       return TALER_MHD_reply_cors_preflight (connection);
    401     if (0 != strcmp (method,
    402                      MHD_HTTP_METHOD_POST))
    403       return TMH_MHD_handler_static_response (&h405,
    404                                               connection);
    405     if (NULL == end)
    406     {
    407       return AH_handler_truth_post (connection,
    408                                     hc,
    409                                     &tu,
    410                                     upload_data,
    411                                     upload_data_size);
    412     }
    413     if (0 == strcmp (end,
    414                      "/solve"))
    415     {
    416       return AH_handler_truth_solve (connection,
    417                                      hc,
    418                                      &tu,
    419                                      upload_data,
    420                                      upload_data_size);
    421     }
    422     if (0 == strcmp (end,
    423                      "/challenge"))
    424     {
    425       return AH_handler_truth_challenge (connection,
    426                                          hc,
    427                                          &tu,
    428                                          upload_data,
    429                                          upload_data_size);
    430     }
    431     /* should be impossible to get here */
    432     GNUNET_assert (0);
    433   } /* end of "/truth/" prefix */
    434   path_matched = false;
    435   for (unsigned int i = 0; NULL != handlers[i].url; i++)
    436   {
    437     struct AH_RequestHandler *rh = &handlers[i];
    438 
    439     if (0 == strcmp (url,
    440                      rh->url))
    441     {
    442       path_matched = true;
    443       if (0 == strcasecmp (method,
    444                            MHD_HTTP_METHOD_OPTIONS))
    445       {
    446         return TALER_MHD_reply_cors_preflight (connection);
    447       }
    448       if ( (NULL == rh->method) ||
    449            (0 == strcasecmp (method,
    450                              rh->method)) )
    451       {
    452         return rh->handler (rh,
    453                             connection);
    454       }
    455     }
    456   }
    457   if (path_matched)
    458     return TMH_MHD_handler_static_response (&h405,
    459                                             connection);
    460   return TMH_MHD_handler_static_response (&h404,
    461                                           connection);
    462 }
    463 
    464 
    465 /**
    466  * Shutdown task (magically invoked when the application is being
    467  * quit)
    468  *
    469  * @param cls NULL
    470  */
    471 static void
    472 do_shutdown (void *cls)
    473 {
    474   (void) cls;
    475   TALER_MHD_daemons_halt ();
    476   AH_resume_all_bc ();
    477   AH_truth_challenge_shutdown ();
    478   AH_truth_solve_shutdown ();
    479   AH_truth_upload_shutdown ();
    480   TALER_MHD_daemons_destroy ();
    481   if (NULL != AH_ctx)
    482   {
    483     GNUNET_CURL_fini (AH_ctx);
    484     AH_ctx = NULL;
    485   }
    486   if (NULL != rc)
    487   {
    488     GNUNET_CURL_gnunet_rc_destroy (rc);
    489     rc = NULL;
    490   }
    491   ANASTASIS_DB_fini ();
    492   if (NULL != AH_to_heap)
    493   {
    494     GNUNET_CONTAINER_heap_destroy (AH_to_heap);
    495     AH_to_heap = NULL;
    496   }
    497   TALER_amount_list_free (&AH_annual_fees);
    498   TALER_amount_list_free (&AH_truth_upload_fees);
    499   TALER_amount_list_free (&AH_insurance);
    500   TALER_amount_list_free (&AH_question_costs);
    501   for (unsigned int i = 0; i<AH_currencies_len; i++)
    502     GNUNET_free (AH_currencies[i]);
    503   GNUNET_array_grow (AH_currencies,
    504                      AH_currencies_len,
    505                      0);
    506 }
    507 
    508 
    509 json_t *
    510 AH_make_order (const char *order_id,
    511                const char *summary,
    512                const struct TALER_AmountList *prices)
    513 {
    514   json_t *choices;
    515   json_t *order;
    516 
    517   GNUNET_assert (0 != prices->tal_len);
    518   choices = json_array ();
    519   GNUNET_assert (NULL != choices);
    520   for (unsigned int i = 0; i<prices->tal_len; i++)
    521   {
    522     json_t *choice;
    523 
    524     GNUNET_assert (! TALER_amount_is_zero (&prices->tal[i]));
    525     choice = GNUNET_JSON_PACK (
    526       TALER_JSON_pack_amount ("amount",
    527                               &prices->tal[i]),
    528       GNUNET_JSON_pack_string ("description",
    529                                summary));
    530     GNUNET_assert (0 ==
    531                    json_array_append_new (choices,
    532                                           choice));
    533   }
    534   order = GNUNET_JSON_PACK (
    535     GNUNET_JSON_pack_uint64 ("version",
    536                              1),
    537     GNUNET_JSON_pack_string ("order_id",
    538                              order_id),
    539     GNUNET_JSON_pack_string ("summary",
    540                              summary),
    541     GNUNET_JSON_pack_array_steal ("choices",
    542                                   choices));
    543   return order;
    544 }
    545 
    546 
    547 enum GNUNET_GenericReturnValue
    548 AH_paid_amount (const struct TALER_MERCHANT_GetPrivateOrderResponse *osr,
    549                 struct TALER_Amount *amount)
    550 {
    551   const json_t *contract = osr->details.ok.details.paid.contract_terms;
    552   int ci = osr->details.ok.details.paid.choice_index;
    553   const json_t *amount_obj;
    554   struct GNUNET_JSON_Specification cspec[] = {
    555     TALER_JSON_spec_amount_any ("amount",
    556                                 amount),
    557     GNUNET_JSON_spec_end ()
    558   };
    559 
    560   amount_obj = (0 > ci)
    561     ? contract    /* legacy "v0" order, made before the upgrade */
    562     : json_array_get (json_object_get (contract,
    563                                        "choices"),
    564                       ci);
    565   if (NULL == amount_obj)
    566   {
    567     GNUNET_break (0);
    568     return GNUNET_SYSERR;
    569   }
    570   return GNUNET_JSON_parse (amount_obj,
    571                             cspec,
    572                             NULL, NULL);
    573 }
    574 
    575 
    576 const struct TALER_Amount *
    577 AH_primary_price (const struct TALER_AmountList *al)
    578 {
    579   static struct TALER_Amount zero;
    580   const struct TALER_Amount *a;
    581 
    582   GNUNET_assert (0 < AH_currencies_len);
    583   a = TALER_amount_list_find (al,
    584                               AH_currencies[0]);
    585   if (NULL != a)
    586     return a;
    587   /* The option is free.  Report zero in the primary currency, which is
    588      exactly what a single-currency provider running for free emitted
    589      before there were price lists. */
    590   GNUNET_assert (GNUNET_OK ==
    591                  TALER_amount_set_zero (AH_currencies[0],
    592                                         &zero));
    593   return &zero;
    594 }
    595 
    596 
    597 enum GNUNET_GenericReturnValue
    598 AH_check_price (const struct TALER_AmountList *al,
    599                 const char *section,
    600                 const char *option)
    601 {
    602   switch (TALER_amount_list_check_uniform (al))
    603   {
    604   case GNUNET_SYSERR:
    605     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    606                 "Price list error: option `%s' in section `%s' is `%s',"
    607                 " which prices some currencies at zero and others above"
    608                 " zero.  Either all of them are zero (the option is free)"
    609                 " or none is.\n",
    610                 option,
    611                 section,
    612                 TALER_amount_list2s (al));
    613     return GNUNET_SYSERR;
    614   case GNUNET_NO:
    615     /* Free is a first-class answer, and exempt from the coverage check
    616        below: an operator must be able to say "free" without having to
    617        enumerate every currency to do it. */
    618     return GNUNET_NO;
    619   case GNUNET_OK:
    620     break;
    621   }
    622   if (! TALER_amount_list_covers (al,
    623                                   (const char *const *) AH_currencies,
    624                                   AH_currencies_len))
    625   {
    626     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    627                 "Price list error: option `%s' in section `%s' is `%s',"
    628                 " which does not price in exactly the currencies given in"
    629                 " [anastasis] CURRENCIES.\n",
    630                 option,
    631                 section,
    632                 TALER_amount_list2s (al));
    633     /* Naming the missing currency is what makes this actionable: the
    634        failure mode it guards against is an operator adding a currency
    635        and overlooking one of the many COST options. */
    636     for (unsigned int i = 0; i<AH_currencies_len; i++)
    637       if (NULL ==
    638           TALER_amount_list_find (al,
    639                                   AH_currencies[i]))
    640         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    641                     "No price given for currency `%s' in option `%s' of"
    642                     " section `%s'\n",
    643                     AH_currencies[i],
    644                     option,
    645                     section);
    646     for (unsigned int i = 0; i<al->tal_len; i++)
    647     {
    648       bool found = false;
    649 
    650       for (unsigned int j = 0; j<AH_currencies_len; j++)
    651         if (0 == strcasecmp (al->tal[i].currency,
    652                              AH_currencies[j]))
    653           found = true;
    654       if (! found)
    655         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    656                     "Currency `%s' is priced in option `%s' of section"
    657                     " `%s' but is not in [anastasis] CURRENCIES\n",
    658                     al->tal[i].currency,
    659                     option,
    660                     section);
    661     }
    662     return GNUNET_SYSERR;
    663   }
    664   return GNUNET_OK;
    665 }
    666 
    667 
    668 /**
    669  * Closure for #check_method_cost_cb().
    670  */
    671 struct MethodCostContext
    672 {
    673   /**
    674    * Set to true if some enabled authorization method is misconfigured.
    675    */
    676   bool failure;
    677 };
    678 
    679 
    680 /**
    681  * Validate the COST of one enabled authorization method.
    682  *
    683  * The plugins themselves are loaded lazily, on the first ``/config``
    684  * request, so waiting for the loader to notice a bad COST would mean the
    685  * provider starts and only fails once a client asks --- by which time the
    686  * operator is no longer watching.  The configuration file can be checked
    687  * without loading anything, so it is checked here.
    688  *
    689  * @param cls a `struct MethodCostContext *`
    690  * @param section configuration section to inspect
    691  */
    692 static void
    693 check_method_cost_cb (void *cls,
    694                       const char *section)
    695 {
    696   struct MethodCostContext *mcc = cls;
    697   struct TALER_AmountList costs;
    698 
    699   if (0 != strncasecmp (section,
    700                         "authorization-",
    701                         strlen ("authorization-")))
    702     return;
    703   if (GNUNET_YES !=
    704       GNUNET_CONFIGURATION_get_value_yesno (AH_cfg,
    705                                             section,
    706                                             "ENABLED"))
    707     return;
    708   /* An absent COST is an error, not "free": a typo in the option name
    709      must not silently give the method away.  Free is spelled with an
    710      empty value, or with a zero in every currency. */
    711   if (GNUNET_OK !=
    712       TALER_config_get_amount_list (AH_cfg,
    713                                     section,
    714                                     "COST",
    715                                     &costs))
    716   {
    717     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    718                                section,
    719                                "COST");
    720     mcc->failure = true;
    721     return;
    722   }
    723   if (GNUNET_SYSERR ==
    724       AH_check_price (&costs,
    725                       section,
    726                       "COST"))
    727     mcc->failure = true;
    728   TALER_amount_list_free (&costs);
    729 }
    730 
    731 
    732 /**
    733  * Determine the currencies this provider prices its service in.
    734  *
    735  * Normally these are given explicitly in [anastasis] CURRENCIES, primary
    736  * currency first.  A configuration written before multi-currency support
    737  * has no such option; for those, the single currency the provider was
    738  * already using is derived from the first priced option, which keeps every
    739  * existing configuration file working unchanged.  Any disagreement between
    740  * the options is then caught by the per-option coverage check, which can
    741  * name the offending option.
    742  *
    743  * @param cfg configuration to use
    744  * @return #GNUNET_OK on success
    745  */
    746 static enum GNUNET_GenericReturnValue
    747 parse_currencies (const struct GNUNET_CONFIGURATION_Handle *cfg)
    748 {
    749   char *str;
    750 
    751   if (GNUNET_OK !=
    752       GNUNET_CONFIGURATION_get_value_string (cfg,
    753                                              "anastasis",
    754                                              "CURRENCIES",
    755                                              &str))
    756   {
    757     const struct TALER_AmountList *als[] = {
    758       &AH_annual_fees,
    759       &AH_truth_upload_fees,
    760       &AH_insurance,
    761       &AH_question_costs
    762     };
    763 
    764     /* A priced option names the currency authoritatively; failing that,
    765        an option that is free but written out as e.g. "EUR:0" still names
    766        one, which is how a provider running entirely for free is
    767        configured today. */
    768     for (unsigned int pass = 0; pass < 2; pass++)
    769       for (unsigned int i = 0; i<sizeof (als) / sizeof (als[0]); i++)
    770       {
    771         if ( (0 == pass) &&
    772              (GNUNET_OK !=
    773               TALER_amount_list_check_uniform (als[i])) )
    774           continue;
    775         if (0 == als[i]->tal_len)
    776           continue;
    777         if (1 != als[i]->tal_len)
    778         {
    779           GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    780                                      "anastasis",
    781                                      "CURRENCIES");
    782           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    783                       "A provider pricing in more than one currency must say so explicitly.\n");
    784           return GNUNET_SYSERR;
    785         }
    786         GNUNET_array_append (AH_currencies,
    787                              AH_currencies_len,
    788                              GNUNET_strdup (als[i]->tal[0].currency));
    789         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    790                     "[anastasis] CURRENCIES not configured, assuming `%s'\n",
    791                     AH_currencies[0]);
    792         return GNUNET_OK;
    793       }
    794     /* Not one option names a currency, so there is nothing to derive
    795        from and the legacy scalar fields of /config would have no
    796        currency to report either. */
    797     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    798                                "anastasis",
    799                                "CURRENCIES");
    800     return GNUNET_SYSERR;
    801   }
    802   for (const char *tok = strtok (str, ";");
    803        NULL != tok;
    804        tok = strtok (NULL, ";"))
    805   {
    806     if (GNUNET_OK !=
    807         TALER_check_currency (tok))
    808     {
    809       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    810                                  "anastasis",
    811                                  "CURRENCIES",
    812                                  tok);
    813       GNUNET_free (str);
    814       return GNUNET_SYSERR;
    815     }
    816     for (unsigned int i = 0; i<AH_currencies_len; i++)
    817       if (0 == strcasecmp (tok,
    818                            AH_currencies[i]))
    819       {
    820         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    821                                    "anastasis",
    822                                    "CURRENCIES",
    823                                    "currency given more than once");
    824         GNUNET_free (str);
    825         return GNUNET_SYSERR;
    826       }
    827     GNUNET_array_append (AH_currencies,
    828                          AH_currencies_len,
    829                          GNUNET_strdup (tok));
    830   }
    831   GNUNET_free (str);
    832   if (0 == AH_currencies_len)
    833   {
    834     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    835                                "anastasis",
    836                                "CURRENCIES",
    837                                "no currency given");
    838     return GNUNET_SYSERR;
    839   }
    840   return GNUNET_OK;
    841 }
    842 
    843 
    844 /**
    845  * Parse and validate everything this provider charges for.
    846  *
    847  * @param cfg configuration to use
    848  * @return #GNUNET_OK on success
    849  */
    850 static enum GNUNET_GenericReturnValue
    851 parse_prices (const struct GNUNET_CONFIGURATION_Handle *cfg)
    852 {
    853   static const struct
    854   {
    855     const char *section;
    856     const char *option;
    857     struct TALER_AmountList *al;
    858   } prices[] = {
    859     { "anastasis", "INSURANCE", &AH_insurance },
    860     { "authorization-question", "COST", &AH_question_costs },
    861     { "anastasis", "ANNUAL_FEE", &AH_annual_fees },
    862     { "anastasis", "TRUTH_UPLOAD_FEE", &AH_truth_upload_fees }
    863   };
    864   struct MethodCostContext mcc = {
    865     .failure = false
    866   };
    867 
    868   for (unsigned int i = 0; i<sizeof (prices) / sizeof (prices[0]); i++)
    869   {
    870     if (GNUNET_OK !=
    871         TALER_config_get_amount_list (cfg,
    872                                       prices[i].section,
    873                                       prices[i].option,
    874                                       prices[i].al))
    875     {
    876       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    877                                  prices[i].section,
    878                                  prices[i].option);
    879       return GNUNET_SYSERR;
    880     }
    881   }
    882   /* The currency list has to exist before anything can be checked
    883      against it, but deriving it from the prices needs them parsed. */
    884   if (GNUNET_OK !=
    885       parse_currencies (cfg))
    886     return GNUNET_SYSERR;
    887   for (unsigned int i = 0; i<sizeof (prices) / sizeof (prices[0]); i++)
    888     if (GNUNET_SYSERR ==
    889         AH_check_price (prices[i].al,
    890                         prices[i].section,
    891                         prices[i].option))
    892       return GNUNET_SYSERR;
    893   GNUNET_CONFIGURATION_iterate_sections (cfg,
    894                                          &check_method_cost_cb,
    895                                          &mcc);
    896   if (mcc.failure)
    897     return GNUNET_SYSERR;
    898   return GNUNET_OK;
    899 }
    900 
    901 
    902 /**
    903  * Function called whenever MHD is done with a request.  If the
    904  * request was a POST, we may have stored a `struct Buffer *` in the
    905  * @a con_cls that might still need to be cleaned up.  Call the
    906  * respective function to free the memory.
    907  *
    908  * @param cls client-defined closure
    909  * @param connection connection handle
    910  * @param con_cls value as set by the last call to
    911  *        the #MHD_AccessHandlerCallback
    912  * @param toe reason for request termination
    913  * @see #MHD_OPTION_NOTIFY_COMPLETED
    914  * @ingroup request
    915  */
    916 static void
    917 handle_mhd_completion_callback (void *cls,
    918                                 struct MHD_Connection *connection,
    919                                 void **con_cls,
    920                                 enum MHD_RequestTerminationCode toe)
    921 {
    922   struct TM_HandlerContext *hc = *con_cls;
    923   struct GNUNET_AsyncScopeSave old_scope;
    924 
    925   (void) cls;
    926   (void) connection;
    927   if (NULL == hc)
    928     return;
    929   GNUNET_async_scope_enter (&hc->async_scope_id,
    930                             &old_scope);
    931   {
    932 #if MHD_VERSION >= 0x00097304
    933     const union MHD_ConnectionInfo *ci;
    934     unsigned int http_status = 0;
    935 
    936     ci = MHD_get_connection_info (connection,
    937                                   MHD_CONNECTION_INFO_HTTP_STATUS);
    938     if (NULL != ci)
    939       http_status = ci->http_status;
    940     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    941                 "Request for `%s' completed with HTTP status %u (%d)\n",
    942                 hc->url,
    943                 http_status,
    944                 toe);
    945 #else
    946     (void) connection;
    947     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    948                 "Request for `%s' completed (%d)\n",
    949                 hc->url,
    950                 toe);
    951 #endif
    952   }
    953   if (NULL != hc->cc)
    954     hc->cc (hc);
    955   GNUNET_free (hc);
    956   *con_cls = NULL;
    957 }
    958 
    959 
    960 /**
    961  * Callback invoked on every listen socket to start the
    962  * respective MHD HTTP daemon.
    963  *
    964  * @param cls unused
    965  * @param lsock the listen socket
    966  */
    967 static void
    968 start_daemon (void *cls,
    969               int lsock)
    970 {
    971   struct MHD_Daemon *mhd;
    972 
    973   (void) cls;
    974   GNUNET_assert (-1 != lsock);
    975   mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK,
    976                           0 /* port */,
    977                           NULL, NULL,
    978                           &url_handler, NULL,
    979                           MHD_OPTION_LISTEN_SOCKET, lsock,
    980                           MHD_OPTION_NOTIFY_COMPLETED,
    981                           &handle_mhd_completion_callback, NULL,
    982                           MHD_OPTION_CONNECTION_TIMEOUT, (unsigned
    983                                                           int) 10 /* 10s */,
    984                           MHD_OPTION_END);
    985   if (NULL == mhd)
    986   {
    987     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    988                 "Failed to launch HTTP service.\n");
    989     GNUNET_SCHEDULER_shutdown ();
    990     return;
    991   }
    992   have_daemons = true;
    993   TALER_MHD_daemon_start (mhd);
    994 }
    995 
    996 
    997 /**
    998  * Main function that will be run by the scheduler.
    999  *
   1000  * @param cls closure
   1001  * @param args remaining command-line arguments
   1002  * @param cfgfile name of the configuration file used (for saving, can be
   1003  *        NULL!)
   1004  * @param config configuration
   1005  */
   1006 static void
   1007 run (void *cls,
   1008      char *const *args,
   1009      const char *cfgfile,
   1010      const struct GNUNET_CONFIGURATION_Handle *config)
   1011 {
   1012   enum TALER_MHD_GlobalOptions go;
   1013 
   1014   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1015               "Starting anastasis-httpd\n");
   1016   go = TALER_MHD_GO_NONE;
   1017   if (AH_connection_close)
   1018     go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
   1019   AH_load_terms (config);
   1020   TALER_MHD_setup (go);
   1021   AH_cfg = config;
   1022   global_result = EXIT_NO_RESTART;
   1023   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1024                                  NULL);
   1025   if (GNUNET_OK !=
   1026       GNUNET_CONFIGURATION_get_value_number (config,
   1027                                              "anastasis",
   1028                                              "UPLOAD_LIMIT_MB",
   1029                                              &AH_upload_limit_mb))
   1030   {
   1031     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1032                                "anastasis",
   1033                                "UPLOAD_LIMIT_MB");
   1034     GNUNET_SCHEDULER_shutdown ();
   1035     return;
   1036   }
   1037   if (GNUNET_OK !=
   1038       parse_prices (config))
   1039   {
   1040     GNUNET_SCHEDULER_shutdown ();
   1041     return;
   1042   }
   1043   if (GNUNET_OK !=
   1044       GNUNET_CONFIGURATION_get_value_string (config,
   1045                                              "anastasis-merchant-backend",
   1046                                              "PAYMENT_BACKEND_URL",
   1047                                              &AH_backend_url))
   1048   {
   1049     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1050                                "anastasis-merchant-backend",
   1051                                "PAYMENT_BACKEND_URL");
   1052     GNUNET_SCHEDULER_shutdown ();
   1053     return;
   1054   }
   1055   if ( (0 != strncasecmp ("https://",
   1056                           AH_backend_url,
   1057                           strlen ("https://"))) &&
   1058        (0 != strncasecmp ("http://",
   1059                           AH_backend_url,
   1060                           strlen ("http://"))) )
   1061   {
   1062     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1063                                "anastasis-merchant-backend",
   1064                                "PAYMENT_BACKEND_URL",
   1065                                "Must be HTTP(S) URL");
   1066     GNUNET_SCHEDULER_shutdown ();
   1067     return;
   1068   }
   1069 
   1070   if ( (0 == strcasecmp ("https://",
   1071                          AH_backend_url)) ||
   1072        (0 == strcasecmp ("http://",
   1073                          AH_backend_url)) )
   1074   {
   1075     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
   1076                                "anastasis-merchant-backend",
   1077                                "PAYMENT_BACKEND_URL",
   1078                                "Must have domain name");
   1079     GNUNET_SCHEDULER_shutdown ();
   1080     return;
   1081   }
   1082 
   1083   if (GNUNET_OK !=
   1084       GNUNET_CONFIGURATION_get_value_string (config,
   1085                                              "anastasis",
   1086                                              "FULFILLMENT_URL",
   1087                                              &AH_fulfillment_url))
   1088   {
   1089     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1090                                "anastasis",
   1091                                "FULFILLMENT_URL");
   1092     GNUNET_SCHEDULER_shutdown ();
   1093     return;
   1094   }
   1095   if (GNUNET_OK !=
   1096       GNUNET_CONFIGURATION_get_value_number (config,
   1097                                              "anastasis",
   1098                                              "ANNUAL_POLICY_UPLOAD_LIMIT",
   1099                                              &AH_post_counter))
   1100   {
   1101     /* only warn, we will use the default */
   1102     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
   1103                                "anastasis",
   1104                                "ANNUAL_POLICY_UPLOAD_LIMIT");
   1105   }
   1106 
   1107   if (GNUNET_OK !=
   1108       GNUNET_CONFIGURATION_get_value_string (config,
   1109                                              "anastasis",
   1110                                              "BUSINESS_NAME",
   1111                                              &AH_business_name))
   1112   {
   1113     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1114                                "anastasis",
   1115                                "BUSINESS_NAME");
   1116     GNUNET_SCHEDULER_shutdown ();
   1117     return;
   1118   }
   1119   {
   1120     char *provider_salt;
   1121 
   1122     if (GNUNET_OK !=
   1123         GNUNET_CONFIGURATION_get_value_string (config,
   1124                                                "anastasis",
   1125                                                "PROVIDER_SALT",
   1126                                                &provider_salt))
   1127     {
   1128       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1129                                  "anastasis",
   1130                                  "PROVIDER_SALT");
   1131       GNUNET_SCHEDULER_shutdown ();
   1132       return;
   1133     }
   1134     GNUNET_assert (GNUNET_YES ==
   1135                    GNUNET_CRYPTO_hkdf_gnunet (&AH_provider_salt,
   1136                                               sizeof (AH_provider_salt),
   1137                                               "anastasis-provider-salt",
   1138                                               strlen ("anastasis-provider-salt")
   1139                                               ,
   1140                                               provider_salt,
   1141                                               strlen (provider_salt)));
   1142     GNUNET_free (provider_salt);
   1143   }
   1144 
   1145   /* setup HTTP client event loop */
   1146   AH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1147                              &rc);
   1148   rc = GNUNET_CURL_gnunet_rc_create (AH_ctx);
   1149   if (NULL != userpass)
   1150     GNUNET_CURL_set_userpass (AH_ctx,
   1151                               userpass);
   1152   if (NULL != keyfile)
   1153     GNUNET_CURL_set_tlscert (AH_ctx,
   1154                              certtype,
   1155                              certfile,
   1156                              keyfile,
   1157                              keypass);
   1158   if (NULL == apikey)
   1159   {
   1160     (void) GNUNET_CONFIGURATION_get_value_string (config,
   1161                                                   "anastasis-merchant-backend",
   1162                                                   "API_KEY",
   1163                                                   &apikey);
   1164   }
   1165   if (NULL != apikey)
   1166   {
   1167     char *auth_header;
   1168 
   1169     GNUNET_asprintf (&auth_header,
   1170                      "%s: %s",
   1171                      MHD_HTTP_HEADER_AUTHORIZATION,
   1172                      apikey);
   1173     if (GNUNET_OK !=
   1174         GNUNET_CURL_append_header (AH_ctx,
   1175                                    auth_header))
   1176     {
   1177       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1178                   "Failed so set %s header, trying without\n",
   1179                   MHD_HTTP_HEADER_AUTHORIZATION);
   1180     }
   1181     GNUNET_free (auth_header);
   1182   }
   1183 
   1184   if (GNUNET_OK !=
   1185       ANASTASIS_DB_init (config))
   1186   {
   1187     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1188                 "Database not setup. Did you run anastasis-dbinit?\n");
   1189     GNUNET_SCHEDULER_shutdown ();
   1190     return;
   1191   }
   1192 
   1193   {
   1194     enum GNUNET_GenericReturnValue ret;
   1195 
   1196     ret = TALER_MHD_listen_bind (config,
   1197                                  "anastasis",
   1198                                  &start_daemon,
   1199                                  NULL);
   1200     switch (ret)
   1201     {
   1202     case GNUNET_SYSERR:
   1203       global_result = EXIT_NOTCONFIGURED;
   1204       GNUNET_SCHEDULER_shutdown ();
   1205       return;
   1206     case GNUNET_NO:
   1207       if (! have_daemons)
   1208       {
   1209         global_result = EXIT_NOTCONFIGURED;
   1210         GNUNET_SCHEDULER_shutdown ();
   1211         return;
   1212       }
   1213       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1214                   "Could not open all configured listen sockets\n");
   1215       break;
   1216     case GNUNET_OK:
   1217       break;
   1218     }
   1219   }
   1220   global_result = EXIT_SUCCESS;
   1221 }
   1222 
   1223 
   1224 /**
   1225  * The main function of the serve tool
   1226  *
   1227  * @param argc number of arguments from the command line
   1228  * @param argv command line arguments
   1229  * @return 0 ok, 1 on error
   1230  */
   1231 int
   1232 main (int argc,
   1233       char *const *argv)
   1234 {
   1235   enum GNUNET_GenericReturnValue res;
   1236   struct GNUNET_GETOPT_CommandLineOption options[] = {
   1237     GNUNET_GETOPT_option_string ('A',
   1238                                  "auth",
   1239                                  "USERNAME:PASSWORD",
   1240                                  "use the given USERNAME and PASSWORD for client authentication",
   1241                                  &userpass),
   1242     GNUNET_GETOPT_option_flag ('C',
   1243                                "connection-close",
   1244                                "force HTTP connections to be closed after each request",
   1245                                &AH_connection_close),
   1246     GNUNET_GETOPT_option_string ('k',
   1247                                  "key",
   1248                                  "KEYFILE",
   1249                                  "file with the private TLS key for TLS client authentication",
   1250                                  &keyfile),
   1251     GNUNET_GETOPT_option_string ('p',
   1252                                  "pass",
   1253                                  "KEYFILEPASSPHRASE",
   1254                                  "passphrase needed to decrypt the TLS client private key file",
   1255                                  &keypass),
   1256     GNUNET_GETOPT_option_string ('K',
   1257                                  "apikey",
   1258                                  "APIKEY",
   1259                                  "API key to use in the HTTP request to the merchant backend",
   1260                                  &apikey),
   1261     GNUNET_GETOPT_option_string ('t',
   1262                                  "type",
   1263                                  "CERTTYPE",
   1264                                  "type of the TLS client certificate, defaults to PEM if not specified",
   1265                                  &certtype),
   1266     GNUNET_GETOPT_OPTION_END
   1267   };
   1268 
   1269   res = GNUNET_PROGRAM_run (ANASTASIS_project_data (),
   1270                             argc, argv,
   1271                             "anastasis-httpd",
   1272                             "Anastasis HTTP interface",
   1273                             options, &run, NULL);
   1274   if (GNUNET_SYSERR == res)
   1275     return 3;
   1276   if (GNUNET_NO == res)
   1277     return 0;
   1278   return global_result;
   1279 }