sync

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

sync-httpd2_backup.c (8881B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2019--2025 Taler Systems SA
      4 
      5   TALER 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   TALER 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   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file sync-httpd2_backup.c
     18  * @brief functions to handle incoming requests for backups
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "sync-httpd2.h"
     23 #include <gnunet/gnunet_util_lib.h>
     24 #include "sync-httpd2_backup.h"
     25 
     26 
     27 /**
     28  * Handle request on @a request for retrieval of the latest
     29  * backup of @a account.
     30  *
     31  * @param request the MHD request to handle
     32  * @param account public key of the account the request is for
     33  * @return MHD action
     34  */
     35 const struct MHD_Action *
     36 SH_backup_get (struct MHD_Request *request,
     37                const struct SYNC_AccountPublicKeyP *account)
     38 {
     39   struct GNUNET_HashCode backup_hash;
     40   enum SYNC_DB_QueryStatus qs;
     41 
     42   qs = SYNCDB_lookup_account_TR (
     43     account,
     44     &backup_hash);
     45   switch (qs)
     46   {
     47   case SYNC_DB_OLD_BACKUP_MISSING:
     48     GNUNET_break (0);
     49     return TALER_MHD2_reply_with_error (request,
     50                                         MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR,
     51                                         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     52                                         NULL);
     53   case SYNC_DB_OLD_BACKUP_MISMATCH:
     54     GNUNET_break (0);
     55     return TALER_MHD2_reply_with_error (request,
     56                                         MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR,
     57                                         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
     58                                         NULL);
     59   case SYNC_DB_PAYMENT_REQUIRED:
     60     return TALER_MHD2_reply_with_error (request,
     61                                         MHD_HTTP_STATUS_NOT_FOUND,
     62                                         TALER_EC_SYNC_ACCOUNT_UNKNOWN,
     63                                         NULL);
     64   case SYNC_DB_HARD_ERROR:
     65     return TALER_MHD2_reply_with_error (request,
     66                                         MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR,
     67                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
     68                                         NULL);
     69   case SYNC_DB_SOFT_ERROR:
     70     GNUNET_break (0);
     71     return TALER_MHD2_reply_with_error (request,
     72                                         MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR,
     73                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
     74                                         NULL);
     75   case SYNC_DB_NO_RESULTS:
     76     {
     77       struct MHD_Response *resp;
     78 
     79       resp = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT);
     80       TALER_MHD2_add_global_headers (resp,
     81                                      false);
     82       return MHD_action_from_response (request,
     83                                        resp);
     84     }
     85   case SYNC_DB_ONE_RESULT:
     86     {
     87       struct MHD_StringNullable inm;
     88 
     89       if ( (MHD_YES ==
     90             MHD_request_get_value (request,
     91                                    MHD_VK_HEADER,
     92                                    MHD_HTTP_HEADER_IF_NONE_MATCH,
     93                                    &inm)) &&
     94            (2 < inm.len) &&
     95            ('"' == inm.cstr[0]) &&
     96            ('=' == inm.cstr[inm.len - 1]) )
     97       {
     98         struct GNUNET_HashCode inm_h;
     99 
    100         if (GNUNET_OK !=
    101             GNUNET_STRINGS_string_to_data (inm.cstr + 1,
    102                                            inm.len - 2,
    103                                            &inm_h,
    104                                            sizeof (inm_h)))
    105         {
    106           GNUNET_break_op (0);
    107           return TALER_MHD2_reply_with_error (request,
    108                                               MHD_HTTP_STATUS_BAD_REQUEST,
    109                                               TALER_EC_SYNC_BAD_IF_NONE_MATCH,
    110                                               "Etag does not include a base32-encoded SHA-512 hash");
    111         }
    112         if (0 == GNUNET_memcmp (&inm_h,
    113                                 &backup_hash))
    114         {
    115           struct MHD_Response *resp;
    116 
    117           resp = MHD_response_from_empty (MHD_HTTP_STATUS_NOT_MODIFIED);
    118           TALER_MHD2_add_global_headers (resp,
    119                                          false);
    120           return MHD_action_from_response (request,
    121                                            resp);
    122         }
    123       }
    124     }
    125     /* We have a result, should fetch and return it! */
    126     break;
    127   }
    128   return MHD_action_from_response (request,
    129                                    SH_make_backup (account,
    130                                                    MHD_HTTP_STATUS_OK));
    131 }
    132 
    133 
    134 /**
    135  * Return the current backup of @a account on @a request
    136  * using @a default_http_status on success.
    137  *
    138  * @param account account to query
    139  * @param default_http_status HTTP status to queue response
    140  *  with on success (#MHD_HTTP_STATUS_OK or #MHD_HTTP_STATUS_CONFLICT)
    141  * @return MHD response
    142  */
    143 struct MHD_Response *
    144 SH_make_backup (const struct SYNC_AccountPublicKeyP *account,
    145                 enum MHD_HTTP_StatusCode default_http_status)
    146 {
    147   enum SYNC_DB_QueryStatus qs;
    148   struct MHD_Response *resp;
    149   struct SYNC_AccountSignatureP account_sig;
    150   struct GNUNET_HashCode backup_hash;
    151   struct GNUNET_HashCode prev_hash;
    152   size_t backup_size;
    153   void *backup;
    154 
    155   qs = SYNCDB_lookup_backup_TR (
    156     account,
    157     &account_sig,
    158     &prev_hash,
    159     &backup_hash,
    160     &backup_size,
    161     &backup);
    162   switch (qs)
    163   {
    164   case SYNC_DB_OLD_BACKUP_MISSING:
    165     GNUNET_break (0);
    166     return TALER_MHD2_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    167                                   "unexpected return status (backup missing)");
    168   case SYNC_DB_OLD_BACKUP_MISMATCH:
    169     GNUNET_break (0);
    170     return TALER_MHD2_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    171                                   "unexpected return status (backup mismatch)");
    172   case SYNC_DB_PAYMENT_REQUIRED:
    173     GNUNET_break (0);
    174     return TALER_MHD2_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    175                                   "unexpected return status (payment required)")
    176     ;
    177   case SYNC_DB_HARD_ERROR:
    178     GNUNET_break (0);
    179     return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
    180                                   NULL);
    181   case SYNC_DB_SOFT_ERROR:
    182     GNUNET_break (0);
    183     return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_SOFT_FAILURE,
    184                                   NULL);
    185   case SYNC_DB_NO_RESULTS:
    186     GNUNET_break (0);
    187     /* Note: can theoretically happen due to non-transactional nature if
    188        the backup expired / was gc'ed JUST between the two SQL calls.
    189        But too rare to handle properly, as doing a transaction would be
    190        expensive. Just admit to failure ;-) */
    191     return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    192                                   NULL);
    193   case SYNC_DB_ONE_RESULT:
    194     /* interesting case below */
    195     break;
    196   }
    197   resp = MHD_response_from_buffer (default_http_status,
    198                                    backup_size,
    199                                    backup,
    200                                    &free,
    201                                    backup);
    202   TALER_MHD2_add_global_headers (resp,
    203                                  false);
    204   {
    205     char *sig_s;
    206     char *prev_s;
    207     char *etag;
    208     char *etagq;
    209 
    210     sig_s = GNUNET_STRINGS_data_to_string_alloc (&account_sig,
    211                                                  sizeof (account_sig));
    212     prev_s = GNUNET_STRINGS_data_to_string_alloc (&prev_hash,
    213                                                   sizeof (prev_hash));
    214     etag = GNUNET_STRINGS_data_to_string_alloc (&backup_hash,
    215                                                 sizeof (backup_hash));
    216     GNUNET_break (MHD_SC_OK ==
    217                   MHD_response_add_header (resp,
    218                                            "Sync-Signature",
    219                                            sig_s));
    220     GNUNET_break (MHD_SC_OK ==
    221                   MHD_response_add_header (resp,
    222                                            "Sync-Previous",
    223                                            prev_s));
    224     GNUNET_asprintf (&etagq,
    225                      "\"%s\"",
    226                      etag);
    227     GNUNET_break (MHD_SC_OK ==
    228                   MHD_response_add_header (resp,
    229                                            MHD_HTTP_HEADER_ETAG,
    230                                            etagq));
    231     GNUNET_free (etagq);
    232     GNUNET_free (etag);
    233     GNUNET_free (prev_s);
    234     GNUNET_free (sig_s);
    235   }
    236   return resp;
    237 }