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 }