/* This file is part of TALER Copyright (C) 2019 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 Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, see */ /** * @file sync-httpd_backup_post.c * @brief functions to handle incoming requests for backups * @author Christian Grothoff */ #include "platform.h" #include "sync-httpd.h" #include #include "sync-httpd_backup.h" #include #include #include /** * How long do we hold an HTTP client connection if * we are awaiting payment before giving up? */ #define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_MINUTES, 30) /** * Context for an upload operation. */ struct BackupContext { /** * Context for cleanup logic. */ struct TM_HandlerContext hc; /** * Signature of the account holder. */ struct SYNC_AccountSignatureP account_sig; /** * Public key of the account holder. */ struct SYNC_AccountPublicKeyP account; /** * Hash of the previous upload, or zeros if first upload. */ struct GNUNET_HashCode old_backup_hash; /** * Hash of the upload we are receiving right now (as promised * by the client, to be verified!). */ struct GNUNET_HashCode new_backup_hash; /** * Claim token, all zeros if not known. Only set if @e existing_order_id is non-NULL. */ 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; /** * Kept in DLL for shutdown handling while suspended. */ struct BackupContext *prev; /** * Used while suspended for resumption. */ struct MHD_Connection *con; /** * Upload, with as many bytes as we have received so far. */ char *upload; /** * Used while we are awaiting proposal creation. */ struct TALER_MERCHANT_PostOrdersHandle *po; /** * Used while we are waiting payment. */ 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; /** * Order ID for the client that we found in our database. */ char *existing_order_id; /** * Timestamp of the order in @e existing_order_id. Used to * select the most recent unpaid offer. */ 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; }; /** * Kept in DLL for shutdown handling while suspended. */ static struct BackupContext *bc_head; /** * Kept in DLL for shutdown handling while suspended. */ static struct BackupContext *bc_tail; /** * Service is shutting down, resume all MHD connections NOW. */ void SH_resume_all_bc () { struct BackupContext *bc; while (NULL != (bc = bc_head)) { GNUNET_CONTAINER_DLL_remove (bc_head, bc_tail, bc); MHD_resume_connection (bc->con); if (NULL != bc->po) { TALER_MERCHANT_orders_post_cancel (bc->po); bc->po = NULL; } if (NULL != bc->omgh) { TALER_MERCHANT_merchant_order_get_cancel (bc->omgh); bc->omgh = NULL; } } } /** * Function called to clean up a backup context. * * @param hc a `struct BackupContext` */ static void cleanup_ctx (struct TM_HandlerContext *hc) { struct BackupContext *bc = (struct BackupContext *) hc; 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 * * @param order_id our backend's order ID * @param token the claim token generated by the merchant (NULL if * it wasn't generated). * @return MHD response to use */ static struct MHD_Response * make_payment_request (const char *order_id, const struct TALER_ClaimTokenP *token) { struct MHD_Response *resp; /* request payment via Taler */ 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); { char *hdr; char *pfx; char *hn; struct GNUNET_Buffer hdr_buf = { 0 }; if (0 == strncasecmp ("https://", SH_backend_url, strlen ("https://"))) { pfx = "taler://"; hn = &SH_backend_url[strlen ("https://")]; } else if (0 == strncasecmp ("http://", SH_backend_url, strlen ("http://"))) { pfx = "taler+http://"; hn = &SH_backend_url[strlen ("http://")]; } else { GNUNET_break (0); MHD_destroy_response (resp); return NULL; } if (0 == strlen (hn)) { GNUNET_break (0); MHD_destroy_response (resp); return NULL; } GNUNET_buffer_write_str (&hdr_buf, pfx); GNUNET_buffer_write_str (&hdr_buf, "pay/"); GNUNET_buffer_write_str (&hdr_buf, hn); GNUNET_buffer_write_path (&hdr_buf, order_id); /* No session ID */ GNUNET_buffer_write_path (&hdr_buf, ""); if (NULL != token) { GNUNET_buffer_write_str (&hdr_buf, "?c="); 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, "Taler", hdr)); GNUNET_free (hdr); } return resp; } /** * Callbacks of this type are used to serve the result of submitting a * /contract request to a merchant. * * @param cls our `struct BackupContext` * @param por response details */ static void proposal_cb (void *cls, const struct TALER_MERCHANT_PostOrdersReply *por) { struct BackupContext *bc = cls; enum SYNC_DB_QueryStatus qs; bc->po = NULL; GNUNET_CONTAINER_DLL_remove (bc_head, bc_tail, bc); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Resuming connection with order `%s'\n", bc->order_id); MHD_resume_connection (bc->con); SH_trigger_daemon (); if (MHD_HTTP_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 ( TALER_JSON_pack_ec (TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR), GNUNET_JSON_pack_uint64 ("backend-ec", por->hr.ec), GNUNET_JSON_pack_uint64 ("backend-http-status", por->hr.http_status), 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, "Storing payment request for order `%s'\n", por->details.ok.order_id); qs = db->store_payment_TR (db->cls, &bc->account, por->details.ok.order_id, por->details.ok.token, &SH_annual_fee); 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"); GNUNET_assert (NULL != bc->resp); bc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Obtained fresh order `%s'\n", por->details.ok.order_id); 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; } /** * Function called on all pending payments for the right * account. * * @param cls closure, our `struct BackupContext` * @param timestamp for how long have we been waiting * @param order_id order id in the backend * @param token claim token to use (or NULL for none) * @param amount how much is the order for */ static void ongoing_payment_cb (void *cls, struct GNUNET_TIME_Timestamp timestamp, const char *order_id, const struct TALER_ClaimTokenP *token, const struct TALER_Amount *amount) { struct BackupContext *bc = cls; (void) amount; if (0 != TALER_amount_cmp (amount, &SH_annual_fee)) return; /* can't reuse, fees changed */ if ( (NULL == bc->existing_order_id) || (GNUNET_TIME_timestamp_cmp (bc->existing_order_timestamp, <, timestamp)) ) { GNUNET_free (bc->existing_order_id); bc->existing_order_id = GNUNET_strdup (order_id); bc->existing_order_timestamp = timestamp; if (NULL != token) bc->token = *token; } } /** * Callback to process a GET /check-payment request * * @param cls our `struct BackupContext` * @param osr order status */ static void check_payment_cb (void *cls, const struct TALER_MERCHANT_OrderStatusResponse *osr) { struct BackupContext *bc = cls; const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr; /* refunds are not supported, verify */ bc->omgh = NULL; GNUNET_CONTAINER_DLL_remove (bc_head, bc_tail, bc); MHD_resume_connection (bc->con); 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 ( TALER_EC_SYNC_GENERIC_BACKEND_TIMEOUT, NULL); return; case MHD_HTTP_OK: break; /* handled below */ default: /* Unexpected backend response */ bc->response_code = MHD_HTTP_BAD_GATEWAY; bc->resp = TALER_MHD_MAKE_JSON_PACK ( GNUNET_JSON_pack_uint64 ("code", TALER_EC_SYNC_GENERIC_BACKEND_ERROR), GNUNET_JSON_pack_string ("hint", TALER_ErrorCode_get_hint ( TALER_EC_SYNC_GENERIC_BACKEND_ERROR)), GNUNET_JSON_pack_uint64 ("backend-ec", (json_int_t) hr->ec), GNUNET_JSON_pack_uint64 ("backend-http-status", (json_int_t) hr->http_status), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("backend-reply", (json_t *) hr->reply))); return; } GNUNET_assert (MHD_HTTP_OK == hr->http_status); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Payment status checked: %d\n", osr->details.ok.status); switch (osr->details.ok.status) { case TALER_MERCHANT_OSC_PAID: { enum SYNC_DB_QueryStatus qs; qs = db->increment_lifetime_TR (db->cls, &bc->account, bc->order_id, GNUNET_TIME_UNIT_YEARS); /* always annual */ if (0 <= qs) return; /* continue as planned */ GNUNET_break (0); bc->resp = TALER_MHD_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: case TALER_MERCHANT_OSC_CLAIMED: break; } if (NULL != bc->existing_order_id) { /* repeat payment request */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Repeating payment request\n"); bc->resp = make_payment_request (bc->existing_order_id, (GNUNET_YES == GNUNET_is_zero (&bc->token)) ? 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"); GNUNET_assert (NULL != bc->resp); bc->response_code = MHD_HTTP_REQUEST_TIMEOUT; } /** * Helper function used to ask our backend to await * a payment for the user's account. * * @param bc context to begin payment for. * @param timeout when to give up trying * @param order_id which order to check for the payment */ static void await_payment (struct BackupContext *bc, struct GNUNET_TIME_Relative timeout, const char *order_id) { 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, order_id, NULL /* our payments are NOT session-bound */, timeout, &check_payment_cb, bc); SH_trigger_curl (); } /** * 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 * 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 */ static MHD_RESULT begin_payment (struct BackupContext *bc, int pay_req) { static const char *no_uuids[1] = { NULL }; json_t *order; if (! bc->force_fresh_order) { enum GNUNET_DB_QueryStatus qs; qs = db->lookup_pending_payments_by_account_TR (db->cls, &bc->account, &ongoing_payment_cb, 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; } if (NULL != bc->existing_order_id) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Have existing order, waiting for `%s' to complete\n", bc->existing_order_id); await_payment (bc, GNUNET_TIME_UNIT_ZERO /* no long polling */, bc->existing_order_id); return MHD_YES; } } GNUNET_CONTAINER_DLL_insert (bc_head, bc_tail, bc); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Suspending connection while creating order at `%s'\n", SH_backend_url); MHD_suspend_connection (bc->con); order = GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("amount", &SH_annual_fee), GNUNET_JSON_pack_string ("summary", "annual fee for sync service"), GNUNET_JSON_pack_string ("fulfillment_url", SH_fulfillment_url)); bc->po = TALER_MERCHANT_orders_post2 (SH_ctx, SH_backend_url, order, GNUNET_TIME_UNIT_ZERO, NULL, /* no payment target */ 0, NULL, /* no inventory products */ 0, no_uuids, /* no uuids */ false, /* do NOT require claim token */ &proposal_cb, bc); SH_trigger_curl (); json_decref (order); return MHD_YES; } /** * We got some query status from the DB. Handle the error cases. * May perform asynchronous operations by suspending the connection * if required. * * @param bc connection to handle status for * @param qs query status to handle * @return #MHD_YES or #MHD_NO */ static MHD_RESULT handle_database_error (struct BackupContext *bc, enum SYNC_DB_QueryStatus qs) { 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); 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); case SYNC_DB_PAYMENT_REQUIRED: { const char *order_id; order_id = MHD_lookup_connection_value (bc->con, MHD_GET_ARGUMENT_KIND, "paying"); if (NULL == order_id) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Payment required, starting payment process\n"); return begin_payment (bc, GNUNET_NO); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Payment required, awaiting completion of `%s'\n", order_id); await_payment (bc, CHECK_PAYMENT_GENERIC_TIMEOUT, order_id); } 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); 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); case SYNC_DB_NO_RESULTS: GNUNET_assert (0); return MHD_NO; /* intentional fall-through! */ case SYNC_DB_ONE_RESULT: GNUNET_assert (0); return MHD_NO; } GNUNET_break (0); return MHD_NO; } 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) { 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; 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; } /* finished with upload, check hash */ { struct GNUNET_HashCode our_hash; GNUNET_CRYPTO_hash_context_finish (bc->hash_ctx, &our_hash); bc->hash_ctx = NULL; 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); } } /* store backup to database */ { enum SYNC_DB_QueryStatus qs; if (GNUNET_YES == 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_sig, &bc->new_backup_hash, bc->upload_size, bc->upload); } else { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Uploading existing backup of account\n"); qs = db->update_backup_TR (db->cls, account, &bc->old_backup_hash, &bc->account_sig, &bc->new_backup_hash, bc->upload_size, bc->upload); } if (qs < 0) return handle_database_error (bc, qs); 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; } } /* 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; } }