sync

Backup service to store encrypted wallet databases (experimental)
Log | Files | Refs | Submodules | README | LICENSE

commit 10d0e97f191a8bd7de07049559a2eccb29a78507
parent f0752580e0a1fb8ae2afa25ee12fad2bf594294b
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  2 Mar 2025 12:13:52 +0100

more porting to MHD2

Diffstat:
Msrc/sync/sync-httpd2.c | 6+++---
Msrc/sync/sync-httpd2_backup-post.c | 809+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/sync/sync-httpd2_backup.c | 68+++++++++++++++++++++++++++-----------------------------------------
Msrc/sync/sync-httpd2_backup.h | 14++++++--------
4 files changed, 417 insertions(+), 480 deletions(-)

diff --git a/src/sync/sync-httpd2.c b/src/sync/sync-httpd2.c @@ -500,9 +500,9 @@ run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } - fh = TALER_MHD2_bind (config, - "sync", - &port); + fh = TALER_MHD_bind (config, + "sync", + &port); if ( (0 == port) && (-1 == fh) ) { diff --git a/src/sync/sync-httpd2_backup-post.c b/src/sync/sync-httpd2_backup-post.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2019 Taler Systems SA + Copyright (C) 2019--2025 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -19,10 +19,12 @@ * @author Christian Grothoff */ #include "platform.h" -#include "sync-httpd.h" +#include "sync-httpd2.h" #include <gnunet/gnunet_util_lib.h> -#include "sync-httpd_backup.h" +#include "sync-httpd2_backup.h" +#include <microhttpd2.h> #include <taler/taler_json_lib.h> +#include <taler/taler_mhd2_lib.h> #include <taler/taler_merchant_service.h> #include <taler/taler_signatures.h> @@ -73,11 +75,6 @@ struct BackupContext struct TALER_ClaimTokenP token; /** - * Hash context for the upload. - */ - struct GNUNET_HashContext *hash_ctx; - - /** * Kept in DLL for shutdown handling while suspended. */ struct BackupContext *next; @@ -90,12 +87,12 @@ struct BackupContext /** * Used while suspended for resumption. */ - struct MHD_Connection *con; + struct MHD_Request *request; /** - * Upload, with as many bytes as we have received so far. + * To be returned upon resuming. */ - char *upload; + struct MHD_Response *resp; /** * Used while we are awaiting proposal creation. @@ -108,12 +105,6 @@ struct BackupContext struct TALER_MERCHANT_OrderMerchantGetHandle *omgh; /** - * HTTP response code to use on resume, if non-NULL. - - */ - struct MHD_Response *resp; - - /** * Order under which the client promised payment, or NULL. */ const char *order_id; @@ -130,21 +121,6 @@ struct BackupContext struct GNUNET_TIME_Timestamp existing_order_timestamp; /** - * Expected total upload size. - */ - size_t upload_size; - - /** - * Current offset for the upload. - */ - size_t upload_off; - - /** - * HTTP response code to use on resume, if resp is set. - */ - unsigned int response_code; - - /** * Do not look for an existing order, force a fresh order to be created. */ bool force_fresh_order; @@ -163,7 +139,7 @@ static struct BackupContext *bc_tail; /** - * Service is shutting down, resume all MHD connections NOW. + * Service is shutting down, resume all MHD requests NOW. */ void SH_resume_all_bc () @@ -175,7 +151,7 @@ SH_resume_all_bc () GNUNET_CONTAINER_DLL_remove (bc_head, bc_tail, bc); - MHD_resume_connection (bc->con); + MHD_request_resume (bc->request); if (NULL != bc->po) { TALER_MERCHANT_orders_post_cancel (bc->po); @@ -193,27 +169,27 @@ SH_resume_all_bc () /** * Function called to clean up a backup context. * - * @param hc a `struct BackupContext` + * @param cls the `struct BackupContext` + * @param data the details about the event + * @param request_app_context the application request context */ static void -cleanup_ctx (struct TM_HandlerContext *hc) +cleanup_ctx (void *cls, + const struct MHD_RequestEndedData *data, + void *request_app_context) { - struct BackupContext *bc = (struct BackupContext *) hc; + struct BackupContext *bc = cls; + GNUNET_assert (cls == request_app_context); if (NULL != bc->po) TALER_MERCHANT_orders_post_cancel (bc->po); - if (NULL != bc->hash_ctx) - GNUNET_CRYPTO_hash_context_abort (bc->hash_ctx); - if (NULL != bc->resp) - MHD_destroy_response (bc->resp); GNUNET_free (bc->existing_order_id); - GNUNET_free (bc->upload); GNUNET_free (bc); } /** - * Transmit a payment request for @a order_id on @a connection + * Transmit a payment request for @a order_id on @a request * * @param order_id our backend's order ID * @param token the claim token generated by the merchant (NULL if @@ -230,10 +206,8 @@ make_payment_request (const char *order_id, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Creating payment request for order `%s'\n", order_id); - resp = MHD_create_response_from_buffer (0, - NULL, - MHD_RESPMEM_PERSISTENT); - TALER_MHD_add_global_headers (resp); + resp = MHD_response_from_empty (MHD_HTTP_STATUS_PAYMENT_REQUIRED); + TALER_MHD2_add_global_headers (resp); { char *hdr; const char *pfx; @@ -257,13 +231,13 @@ make_payment_request (const char *order_id, else { GNUNET_break (0); - MHD_destroy_response (resp); + MHD_response_destroy (resp); return NULL; } if (0 == strlen (hn)) { GNUNET_break (0); - MHD_destroy_response (resp); + MHD_response_destroy (resp); return NULL; } @@ -276,11 +250,13 @@ make_payment_request (const char *order_id, if (NULL != token) { GNUNET_buffer_write_str (&hdr_buf, "?c="); - GNUNET_buffer_write_data_encoded (&hdr_buf, token, sizeof (*token)); + GNUNET_buffer_write_data_encoded (&hdr_buf, + token, + sizeof (*token)); } hdr = GNUNET_buffer_reap_str (&hdr_buf); - GNUNET_break (MHD_YES == - MHD_add_response_header (resp, + GNUNET_break (MHD_SC_OK == + MHD_response_add_header (resp, "Taler", hdr)); GNUNET_free (hdr); @@ -308,18 +284,19 @@ proposal_cb (void *cls, bc_tail, bc); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Resuming connection with order `%s'\n", + "Resuming request with order `%s'\n", bc->order_id); - MHD_resume_connection (bc->con); + MHD_request_resume (bc->request); SH_trigger_daemon (); - if (MHD_HTTP_OK != por->hr.http_status) + if (MHD_HTTP_STATUS_OK != por->hr.http_status) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Backend returned status %u/%u\n", por->hr.http_status, (unsigned int) por->hr.ec); GNUNET_break_op (0); - bc->resp = TALER_MHD_MAKE_JSON_PACK ( + bc->resp = TALER_MHD2_MAKE_JSON_PACK ( + MHD_HTTP_STATUS_BAD_GATEWAY, TALER_JSON_pack_ec (TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR), GNUNET_JSON_pack_uint64 ("backend-ec", por->hr.ec), @@ -328,7 +305,6 @@ proposal_cb (void *cls, GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("backend-reply", (json_t *) por->hr.reply))); - bc->response_code = MHD_HTTP_BAD_GATEWAY; return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -342,10 +318,9 @@ proposal_cb (void *cls, if (0 >= qs) { GNUNET_break (0); - bc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, - "Failed to persist payment request in sync database"); + bc->resp = TALER_MHD2_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, + "Failed to persist payment request in sync database"); GNUNET_assert (NULL != bc->resp); - bc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -354,7 +329,6 @@ proposal_cb (void *cls, bc->resp = make_payment_request (por->details.ok.order_id, por->details.ok.token); GNUNET_assert (NULL != bc->resp); - bc->response_code = MHD_HTTP_PAYMENT_REQUIRED; } @@ -413,23 +387,22 @@ check_payment_cb (void *cls, GNUNET_CONTAINER_DLL_remove (bc_head, bc_tail, bc); - MHD_resume_connection (bc->con); + MHD_request_resume (bc->request); SH_trigger_daemon (); switch (hr->http_status) { case 0: /* Likely timeout, complain! */ - bc->response_code = MHD_HTTP_GATEWAY_TIMEOUT; - bc->resp = TALER_MHD_make_error ( + bc->resp = TALER_MHD2_make_error ( TALER_EC_SYNC_GENERIC_BACKEND_TIMEOUT, NULL); return; - case MHD_HTTP_OK: + case MHD_HTTP_STATUS_OK: break; /* handled below */ default: /* Unexpected backend response */ - bc->response_code = MHD_HTTP_BAD_GATEWAY; - bc->resp = TALER_MHD_MAKE_JSON_PACK ( + bc->resp = TALER_MHD2_MAKE_JSON_PACK ( + MHD_HTTP_STATUS_BAD_GATEWAY, GNUNET_JSON_pack_uint64 ("code", TALER_EC_SYNC_GENERIC_BACKEND_ERROR), GNUNET_JSON_pack_string ("hint", @@ -445,7 +418,7 @@ check_payment_cb (void *cls, return; } - GNUNET_assert (MHD_HTTP_OK == hr->http_status); + GNUNET_assert (MHD_HTTP_STATUS_OK == hr->http_status); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Payment status checked: %d\n", osr->details.ok.status); @@ -462,10 +435,9 @@ check_payment_cb (void *cls, if (0 <= qs) return; /* continue as planned */ GNUNET_break (0); - bc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, - "increment lifetime"); + bc->resp = TALER_MHD2_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, + "increment lifetime"); GNUNET_assert (NULL != bc->resp); - bc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; return; /* continue as planned */ } case TALER_MERCHANT_OSC_UNPAID: @@ -482,15 +454,13 @@ check_payment_cb (void *cls, ? NULL : &bc->token); GNUNET_assert (NULL != bc->resp); - bc->response_code = MHD_HTTP_PAYMENT_REQUIRED; return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Timeout waiting for payment\n"); - bc->resp = TALER_MHD_make_error (TALER_EC_SYNC_PAYMENT_GENERIC_TIMEOUT, - "Timeout awaiting promised payment"); + bc->resp = TALER_MHD2_make_error (TALER_EC_SYNC_PAYMENT_GENERIC_TIMEOUT, + "Timeout awaiting promised payment"); GNUNET_assert (NULL != bc->resp); - bc->response_code = MHD_HTTP_REQUEST_TIMEOUT; } @@ -510,7 +480,6 @@ await_payment (struct BackupContext *bc, GNUNET_CONTAINER_DLL_insert (bc_head, bc_tail, bc); - MHD_suspend_connection (bc->con); bc->order_id = order_id; bc->omgh = TALER_MERCHANT_merchant_order_get (SH_ctx, SH_backend_url, @@ -527,21 +496,24 @@ await_payment (struct BackupContext *bc, /** * Helper function used to ask our backend to begin * processing a payment for the user's account. - * May perform asynchronous operations by suspending the connection + * May perform asynchronous operations by suspending the request * if required. * * @param bc context to begin payment for. * @param pay_req #GNUNET_YES if payment was explicitly requested, * #GNUNET_NO if payment is needed - * @return MHD status code + * @param[out] suspend set to true if the request should be suspended + * @return MHD response */ -static MHD_RESULT +static struct MHD_Response * begin_payment (struct BackupContext *bc, - int pay_req) + int pay_req, + bool *suspend) { static const char *no_uuids[1] = { NULL }; json_t *order; + *suspend = false; if (! bc->force_fresh_order) { enum GNUNET_DB_QueryStatus qs; @@ -552,17 +524,9 @@ begin_payment (struct BackupContext *bc, bc); if (qs < 0) { - struct MHD_Response *resp; - MHD_RESULT ret; - - resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, - "pending payments"); - ret = MHD_queue_response (bc->con, - MHD_HTTP_INTERNAL_SERVER_ERROR, - resp); - GNUNET_break (MHD_YES == ret); - MHD_destroy_response (resp); - return ret; + GNUNET_break (0); + return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, + "pending payments"); } if (NULL != bc->existing_order_id) { @@ -572,16 +536,17 @@ begin_payment (struct BackupContext *bc, await_payment (bc, GNUNET_TIME_UNIT_ZERO /* no long polling */, bc->existing_order_id); - return MHD_YES; + *suspend = true; + return NULL; } } GNUNET_CONTAINER_DLL_insert (bc_head, bc_tail, bc); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Suspending connection while creating order at `%s'\n", + "Suspending request while creating order at `%s'\n", SH_backend_url); - MHD_suspend_connection (bc->con); + *suspend = true; order = GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("amount", &SH_annual_fee), @@ -603,366 +568,136 @@ begin_payment (struct BackupContext *bc, bc); SH_trigger_curl (); json_decref (order); - return MHD_YES; + return NULL; } /** * We got some query status from the DB. Handle the error cases. - * May perform asynchronous operations by suspending the connection + * May perform asynchronous operations by suspending the request * if required. * - * @param bc connection to handle status for + * @param bc request to handle status for * @param qs query status to handle - * @return #MHD_YES or #MHD_NO + * @param[out] suspend set to true if the request should be suspended + * @return MHD response to return */ -static MHD_RESULT +static struct MHD_Response * handle_database_error (struct BackupContext *bc, - enum SYNC_DB_QueryStatus qs) + enum SYNC_DB_QueryStatus qs, + bool *suspend) { + *suspend = false; switch (qs) { case SYNC_DB_OLD_BACKUP_MISSING: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Update failed: no existing backup\n"); - return TALER_MHD_reply_with_error (bc->con, - MHD_HTTP_NOT_FOUND, - TALER_EC_SYNC_PREVIOUS_BACKUP_UNKNOWN, - NULL); + return TALER_MHD2_make_error (TALER_EC_SYNC_PREVIOUS_BACKUP_UNKNOWN, + NULL); case SYNC_DB_OLD_BACKUP_MISMATCH: GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Conflict detected, returning existing backup\n"); - return SH_return_backup (bc->con, - &bc->account, - MHD_HTTP_CONFLICT); + return SH_make_backup (&bc->account, + MHD_HTTP_STATUS_CONFLICT); case SYNC_DB_PAYMENT_REQUIRED: { - const char *order_id; + const struct MHD_StringNullable *order_id; - order_id = MHD_lookup_connection_value (bc->con, - MHD_GET_ARGUMENT_KIND, - "paying"); + order_id = MHD_request_get_value (bc->request, + MHD_VK_GET_ARGUMENT, + "paying"); if (NULL == order_id) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Payment required, starting payment process\n"); return begin_payment (bc, - GNUNET_NO); + GNUNET_NO, + suspend); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Payment required, awaiting completion of `%s'\n", - order_id); + order_id->cstr); await_payment (bc, CHECK_PAYMENT_GENERIC_TIMEOUT, - order_id); + order_id->cstr); + *suspend = true; + return NULL; } - return MHD_YES; case SYNC_DB_HARD_ERROR: GNUNET_break (0); - return TALER_MHD_reply_with_error (bc->con, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_COMMIT_FAILED, - NULL); + return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_COMMIT_FAILED, + NULL); case SYNC_DB_SOFT_ERROR: GNUNET_break (0); - return TALER_MHD_reply_with_error (bc->con, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_SOFT_FAILURE, - NULL); + return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_SOFT_FAILURE, + NULL); case SYNC_DB_NO_RESULTS: GNUNET_assert (0); - return MHD_NO; + return NULL; /* intentional fall-through! */ case SYNC_DB_ONE_RESULT: GNUNET_assert (0); - return MHD_NO; + return NULL; } GNUNET_break (0); - return MHD_NO; + return NULL; } -MHD_RESULT -SH_backup_post (struct MHD_Connection *connection, - void **con_cls, - const struct SYNC_AccountPublicKeyP *account, - const char *upload_data, - size_t *upload_data_size) +/** + * Function to process data uploaded by a client. + * + * @param upload_cls our `struct BackupContext *` + * @param request the request is being processed + * @param content_data_size the size of the @a content_data, + * zero when all data have been processed + * @param[in] content_data the uploaded content data, + * may be modified in the callback, + * valid only until return from the callback, + * NULL when all data have been processed + * @return action specifying how to proceed: + * #MHD_upload_action_continue() to continue upload (for incremental + * upload processing only), + * #MHD_upload_action_suspend() to stop reading the upload until + * the request is resumed, + * #MHD_upload_action_abort_request() to close the socket, + * or a response to discard the rest of the upload and transmit + * the response + */ +static const struct MHD_UploadAction * +handle_upload (void *upload_cls, + struct MHD_Request *request, + size_t content_data_size, + void *content_data) { - struct BackupContext *bc; - - bc = *con_cls; - if (NULL == bc) - { - /* first call, setup internals */ - bc = GNUNET_new (struct BackupContext); - bc->hc.cc = &cleanup_ctx; - bc->con = connection; - bc->account = *account; - { - const char *fresh; - - fresh = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "fresh"); - if (NULL != fresh) - bc->force_fresh_order = true; - } - *con_cls = bc; - - /* now setup 'bc' */ - { - const char *lens; - unsigned long len; - - lens = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_CONTENT_LENGTH); - if ( (NULL == lens) || - (1 != - sscanf (lens, - "%lu", - &len)) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - (NULL == lens) - ? TALER_EC_SYNC_MALFORMED_CONTENT_LENGTH - : TALER_EC_SYNC_MISSING_CONTENT_LENGTH, - lens); - } - if (len / 1024 / 1024 >= SH_upload_limit_mb) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_PAYLOAD_TOO_LARGE, - TALER_EC_SYNC_EXCESSIVE_CONTENT_LENGTH, - NULL); - } - bc->upload = GNUNET_malloc_large (len); - if (NULL == bc->upload) - { - GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, - "malloc"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_PAYLOAD_TOO_LARGE, - TALER_EC_SYNC_OUT_OF_MEMORY_ON_CONTENT_LENGTH, - NULL); - } - bc->upload_size = (size_t) len; - } - { - const char *im; + struct BackupContext *bc = upload_cls; - im = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_IF_MATCH); - if (NULL != im) - { - if ( (2 >= strlen (im)) || - ('"' != im[0]) || - ('"' != im[strlen (im) - 1]) || - (GNUNET_OK != - GNUNET_STRINGS_string_to_data (im + 1, - strlen (im) - 2, - &bc->old_backup_hash, - sizeof (bc->old_backup_hash))) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_SYNC_BAD_IF_MATCH, - NULL); - } - } - } - { - const char *sig_s; - - sig_s = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - "Sync-Signature"); - if ( (NULL == sig_s) || - (GNUNET_OK != - GNUNET_STRINGS_string_to_data (sig_s, - strlen (sig_s), - &bc->account_sig, - sizeof (bc->account_sig))) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_SYNC_BAD_SYNC_SIGNATURE, - NULL); - } - } - { - const char *etag; - - etag = MHD_lookup_connection_value (connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_IF_NONE_MATCH); - if ( (NULL == etag) || - (2 >= strlen (etag)) || - ('"' != etag[0]) || - ('"' != etag[strlen (etag) - 1]) || - (GNUNET_OK != - GNUNET_STRINGS_string_to_data (etag + 1, - strlen (etag) - 2, - &bc->new_backup_hash, - sizeof (bc->new_backup_hash))) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_SYNC_BAD_IF_NONE_MATCH, - NULL); - } - } - /* validate signature */ - { - struct SYNC_UploadSignaturePS usp = { - .purpose.size = htonl (sizeof (usp)), - .purpose.purpose = htonl (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD), - .old_backup_hash = bc->old_backup_hash, - .new_backup_hash = bc->new_backup_hash - }; - - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD, - &usp, - &bc->account_sig.eddsa_sig, - &account->eddsa_pub)) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_SYNC_INVALID_SIGNATURE, - NULL); - } - } - /* get ready to hash (done here as we may go async for payments next) */ - bc->hash_ctx = GNUNET_CRYPTO_hash_context_start (); - - /* Check database to see if the transaction is permissible */ - { - struct GNUNET_HashCode hc; - enum SYNC_DB_QueryStatus qs; - - qs = db->lookup_account_TR (db->cls, - account, - &hc); - if (qs < 0) - return handle_database_error (bc, - qs); - if (SYNC_DB_NO_RESULTS == qs) - memset (&hc, 0, sizeof (hc)); - if (0 == GNUNET_memcmp (&hc, - &bc->new_backup_hash)) - { - /* Refuse upload: we already have that backup! */ - struct MHD_Response *resp; - MHD_RESULT ret; - - resp = MHD_create_response_from_buffer (0, - NULL, - MHD_RESPMEM_PERSISTENT); - TALER_MHD_add_global_headers (resp); - ret = MHD_queue_response (connection, - MHD_HTTP_NOT_MODIFIED, - resp); - GNUNET_break (MHD_YES == ret); - MHD_destroy_response (resp); - return ret; - } - if (0 != GNUNET_memcmp (&hc, - &bc->old_backup_hash)) - { - /* Refuse upload: if-none-match failed! */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Conflict detected, returning existing backup\n"); - return SH_return_backup (connection, - account, - MHD_HTTP_CONFLICT); - } - } - /* check if the client insists on paying */ - { - const char *order_req; - - order_req = MHD_lookup_connection_value (connection, - MHD_GET_ARGUMENT_KIND, - "pay"); - if (NULL != order_req) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Payment requested, starting payment process\n"); - return begin_payment (bc, - GNUNET_YES); - } - } - /* ready to begin! */ - return MHD_YES; - } - /* handle upload */ - if (0 != *upload_data_size) - { - /* check MHD invariant */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Processing %u bytes of upload data\n", - (unsigned int) *upload_data_size); - GNUNET_assert (bc->upload_off + *upload_data_size <= bc->upload_size); - memcpy (&bc->upload[bc->upload_off], - upload_data, - *upload_data_size); - bc->upload_off += *upload_data_size; - GNUNET_CRYPTO_hash_context_read (bc->hash_ctx, - upload_data, - *upload_data_size); - *upload_data_size = 0; - return MHD_YES; - } - else if ( (0 == bc->upload_off) && - (0 != bc->upload_size) && - (NULL == bc->resp) ) - { - /* wait for upload */ - return MHD_YES; - } if (NULL != bc->resp) { - MHD_RESULT ret; - - /* We generated a response asynchronously, queue that */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Returning asynchronously generated response with HTTP status %u\n", - bc->response_code); - ret = MHD_queue_response (connection, - bc->response_code, - bc->resp); - GNUNET_break (MHD_YES == ret); - MHD_destroy_response (bc->resp); - bc->resp = NULL; - return ret; + /* This can happen on resume */ + return MHD_upload_action_from_response (request, + bc->resp); } - - /* finished with upload, check hash */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Processing %llu bytes of upload data\n", + (unsigned long long) content_data_size); + /* Check hash matches */ { struct GNUNET_HashCode our_hash; - GNUNET_CRYPTO_hash_context_finish (bc->hash_ctx, - &our_hash); - bc->hash_ctx = NULL; + GNUNET_CRYPTO_hash (content_data, + content_data_size, + &our_hash); if (0 != GNUNET_memcmp (&our_hash, &bc->new_backup_hash)) { GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_SYNC_INVALID_UPLOAD, - NULL); + return MHD_upload_action_from_response ( + request, + TALER_MHD2_make_error ( + TALER_EC_SYNC_INVALID_UPLOAD, + NULL)); } } @@ -970,67 +705,285 @@ SH_backup_post (struct MHD_Connection *connection, { enum SYNC_DB_QueryStatus qs; - if (GNUNET_YES == GNUNET_is_zero (&bc->old_backup_hash)) + if (GNUNET_is_zero (&bc->old_backup_hash)) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Uploading first backup to account\n"); qs = db->store_backup_TR (db->cls, - account, + &bc->account, &bc->account_sig, &bc->new_backup_hash, - bc->upload_size, - bc->upload); + content_data_size, + content_data); } else { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Uploading existing backup of account\n"); qs = db->update_backup_TR (db->cls, - account, + &bc->account, &bc->old_backup_hash, &bc->account_sig, &bc->new_backup_hash, - bc->upload_size, - bc->upload); + content_data_size, + content_data); } if (qs < 0) - return handle_database_error (bc, - qs); + { + bool suspend; + struct MHD_Response *resp; + + resp = handle_database_error (bc, + qs, + &suspend); + if (suspend) + return MHD_upload_action_suspend (request); + return MHD_upload_action_from_response (request, + resp); + } if (0 == qs) { /* database says nothing actually changed, 304 (could theoretically happen if another equivalent upload succeeded since we last checked!) */ struct MHD_Response *resp; - MHD_RESULT ret; - - resp = MHD_create_response_from_buffer (0, - NULL, - MHD_RESPMEM_PERSISTENT); - TALER_MHD_add_global_headers (resp); - ret = MHD_queue_response (connection, - MHD_HTTP_NOT_MODIFIED, - resp); - GNUNET_break (MHD_YES == ret); - MHD_destroy_response (resp); - return ret; + + resp = MHD_response_from_empty (MHD_HTTP_STATUS_NOT_MODIFIED); + TALER_MHD2_add_global_headers (resp); + return MHD_upload_action_from_response (request, + resp); } } /* generate main (204) standard success reply */ { struct MHD_Response *resp; - MHD_RESULT ret; - - resp = MHD_create_response_from_buffer (0, - NULL, - MHD_RESPMEM_PERSISTENT); - TALER_MHD_add_global_headers (resp); - ret = MHD_queue_response (connection, - MHD_HTTP_NO_CONTENT, - resp); - GNUNET_break (MHD_YES == ret); - MHD_destroy_response (resp); - return ret; + + resp = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT); + TALER_MHD2_add_global_headers (resp); + return MHD_upload_action_from_response (request, + resp); + } +} + + +const struct MHD_Action * +SH_backup_post (struct MHD_Request *request, + const struct SYNC_AccountPublicKeyP *account, + uint_fast64_t upload_size) +{ + struct BackupContext *bc; + union MHD_RequestInfoFixedData fi; + + GNUNET_assert (MHD_SC_OK == + MHD_request_get_info_fixed ( + request, + MHD_REQUEST_INFO_FIXED_APP_CONTEXT, + &fi)); + bc = *fi.v_ppvoid; + if (NULL == bc) + { + /* first call, setup internals */ + bc = GNUNET_new (struct BackupContext); + bc->request = request; + bc->account = *account; + GNUNET_assert (MHD_SC_OK == + MHD_REQUEST_SET_OPTIONS ( + MHD_R_OPTION_TERMINATION_CALLBACK (&cleanup_ctx, + bc))); + *fi.v_ppvoid = bc; + } + if (NULL != bc->resp) + return MHD_action_from_response (request, + bc->resp); + if ( (upload_size / 1024 / 1024 >= SH_upload_limit_mb) || + (upload_size > SIZE_MAX) ) + { + GNUNET_break_op (0); + return TALER_MHD2_reply_with_error ( + request, + MHD_HTTP_STATUS_CONTENT_TOO_LARGE, + TALER_EC_SYNC_EXCESSIVE_CONTENT_LENGTH, + NULL); + } + { + const struct MHD_StringNullable *fresh; + + fresh = MHD_request_get_value (request, + MHD_VK_GET_ARGUMENT, + "fresh"); + if (NULL != fresh) + bc->force_fresh_order = true; + } + { + const struct MHD_StringNullable *im; + + im = MHD_request_get_value (request, + MHD_VK_HEADER, + MHD_HTTP_HEADER_IF_MATCH); + if (NULL != im) + { + if ( (2 >= im->len) || + ('"' != im->cstr[0]) || + ('"' != im->cstr[im->len - 1]) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (im->cstr + 1, + im->len - 2, + &bc->old_backup_hash, + sizeof (bc->old_backup_hash))) ) + { + GNUNET_break_op (0); + return TALER_MHD2_reply_with_error ( + request, + MHD_HTTP_STATUS_BAD_REQUEST, + TALER_EC_SYNC_BAD_IF_MATCH, + NULL); + } + } + } + { + const struct MHD_StringNullable *sig_s; + + sig_s = MHD_request_get_value (request, + MHD_VK_HEADER, + "Sync-Signature"); + if ( (NULL == sig_s) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (sig_s->cstr, + sig_s->len, + &bc->account_sig, + sizeof (bc->account_sig))) ) + { + GNUNET_break_op (0); + return TALER_MHD2_reply_with_error ( + request, + MHD_HTTP_STATUS_BAD_REQUEST, + TALER_EC_SYNC_BAD_SYNC_SIGNATURE, + NULL); + } + } + { + const struct MHD_StringNullable *etag; + + etag = MHD_request_get_value (request, + MHD_VK_HEADER, + MHD_HTTP_HEADER_IF_NONE_MATCH); + if ( (NULL == etag) || + (2 >= etag->len) || + ('"' != etag->cstr[0]) || + ('"' != etag->cstr[etag->len - 1]) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (etag->cstr + 1, + etag->len - 2, + &bc->new_backup_hash, + sizeof (bc->new_backup_hash))) ) + { + GNUNET_break_op (0); + return TALER_MHD2_reply_with_error ( + request, + MHD_HTTP_STATUS_BAD_REQUEST, + TALER_EC_SYNC_BAD_IF_NONE_MATCH, + NULL); + } + } + /* validate signature */ + { + struct SYNC_UploadSignaturePS usp = { + .purpose.size = htonl (sizeof (usp)), + .purpose.purpose = htonl (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD), + .old_backup_hash = bc->old_backup_hash, + .new_backup_hash = bc->new_backup_hash + }; + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD, + &usp, + &bc->account_sig.eddsa_sig, + &account->eddsa_pub)) + { + GNUNET_break_op (0); + return TALER_MHD2_reply_with_error ( + request, + MHD_HTTP_STATUS_FORBIDDEN, + TALER_EC_SYNC_INVALID_SIGNATURE, + NULL); + } + } + + /* Check database to see if the transaction is permissible */ + { + struct GNUNET_HashCode hc; + enum SYNC_DB_QueryStatus qs; + + qs = db->lookup_account_TR (db->cls, + account, + &hc); + if (qs < 0) + { + bool suspend; + struct MHD_Response *resp; + + resp = handle_database_error (bc, + qs, + &suspend); + if (suspend) + return MHD_action_suspend (request); + return MHD_action_from_response (request, + resp); + } + if (SYNC_DB_NO_RESULTS == qs) + memset (&hc, + 0, + sizeof (hc)); + if (0 == GNUNET_memcmp (&hc, + &bc->new_backup_hash)) + { + /* Refuse upload: we already have that backup! */ + struct MHD_Response *resp; + + resp = MHD_response_from_empty (MHD_HTTP_STATUS_NOT_MODIFIED); + TALER_MHD2_add_global_headers (resp); + return MHD_action_from_response (request, + resp); + } + if (0 != GNUNET_memcmp (&hc, + &bc->old_backup_hash)) + { + /* Refuse upload: if-none-match failed! */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Conflict detected, returning existing backup\n"); + return MHD_action_from_response ( + request, + SH_make_backup (account, + MHD_HTTP_STATUS_CONFLICT)); + } + } + /* check if the client insists on paying */ + { + const struct MHD_StringNullable *order_req; + + order_req = MHD_request_get_value (request, + MHD_VK_GET_ARGUMENT, + "pay"); + if (NULL != order_req) + { + struct MHD_Response *resp; + bool suspend; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Payment requested, starting payment process\n"); + resp = begin_payment (bc, + GNUNET_YES, + &suspend); + if (suspend) + return MHD_action_suspend (request); + return MHD_action_from_response (request, + resp); + } } + /* ready to begin upload! */ + return MHD_action_process_upload_full (request, + upload_size, + &handle_upload, + bc); } diff --git a/src/sync/sync-httpd2_backup.c b/src/sync/sync-httpd2_backup.c @@ -83,21 +83,21 @@ SH_backup_get (struct MHD_Request *request, } case SYNC_DB_ONE_RESULT: { - const char *inm; + const struct MHD_StringNullable *inm; inm = MHD_request_get_value (request, MHD_VK_HEADER, MHD_HTTP_HEADER_IF_NONE_MATCH); if ( (NULL != inm) && - (2 < strlen (inm)) && - ('"' == inm[0]) && - ('=' == inm[strlen (inm) - 1]) ) + (2 < inm->len) && + ('"' == inm->cstr[0]) && + ('=' == inm->cstr[inm->len - 1]) ) { struct GNUNET_HashCode inm_h; if (GNUNET_OK != - GNUNET_STRINGS_string_to_data (inm + 1, - strlen (inm) - 2, + GNUNET_STRINGS_string_to_data (inm->cstr + 1, + inm->len - 2, &inm_h, sizeof (inm_h))) { @@ -122,9 +122,9 @@ SH_backup_get (struct MHD_Request *request, /* We have a result, should fetch and return it! */ break; } - return SH_return_backup (request, - account, - MHD_HTTP_STATUS_OK); + return MHD_action_from_response (request, + SH_make_backup (account, + MHD_HTTP_STATUS_OK)); } @@ -132,16 +132,14 @@ SH_backup_get (struct MHD_Request *request, * Return the current backup of @a account on @a request * using @a default_http_status on success. * - * @param request MHD request to use * @param account account to query * @param default_http_status HTTP status to queue response * with on success (#MHD_HTTP_STATUS_OK or #MHD_HTTP_STATUS_CONFLICT) - * @return MHD action + * @return MHD response */ -const struct MHD_Action * -SH_return_backup (struct MHD_Request *request, - const struct SYNC_AccountPublicKeyP *account, - enum MHD_HTTP_StatusCode default_http_status) +struct MHD_Response * +SH_make_backup (const struct SYNC_AccountPublicKeyP *account, + enum MHD_HTTP_StatusCode default_http_status) { enum SYNC_DB_QueryStatus qs; struct MHD_Response *resp; @@ -162,44 +160,33 @@ SH_return_backup (struct MHD_Request *request, { case SYNC_DB_OLD_BACKUP_MISSING: GNUNET_break (0); - return TALER_MHD2_reply_with_error (request, - MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "unexpected return status (backup missing)"); + return TALER_MHD2_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "unexpected return status (backup missing)"); case SYNC_DB_OLD_BACKUP_MISMATCH: GNUNET_break (0); - return TALER_MHD2_reply_with_error (request, - MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "unexpected return status (backup mismatch)"); + return TALER_MHD2_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "unexpected return status (backup mismatch)"); case SYNC_DB_PAYMENT_REQUIRED: GNUNET_break (0); - return TALER_MHD2_reply_with_error (request, - MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "unexpected return status (payment required)"); + return TALER_MHD2_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "unexpected return status (payment required)") + ; case SYNC_DB_HARD_ERROR: GNUNET_break (0); - return TALER_MHD2_reply_with_error (request, - MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - NULL); + return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, + NULL); case SYNC_DB_SOFT_ERROR: GNUNET_break (0); - return TALER_MHD2_reply_with_error (request, - MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_SOFT_FAILURE, - NULL); + return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_SOFT_FAILURE, + NULL); case SYNC_DB_NO_RESULTS: GNUNET_break (0); /* Note: can theoretically happen due to non-transactional nature if the backup expired / was gc'ed JUST between the two SQL calls. But too rare to handle properly, as doing a transaction would be expensive. Just admit to failure ;-) */ - return TALER_MHD2_reply_with_error (request, - MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_INVARIANT_FAILURE, - NULL); + return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_INVARIANT_FAILURE, + NULL); case SYNC_DB_ONE_RESULT: /* interesting case below */ break; @@ -242,6 +229,5 @@ SH_return_backup (struct MHD_Request *request, GNUNET_free (prev_s); GNUNET_free (sig_s); } - return MHD_action_from_response (request, - resp); + return resp; } diff --git a/src/sync/sync-httpd2_backup.h b/src/sync/sync-httpd2_backup.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014, 2015, 2016 GNUnet e.V. + Copyright (C) 2014--2025 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -34,16 +34,14 @@ SH_resume_all_bc (void); * Return the current backup of @a account on @a request * using @a default_http_status on success. * - * @param request MHD request to use * @param account account to query * @param default_http_status HTTP status to queue response - * with on success (#MHD_HTTP_OK or #MHD_HTTP_CONFLICT) - * @return MHD action + * with on success (#MHD_HTTP_STATUS_OK or #MHD_HTTP_STATUS_CONFLICT) + * @return MHD response */ -const struct MHD_Action * -SH_return_backup (struct MHD_Request *request, - const struct SYNC_AccountPublicKeyP *account, - unsigned int default_http_status); +struct MHD_Response * +SH_make_backup (const struct SYNC_AccountPublicKeyP *account, + enum MHD_HTTP_StatusCode default_http_status); /**