anastasis-db_store_recovery_document.c (10876B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2020-2022 Anastasis SARL 4 5 Anastasis 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 Anastasis 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 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file stasis/anastasis-db_store_recovery_document.c 18 * @brief Anastasis database: store recovery document 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include "anastasis-db_pg.h" 23 #include "anastasis/anastasis-database/store_recovery_document.h" 24 #include "anastasis/anastasis-database/transaction.h" 25 #include "anastasis/anastasis-database/preflight.h" 26 #include <taler/taler_pq_lib.h> 27 28 29 /** 30 * Store encrypted recovery document. 31 * 32 * @param account_pub public key of the user's account 33 * @param account_sig signature affirming storage request 34 * @param recovery_data_hash hash of @a data 35 * @param recovery_data contains encrypted_recovery_document 36 * @param recovery_data_size size of data blob 37 * @param payment_secret identifier for the payment, used to later charge on uploads 38 * @param[out] version set to the version assigned to the document by the database 39 * @return transaction status, 0 if upload could not be finished because @a payment_secret 40 * did not have enough upload left; HARD error if @a payment_secret is unknown, ... 41 */ 42 enum ANASTASIS_DB_StoreStatus 43 ANASTASIS_DB_store_recovery_document ( 44 const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, 45 const struct ANASTASIS_AccountSignatureP *account_sig, 46 const struct GNUNET_HashCode *recovery_data_hash, 47 const void *recovery_data, 48 size_t recovery_data_size, 49 const void *recovery_meta_data, 50 size_t recovery_meta_data_size, 51 const struct ANASTASIS_PaymentSecretP *payment_secret, 52 uint32_t *version) 53 { 54 enum GNUNET_DB_QueryStatus qs; 55 56 GNUNET_break (GNUNET_OK == 57 ANASTASIS_DB_preflight ()); 58 59 PREPARE ("user_select5", 60 "SELECT" 61 " expiration_date " 62 "FROM anastasis_user" 63 " WHERE user_id=$1" 64 " FOR UPDATE;"); 65 PREPARE ("latest_recovery_version_select2", 66 "SELECT" 67 " version" 68 ",recovery_data_hash" 69 ",expiration_date" 70 " FROM anastasis_recoverydocument" 71 " JOIN anastasis_user USING (user_id)" 72 " WHERE user_id=$1" 73 " ORDER BY version DESC" 74 " LIMIT 1;"); 75 PREPARE ("postcounter_select", 76 "SELECT" 77 " post_counter" 78 " FROM anastasis_recdoc_payment" 79 " WHERE user_id=$1" 80 " AND payment_identifier=$2;"); 81 PREPARE ("postcounter_update", 82 "UPDATE" 83 " anastasis_recdoc_payment" 84 " SET" 85 " post_counter=$1" 86 " WHERE user_id =$2" 87 " AND payment_identifier=$3;"); 88 PREPARE ("recovery_document_insert", 89 "INSERT INTO anastasis_recoverydocument " 90 "(user_id" 91 ",version" 92 ",account_sig" 93 ",recovery_data_hash" 94 ",recovery_data" 95 ",recovery_meta_data" 96 ",creation_date" 97 ") VALUES " 98 "($1, $2, $3, $4, $5, $6, $7);"); 99 for (unsigned int retry = 0; retry<MAX_RETRIES; retry++) 100 { 101 if (GNUNET_OK != 102 ANASTASIS_DB_start ( 103 "store_recovery_document")) 104 { 105 GNUNET_break (0); 106 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 107 } 108 /* get the current version and hash of the latest recovery document 109 for this account */ 110 { 111 struct GNUNET_HashCode dh; 112 struct GNUNET_PQ_QueryParam params[] = { 113 GNUNET_PQ_query_param_auto_from_type (account_pub), 114 GNUNET_PQ_query_param_end 115 }; 116 struct GNUNET_PQ_ResultSpec rs[] = { 117 GNUNET_PQ_result_spec_uint32 ("version", 118 version), 119 GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash", 120 &dh), 121 GNUNET_PQ_result_spec_end 122 }; 123 124 qs = GNUNET_PQ_eval_prepared_singleton_select ( 125 pg->conn, 126 "latest_recovery_version_select2", 127 params, 128 rs); 129 switch (qs) 130 { 131 case GNUNET_DB_STATUS_HARD_ERROR: 132 GNUNET_break (0); 133 ANASTASIS_DB_rollback (); 134 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 135 case GNUNET_DB_STATUS_SOFT_ERROR: 136 goto retry; 137 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 138 *version = 1; 139 break; 140 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 141 /* had an existing recovery_data, is it identical? */ 142 if (0 == GNUNET_memcmp (&dh, 143 recovery_data_hash)) 144 { 145 /* Yes. Previous identical recovery data exists */ 146 ANASTASIS_DB_rollback (); 147 return ANASTASIS_DB_STORE_STATUS_NO_RESULTS; 148 } 149 (*version)++; 150 break; 151 default: 152 GNUNET_break (0); 153 ANASTASIS_DB_rollback (); 154 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 155 } 156 } 157 158 /* First, check if account exists */ 159 { 160 struct GNUNET_PQ_QueryParam params[] = { 161 GNUNET_PQ_query_param_auto_from_type (account_pub), 162 GNUNET_PQ_query_param_end 163 }; 164 struct GNUNET_PQ_ResultSpec rs[] = { 165 GNUNET_PQ_result_spec_end 166 }; 167 168 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 169 "user_select5", 170 params, 171 rs); 172 } 173 switch (qs) 174 { 175 case GNUNET_DB_STATUS_HARD_ERROR: 176 ANASTASIS_DB_rollback (); 177 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 178 case GNUNET_DB_STATUS_SOFT_ERROR: 179 goto retry; 180 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 181 ANASTASIS_DB_rollback (); 182 return ANASTASIS_DB_STORE_STATUS_PAYMENT_REQUIRED; 183 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 184 /* handle interesting case below */ 185 break; 186 } 187 188 { 189 uint32_t postcounter; 190 191 /* lookup if the user has enough uploads left and decrement */ 192 { 193 struct GNUNET_PQ_QueryParam params[] = { 194 GNUNET_PQ_query_param_auto_from_type (account_pub), 195 GNUNET_PQ_query_param_auto_from_type (payment_secret), 196 GNUNET_PQ_query_param_end 197 }; 198 struct GNUNET_PQ_ResultSpec rs[] = { 199 GNUNET_PQ_result_spec_uint32 ("post_counter", 200 &postcounter), 201 GNUNET_PQ_result_spec_end 202 }; 203 204 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 205 "postcounter_select", 206 params, 207 rs); 208 switch (qs) 209 { 210 case GNUNET_DB_STATUS_HARD_ERROR: 211 ANASTASIS_DB_rollback (); 212 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 213 case GNUNET_DB_STATUS_SOFT_ERROR: 214 goto retry; 215 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 216 ANASTASIS_DB_rollback (); 217 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 218 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 219 break; 220 } 221 } 222 223 if (0 == postcounter) 224 { 225 ANASTASIS_DB_rollback (); 226 return ANASTASIS_DB_STORE_STATUS_STORE_LIMIT_EXCEEDED; 227 } 228 /* Decrement the postcounter by one */ 229 postcounter--; 230 231 /* Update the postcounter in the Database */ 232 { 233 struct GNUNET_PQ_QueryParam params[] = { 234 GNUNET_PQ_query_param_uint32 (&postcounter), 235 GNUNET_PQ_query_param_auto_from_type (account_pub), 236 GNUNET_PQ_query_param_auto_from_type (payment_secret), 237 GNUNET_PQ_query_param_end 238 }; 239 240 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 241 "postcounter_update", 242 params); 243 switch (qs) 244 { 245 case GNUNET_DB_STATUS_HARD_ERROR: 246 GNUNET_break (0); 247 ANASTASIS_DB_rollback (); 248 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 249 case GNUNET_DB_STATUS_SOFT_ERROR: 250 goto retry; 251 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 252 GNUNET_break (0); 253 ANASTASIS_DB_rollback (); 254 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 255 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 256 break; 257 default: 258 GNUNET_break (0); 259 ANASTASIS_DB_rollback (); 260 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 261 } 262 } 263 } 264 265 /* finally, actually insert the recovery document */ 266 { 267 struct GNUNET_TIME_Timestamp now 268 = GNUNET_TIME_timestamp_get (); 269 struct GNUNET_PQ_QueryParam params[] = { 270 GNUNET_PQ_query_param_auto_from_type (account_pub), 271 GNUNET_PQ_query_param_uint32 (version), 272 GNUNET_PQ_query_param_auto_from_type (account_sig), 273 GNUNET_PQ_query_param_auto_from_type (recovery_data_hash), 274 GNUNET_PQ_query_param_fixed_size (recovery_data, 275 recovery_data_size), 276 GNUNET_PQ_query_param_fixed_size (recovery_meta_data, 277 recovery_meta_data_size), 278 GNUNET_PQ_query_param_timestamp (&now), 279 GNUNET_PQ_query_param_end 280 }; 281 282 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 283 "recovery_document_insert", 284 params); 285 switch (qs) 286 { 287 case GNUNET_DB_STATUS_HARD_ERROR: 288 ANASTASIS_DB_rollback (); 289 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 290 case GNUNET_DB_STATUS_SOFT_ERROR: 291 goto retry; 292 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 293 GNUNET_break (0); 294 ANASTASIS_DB_rollback (); 295 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 296 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 297 qs = ANASTASIS_DB_commit (); 298 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 299 goto retry; 300 if (qs < 0) 301 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 302 return ANASTASIS_DB_STORE_STATUS_SUCCESS; 303 } 304 } 305 retry: 306 ANASTASIS_DB_rollback (); 307 } 308 return ANASTASIS_DB_STORE_STATUS_SOFT_ERROR; 309 } 310 311 312 /* end of anastasis-db_store_recovery_document.c */