challenger

OAuth 2.0-based authentication service that validates user can receive messages at a certain address
Log | Files | Refs | Submodules | README | LICENSE

challenger-httpd_solve.c (11554B)


      1 /*
      2   This file is part of Challenger
      3   Copyright (C) 2023, 2024 Taler Systems SA
      4 
      5   Challenger 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   Challenger 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   Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file challenger-httpd_solve.c
     18  * @brief functions to handle incoming /solve requests
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "challenger-httpd.h"
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include <gnunet/gnunet_db_lib.h>
     25 #include "challenger-httpd_common.h"
     26 #include "challenger-httpd_solve.h"
     27 #include <taler/taler_json_lib.h>
     28 #include <taler/taler_templating_lib.h>
     29 #include <taler/taler_signatures.h>
     30 #include "challenger-database/validation_get.h"
     31 #include "challenger-database/validate_solve_pin.h"
     32 
     33 
     34 /**
     35  * Maximum number of retries for the database interaction.
     36  */
     37 #define MAX_RETRIES 3
     38 
     39 /**
     40  * Context for a /solve operation.
     41  */
     42 struct SolveContext
     43 {
     44 
     45   /**
     46    * Nonce of the operation.
     47    */
     48   struct CHALLENGER_ValidationNonceP nonce;
     49 
     50   /**
     51    * HTTP request context.
     52    */
     53   struct CH_HandlerContext *hc;
     54 
     55   /**
     56    * Handle for processing uploaded data.
     57    */
     58   struct MHD_PostProcessor *pp;
     59 
     60   /**
     61    * OAuth2 client redirection URI for error reporting.
     62    */
     63   char *client_redirect_uri;
     64 
     65   /**
     66    * 0-terminated PIN submitted to us.
     67    */
     68   char *pin;
     69 
     70   /**
     71    * OAuth2 state.
     72    */
     73   char *state;
     74 
     75   /**
     76    * Number of bytes in @a pin, excluding 0-terminator.
     77    */
     78   size_t pin_len;
     79 
     80   /**
     81    * How many address changes are still allowed?
     82    */
     83   uint32_t addr_left;
     84 
     85   /**
     86    * How many authentication attempts are still allowed?
     87    */
     88   uint32_t auth_attempts_left;
     89 
     90   /**
     91    * How many pin transmissions can still be requested?
     92    */
     93   uint32_t pin_transmissions_left;
     94 
     95 };
     96 
     97 
     98 /**
     99  * Function called to clean up a backup context.
    100  *
    101  * @param cls a `struct SolveContext`
    102  */
    103 static void
    104 cleanup_ctx (void *cls)
    105 {
    106   struct SolveContext *bc = cls;
    107 
    108   if (NULL != bc->pp)
    109   {
    110     GNUNET_break_op (MHD_YES ==
    111                      MHD_destroy_post_processor (bc->pp));
    112   }
    113   GNUNET_free (bc->pin);
    114   GNUNET_free (bc->state);
    115   GNUNET_free (bc->client_redirect_uri);
    116   GNUNET_free (bc);
    117 }
    118 
    119 
    120 /**
    121  * Iterator over key-value pairs where the value may be made available
    122  * in increments and/or may not be zero-terminated.  Used for
    123  * processing POST data.
    124  *
    125  * @param cls a `struct SolveContext *`
    126  * @param kind type of the value, always #MHD_POSTDATA_KIND when called from MHD
    127  * @param key 0-terminated key for the value
    128  * @param filename name of the uploaded file, NULL if not known
    129  * @param content_type mime-type of the data, NULL if not known
    130  * @param transfer_encoding encoding of the data, NULL if not known
    131  * @param data pointer to @a size bytes of data at the
    132  *              specified offset
    133  * @param off offset of data in the overall value
    134  * @param size number of bytes in @a data available
    135  * @return #MHD_YES to continue iterating,
    136  *         #MHD_NO to abort the iteration
    137  */
    138 static enum MHD_Result
    139 post_iter (void *cls,
    140            enum MHD_ValueKind kind,
    141            const char *key,
    142            const char *filename,
    143            const char *content_type,
    144            const char *transfer_encoding,
    145            const char *data,
    146            uint64_t off,
    147            size_t size)
    148 {
    149   struct SolveContext *bc = cls;
    150 
    151   (void) filename;
    152   (void) content_type;
    153   (void) transfer_encoding;
    154   (void) off;
    155   if (0 != strcmp (key,
    156                    "pin"))
    157     return MHD_YES;
    158   if (MHD_POSTDATA_KIND != kind)
    159     return MHD_YES;
    160   bc->pin = GNUNET_realloc (bc->pin,
    161                             bc->pin_len + size + 1);
    162   memcpy (bc->pin + bc->pin_len,
    163           data,
    164           size);
    165   bc->pin_len += size;
    166   bc->pin[bc->pin_len] = '\0';
    167   return MHD_YES;
    168 }
    169 
    170 
    171 enum MHD_Result
    172 CH_handler_solve (struct CH_HandlerContext *hc,
    173                   const char *upload_data,
    174                   size_t *upload_data_size)
    175 {
    176   struct SolveContext *bc = hc->ctx;
    177 
    178   if (NULL == bc)
    179   {
    180     /* first call, setup internals */
    181     bc = GNUNET_new (struct SolveContext);
    182     hc->cc = &cleanup_ctx;
    183     hc->ctx = bc;
    184     bc->hc = hc;
    185     bc->pp = MHD_create_post_processor (hc->connection,
    186                                         1024,
    187                                         &post_iter,
    188                                         bc);
    189     if (GNUNET_OK !=
    190         GNUNET_STRINGS_string_to_data (hc->path,
    191                                        strlen (hc->path),
    192                                        &bc->nonce,
    193                                        sizeof (bc->nonce)))
    194     {
    195       GNUNET_break_op (0);
    196       return TALER_MHD_reply_with_error (
    197         hc->connection,
    198         MHD_HTTP_BAD_REQUEST,
    199         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    200         "nonce");
    201     }
    202     TALER_MHD_check_content_length (hc->connection,
    203                                     1024);
    204     return MHD_YES;
    205   }
    206   /* handle upload */
    207   if (0 != *upload_data_size)
    208   {
    209     enum MHD_Result res;
    210 
    211     res = MHD_post_process (bc->pp,
    212                             upload_data,
    213                             *upload_data_size);
    214     *upload_data_size = 0;
    215     if (MHD_YES == res)
    216       return MHD_YES;
    217     return MHD_NO;
    218   }
    219   if (NULL == bc->pin)
    220   {
    221     GNUNET_break_op (0);
    222     return TALER_MHD_reply_with_error (
    223       hc->connection,
    224       MHD_HTTP_BAD_REQUEST,
    225       TALER_EC_GENERIC_PARAMETER_MISSING,
    226       "pin");
    227   }
    228   {
    229     unsigned int pin;
    230     char dummy;
    231     enum GNUNET_DB_QueryStatus qs;
    232     bool solved;
    233     bool exhausted;
    234     bool no_challenge;
    235 
    236     if (1 != sscanf (bc->pin,
    237                      "%u%c",
    238                      &pin,
    239                      &dummy))
    240     {
    241       GNUNET_break_op (0);
    242       return TALER_MHD_reply_with_error (
    243         hc->connection,
    244         MHD_HTTP_BAD_REQUEST,
    245         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    246         "pin");
    247     }
    248 
    249     for (unsigned int r = 0; r<MAX_RETRIES; r++)
    250     {
    251       qs = CHALLENGERDB_validate_solve_pin (CH_context,
    252                                             &bc->nonce,
    253                                             pin,
    254                                             &solved,
    255                                             &exhausted,
    256                                             &no_challenge,
    257                                             &bc->state,
    258                                             &bc->addr_left,
    259                                             &bc->auth_attempts_left,
    260                                             &bc->pin_transmissions_left,
    261                                             &bc->client_redirect_uri);
    262       switch (qs)
    263       {
    264       case GNUNET_DB_STATUS_HARD_ERROR:
    265         GNUNET_break (0);
    266         return TALER_MHD_reply_with_error (
    267           hc->connection,
    268           MHD_HTTP_INTERNAL_SERVER_ERROR,
    269           TALER_EC_GENERIC_DB_FETCH_FAILED,
    270           "validate_solve_pin");
    271       case GNUNET_DB_STATUS_SOFT_ERROR:
    272         if (r < MAX_RETRIES - 1)
    273           continue;
    274         GNUNET_break (0);
    275         return TALER_MHD_reply_with_error (
    276           hc->connection,
    277           MHD_HTTP_INTERNAL_SERVER_ERROR,
    278           TALER_EC_GENERIC_DB_FETCH_FAILED,
    279           "validate_solve_pin");
    280       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    281         return TALER_MHD_reply_with_error (
    282           hc->connection,
    283           MHD_HTTP_NOT_FOUND,
    284           TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN,
    285           NULL);
    286       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    287         break;
    288       }
    289       break;
    290     }
    291     if (! solved)
    292     {
    293       enum MHD_Result ret;
    294       json_t *details;
    295 
    296       if ( (NULL != bc->state) &&
    297            (0 == bc->addr_left) &&
    298            (0 == bc->auth_attempts_left) )
    299       {
    300         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    301                     "Client exhausted all chances to satisfy challenge\n");
    302         return TALER_MHD_reply_with_error (
    303           hc->connection,
    304           MHD_HTTP_TOO_MANY_REQUESTS,
    305           TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS,
    306           "users exhausted all possibilities of passing the check");
    307       }
    308 
    309       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    310                   "Invalid PIN supplied\n");
    311       details = GNUNET_JSON_PACK (
    312         GNUNET_JSON_pack_string ("type",
    313                                  "pending"),
    314         TALER_JSON_pack_ec (TALER_EC_CHALLENGER_INVALID_PIN),
    315         GNUNET_JSON_pack_uint64 ("addresses_left",
    316                                  bc->addr_left),
    317         GNUNET_JSON_pack_uint64 ("pin_transmissions_left",
    318                                  bc->pin_transmissions_left),
    319         GNUNET_JSON_pack_uint64 ("auth_attempts_left",
    320                                  bc->auth_attempts_left),
    321         GNUNET_JSON_pack_bool ("exhausted",
    322                                exhausted),
    323         GNUNET_JSON_pack_bool ("no_challenge",
    324                                no_challenge)
    325         );
    326       ret = TALER_MHD_reply_json (hc->connection,
    327                                   details,
    328                                   MHD_HTTP_FORBIDDEN);
    329       json_decref (details);
    330       return ret;
    331     }
    332   }
    333 
    334   {
    335     struct MHD_Response *response;
    336     char *url;
    337     unsigned int http_status;
    338     enum GNUNET_GenericReturnValue ret;
    339 
    340     ret = CH_build_full_redirect_url (&bc->nonce,
    341                                       hc->connection,
    342                                       &url);
    343     if (GNUNET_OK != ret)
    344       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
    345     if (0 == CH_get_output_type (hc->connection))
    346     {
    347       {
    348         const char *ok = "Ok!";
    349 
    350         response = MHD_create_response_from_buffer (strlen (ok),
    351                                                     (void *) ok,
    352                                                     MHD_RESPMEM_PERSISTENT);
    353       }
    354       if (NULL == response)
    355       {
    356         GNUNET_break (0);
    357         GNUNET_free (url);
    358         return MHD_NO;
    359       }
    360       TALER_MHD_add_global_headers (response,
    361                                     false);
    362       GNUNET_break (MHD_YES ==
    363                     MHD_add_response_header (response,
    364                                              MHD_HTTP_HEADER_CONTENT_TYPE,
    365                                              "text/plain"));
    366       if (MHD_NO ==
    367           MHD_add_response_header (response,
    368                                    MHD_HTTP_HEADER_LOCATION,
    369                                    url))
    370       {
    371         GNUNET_break (0);
    372         MHD_destroy_response (response);
    373         GNUNET_free (url);
    374         return MHD_NO;
    375       }
    376       http_status = MHD_HTTP_FOUND;
    377       GNUNET_free (url);
    378     }
    379     else
    380     {
    381       json_t *args;
    382 
    383       args = GNUNET_JSON_PACK (
    384         GNUNET_JSON_pack_string ("type",
    385                                  "completed"),
    386         GNUNET_JSON_pack_string ("redirect_url",
    387                                  url)
    388         );
    389       GNUNET_free (url);
    390       response = TALER_MHD_make_json (args);
    391       http_status = MHD_HTTP_OK;
    392     }
    393 
    394     {
    395       enum MHD_Result mret;
    396 
    397       mret = MHD_queue_response (hc->connection,
    398                                  http_status,
    399                                  response);
    400       MHD_destroy_response (response);
    401       return mret;
    402     }
    403   }
    404 }