plugin_anastasis_postgres.c (101867B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2020, 2021, 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/plugin_anastasis_postgres.c 18 * @brief database helper functions for postgres used by GNU Anastasis 19 * @author Christian Grothoff 20 * @author Marcello Stanisci 21 */ 22 #include "platform.h" 23 #include "anastasis_database_plugin.h" 24 #include "anastasis_database_lib.h" 25 #include <taler/taler_pq_lib.h> 26 27 /** 28 * How long do we keep transient accounts open (those that have 29 * not been paid at all, but are awaiting payment). This puts 30 * a cap on how long users have to make a payment after a payment 31 * request was generated. 32 */ 33 #define TRANSIENT_LIFETIME GNUNET_TIME_UNIT_WEEKS 34 35 /** 36 * How often do we re-try if we run into a DB serialization error? 37 */ 38 #define MAX_RETRIES 3 39 40 41 /** 42 * Type of the "cls" argument given to each of the functions in 43 * our API. 44 */ 45 struct PostgresClosure 46 { 47 48 /** 49 * Postgres connection handle. 50 */ 51 struct GNUNET_PQ_Context *conn; 52 53 /** 54 * Underlying configuration. 55 */ 56 const struct GNUNET_CONFIGURATION_Handle *cfg; 57 58 /** 59 * Name of the currently active transaction, NULL if none is active. 60 */ 61 const char *transaction_name; 62 63 /** 64 * Currency we accept payments in. 65 */ 66 char *currency; 67 68 /** 69 * Prepared statements have been initialized. 70 */ 71 bool init; 72 }; 73 74 75 /** 76 * Drop anastasis tables 77 * 78 * @param cls closure our `struct Plugin` 79 * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure 80 */ 81 static enum GNUNET_GenericReturnValue 82 postgres_drop_tables (void *cls) 83 { 84 struct PostgresClosure *pg = cls; 85 struct GNUNET_PQ_Context *conn; 86 enum GNUNET_GenericReturnValue ret; 87 88 conn = GNUNET_PQ_connect_with_cfg (pg->cfg, 89 "stasis-postgres", 90 NULL, 91 NULL, 92 NULL); 93 if (NULL == conn) 94 return GNUNET_SYSERR; 95 ret = GNUNET_PQ_exec_sql (conn, 96 "drop"); 97 GNUNET_PQ_disconnect (conn); 98 return ret; 99 } 100 101 102 /** 103 * Initialize tables. 104 * 105 * @param cls the `struct PostgresClosure` with the plugin-specific state 106 * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure 107 */ 108 static enum GNUNET_GenericReturnValue 109 postgres_create_tables (void *cls) 110 { 111 struct PostgresClosure *pc = cls; 112 struct GNUNET_PQ_Context *conn; 113 struct GNUNET_PQ_ExecuteStatement es[] = { 114 GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), 115 GNUNET_PQ_EXECUTE_STATEMENT_END 116 }; 117 118 conn = GNUNET_PQ_connect_with_cfg (pc->cfg, 119 "stasis-postgres", 120 "stasis-", 121 es, 122 NULL); 123 if (NULL == conn) 124 return GNUNET_SYSERR; 125 GNUNET_PQ_disconnect (conn); 126 return GNUNET_OK; 127 } 128 129 130 /** 131 * Establish connection to the database. 132 * 133 * @param cls plugin context 134 * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure 135 */ 136 static enum GNUNET_GenericReturnValue 137 prepare_statements (void *cls) 138 { 139 struct PostgresClosure *pg = cls; 140 struct GNUNET_PQ_PreparedStatement ps[] = { 141 GNUNET_PQ_make_prepare ("user_insert", 142 "INSERT INTO anastasis_user " 143 "(user_id" 144 ",expiration_date" 145 ") VALUES " 146 "($1, $2);"), 147 GNUNET_PQ_make_prepare ("do_commit", 148 "COMMIT"), 149 GNUNET_PQ_make_prepare ("user_select", 150 "SELECT" 151 " expiration_date " 152 "FROM anastasis_user" 153 " WHERE user_id=$1" 154 " FOR UPDATE;"), 155 GNUNET_PQ_make_prepare ("user_update", 156 "UPDATE anastasis_user" 157 " SET " 158 " expiration_date=$1" 159 " WHERE user_id=$2;"), 160 GNUNET_PQ_make_prepare ("recdoc_payment_insert", 161 "INSERT INTO anastasis_recdoc_payment " 162 "(user_id" 163 ",post_counter" 164 ",amount" 165 ",payment_identifier" 166 ",creation_date" 167 ") VALUES " 168 "($1, $2, $3, $4, $5);"), 169 GNUNET_PQ_make_prepare ("challenge_payment_insert", 170 "INSERT INTO anastasis_challenge_payment " 171 "(truth_uuid" 172 ",amount" 173 ",payment_identifier" 174 ",creation_date" 175 ") VALUES " 176 "($1, $2, $3, $4);"), 177 GNUNET_PQ_make_prepare ("truth_payment_insert", 178 "INSERT INTO anastasis_truth_payment " 179 "(truth_uuid" 180 ",amount" 181 ",expiration" 182 ") VALUES " 183 "($1, $2, $3);"), 184 GNUNET_PQ_make_prepare ("recdoc_payment_done", 185 "UPDATE anastasis_recdoc_payment " 186 "SET" 187 " paid=TRUE " 188 "WHERE" 189 " payment_identifier=$1" 190 " AND" 191 " user_id=$2" 192 " AND" 193 " paid=FALSE;"), 194 GNUNET_PQ_make_prepare ("challenge_refund_update", 195 "UPDATE anastasis_challenge_payment " 196 "SET" 197 " refunded=TRUE " 198 "WHERE" 199 " payment_identifier=$1" 200 " AND" 201 " paid=TRUE" 202 " AND" 203 " truth_uuid=$2;"), 204 GNUNET_PQ_make_prepare ("challenge_payment_done", 205 "UPDATE anastasis_challenge_payment " 206 "SET" 207 " paid=TRUE " 208 "WHERE" 209 " payment_identifier=$1" 210 " AND" 211 " refunded=FALSE" 212 " AND" 213 " truth_uuid=$2" 214 " AND" 215 " paid=FALSE;"), 216 GNUNET_PQ_make_prepare ("recdoc_payment_select", 217 "SELECT" 218 " creation_date" 219 ",post_counter" 220 ",amount" 221 ",paid" 222 " FROM anastasis_recdoc_payment" 223 " WHERE payment_identifier=$1;"), 224 GNUNET_PQ_make_prepare ("truth_payment_select", 225 "SELECT" 226 " expiration" 227 " FROM anastasis_truth_payment" 228 " WHERE truth_uuid=$1" 229 " AND expiration>$2;"), 230 GNUNET_PQ_make_prepare ("challenge_payment_select", 231 "SELECT" 232 " creation_date" 233 ",amount" 234 ",paid" 235 " FROM anastasis_challenge_payment" 236 " WHERE payment_identifier=$1" 237 " AND truth_uuid=$2" 238 " AND refunded=FALSE" 239 " AND counter>0;"), 240 GNUNET_PQ_make_prepare ("challenge_pending_payment_select", 241 "SELECT" 242 " creation_date" 243 ",payment_identifier" 244 ",amount" 245 " FROM anastasis_challenge_payment" 246 " WHERE" 247 " paid=FALSE" 248 " AND" 249 " refunded=FALSE" 250 " AND" 251 " truth_uuid=$1" 252 " AND" 253 " creation_date > $2;"), 254 GNUNET_PQ_make_prepare ("recdoc_payments_select", 255 "SELECT" 256 " user_id" 257 ",payment_identifier" 258 ",amount" 259 " FROM anastasis_recdoc_payment" 260 " WHERE paid=FALSE;"), 261 GNUNET_PQ_make_prepare ("gc_accounts", 262 "DELETE FROM anastasis_user " 263 "WHERE" 264 " expiration_date < $1;"), 265 GNUNET_PQ_make_prepare ("gc_recdoc_pending_payments", 266 "DELETE FROM anastasis_recdoc_payment " 267 "WHERE" 268 " paid=FALSE" 269 " AND" 270 " creation_date < $1;"), 271 GNUNET_PQ_make_prepare ("gc_challenge_pending_payments", 272 "DELETE FROM anastasis_challenge_payment " 273 "WHERE" 274 " (paid=FALSE" 275 " OR" 276 " refunded=TRUE)" 277 " AND" 278 " creation_date < $1;"), 279 GNUNET_PQ_make_prepare ("truth_insert", 280 "INSERT INTO anastasis_truth " 281 "(truth_uuid" 282 ",key_share_data" 283 ",method_name" 284 ",encrypted_truth" 285 ",truth_mime" 286 ",expiration" 287 ") VALUES " 288 "($1, $2, $3, $4, $5, $6);"), 289 GNUNET_PQ_make_prepare ("test_auth_iban_payment", 290 "SELECT" 291 " credit" 292 ",wire_subject" 293 " FROM anastasis_auth_iban_in" 294 " WHERE debit_account_details=$1" 295 " AND execution_date>=$2;"), 296 GNUNET_PQ_make_prepare ("store_auth_iban_payment_details", 297 "INSERT INTO anastasis_auth_iban_in " 298 "(wire_reference" 299 ",wire_subject" 300 ",credit" 301 ",debit_account_details" 302 ",credit_account_details" 303 ",execution_date" 304 ") VALUES " 305 "($1, $2, $3, $4, $5, $6);"), 306 GNUNET_PQ_make_prepare ("recovery_document_insert", 307 "INSERT INTO anastasis_recoverydocument " 308 "(user_id" 309 ",version" 310 ",account_sig" 311 ",recovery_data_hash" 312 ",recovery_data" 313 ",recovery_meta_data" 314 ",creation_date" 315 ") VALUES " 316 "($1, $2, $3, $4, $5, $6, $7);"), 317 GNUNET_PQ_make_prepare ("truth_select", 318 "SELECT " 319 " method_name" 320 ",encrypted_truth" 321 ",truth_mime" 322 " FROM anastasis_truth" 323 " WHERE truth_uuid=$1;"), 324 GNUNET_PQ_make_prepare ("recoverydocument_select_meta", 325 "SELECT " 326 " version" 327 ",creation_date" 328 ",recovery_meta_data" 329 " FROM anastasis_recoverydocument" 330 " WHERE user_id=$1" 331 " AND version < $2" 332 " ORDER BY version DESC" 333 " LIMIT 1000;"), 334 GNUNET_PQ_make_prepare ("latest_recoverydocument_select", 335 "SELECT " 336 " version" 337 ",account_sig" 338 ",recovery_data_hash" 339 ",recovery_data" 340 " FROM anastasis_recoverydocument" 341 " WHERE user_id=$1" 342 " ORDER BY version DESC" 343 " LIMIT 1;"), 344 GNUNET_PQ_make_prepare ("latest_recovery_version_select", 345 "SELECT" 346 " version" 347 ",recovery_data_hash" 348 ",expiration_date" 349 " FROM anastasis_recoverydocument" 350 " JOIN anastasis_user USING (user_id)" 351 " WHERE user_id=$1" 352 " ORDER BY version DESC" 353 " LIMIT 1;"), 354 GNUNET_PQ_make_prepare ("recoverydocument_select", 355 "SELECT " 356 " account_sig" 357 ",recovery_data_hash" 358 ",recovery_data" 359 " FROM anastasis_recoverydocument" 360 " WHERE user_id=$1" 361 " AND version=$2;"), 362 GNUNET_PQ_make_prepare ("postcounter_select", 363 "SELECT" 364 " post_counter" 365 " FROM anastasis_recdoc_payment" 366 " WHERE user_id=$1" 367 " AND payment_identifier=$2;"), 368 GNUNET_PQ_make_prepare ("postcounter_update", 369 "UPDATE" 370 " anastasis_recdoc_payment" 371 " SET" 372 " post_counter=$1" 373 " WHERE user_id =$2" 374 " AND payment_identifier=$3;"), 375 GNUNET_PQ_make_prepare ("key_share_select", 376 "SELECT " 377 "key_share_data " 378 "FROM " 379 "anastasis_truth " 380 "WHERE truth_uuid =$1;"), 381 GNUNET_PQ_make_prepare ("challengecode_insert", 382 "INSERT INTO anastasis_challengecode " 383 "(truth_uuid" 384 ",code" 385 ",creation_date" 386 ",expiration_date" 387 ",retry_counter" 388 ") VALUES " 389 "($1, $2, $3, $4, $5);"), 390 GNUNET_PQ_make_prepare ("challengecode_select", 391 "SELECT " 392 " code" 393 ",satisfied" 394 " FROM anastasis_challengecode" 395 " WHERE truth_uuid=$1" 396 " AND expiration_date > $2" 397 " AND retry_counter != 0;"), 398 GNUNET_PQ_make_prepare ("challengecode_set_satisfied", 399 "UPDATE anastasis_challengecode" 400 " SET satisfied=TRUE" 401 " WHERE truth_uuid=$1" 402 " AND code=$2" 403 " AND creation_date IN" 404 " (SELECT creation_date" 405 " FROM anastasis_challengecode" 406 " WHERE truth_uuid=$1" 407 " AND code=$2" 408 " ORDER BY creation_date DESC" 409 " LIMIT 1);"), 410 GNUNET_PQ_make_prepare ("challengecode_test_satisfied", 411 "SELECT 1 FROM anastasis_challengecode" 412 " WHERE truth_uuid=$1" 413 " AND satisfied=TRUE" 414 " AND code=$2" 415 " AND creation_date >= $3" 416 " LIMIT 1;"), 417 GNUNET_PQ_make_prepare ("challengecode_select_meta", 418 "SELECT " 419 " code" 420 ",retry_counter" 421 ",retransmission_date" 422 " FROM anastasis_challengecode" 423 " WHERE truth_uuid=$1" 424 " AND expiration_date > $2" 425 " AND creation_date > $3" 426 " ORDER BY creation_date DESC" 427 " LIMIT 1;"), 428 GNUNET_PQ_make_prepare ("challengecode_update_retry", 429 "UPDATE anastasis_challengecode" 430 " SET retry_counter=retry_counter - 1" 431 " WHERE truth_uuid=$1" 432 " AND code=$2" 433 " AND retry_counter != 0;"), 434 GNUNET_PQ_make_prepare ("challengepayment_dec_counter", 435 "UPDATE anastasis_challenge_payment" 436 " SET counter=counter - 1" 437 " WHERE truth_uuid=$1" 438 " AND payment_identifier=$2" 439 " AND counter > 0;"), 440 GNUNET_PQ_make_prepare ("challengecode_mark_sent", 441 "UPDATE anastasis_challengecode" 442 " SET retransmission_date=$3" 443 " WHERE truth_uuid=$1" 444 " AND code=$2" 445 " AND creation_date IN" 446 " (SELECT creation_date" 447 " FROM anastasis_challengecode" 448 " WHERE truth_uuid=$1" 449 " AND code=$2" 450 " ORDER BY creation_date DESC" 451 " LIMIT 1);"), 452 GNUNET_PQ_make_prepare ("get_last_auth_iban_payment", 453 "SELECT " 454 " wire_reference" 455 " FROM anastasis_auth_iban_in" 456 " WHERE credit_account_details=$1" 457 " ORDER BY wire_reference DESC" 458 " LIMIT 1;"), 459 GNUNET_PQ_make_prepare ("gc_challengecodes", 460 "DELETE FROM anastasis_challengecode " 461 "WHERE " 462 "expiration_date < $1;"), 463 GNUNET_PQ_PREPARED_STATEMENT_END 464 }; 465 466 { 467 enum GNUNET_GenericReturnValue ret; 468 469 ret = GNUNET_PQ_prepare_statements (pg->conn, 470 ps); 471 if (GNUNET_OK != ret) 472 return ret; 473 pg->init = true; 474 return GNUNET_OK; 475 } 476 } 477 478 479 /** 480 * Check that the database connection is still up. 481 * 482 * @param cls a `struct PostgresClosure` with connection to check 483 */ 484 static void 485 check_connection (void *cls) 486 { 487 struct PostgresClosure *pg = cls; 488 489 GNUNET_PQ_reconnect_if_down (pg->conn); 490 } 491 492 493 /** 494 * Connect to the database if the connection does not exist yet. 495 * 496 * @param pg the plugin-specific state 497 * @return #GNUNET_OK on success 498 */ 499 static enum GNUNET_GenericReturnValue 500 internal_setup (struct PostgresClosure *pg) 501 { 502 if (NULL == pg->conn) 503 { 504 #if AUTO_EXPLAIN 505 /* Enable verbose logging to see where queries do not 506 properly use indices */ 507 struct GNUNET_PQ_ExecuteStatement es[] = { 508 GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"), 509 GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"), 510 GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"), 511 GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"), 512 /* https://wiki.postgresql.org/wiki/Serializable suggests to really 513 force the default to 'serializable' if SSI is to be used. */ 514 GNUNET_PQ_make_try_execute ( 515 "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"), 516 GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"), 517 GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"), 518 GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), 519 GNUNET_PQ_EXECUTE_STATEMENT_END 520 }; 521 #else 522 struct GNUNET_PQ_ExecuteStatement es[] = { 523 GNUNET_PQ_make_execute ("SET search_path TO anastasis;"), 524 GNUNET_PQ_EXECUTE_STATEMENT_END 525 }; 526 #endif 527 struct GNUNET_PQ_Context *db_conn; 528 529 db_conn = GNUNET_PQ_connect_with_cfg2 (pg->cfg, 530 "stasis-postgres", 531 "statis-", 532 es, 533 NULL, 534 GNUNET_PQ_FLAG_CHECK_CURRENT); 535 if (NULL == db_conn) 536 return GNUNET_SYSERR; 537 pg->conn = db_conn; 538 } 539 if (NULL == pg->transaction_name) 540 GNUNET_PQ_reconnect_if_down (pg->conn); 541 if (pg->init) 542 return GNUNET_OK; 543 return prepare_statements (pg); 544 } 545 546 547 /** 548 * Do a pre-flight check that we are not in an uncommitted transaction. 549 * If we are, try to commit the previous transaction and output a warning. 550 * Does not return anything, as we will continue regardless of the outcome. 551 * 552 * @param cls the `struct PostgresClosure` with the plugin-specific state 553 * @return #GNUNET_OK if everything is fine 554 * #GNUNET_NO if a transaction was rolled back 555 * #GNUNET_SYSERR on hard errors 556 */ 557 static enum GNUNET_GenericReturnValue 558 postgres_preflight (void *cls) 559 { 560 struct PostgresClosure *pg = cls; 561 struct GNUNET_PQ_ExecuteStatement es[] = { 562 GNUNET_PQ_make_execute ("ROLLBACK"), 563 GNUNET_PQ_EXECUTE_STATEMENT_END 564 }; 565 566 if (! pg->init) 567 { 568 if (GNUNET_OK != 569 internal_setup (pg)) 570 return GNUNET_SYSERR; 571 } 572 if (NULL == pg->transaction_name) 573 return GNUNET_OK; /* all good */ 574 if (GNUNET_OK == 575 GNUNET_PQ_exec_statements (pg->conn, 576 es)) 577 { 578 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 579 "BUG: Preflight check rolled back transaction `%s'!\n", 580 pg->transaction_name); 581 } 582 else 583 { 584 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 585 "BUG: Preflight check failed to rollback transaction `%s'!\n", 586 pg->transaction_name); 587 } 588 pg->transaction_name = NULL; 589 return GNUNET_NO; 590 } 591 592 593 /** 594 * Start a transaction. 595 * 596 * @param cls the `struct PostgresClosure` with the plugin-specific state 597 * @param name unique name identifying the transaction (for debugging), 598 * must point to a constant 599 * @return #GNUNET_OK on success 600 */ 601 static enum GNUNET_GenericReturnValue 602 begin_transaction (void *cls, 603 const char *name) 604 { 605 struct PostgresClosure *pg = cls; 606 struct GNUNET_PQ_ExecuteStatement es[] = { 607 GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"), 608 GNUNET_PQ_EXECUTE_STATEMENT_END 609 }; 610 611 check_connection (pg); 612 GNUNET_break (GNUNET_OK == 613 postgres_preflight (pg)); 614 pg->transaction_name = name; 615 if (GNUNET_OK != 616 GNUNET_PQ_exec_statements (pg->conn, 617 es)) 618 { 619 TALER_LOG_ERROR ("Failed to start transaction\n"); 620 GNUNET_break (0); 621 return GNUNET_SYSERR; 622 } 623 return GNUNET_OK; 624 } 625 626 627 /** 628 * Roll back the current transaction of a database connection. 629 * 630 * @param cls the `struct PostgresClosure` with the plugin-specific state 631 * @return #GNUNET_OK on success 632 */ 633 static void 634 rollback (void *cls) 635 { 636 struct PostgresClosure *pg = cls; 637 struct GNUNET_PQ_ExecuteStatement es[] = { 638 GNUNET_PQ_make_execute ("ROLLBACK"), 639 GNUNET_PQ_EXECUTE_STATEMENT_END 640 }; 641 642 if (GNUNET_OK != 643 GNUNET_PQ_exec_statements (pg->conn, 644 es)) 645 { 646 TALER_LOG_ERROR ("Failed to rollback transaction\n"); 647 GNUNET_break (0); 648 } 649 pg->transaction_name = NULL; 650 } 651 652 653 /** 654 * Commit the current transaction of a database connection. 655 * 656 * @param cls the `struct PostgresClosure` with the plugin-specific state 657 * @return transaction status code 658 */ 659 static enum GNUNET_DB_QueryStatus 660 commit_transaction (void *cls) 661 { 662 struct PostgresClosure *pg = cls; 663 enum GNUNET_DB_QueryStatus qs; 664 struct GNUNET_PQ_QueryParam no_params[] = { 665 GNUNET_PQ_query_param_end 666 }; 667 668 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 669 "do_commit", 670 no_params); 671 pg->transaction_name = NULL; 672 return qs; 673 } 674 675 676 /** 677 * Register callback to be invoked on events of type @a es. 678 * 679 * @param cls database context to use 680 * @param es specification of the event to listen for 681 * @param timeout how long to wait for the event 682 * @param cb function to call when the event happens, possibly 683 * multiple times (until cancel is invoked) 684 * @param cb_cls closure for @a cb 685 * @return handle useful to cancel the listener 686 */ 687 static struct GNUNET_DB_EventHandler * 688 postgres_event_listen (void *cls, 689 const struct GNUNET_DB_EventHeaderP *es, 690 struct GNUNET_TIME_Relative timeout, 691 GNUNET_DB_EventCallback cb, 692 void *cb_cls) 693 { 694 struct PostgresClosure *pg = cls; 695 696 return GNUNET_PQ_event_listen (pg->conn, 697 es, 698 timeout, 699 cb, 700 cb_cls); 701 } 702 703 704 /** 705 * Stop notifications. 706 * 707 * @param eh handle to unregister. 708 */ 709 static void 710 postgres_event_listen_cancel (struct GNUNET_DB_EventHandler *eh) 711 { 712 GNUNET_PQ_event_listen_cancel (eh); 713 } 714 715 716 /** 717 * Notify all that listen on @a es of an event. 718 * 719 * @param cls database context to use 720 * @param es specification of the event to generate 721 * @param extra additional event data provided 722 * @param extra_size number of bytes in @a extra 723 */ 724 static void 725 postgres_event_notify (void *cls, 726 const struct GNUNET_DB_EventHeaderP *es, 727 const void *extra, 728 size_t extra_size) 729 { 730 struct PostgresClosure *pg = cls; 731 732 return GNUNET_PQ_event_notify (pg->conn, 733 es, 734 extra, 735 extra_size); 736 } 737 738 739 /** 740 * Function called to perform "garbage collection" on the 741 * database, expiring records we no longer require. Deletes 742 * all user records that are not paid up (and by cascade deletes 743 * the associated recovery documents). Also deletes expired 744 * truth and financial records older than @a fin_expire. 745 * 746 * @param cls closure 747 * @param expire_backups backups older than the given time stamp should be garbage collected 748 * @param expire_pending_payments payments still pending from since before 749 * this value should be garbage collected 750 * @return transaction status 751 */ 752 static enum GNUNET_DB_QueryStatus 753 postgres_gc (void *cls, 754 struct GNUNET_TIME_Absolute expire_backups, 755 struct GNUNET_TIME_Absolute expire_pending_payments) 756 { 757 struct PostgresClosure *pg = cls; 758 struct GNUNET_PQ_QueryParam params[] = { 759 GNUNET_PQ_query_param_absolute_time (&expire_backups), 760 GNUNET_PQ_query_param_end 761 }; 762 struct GNUNET_PQ_QueryParam params2[] = { 763 GNUNET_PQ_query_param_absolute_time (&expire_pending_payments), 764 GNUNET_PQ_query_param_end 765 }; 766 enum GNUNET_DB_QueryStatus qs; 767 768 check_connection (pg); 769 GNUNET_break (GNUNET_OK == 770 postgres_preflight (pg)); 771 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 772 "gc_accounts", 773 params); 774 if (qs < 0) 775 return qs; 776 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 777 "gc_recdoc_pending_payments", 778 params2); 779 } 780 781 782 /** 783 * Store encrypted recovery document. 784 * 785 * @param cls closure 786 * @param account_pub public key of the user's account 787 * @param account_sig signature affirming storage request 788 * @param recovery_data_hash hash of @a data 789 * @param recovery_data contains encrypted_recovery_document 790 * @param recovery_data_size size of data blob 791 * @param payment_secret identifier for the payment, used to later charge on uploads 792 * @param[out] version set to the version assigned to the document by the database 793 * @return transaction status, 0 if upload could not be finished because @a payment_secret 794 * did not have enough upload left; HARD error if @a payment_secret is unknown, ... 795 */ 796 static enum ANASTASIS_DB_StoreStatus 797 postgres_store_recovery_document ( 798 void *cls, 799 const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, 800 const struct ANASTASIS_AccountSignatureP *account_sig, 801 const struct GNUNET_HashCode *recovery_data_hash, 802 const void *recovery_data, 803 size_t recovery_data_size, 804 const void *recovery_meta_data, 805 size_t recovery_meta_data_size, 806 const struct ANASTASIS_PaymentSecretP *payment_secret, 807 uint32_t *version) 808 { 809 struct PostgresClosure *pg = cls; 810 enum GNUNET_DB_QueryStatus qs; 811 812 check_connection (pg); 813 GNUNET_break (GNUNET_OK == 814 postgres_preflight (pg)); 815 for (unsigned int retry = 0; retry<MAX_RETRIES; retry++) 816 { 817 if (GNUNET_OK != 818 begin_transaction (pg, 819 "store_recovery_document")) 820 { 821 GNUNET_break (0); 822 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 823 } 824 /* get the current version and hash of the latest recovery document 825 for this account */ 826 { 827 struct GNUNET_HashCode dh; 828 struct GNUNET_PQ_QueryParam params[] = { 829 GNUNET_PQ_query_param_auto_from_type (account_pub), 830 GNUNET_PQ_query_param_end 831 }; 832 struct GNUNET_PQ_ResultSpec rs[] = { 833 GNUNET_PQ_result_spec_uint32 ("version", 834 version), 835 GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash", 836 &dh), 837 GNUNET_PQ_result_spec_end 838 }; 839 840 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 841 "latest_recovery_version_select", 842 params, 843 rs); 844 switch (qs) 845 { 846 case GNUNET_DB_STATUS_HARD_ERROR: 847 GNUNET_break (0); 848 rollback (pg); 849 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 850 case GNUNET_DB_STATUS_SOFT_ERROR: 851 goto retry; 852 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 853 *version = 1; 854 break; 855 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 856 /* had an existing recovery_data, is it identical? */ 857 if (0 == GNUNET_memcmp (&dh, 858 recovery_data_hash)) 859 { 860 /* Yes. Previous identical recovery data exists */ 861 rollback (pg); 862 return ANASTASIS_DB_STORE_STATUS_NO_RESULTS; 863 } 864 (*version)++; 865 break; 866 default: 867 GNUNET_break (0); 868 rollback (pg); 869 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 870 } 871 } 872 873 /* First, check if account exists */ 874 { 875 struct GNUNET_PQ_QueryParam params[] = { 876 GNUNET_PQ_query_param_auto_from_type (account_pub), 877 GNUNET_PQ_query_param_end 878 }; 879 struct GNUNET_PQ_ResultSpec rs[] = { 880 GNUNET_PQ_result_spec_end 881 }; 882 883 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 884 "user_select", 885 params, 886 rs); 887 } 888 switch (qs) 889 { 890 case GNUNET_DB_STATUS_HARD_ERROR: 891 rollback (pg); 892 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 893 case GNUNET_DB_STATUS_SOFT_ERROR: 894 goto retry; 895 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 896 rollback (pg); 897 return ANASTASIS_DB_STORE_STATUS_PAYMENT_REQUIRED; 898 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 899 /* handle interesting case below */ 900 break; 901 } 902 903 { 904 uint32_t postcounter; 905 906 /* lookup if the user has enough uploads left and decrement */ 907 { 908 struct GNUNET_PQ_QueryParam params[] = { 909 GNUNET_PQ_query_param_auto_from_type (account_pub), 910 GNUNET_PQ_query_param_auto_from_type (payment_secret), 911 GNUNET_PQ_query_param_end 912 }; 913 struct GNUNET_PQ_ResultSpec rs[] = { 914 GNUNET_PQ_result_spec_uint32 ("post_counter", 915 &postcounter), 916 GNUNET_PQ_result_spec_end 917 }; 918 919 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 920 "postcounter_select", 921 params, 922 rs); 923 switch (qs) 924 { 925 case GNUNET_DB_STATUS_HARD_ERROR: 926 rollback (pg); 927 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 928 case GNUNET_DB_STATUS_SOFT_ERROR: 929 goto retry; 930 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 931 rollback (pg); 932 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 933 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 934 break; 935 } 936 } 937 938 if (0 == postcounter) 939 { 940 rollback (pg); 941 return ANASTASIS_DB_STORE_STATUS_STORE_LIMIT_EXCEEDED; 942 } 943 /* Decrement the postcounter by one */ 944 postcounter--; 945 946 /* Update the postcounter in the Database */ 947 { 948 struct GNUNET_PQ_QueryParam params[] = { 949 GNUNET_PQ_query_param_uint32 (&postcounter), 950 GNUNET_PQ_query_param_auto_from_type (account_pub), 951 GNUNET_PQ_query_param_auto_from_type (payment_secret), 952 GNUNET_PQ_query_param_end 953 }; 954 955 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 956 "postcounter_update", 957 params); 958 switch (qs) 959 { 960 case GNUNET_DB_STATUS_HARD_ERROR: 961 GNUNET_break (0); 962 rollback (pg); 963 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 964 case GNUNET_DB_STATUS_SOFT_ERROR: 965 goto retry; 966 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 967 GNUNET_break (0); 968 rollback (pg); 969 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 970 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 971 break; 972 default: 973 GNUNET_break (0); 974 rollback (pg); 975 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 976 } 977 } 978 } 979 980 /* finally, actually insert the recovery document */ 981 { 982 struct GNUNET_TIME_Timestamp now 983 = GNUNET_TIME_timestamp_get (); 984 struct GNUNET_PQ_QueryParam params[] = { 985 GNUNET_PQ_query_param_auto_from_type (account_pub), 986 GNUNET_PQ_query_param_uint32 (version), 987 GNUNET_PQ_query_param_auto_from_type (account_sig), 988 GNUNET_PQ_query_param_auto_from_type (recovery_data_hash), 989 GNUNET_PQ_query_param_fixed_size (recovery_data, 990 recovery_data_size), 991 GNUNET_PQ_query_param_fixed_size (recovery_meta_data, 992 recovery_meta_data_size), 993 GNUNET_PQ_query_param_timestamp (&now), 994 GNUNET_PQ_query_param_end 995 }; 996 997 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 998 "recovery_document_insert", 999 params); 1000 switch (qs) 1001 { 1002 case GNUNET_DB_STATUS_HARD_ERROR: 1003 rollback (pg); 1004 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 1005 case GNUNET_DB_STATUS_SOFT_ERROR: 1006 goto retry; 1007 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1008 GNUNET_break (0); 1009 rollback (pg); 1010 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 1011 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1012 qs = commit_transaction (pg); 1013 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 1014 goto retry; 1015 if (qs < 0) 1016 return ANASTASIS_DB_STORE_STATUS_HARD_ERROR; 1017 return ANASTASIS_DB_STORE_STATUS_SUCCESS; 1018 } 1019 } 1020 retry: 1021 rollback (pg); 1022 } 1023 return ANASTASIS_DB_STORE_STATUS_SOFT_ERROR; 1024 } 1025 1026 1027 /** 1028 * Increment account lifetime based on payment having been received. 1029 * Does nothing if the payment is not new. 1030 * 1031 * @param cls closure 1032 * @param account_pub which account received a payment 1033 * @param payment_identifier proof of payment, must be unique and match pending payment 1034 * @param lifetime for how long is the account now paid (increment) 1035 * @param[out] paid_until set to the end of the lifetime after the operation 1036 * @return transaction status 1037 */ 1038 static enum GNUNET_DB_QueryStatus 1039 postgres_increment_lifetime ( 1040 void *cls, 1041 const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, 1042 const struct ANASTASIS_PaymentSecretP *payment_identifier, 1043 struct GNUNET_TIME_Relative lifetime, 1044 struct GNUNET_TIME_Timestamp *paid_until) 1045 { 1046 struct PostgresClosure *pg = cls; 1047 enum GNUNET_DB_QueryStatus qs; 1048 1049 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1050 "Incrementing lifetime of account %s based on payment by %s\n", 1051 TALER_B2S (account_pub), 1052 GNUNET_TIME_relative2s (lifetime, 1053 true)); 1054 check_connection (pg); 1055 for (unsigned int retries = 0; retries<MAX_RETRIES; retries++) 1056 { 1057 if (GNUNET_OK != 1058 begin_transaction (pg, 1059 "increment lifetime")) 1060 { 1061 GNUNET_break (0); 1062 return GNUNET_DB_STATUS_HARD_ERROR; 1063 } 1064 1065 { 1066 struct GNUNET_PQ_QueryParam params[] = { 1067 GNUNET_PQ_query_param_auto_from_type (payment_identifier), 1068 GNUNET_PQ_query_param_auto_from_type (account_pub), 1069 GNUNET_PQ_query_param_end 1070 }; 1071 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1072 "recdoc_payment_done", 1073 params); 1074 switch (qs) 1075 { 1076 case GNUNET_DB_STATUS_HARD_ERROR: 1077 rollback (pg); 1078 *paid_until = GNUNET_TIME_UNIT_ZERO_TS; 1079 return qs; 1080 case GNUNET_DB_STATUS_SOFT_ERROR: 1081 goto retry; 1082 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1083 /* Payment not new or payment request unknown. */ 1084 /* continued below */ 1085 break; 1086 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1087 /* Payment just now marked as 'paid' */ 1088 /* continued below */ 1089 break; 1090 } 1091 } 1092 1093 { 1094 enum GNUNET_DB_QueryStatus qs2; 1095 struct GNUNET_PQ_QueryParam params[] = { 1096 GNUNET_PQ_query_param_auto_from_type (account_pub), 1097 GNUNET_PQ_query_param_end 1098 }; 1099 struct GNUNET_TIME_Timestamp expiration; 1100 struct GNUNET_PQ_ResultSpec rs[] = { 1101 GNUNET_PQ_result_spec_timestamp ("expiration_date", 1102 &expiration), 1103 GNUNET_PQ_result_spec_end 1104 }; 1105 1106 qs2 = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1107 "user_select", 1108 params, 1109 rs); 1110 switch (qs2) 1111 { 1112 case GNUNET_DB_STATUS_HARD_ERROR: 1113 rollback (pg); 1114 return qs2; 1115 case GNUNET_DB_STATUS_SOFT_ERROR: 1116 goto retry; 1117 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1118 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1119 { 1120 /* inconsistent, cannot have recdoc payment but no user!? */ 1121 GNUNET_break (0); 1122 rollback (pg); 1123 return GNUNET_DB_STATUS_HARD_ERROR; 1124 } 1125 else 1126 { 1127 /* user does not exist, create new one */ 1128 struct GNUNET_PQ_QueryParam iparams[] = { 1129 GNUNET_PQ_query_param_auto_from_type (account_pub), 1130 GNUNET_PQ_query_param_timestamp (&expiration), 1131 GNUNET_PQ_query_param_end 1132 }; 1133 1134 expiration = GNUNET_TIME_relative_to_timestamp (lifetime); 1135 GNUNET_break (! GNUNET_TIME_absolute_is_never (expiration.abs_time)); 1136 *paid_until = expiration; 1137 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1138 "Creating new account %s with initial lifetime of %s\n", 1139 TALER_B2S (account_pub), 1140 GNUNET_TIME_relative2s (lifetime, 1141 true)); 1142 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1143 "user_insert", 1144 iparams); 1145 } 1146 break; 1147 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1148 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1149 { 1150 /* existing rec doc payment (payment replay), return 1151 existing expiration */ 1152 *paid_until = expiration; 1153 rollback (pg); 1154 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1155 "Payment existed, lifetime of account %s unchanged at %s\n", 1156 TALER_B2S (account_pub), 1157 GNUNET_TIME_timestamp2s (*paid_until)); 1158 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 1159 } 1160 else 1161 { 1162 /* user exists, payment is new, update expiration_date */ 1163 struct GNUNET_PQ_QueryParam iparams[] = { 1164 GNUNET_PQ_query_param_timestamp (&expiration), 1165 GNUNET_PQ_query_param_auto_from_type (account_pub), 1166 GNUNET_PQ_query_param_end 1167 }; 1168 1169 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1170 "Incrementing lifetime of account %s by %s\n", 1171 TALER_B2S (account_pub), 1172 GNUNET_TIME_relative2s (lifetime, 1173 true)); 1174 expiration 1175 = GNUNET_TIME_absolute_to_timestamp ( 1176 GNUNET_TIME_absolute_add (expiration.abs_time, 1177 lifetime)); 1178 GNUNET_break (! GNUNET_TIME_absolute_is_never ( 1179 expiration.abs_time)); 1180 *paid_until = expiration; 1181 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1182 "user_update", 1183 iparams); 1184 } 1185 break; 1186 } 1187 } 1188 1189 switch (qs) 1190 { 1191 case GNUNET_DB_STATUS_HARD_ERROR: 1192 rollback (pg); 1193 return qs; 1194 case GNUNET_DB_STATUS_SOFT_ERROR: 1195 goto retry; 1196 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1197 GNUNET_break (0); 1198 rollback (pg); 1199 return GNUNET_DB_STATUS_HARD_ERROR; 1200 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1201 break; 1202 } 1203 qs = commit_transaction (pg); 1204 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 1205 goto retry; 1206 if (qs < 0) 1207 return GNUNET_DB_STATUS_HARD_ERROR; 1208 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1209 "Incremented lifetime of account %s to %s\n", 1210 TALER_B2S (account_pub), 1211 GNUNET_TIME_timestamp2s (*paid_until)); 1212 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1213 retry: 1214 rollback (pg); 1215 } 1216 return GNUNET_DB_STATUS_SOFT_ERROR; 1217 } 1218 1219 1220 /** 1221 * Update account lifetime to the maximum of the current 1222 * value and @a eol. 1223 * 1224 * @param cls closure 1225 * @param account_pub which account received a payment 1226 * @param payment_identifier proof of payment, must be unique and match pending payment 1227 * @param eol for how long is the account now paid (absolute) 1228 * @return transaction status 1229 */ 1230 static enum GNUNET_DB_QueryStatus 1231 postgres_update_lifetime ( 1232 void *cls, 1233 const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, 1234 const struct ANASTASIS_PaymentSecretP *payment_identifier, 1235 struct GNUNET_TIME_Timestamp eol) 1236 { 1237 struct PostgresClosure *pg = cls; 1238 enum GNUNET_DB_QueryStatus qs; 1239 1240 check_connection (pg); 1241 for (unsigned int retries = 0; retries<MAX_RETRIES; retries++) 1242 { 1243 if (GNUNET_OK != 1244 begin_transaction (pg, 1245 "update lifetime")) 1246 { 1247 GNUNET_break (0); 1248 return GNUNET_DB_STATUS_HARD_ERROR; 1249 } 1250 1251 { 1252 struct GNUNET_PQ_QueryParam params[] = { 1253 GNUNET_PQ_query_param_auto_from_type (payment_identifier), 1254 GNUNET_PQ_query_param_auto_from_type (account_pub), 1255 GNUNET_PQ_query_param_end 1256 }; 1257 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1258 "recdoc_payment_done", 1259 params); 1260 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 1261 goto retry; 1262 if (0 >= qs) 1263 { 1264 /* same payment made before, or unknown, or error 1265 => no further action! */ 1266 rollback (pg); 1267 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1268 "Payment existed, lifetime of account %s unchanged\n", 1269 TALER_B2S (account_pub)); 1270 return qs; 1271 } 1272 } 1273 1274 { 1275 struct GNUNET_PQ_QueryParam params[] = { 1276 GNUNET_PQ_query_param_auto_from_type (account_pub), 1277 GNUNET_PQ_query_param_end 1278 }; 1279 struct GNUNET_TIME_Timestamp expiration; 1280 struct GNUNET_PQ_ResultSpec rs[] = { 1281 GNUNET_PQ_result_spec_timestamp ("expiration_date", 1282 &expiration), 1283 GNUNET_PQ_result_spec_end 1284 }; 1285 1286 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1287 "user_select", 1288 params, 1289 rs); 1290 switch (qs) 1291 { 1292 case GNUNET_DB_STATUS_HARD_ERROR: 1293 rollback (pg); 1294 return qs; 1295 case GNUNET_DB_STATUS_SOFT_ERROR: 1296 goto retry; 1297 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1298 { 1299 /* user does not exist, create new one */ 1300 struct GNUNET_PQ_QueryParam iparams[] = { 1301 GNUNET_PQ_query_param_auto_from_type (account_pub), 1302 GNUNET_PQ_query_param_timestamp (&eol), 1303 GNUNET_PQ_query_param_end 1304 }; 1305 1306 GNUNET_break (! GNUNET_TIME_absolute_is_never (eol.abs_time)); 1307 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1308 "user_insert", 1309 iparams); 1310 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1311 "Created new account %s with expiration %s\n", 1312 TALER_B2S (account_pub), 1313 GNUNET_TIME_timestamp2s (eol)); 1314 } 1315 break; 1316 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1317 { 1318 /* user exists, update expiration_date */ 1319 struct GNUNET_PQ_QueryParam iparams[] = { 1320 GNUNET_PQ_query_param_timestamp (&expiration), 1321 GNUNET_PQ_query_param_auto_from_type (account_pub), 1322 GNUNET_PQ_query_param_end 1323 }; 1324 1325 expiration = GNUNET_TIME_timestamp_max (expiration, 1326 eol); 1327 GNUNET_break (! GNUNET_TIME_absolute_is_never (expiration.abs_time)); 1328 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1329 "user_update", 1330 iparams); 1331 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1332 "Updated account %s to new expiration %s\n", 1333 TALER_B2S (account_pub), 1334 GNUNET_TIME_timestamp2s (expiration)); 1335 } 1336 break; 1337 } 1338 } 1339 1340 switch (qs) 1341 { 1342 case GNUNET_DB_STATUS_HARD_ERROR: 1343 rollback (pg); 1344 return qs; 1345 case GNUNET_DB_STATUS_SOFT_ERROR: 1346 goto retry; 1347 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1348 GNUNET_break (0); 1349 rollback (pg); 1350 return GNUNET_DB_STATUS_HARD_ERROR; 1351 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1352 break; 1353 } 1354 qs = commit_transaction (pg); 1355 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 1356 goto retry; 1357 if (qs < 0) 1358 return GNUNET_DB_STATUS_HARD_ERROR; 1359 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1360 retry: 1361 rollback (pg); 1362 } 1363 return GNUNET_DB_STATUS_SOFT_ERROR; 1364 } 1365 1366 1367 /** 1368 * Store payment. Used to begin a payment, not indicative 1369 * that the payment actually was made. (That is done 1370 * when we increment the account's lifetime.) 1371 * 1372 * @param cls closure 1373 * @param account_pub anastasis's public key 1374 * @param post_counter how many uploads does @a amount pay for 1375 * @param payment_secret payment secret which the user must provide with every upload 1376 * @param amount how much we asked for 1377 * @return transaction status 1378 */ 1379 static enum GNUNET_DB_QueryStatus 1380 postgres_record_recdoc_payment ( 1381 void *cls, 1382 const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, 1383 uint32_t post_counter, 1384 const struct ANASTASIS_PaymentSecretP *payment_secret, 1385 const struct TALER_Amount *amount) 1386 { 1387 struct PostgresClosure *pg = cls; 1388 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 1389 struct GNUNET_TIME_Timestamp expiration; 1390 struct GNUNET_PQ_QueryParam params[] = { 1391 GNUNET_PQ_query_param_auto_from_type (account_pub), 1392 GNUNET_PQ_query_param_uint32 (&post_counter), 1393 TALER_PQ_query_param_amount (pg->conn, 1394 amount), 1395 GNUNET_PQ_query_param_auto_from_type (payment_secret), 1396 GNUNET_PQ_query_param_timestamp (&now), 1397 GNUNET_PQ_query_param_end 1398 }; 1399 enum GNUNET_DB_QueryStatus qs; 1400 1401 check_connection (pg); 1402 GNUNET_break (GNUNET_OK == 1403 postgres_preflight (pg)); 1404 1405 /* because of constraint at user_id, first we have to verify 1406 if user exists, and if not, create one */ 1407 { 1408 struct GNUNET_PQ_QueryParam iparams[] = { 1409 GNUNET_PQ_query_param_auto_from_type (account_pub), 1410 GNUNET_PQ_query_param_end 1411 }; 1412 struct GNUNET_PQ_ResultSpec rs[] = { 1413 GNUNET_PQ_result_spec_timestamp ("expiration_date", 1414 &expiration), 1415 GNUNET_PQ_result_spec_end 1416 }; 1417 1418 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1419 "user_select", 1420 iparams, 1421 rs); 1422 } 1423 switch (qs) 1424 { 1425 case GNUNET_DB_STATUS_HARD_ERROR: 1426 return qs; 1427 case GNUNET_DB_STATUS_SOFT_ERROR: 1428 GNUNET_break (0); 1429 return GNUNET_DB_STATUS_HARD_ERROR; 1430 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1431 { 1432 /* create new user with short lifetime */ 1433 struct GNUNET_TIME_Timestamp exp 1434 = GNUNET_TIME_relative_to_timestamp (TRANSIENT_LIFETIME); 1435 struct GNUNET_PQ_QueryParam iparams[] = { 1436 GNUNET_PQ_query_param_auto_from_type (account_pub), 1437 GNUNET_PQ_query_param_timestamp (&exp), 1438 GNUNET_PQ_query_param_end 1439 }; 1440 1441 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 1442 "user_insert", 1443 iparams); 1444 switch (qs) 1445 { 1446 case GNUNET_DB_STATUS_HARD_ERROR: 1447 return GNUNET_DB_STATUS_HARD_ERROR; 1448 case GNUNET_DB_STATUS_SOFT_ERROR: 1449 GNUNET_break (0); 1450 return GNUNET_DB_STATUS_HARD_ERROR; 1451 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1452 GNUNET_break (0); 1453 return GNUNET_DB_STATUS_HARD_ERROR; 1454 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1455 /* successful, continue below */ 1456 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1457 "Created new account %s with transient life until %s\n", 1458 TALER_B2S (account_pub), 1459 GNUNET_TIME_timestamp2s (exp)); 1460 break; 1461 } 1462 } 1463 /* continue below */ 1464 break; 1465 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1466 /* handle case below */ 1467 break; 1468 } 1469 1470 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 1471 "recdoc_payment_insert", 1472 params); 1473 } 1474 1475 1476 /** 1477 * Record truth upload payment was made. 1478 * 1479 * @param cls closure 1480 * @param uuid the truth's UUID 1481 * @param amount the amount that was paid 1482 * @param duration how long is the truth paid for 1483 * @return transaction status 1484 */ 1485 static enum GNUNET_DB_QueryStatus 1486 postgres_record_truth_upload_payment ( 1487 void *cls, 1488 const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid, 1489 const struct TALER_Amount *amount, 1490 struct GNUNET_TIME_Relative duration) 1491 { 1492 struct PostgresClosure *pg = cls; 1493 struct GNUNET_TIME_Timestamp exp = GNUNET_TIME_relative_to_timestamp ( 1494 duration); 1495 struct GNUNET_PQ_QueryParam params[] = { 1496 GNUNET_PQ_query_param_auto_from_type (uuid), 1497 TALER_PQ_query_param_amount (pg->conn, 1498 amount), 1499 GNUNET_PQ_query_param_timestamp (&exp), 1500 GNUNET_PQ_query_param_end 1501 }; 1502 1503 check_connection (pg); 1504 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 1505 "truth_payment_insert", 1506 params); 1507 } 1508 1509 1510 /** 1511 * Inquire whether truth upload payment was made. 1512 * 1513 * @param cls closure 1514 * @param uuid the truth's UUID 1515 * @param[out] paid_until set for how long this truth is paid for 1516 * @return transaction status 1517 */ 1518 static enum GNUNET_DB_QueryStatus 1519 postgres_check_truth_upload_paid ( 1520 void *cls, 1521 const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid, 1522 struct GNUNET_TIME_Timestamp *paid_until) 1523 { 1524 struct PostgresClosure *pg = cls; 1525 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 1526 struct GNUNET_PQ_QueryParam params[] = { 1527 GNUNET_PQ_query_param_auto_from_type (uuid), 1528 GNUNET_PQ_query_param_timestamp (&now), 1529 GNUNET_PQ_query_param_end 1530 }; 1531 struct GNUNET_PQ_ResultSpec rs[] = { 1532 GNUNET_PQ_result_spec_timestamp ("expiration", 1533 paid_until), 1534 GNUNET_PQ_result_spec_end 1535 }; 1536 1537 check_connection (pg); 1538 return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1539 "truth_payment_select", 1540 params, 1541 rs); 1542 } 1543 1544 1545 /** 1546 * Store payment for challenge. 1547 * 1548 * @param cls closure 1549 * @param truth_uuid identifier of the challenge to pay 1550 * @param payment_secret payment secret which the user must provide with every upload 1551 * @param amount how much we asked for 1552 * @return transaction status 1553 */ 1554 static enum GNUNET_DB_QueryStatus 1555 postgres_record_challenge_payment ( 1556 void *cls, 1557 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 1558 const struct ANASTASIS_PaymentSecretP *payment_secret, 1559 const struct TALER_Amount *amount) 1560 { 1561 struct PostgresClosure *pg = cls; 1562 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 1563 struct GNUNET_PQ_QueryParam params[] = { 1564 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 1565 TALER_PQ_query_param_amount (pg->conn, 1566 amount), 1567 GNUNET_PQ_query_param_auto_from_type (payment_secret), 1568 GNUNET_PQ_query_param_timestamp (&now), 1569 GNUNET_PQ_query_param_end 1570 }; 1571 1572 check_connection (pg); 1573 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 1574 "challenge_payment_insert", 1575 params); 1576 } 1577 1578 1579 /** 1580 * Store refund granted for challenge. 1581 * 1582 * @param cls closure 1583 * @param truth_uuid identifier of the challenge to refund 1584 * @param payment_secret payment secret which the user must provide with every upload 1585 * @return transaction status 1586 */ 1587 static enum GNUNET_DB_QueryStatus 1588 postgres_record_challenge_refund ( 1589 void *cls, 1590 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 1591 const struct ANASTASIS_PaymentSecretP *payment_secret) 1592 { 1593 struct PostgresClosure *pg = cls; 1594 struct GNUNET_PQ_QueryParam params[] = { 1595 GNUNET_PQ_query_param_auto_from_type (payment_secret), 1596 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 1597 GNUNET_PQ_query_param_end 1598 }; 1599 1600 check_connection (pg); 1601 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 1602 "challenge_refund_update", 1603 params); 1604 } 1605 1606 1607 /** 1608 * Store inbound IBAN payment made for authentication. 1609 * 1610 * @param cls closure 1611 * @param wire_reference unique identifier inside LibEuFin/Nexus 1612 * @param wire_subject subject of the wire transfer 1613 * @param amount how much was transferred 1614 * @param debit_account account that was debited 1615 * @param credit_account Anastasis operator account credited 1616 * @param execution_date when was the transfer made 1617 * @return transaction status 1618 */ 1619 static enum GNUNET_DB_QueryStatus 1620 postgres_record_auth_iban_payment ( 1621 void *cls, 1622 uint64_t wire_reference, 1623 const char *wire_subject, 1624 const struct TALER_Amount *amount, 1625 const char *debit_account, 1626 const char *credit_account, 1627 struct GNUNET_TIME_Timestamp execution_date) 1628 { 1629 struct PostgresClosure *pg = cls; 1630 struct GNUNET_PQ_QueryParam params[] = { 1631 GNUNET_PQ_query_param_uint64 (&wire_reference), 1632 GNUNET_PQ_query_param_string (wire_subject), 1633 TALER_PQ_query_param_amount (pg->conn, 1634 amount), 1635 GNUNET_PQ_query_param_string (debit_account), 1636 GNUNET_PQ_query_param_string (credit_account), 1637 GNUNET_PQ_query_param_timestamp (&execution_date), 1638 GNUNET_PQ_query_param_end 1639 }; 1640 1641 check_connection (pg); 1642 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 1643 "store_auth_iban_payment_details", 1644 params); 1645 } 1646 1647 1648 /** 1649 * Closure for #test_auth_cb. 1650 */ 1651 struct TestIbanContext 1652 { 1653 1654 /** 1655 * Plugin context. 1656 */ 1657 struct PostgresClosure *pg; 1658 1659 /** 1660 * Function to call on each wire transfer found. 1661 */ 1662 ANASTASIS_DB_AuthIbanTransfercheck cb; 1663 1664 /** 1665 * Closure for @a cb. 1666 */ 1667 void *cb_cls; 1668 1669 /** 1670 * Value to return. 1671 */ 1672 enum GNUNET_DB_QueryStatus qs; 1673 }; 1674 1675 1676 /** 1677 * Helper function for #postgres_test_auth_iban_payment(). 1678 * To be called with the results of a SELECT statement 1679 * that has returned @a num_results results. 1680 * 1681 * @param cls closure of type `struct TestIbanContext *` 1682 * @param result the postgres result 1683 * @param num_results the number of results in @a result 1684 */ 1685 static void 1686 test_auth_cb (void *cls, 1687 PGresult *result, 1688 unsigned int num_results) 1689 { 1690 struct TestIbanContext *tic = cls; 1691 struct PostgresClosure *pg = tic->pg; 1692 1693 for (unsigned int i = 0; i<num_results; i++) 1694 { 1695 struct TALER_Amount credit; 1696 char *wire_subject; 1697 struct GNUNET_PQ_ResultSpec rs[] = { 1698 TALER_PQ_result_spec_amount ("credit", 1699 pg->currency, 1700 &credit), 1701 GNUNET_PQ_result_spec_string ("wire_subject", 1702 &wire_subject), 1703 GNUNET_PQ_result_spec_end 1704 }; 1705 1706 if (GNUNET_OK != 1707 GNUNET_PQ_extract_result (result, 1708 rs, 1709 i)) 1710 { 1711 GNUNET_break (0); 1712 tic->qs = GNUNET_DB_STATUS_HARD_ERROR; 1713 return; 1714 } 1715 if (tic->cb (tic->cb_cls, 1716 &credit, 1717 wire_subject)) 1718 { 1719 GNUNET_free (wire_subject); 1720 tic->qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1721 return; 1722 } 1723 GNUNET_free (wire_subject); 1724 } 1725 } 1726 1727 1728 /** 1729 * Function to check if we are aware of a wire transfer 1730 * that satisfies the IBAN plugin's authentication check. 1731 * 1732 * @param cls closure 1733 * @param debit_account which debit account to check 1734 * @param earliest_date earliest date to check 1735 * @param cb function to call on all entries found 1736 * @param cb_cls closure for @a cb 1737 * @return transaction status, 1738 * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if @a cb 1739 * returned 'true' once 1740 * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if no 1741 * wire transfers existed for which @a cb returned true 1742 */ 1743 static enum GNUNET_DB_QueryStatus 1744 postgres_test_auth_iban_payment ( 1745 void *cls, 1746 const char *debit_account, 1747 struct GNUNET_TIME_Timestamp earliest_date, 1748 ANASTASIS_DB_AuthIbanTransfercheck cb, 1749 void *cb_cls) 1750 { 1751 struct PostgresClosure *pg = cls; 1752 struct TestIbanContext tic = { 1753 .cb = cb, 1754 .cb_cls = cb_cls, 1755 .pg = pg 1756 }; 1757 struct GNUNET_PQ_QueryParam params[] = { 1758 GNUNET_PQ_query_param_string (debit_account), 1759 GNUNET_PQ_query_param_timestamp (&earliest_date), 1760 GNUNET_PQ_query_param_end 1761 }; 1762 enum GNUNET_DB_QueryStatus qs; 1763 1764 check_connection (pg); 1765 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 1766 "test_auth_iban_payment", 1767 params, 1768 &test_auth_cb, 1769 &tic); 1770 if (qs < 0) 1771 return qs; 1772 return tic.qs; 1773 } 1774 1775 1776 /** 1777 * Function to check the last known IBAN payment. 1778 * 1779 * @param cls closure 1780 * @param credit_account which credit account to check 1781 * @param[out] last_row set to the last known row 1782 * @return transaction status, 1783 * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if @a cb 1784 * returned 'true' once 1785 * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if no 1786 * wire transfers existed for which @a cb returned true 1787 */ 1788 static enum GNUNET_DB_QueryStatus 1789 postgres_get_last_auth_iban_payment_row ( 1790 void *cls, 1791 const char *credit_account, 1792 uint64_t *last_row) 1793 { 1794 struct PostgresClosure *pg = cls; 1795 struct GNUNET_PQ_QueryParam params[] = { 1796 GNUNET_PQ_query_param_string (credit_account), 1797 GNUNET_PQ_query_param_end 1798 }; 1799 struct GNUNET_PQ_ResultSpec rs[] = { 1800 GNUNET_PQ_result_spec_uint64 ("wire_reference", 1801 last_row), 1802 GNUNET_PQ_result_spec_end 1803 }; 1804 1805 check_connection (pg); 1806 return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1807 "get_last_auth_iban_payment", 1808 params, 1809 rs); 1810 } 1811 1812 1813 /** 1814 * Check payment identifier. Used to check if a payment identifier given by 1815 * the user is valid (existing and paid). 1816 * 1817 * @param cls closure 1818 * @param payment_secret payment secret which the user must provide with every upload 1819 * @param truth_uuid which truth should we check the payment status of 1820 * @param[out] paid bool value to show if payment is paid 1821 * @return transaction status 1822 */ 1823 static enum GNUNET_DB_QueryStatus 1824 postgres_check_challenge_payment ( 1825 void *cls, 1826 const struct ANASTASIS_PaymentSecretP *payment_secret, 1827 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 1828 bool *paid) 1829 { 1830 struct PostgresClosure *pg = cls; 1831 uint8_t paid8; 1832 struct GNUNET_PQ_QueryParam params[] = { 1833 GNUNET_PQ_query_param_auto_from_type (payment_secret), 1834 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 1835 GNUNET_PQ_query_param_end 1836 }; 1837 enum GNUNET_DB_QueryStatus qs; 1838 struct GNUNET_PQ_ResultSpec rs[] = { 1839 GNUNET_PQ_result_spec_auto_from_type ("paid", 1840 &paid8), 1841 GNUNET_PQ_result_spec_end 1842 }; 1843 1844 check_connection (pg); 1845 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1846 "challenge_payment_select", 1847 params, 1848 rs); 1849 *paid = (0 != paid8); 1850 return qs; 1851 } 1852 1853 1854 /** 1855 * Check payment identifier. Used to check if a payment identifier given by 1856 * the user is valid (existing and paid). 1857 * 1858 * @param cls closure 1859 * @param payment_secret payment secret which the user must provide with every upload 1860 * @param[out] paid bool value to show if payment is paid 1861 * @param[out] valid_counter bool value to show if post_counter is > 0 1862 * @return transaction status 1863 */ 1864 static enum GNUNET_DB_QueryStatus 1865 postgres_check_payment_identifier ( 1866 void *cls, 1867 const struct ANASTASIS_PaymentSecretP *payment_secret, 1868 bool *paid, 1869 bool *valid_counter) 1870 { 1871 struct PostgresClosure *pg = cls; 1872 uint32_t counter; 1873 uint8_t paid8; 1874 struct GNUNET_PQ_QueryParam params[] = { 1875 GNUNET_PQ_query_param_auto_from_type (payment_secret), 1876 GNUNET_PQ_query_param_end 1877 }; 1878 struct GNUNET_PQ_ResultSpec rs[] = { 1879 GNUNET_PQ_result_spec_auto_from_type ("paid", 1880 &paid8), 1881 GNUNET_PQ_result_spec_uint32 ("post_counter", 1882 &counter), 1883 GNUNET_PQ_result_spec_end 1884 }; 1885 enum GNUNET_DB_QueryStatus qs; 1886 1887 check_connection (pg); 1888 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1889 "recdoc_payment_select", 1890 params, 1891 rs); 1892 1893 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 1894 { 1895 if (counter > 0) 1896 *valid_counter = true; 1897 else 1898 *valid_counter = false; 1899 *paid = (0 != paid8); 1900 } 1901 return qs; 1902 } 1903 1904 1905 /** 1906 * Upload Truth, which contains the Truth and the KeyShare. 1907 * 1908 * @param cls closure 1909 * @param truth_uuid the identifier for the Truth 1910 * @param key_share_data contains information of an EncryptedKeyShare 1911 * @param mime_type presumed mime type of data in @a encrypted_truth 1912 * @param encrypted_truth contains the encrypted Truth which includes the ground truth i.e. H(challenge answer), phonenumber, SMS 1913 * @param encrypted_truth_size the size of the Truth 1914 * @param method name of method 1915 * @param truth_expiration time till the according data will be stored 1916 * @return transaction status 1917 */ 1918 static enum GNUNET_DB_QueryStatus 1919 postgres_store_truth ( 1920 void *cls, 1921 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 1922 const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *key_share_data, 1923 const char *mime_type, 1924 const void *encrypted_truth, 1925 size_t encrypted_truth_size, 1926 const char *method, 1927 struct GNUNET_TIME_Relative truth_expiration) 1928 { 1929 struct PostgresClosure *pg = cls; 1930 struct GNUNET_TIME_Timestamp expiration; 1931 struct GNUNET_PQ_QueryParam params[] = { 1932 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 1933 GNUNET_PQ_query_param_auto_from_type (key_share_data), 1934 GNUNET_PQ_query_param_string (method), 1935 GNUNET_PQ_query_param_fixed_size (encrypted_truth, 1936 encrypted_truth_size), 1937 GNUNET_PQ_query_param_string (mime_type), 1938 GNUNET_PQ_query_param_timestamp (&expiration), 1939 GNUNET_PQ_query_param_end 1940 }; 1941 1942 1943 expiration = GNUNET_TIME_relative_to_timestamp (truth_expiration); 1944 check_connection (pg); 1945 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 1946 "truth_insert", 1947 params); 1948 } 1949 1950 1951 /** 1952 * Get the encrypted truth to validate the challenge response 1953 * 1954 * @param cls closure 1955 * @param truth_uuid the identifier for the Truth 1956 * @param[out] truth contains the encrypted truth 1957 * @param[out] truth_size size of the encrypted truth 1958 * @param[out] truth_mime mime type of truth 1959 * @param[out] method type of the challenge 1960 * @return transaction status 1961 */ 1962 static enum GNUNET_DB_QueryStatus 1963 postgres_get_escrow_challenge ( 1964 void *cls, 1965 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 1966 void **truth, 1967 size_t *truth_size, 1968 char **truth_mime, 1969 char **method) 1970 { 1971 struct PostgresClosure *pg = cls; 1972 struct GNUNET_PQ_QueryParam params[] = { 1973 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 1974 GNUNET_PQ_query_param_end 1975 }; 1976 struct GNUNET_PQ_ResultSpec rs[] = { 1977 GNUNET_PQ_result_spec_variable_size ("encrypted_truth", 1978 truth, 1979 truth_size), 1980 GNUNET_PQ_result_spec_string ("truth_mime", 1981 truth_mime), 1982 GNUNET_PQ_result_spec_string ("method_name", 1983 method), 1984 GNUNET_PQ_result_spec_end 1985 }; 1986 1987 check_connection (pg); 1988 return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 1989 "truth_select", 1990 params, 1991 rs); 1992 } 1993 1994 1995 /** 1996 * Lookup (encrypted) key share by @a truth_uuid. 1997 * 1998 * @param cls closure 1999 * @param truth_uuid the identifier for the Truth 2000 * @param[out] key_share contains the encrypted Keyshare 2001 * @return transaction status 2002 */ 2003 static enum GNUNET_DB_QueryStatus 2004 postgres_get_key_share ( 2005 void *cls, 2006 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 2007 struct ANASTASIS_CRYPTO_EncryptedKeyShareP *key_share) 2008 { 2009 struct PostgresClosure *pg = cls; 2010 struct GNUNET_PQ_QueryParam params[] = { 2011 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2012 GNUNET_PQ_query_param_end 2013 }; 2014 struct GNUNET_PQ_ResultSpec rs[] = { 2015 GNUNET_PQ_result_spec_auto_from_type ("key_share_data", 2016 key_share), 2017 GNUNET_PQ_result_spec_end 2018 }; 2019 2020 check_connection (pg); 2021 return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 2022 "key_share_select", 2023 params, 2024 rs); 2025 } 2026 2027 2028 /** 2029 * Check if an account exists, and if so, return the 2030 * current @a recovery_document_hash. 2031 * 2032 * @param cls closure 2033 * @param account_pub account identifier 2034 * @param[out] paid_until until when is the account paid up? 2035 * @param[out] recovery_data_hash set to hash of @a recovery document 2036 * @param[out] version set to the recovery policy version 2037 * @return transaction status 2038 */ 2039 static enum ANASTASIS_DB_AccountStatus 2040 postgres_lookup_account ( 2041 void *cls, 2042 const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, 2043 struct GNUNET_TIME_Timestamp *paid_until, 2044 struct GNUNET_HashCode *recovery_data_hash, 2045 uint32_t *version) 2046 { 2047 struct PostgresClosure *pg = cls; 2048 struct GNUNET_PQ_QueryParam params[] = { 2049 GNUNET_PQ_query_param_auto_from_type (account_pub), 2050 GNUNET_PQ_query_param_end 2051 }; 2052 enum GNUNET_DB_QueryStatus qs; 2053 2054 check_connection (pg); 2055 GNUNET_break (GNUNET_OK == 2056 postgres_preflight (pg)); 2057 { 2058 struct GNUNET_PQ_ResultSpec rs[] = { 2059 GNUNET_PQ_result_spec_timestamp ("expiration_date", 2060 paid_until), 2061 GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash", 2062 recovery_data_hash), 2063 GNUNET_PQ_result_spec_uint32 ("version", 2064 version), 2065 GNUNET_PQ_result_spec_end 2066 }; 2067 2068 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 2069 "latest_recovery_version_select", 2070 params, 2071 rs); 2072 } 2073 switch (qs) 2074 { 2075 case GNUNET_DB_STATUS_HARD_ERROR: 2076 return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR; 2077 case GNUNET_DB_STATUS_SOFT_ERROR: 2078 GNUNET_break (0); 2079 return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR; 2080 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 2081 break; /* handle interesting case below */ 2082 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 2083 return ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED; 2084 } 2085 2086 /* check if account exists */ 2087 { 2088 struct GNUNET_PQ_ResultSpec rs[] = { 2089 GNUNET_PQ_result_spec_timestamp ("expiration_date", 2090 paid_until), 2091 GNUNET_PQ_result_spec_end 2092 }; 2093 2094 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 2095 "user_select", 2096 params, 2097 rs); 2098 } 2099 switch (qs) 2100 { 2101 case GNUNET_DB_STATUS_HARD_ERROR: 2102 return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR; 2103 case GNUNET_DB_STATUS_SOFT_ERROR: 2104 GNUNET_break (0); 2105 return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR; 2106 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 2107 /* indicates: no account */ 2108 return ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED; 2109 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 2110 /* indicates: no backup */ 2111 *version = UINT32_MAX; 2112 memset (recovery_data_hash, 2113 0, 2114 sizeof (*recovery_data_hash)); 2115 return ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS; 2116 default: 2117 GNUNET_break (0); 2118 return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR; 2119 } 2120 } 2121 2122 2123 /** 2124 * Fetch latest recovery document for user. 2125 * 2126 * @param cls closure 2127 * @param account_pub public key of the user's account 2128 * @param account_sig signature 2129 * @param recovery_data_hash hash of the current recovery data 2130 * @param data_size size of data blob 2131 * @param data blob which contains the recovery document 2132 * @param[out] version set to the version number of the policy being returned 2133 * @return transaction status 2134 */ 2135 static enum GNUNET_DB_QueryStatus 2136 postgres_get_latest_recovery_document ( 2137 void *cls, 2138 const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, 2139 struct ANASTASIS_AccountSignatureP *account_sig, 2140 struct GNUNET_HashCode *recovery_data_hash, 2141 size_t *data_size, 2142 void **data, 2143 uint32_t *version) 2144 { 2145 struct PostgresClosure *pg = cls; 2146 struct GNUNET_PQ_QueryParam params[] = { 2147 GNUNET_PQ_query_param_auto_from_type (account_pub), 2148 GNUNET_PQ_query_param_end 2149 }; 2150 struct GNUNET_PQ_ResultSpec rs[] = { 2151 GNUNET_PQ_result_spec_uint32 ("version", 2152 version), 2153 GNUNET_PQ_result_spec_auto_from_type ("account_sig", 2154 account_sig), 2155 GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash", 2156 recovery_data_hash), 2157 GNUNET_PQ_result_spec_variable_size ("recovery_data", 2158 data, 2159 data_size), 2160 GNUNET_PQ_result_spec_end 2161 }; 2162 2163 check_connection (pg); 2164 GNUNET_break (GNUNET_OK == 2165 postgres_preflight (pg)); 2166 return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 2167 "latest_recoverydocument_select", 2168 params, 2169 rs); 2170 } 2171 2172 2173 /** 2174 * Closure for meta_iterator(). 2175 */ 2176 struct MetaIteratorContext 2177 { 2178 /** 2179 * Function to call on each result. 2180 */ 2181 ANASTASIS_DB_RecoveryMetaCallback cb; 2182 2183 /** 2184 * Closure for @e cb. 2185 */ 2186 void *cb_cls; 2187 2188 /** 2189 * Set to true on database failure. 2190 */ 2191 bool db_failure; 2192 }; 2193 2194 2195 /** 2196 * Helper function for #postgres_get_recovery_meta_data(). 2197 * To be called with the results of a SELECT statement 2198 * that has returned @a num_results results. 2199 * 2200 * @param cls closure of type `struct MetaIteratorContext *` 2201 * @param result the postgres result 2202 * @param num_results the number of results in @a result 2203 */ 2204 static void 2205 meta_iterator (void *cls, 2206 PGresult *result, 2207 unsigned int num_results) 2208 { 2209 struct MetaIteratorContext *ctx = cls; 2210 2211 for (unsigned int i = 0; i<num_results; i++) 2212 { 2213 uint32_t version; 2214 void *meta_data; 2215 size_t meta_data_size; 2216 struct GNUNET_TIME_Timestamp ts; 2217 struct GNUNET_PQ_ResultSpec rs[] = { 2218 GNUNET_PQ_result_spec_uint32 ("version", 2219 &version), 2220 GNUNET_PQ_result_spec_timestamp ("creation_date", 2221 &ts), 2222 GNUNET_PQ_result_spec_variable_size ("recovery_meta_data", 2223 &meta_data, 2224 &meta_data_size), 2225 GNUNET_PQ_result_spec_end 2226 }; 2227 enum GNUNET_GenericReturnValue ret; 2228 2229 if (GNUNET_OK != 2230 GNUNET_PQ_extract_result (result, 2231 rs, 2232 i)) 2233 { 2234 GNUNET_break (0); 2235 ctx->db_failure = true; 2236 return; 2237 } 2238 ret = ctx->cb (ctx->cb_cls, 2239 version, 2240 ts, 2241 meta_data, 2242 meta_data_size); 2243 GNUNET_PQ_cleanup_result (rs); 2244 if (GNUNET_OK != ret) 2245 break; 2246 } 2247 } 2248 2249 2250 /** 2251 * Fetch recovery document meta data for user. Returns 2252 * meta data in descending order from @a max_version. 2253 * The size of the result set may be limited. 2254 * 2255 * @param cls closure 2256 * @param account_pub public key of the user's account 2257 * @param max_version the maximum version number the user requests 2258 * @param cb function to call on each result 2259 * @param cb_cls closure for @a cb 2260 * @return transaction status 2261 */ 2262 static enum GNUNET_DB_QueryStatus 2263 postgres_get_recovery_meta_data ( 2264 void *cls, 2265 const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, 2266 uint32_t max_version, 2267 ANASTASIS_DB_RecoveryMetaCallback cb, 2268 void *cb_cls) 2269 { 2270 struct PostgresClosure *pg = cls; 2271 struct MetaIteratorContext ctx = { 2272 .cb = cb, 2273 .cb_cls = cb_cls 2274 }; 2275 struct GNUNET_PQ_QueryParam params[] = { 2276 GNUNET_PQ_query_param_auto_from_type (account_pub), 2277 GNUNET_PQ_query_param_uint32 (&max_version), 2278 GNUNET_PQ_query_param_end 2279 }; 2280 enum GNUNET_DB_QueryStatus qs; 2281 2282 check_connection (pg); 2283 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 2284 "recoverydocument_select_meta", 2285 params, 2286 &meta_iterator, 2287 &ctx); 2288 if (qs < 0) 2289 return qs; 2290 if (ctx.db_failure) 2291 return GNUNET_DB_STATUS_HARD_ERROR; 2292 return qs; 2293 } 2294 2295 2296 /** 2297 * Fetch recovery document for user according given version. 2298 * 2299 * @param cls closure 2300 * @param account_pub public key of the user's account 2301 * @param version the version number of the policy the user requests 2302 * @param[out] account_sig signature 2303 * @param[out] recovery_data_hash hash of the current recovery data 2304 * @param[out] data_size size of data blob 2305 * @param[out] data blob which contains the recovery document 2306 * @return transaction status 2307 */ 2308 static enum GNUNET_DB_QueryStatus 2309 postgres_get_recovery_document ( 2310 void *cls, 2311 const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub, 2312 uint32_t version, 2313 struct ANASTASIS_AccountSignatureP *account_sig, 2314 struct GNUNET_HashCode *recovery_data_hash, 2315 size_t *data_size, 2316 void **data) 2317 { 2318 struct PostgresClosure *pg = cls; 2319 struct GNUNET_PQ_QueryParam params[] = { 2320 GNUNET_PQ_query_param_auto_from_type (account_pub), 2321 GNUNET_PQ_query_param_uint32 (&version), 2322 GNUNET_PQ_query_param_end 2323 }; 2324 struct GNUNET_PQ_ResultSpec rs[] = { 2325 GNUNET_PQ_result_spec_auto_from_type ("account_sig", 2326 account_sig), 2327 GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash", 2328 recovery_data_hash), 2329 GNUNET_PQ_result_spec_variable_size ("recovery_data", 2330 data, 2331 data_size), 2332 GNUNET_PQ_result_spec_end 2333 }; 2334 2335 check_connection (pg); 2336 return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 2337 "recoverydocument_select", 2338 params, 2339 rs); 2340 } 2341 2342 2343 /** 2344 * Closure for check_valid_code(). 2345 */ 2346 struct CheckValidityContext 2347 { 2348 /** 2349 * Code to check for. 2350 */ 2351 const struct GNUNET_HashCode *hashed_code; 2352 2353 /** 2354 * Truth we are processing. 2355 */ 2356 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid; 2357 2358 /** 2359 * Database context. 2360 */ 2361 struct PostgresClosure *pg; 2362 2363 /** 2364 * Set to the matching challenge code (if @e valid). 2365 */ 2366 uint64_t code; 2367 2368 /** 2369 * Set to true if a code matching @e hashed_code was found. 2370 */ 2371 bool valid; 2372 2373 /** 2374 * Set to true if a code matching @e hashed_code was set to 'satisfied' by the plugin. 2375 */ 2376 bool satisfied; 2377 2378 /** 2379 * Set to true if we had a database failure. 2380 */ 2381 bool db_failure; 2382 2383 }; 2384 2385 2386 /** 2387 * Helper function for #postgres_verify_challenge_code(). 2388 * To be called with the results of a SELECT statement 2389 * that has returned @a num_results results. 2390 * 2391 * @param cls closure of type `struct CheckValidityContext *` 2392 * @param result the postgres result 2393 * @param num_results the number of results in @a result 2394 */ 2395 static void 2396 check_valid_code (void *cls, 2397 PGresult *result, 2398 unsigned int num_results) 2399 { 2400 struct CheckValidityContext *cvc = cls; 2401 struct PostgresClosure *pg = cvc->pg; 2402 2403 for (unsigned int i = 0; i < num_results; i++) 2404 { 2405 uint64_t server_code; 2406 uint8_t sat; 2407 struct GNUNET_PQ_ResultSpec rs[] = { 2408 GNUNET_PQ_result_spec_uint64 ("code", 2409 &server_code), 2410 GNUNET_PQ_result_spec_auto_from_type ("satisfied", 2411 &sat), 2412 GNUNET_PQ_result_spec_end 2413 }; 2414 2415 if (GNUNET_OK != 2416 GNUNET_PQ_extract_result (result, 2417 rs, 2418 i)) 2419 { 2420 GNUNET_break (0); 2421 cvc->db_failure = true; 2422 return; 2423 } 2424 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2425 "Found issued challenge %llu (client: %s)\n", 2426 (unsigned long long) server_code, 2427 GNUNET_h2s (cvc->hashed_code)); 2428 { 2429 struct GNUNET_HashCode shashed_code; 2430 2431 ANASTASIS_hash_answer (server_code, 2432 &shashed_code); 2433 if (0 == 2434 GNUNET_memcmp (&shashed_code, 2435 cvc->hashed_code)) 2436 { 2437 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2438 "Challenge is valid challenge (%s)\n", 2439 (0 != sat) ? "satisfied" : "not satisfied"); 2440 cvc->valid = true; 2441 cvc->code = server_code; 2442 cvc->satisfied = (0 != sat); 2443 } 2444 else 2445 { 2446 /* count failures to prevent brute-force attacks */ 2447 struct GNUNET_PQ_QueryParam params[] = { 2448 GNUNET_PQ_query_param_auto_from_type (cvc->truth_uuid), 2449 GNUNET_PQ_query_param_uint64 (&server_code), 2450 GNUNET_PQ_query_param_end 2451 }; 2452 enum GNUNET_DB_QueryStatus qs; 2453 2454 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 2455 "challengecode_update_retry", 2456 params); 2457 if (qs <= 0) 2458 { 2459 GNUNET_break (0); 2460 cvc->db_failure = true; 2461 } 2462 } 2463 } 2464 } 2465 } 2466 2467 2468 /** 2469 * Verify the provided code with the code on the server. 2470 * If the code matches the function will return with success, if the code 2471 * does not match, the retry counter will be decreased by one. 2472 * 2473 * @param cls closure 2474 * @param truth_uuid identification of the challenge which the code corresponds to 2475 * @param hashed_code code which the user provided and wants to verify 2476 * @param[out] code set to the original numeric code 2477 * @param[out] satisfied set to true if the challenge is set to satisfied 2478 * @return code validity status 2479 */ 2480 static enum ANASTASIS_DB_CodeStatus 2481 postgres_verify_challenge_code ( 2482 void *cls, 2483 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 2484 const struct GNUNET_HashCode *hashed_code, 2485 uint64_t *code, 2486 bool *satisfied) 2487 { 2488 struct PostgresClosure *pg = cls; 2489 struct CheckValidityContext cvc = { 2490 .truth_uuid = truth_uuid, 2491 .hashed_code = hashed_code, 2492 .pg = pg 2493 }; 2494 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 2495 struct GNUNET_PQ_QueryParam params[] = { 2496 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2497 GNUNET_PQ_query_param_timestamp (&now), 2498 GNUNET_PQ_query_param_end 2499 }; 2500 enum GNUNET_DB_QueryStatus qs; 2501 2502 *satisfied = false; 2503 check_connection (pg); 2504 qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, 2505 "challengecode_select", 2506 params, 2507 &check_valid_code, 2508 &cvc); 2509 if ( (qs < 0) || 2510 (cvc.db_failure) ) 2511 return ANASTASIS_DB_CODE_STATUS_HARD_ERROR; 2512 *code = cvc.code; 2513 if (cvc.valid) 2514 { 2515 *satisfied = cvc.satisfied; 2516 return ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED; 2517 } 2518 if (0 == qs) 2519 return ANASTASIS_DB_CODE_STATUS_NO_RESULTS; 2520 return ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH; 2521 } 2522 2523 2524 /** 2525 * Set the 'satisfied' bit for the given challenge and code to 2526 * 'true'. 2527 * 2528 * @param cls closure 2529 * @param truth_uuid identification of the challenge which the code corresponds to 2530 * @param code code which is now satisfied 2531 * @return transaction status 2532 */ 2533 static enum GNUNET_DB_QueryStatus 2534 postgres_mark_challenge_code_satisfied ( 2535 void *cls, 2536 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 2537 const uint64_t code) 2538 { 2539 struct PostgresClosure *pg = cls; 2540 struct GNUNET_PQ_QueryParam params[] = { 2541 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2542 GNUNET_PQ_query_param_uint64 (&code), 2543 GNUNET_PQ_query_param_end 2544 }; 2545 2546 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 2547 "challengecode_set_satisfied", 2548 params); 2549 } 2550 2551 2552 /** 2553 * Check if the 'satisfied' bit for the given challenge and code is 2554 * 'true' and the challenge code is not yet expired. 2555 * 2556 * @param cls closure 2557 * @param truth_uuid identification of the challenge which the code corresponds to 2558 * @param code code which is now satisfied 2559 * @return transaction status, 2560 * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the challenge code is not satisfied or expired 2561 * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the challenge code has been marked as satisfied 2562 */ 2563 static enum GNUNET_DB_QueryStatus 2564 postgres_test_challenge_code_satisfied ( 2565 void *cls, 2566 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 2567 const uint64_t code, 2568 struct GNUNET_TIME_Timestamp after) 2569 { 2570 struct PostgresClosure *pg = cls; 2571 struct GNUNET_PQ_QueryParam params[] = { 2572 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2573 GNUNET_PQ_query_param_uint64 (&code), 2574 GNUNET_PQ_query_param_timestamp (&after), 2575 GNUNET_PQ_query_param_end 2576 }; 2577 struct GNUNET_PQ_ResultSpec rs[] = { 2578 GNUNET_PQ_result_spec_end 2579 }; 2580 2581 return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 2582 "challengecode_test_satisfied", 2583 params, 2584 rs); 2585 } 2586 2587 2588 /** 2589 * Lookup pending payment for a certain challenge. 2590 * 2591 * @param cls closure 2592 * @param truth_uuid identification of the challenge 2593 * @param[out] payment_secret set to the challenge payment secret 2594 * @return transaction status 2595 */ 2596 static enum GNUNET_DB_QueryStatus 2597 postgres_lookup_challenge_payment ( 2598 void *cls, 2599 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 2600 struct ANASTASIS_PaymentSecretP *payment_secret) 2601 { 2602 struct PostgresClosure *pg = cls; 2603 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); 2604 struct GNUNET_TIME_Timestamp recent 2605 = GNUNET_TIME_absolute_to_timestamp ( 2606 GNUNET_TIME_absolute_subtract (now, 2607 ANASTASIS_CHALLENGE_OFFER_LIFETIME)); 2608 struct GNUNET_PQ_QueryParam params[] = { 2609 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2610 GNUNET_PQ_query_param_timestamp (&recent), 2611 GNUNET_PQ_query_param_end 2612 }; 2613 struct GNUNET_PQ_ResultSpec rs[] = { 2614 GNUNET_PQ_result_spec_auto_from_type ("payment_identifier", 2615 payment_secret), 2616 GNUNET_PQ_result_spec_end 2617 }; 2618 2619 return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 2620 "challenge_pending_payment_select", 2621 params, 2622 rs); 2623 } 2624 2625 2626 /** 2627 * Update payment status of challenge 2628 * 2629 * @param cls closure 2630 * @param truth_uuid which challenge received a payment 2631 * @param payment_identifier proof of payment, must be unique and match pending payment 2632 * @return transaction status 2633 */ 2634 static enum GNUNET_DB_QueryStatus 2635 postgres_update_challenge_payment ( 2636 void *cls, 2637 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 2638 const struct ANASTASIS_PaymentSecretP *payment_identifier) 2639 { 2640 struct PostgresClosure *pg = cls; 2641 struct GNUNET_PQ_QueryParam params[] = { 2642 GNUNET_PQ_query_param_auto_from_type (payment_identifier), 2643 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2644 GNUNET_PQ_query_param_end 2645 }; 2646 2647 check_connection (pg); 2648 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 2649 "challenge_payment_done", 2650 params); 2651 } 2652 2653 2654 /** 2655 * Create a new challenge code for a given challenge identified by the challenge 2656 * public key. The function will first check if there is already a valid code 2657 * for this challenge present and won't insert a new one in this case. 2658 * 2659 * @param cls closure 2660 * @param truth_uuid the identifier for the challenge 2661 * @param rotation_period for how long is the code available 2662 * @param validity_period for how long is the code available 2663 * @param retry_counter amount of retries allowed 2664 * @param[out] retransmission_date when to next retransmit 2665 * @param[out] code set to the code which will be checked for later 2666 * @return transaction status, 2667 * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we are out of valid tries, 2668 * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if @a code is now in the DB 2669 */ 2670 static enum GNUNET_DB_QueryStatus 2671 postgres_create_challenge_code ( 2672 void *cls, 2673 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 2674 struct GNUNET_TIME_Relative rotation_period, 2675 struct GNUNET_TIME_Relative validity_period, 2676 uint32_t retry_counter, 2677 struct GNUNET_TIME_Timestamp *retransmission_date, 2678 uint64_t *code) 2679 { 2680 struct PostgresClosure *pg = cls; 2681 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 2682 struct GNUNET_TIME_Timestamp expiration_date; 2683 struct GNUNET_TIME_Absolute ex_rot; 2684 2685 check_connection (pg); 2686 expiration_date = GNUNET_TIME_relative_to_timestamp (validity_period); 2687 ex_rot = GNUNET_TIME_absolute_subtract (now.abs_time, 2688 rotation_period); 2689 for (unsigned int retries = 0; retries<MAX_RETRIES; retries++) 2690 { 2691 if (GNUNET_OK != 2692 begin_transaction (pg, 2693 "create_challenge_code")) 2694 { 2695 GNUNET_break (0); 2696 return GNUNET_DB_STATUS_HARD_ERROR; 2697 } 2698 2699 { 2700 uint32_t old_retry_counter; 2701 struct GNUNET_PQ_QueryParam params[] = { 2702 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2703 GNUNET_PQ_query_param_timestamp (&now), 2704 GNUNET_PQ_query_param_absolute_time (&ex_rot), 2705 GNUNET_PQ_query_param_end 2706 }; 2707 struct GNUNET_PQ_ResultSpec rs[] = { 2708 GNUNET_PQ_result_spec_uint64 ("code", 2709 code), 2710 GNUNET_PQ_result_spec_uint32 ("retry_counter", 2711 &old_retry_counter), 2712 GNUNET_PQ_result_spec_timestamp ("retransmission_date", 2713 retransmission_date), 2714 GNUNET_PQ_result_spec_end 2715 }; 2716 enum GNUNET_DB_QueryStatus qs; 2717 2718 qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 2719 "challengecode_select_meta", 2720 params, 2721 rs); 2722 switch (qs) 2723 { 2724 case GNUNET_DB_STATUS_HARD_ERROR: 2725 GNUNET_break (0); 2726 rollback (pg); 2727 return qs; 2728 case GNUNET_DB_STATUS_SOFT_ERROR: 2729 goto retry; 2730 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 2731 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2732 "No active challenge found, creating a fresh one\n"); 2733 break; 2734 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 2735 if (0 == old_retry_counter) 2736 { 2737 rollback (pg); 2738 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2739 "Active challenge %llu has zero tries left, refusing to create another one\n", 2740 (unsigned long long) *code); 2741 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 2742 } 2743 rollback (pg); 2744 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2745 "Active challenge has %u tries left, returning old challenge %llu\n", 2746 (unsigned int) old_retry_counter, 2747 (unsigned long long) *code); 2748 return qs; 2749 } 2750 } 2751 2752 *code = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, 2753 ANASTASIS_PIN_MAX_VALUE); 2754 *retransmission_date = GNUNET_TIME_UNIT_ZERO_TS; 2755 { 2756 struct GNUNET_PQ_QueryParam params[] = { 2757 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2758 GNUNET_PQ_query_param_uint64 (code), 2759 GNUNET_PQ_query_param_timestamp (&now), 2760 GNUNET_PQ_query_param_timestamp (&expiration_date), 2761 GNUNET_PQ_query_param_uint32 (&retry_counter), 2762 GNUNET_PQ_query_param_end 2763 }; 2764 enum GNUNET_DB_QueryStatus qs; 2765 2766 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 2767 "challengecode_insert", 2768 params); 2769 switch (qs) 2770 { 2771 case GNUNET_DB_STATUS_HARD_ERROR: 2772 rollback (pg); 2773 return GNUNET_DB_STATUS_HARD_ERROR; 2774 case GNUNET_DB_STATUS_SOFT_ERROR: 2775 goto retry; 2776 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 2777 GNUNET_break (0); 2778 rollback (pg); 2779 return GNUNET_DB_STATUS_HARD_ERROR; 2780 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 2781 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2782 "Created fresh challenge with %u tries left\n", 2783 (unsigned int) retry_counter); 2784 break; 2785 } 2786 } 2787 2788 { 2789 enum GNUNET_DB_QueryStatus qs; 2790 2791 qs = commit_transaction (pg); 2792 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 2793 goto retry; 2794 if (qs < 0) 2795 return qs; 2796 } 2797 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 2798 retry: 2799 rollback (pg); 2800 } 2801 return GNUNET_DB_STATUS_SOFT_ERROR; 2802 } 2803 2804 2805 /** 2806 * Remember in the database that we successfully sent a challenge. 2807 * 2808 * @param cls closure 2809 * @param payment_secret payment secret which the user must provide with every upload 2810 * @param truth_uuid the identifier for the challenge 2811 * @param code the challenge that was sent 2812 */ 2813 static enum GNUNET_DB_QueryStatus 2814 postgres_mark_challenge_sent ( 2815 void *cls, 2816 const struct ANASTASIS_PaymentSecretP *payment_secret, 2817 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 2818 uint64_t code) 2819 { 2820 struct PostgresClosure *pg = cls; 2821 enum GNUNET_DB_QueryStatus qs; 2822 2823 check_connection (pg); 2824 { 2825 struct GNUNET_TIME_Timestamp now; 2826 struct GNUNET_PQ_QueryParam params[] = { 2827 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2828 GNUNET_PQ_query_param_uint64 (&code), 2829 GNUNET_PQ_query_param_timestamp (&now), 2830 GNUNET_PQ_query_param_end 2831 }; 2832 2833 now = GNUNET_TIME_timestamp_get (); 2834 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 2835 "challengecode_mark_sent", 2836 params); 2837 if (qs <= 0) 2838 return qs; 2839 } 2840 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 2841 "Marking challenge %llu as issued\n", 2842 (unsigned long long) code); 2843 { 2844 struct GNUNET_PQ_QueryParam params[] = { 2845 GNUNET_PQ_query_param_auto_from_type (truth_uuid), 2846 GNUNET_PQ_query_param_auto_from_type (payment_secret), 2847 GNUNET_PQ_query_param_end 2848 }; 2849 2850 qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, 2851 "challengepayment_dec_counter", 2852 params); 2853 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 2854 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* probably was free */ 2855 return qs; 2856 } 2857 } 2858 2859 2860 /** 2861 * Function called to remove all expired codes from the database. 2862 * 2863 * @return transaction status 2864 */ 2865 static enum GNUNET_DB_QueryStatus 2866 postgres_challenge_gc (void *cls) 2867 { 2868 struct PostgresClosure *pg = cls; 2869 struct GNUNET_TIME_Timestamp time_now = GNUNET_TIME_timestamp_get (); 2870 struct GNUNET_PQ_QueryParam params[] = { 2871 GNUNET_PQ_query_param_timestamp (&time_now), 2872 GNUNET_PQ_query_param_end 2873 }; 2874 2875 check_connection (pg); 2876 GNUNET_break (GNUNET_OK == 2877 postgres_preflight (pg)); 2878 return GNUNET_PQ_eval_prepared_non_select (pg->conn, 2879 "gc_challengecodes", 2880 params); 2881 } 2882 2883 2884 /** 2885 * Initialize Postgres database subsystem. 2886 * 2887 * @param cls a configuration instance 2888 * @return NULL on error, otherwise a `struct TALER_ANASTASISDB_Plugin` 2889 */ 2890 void * 2891 libanastasis_plugin_db_postgres_init (void *cls); 2892 2893 /* declaration to fix compiler warning */ 2894 void * 2895 libanastasis_plugin_db_postgres_init (void *cls) 2896 { 2897 struct GNUNET_CONFIGURATION_Handle *cfg = cls; 2898 struct PostgresClosure *pg; 2899 struct ANASTASIS_DatabasePlugin *plugin; 2900 2901 pg = GNUNET_new (struct PostgresClosure); 2902 pg->cfg = cfg; 2903 if (GNUNET_OK != 2904 GNUNET_CONFIGURATION_get_value_string (cfg, 2905 "anastasis", 2906 "CURRENCY", 2907 &pg->currency)) 2908 { 2909 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 2910 "anastasis", 2911 "CURRENCY"); 2912 GNUNET_PQ_disconnect (pg->conn); 2913 GNUNET_free (pg); 2914 return NULL; 2915 } 2916 plugin = GNUNET_new (struct ANASTASIS_DatabasePlugin); 2917 plugin->cls = pg; 2918 /* FIXME: Should this be the same? */ 2919 plugin->connect = &postgres_preflight; 2920 plugin->create_tables = &postgres_create_tables; 2921 plugin->drop_tables = &postgres_drop_tables; 2922 plugin->gc = &postgres_gc; 2923 plugin->preflight = &postgres_preflight; 2924 plugin->rollback = &rollback; 2925 plugin->commit = &commit_transaction; 2926 plugin->event_listen = &postgres_event_listen; 2927 plugin->event_listen_cancel = &postgres_event_listen_cancel; 2928 plugin->event_notify = &postgres_event_notify; 2929 plugin->store_recovery_document = &postgres_store_recovery_document; 2930 plugin->record_recdoc_payment = &postgres_record_recdoc_payment; 2931 plugin->store_truth = &postgres_store_truth; 2932 plugin->get_escrow_challenge = &postgres_get_escrow_challenge; 2933 plugin->get_key_share = &postgres_get_key_share; 2934 plugin->get_latest_recovery_document = &postgres_get_latest_recovery_document; 2935 plugin->get_recovery_meta_data = &postgres_get_recovery_meta_data; 2936 plugin->get_recovery_document = &postgres_get_recovery_document; 2937 plugin->lookup_account = &postgres_lookup_account; 2938 plugin->check_payment_identifier = &postgres_check_payment_identifier; 2939 plugin->increment_lifetime = &postgres_increment_lifetime; 2940 plugin->update_lifetime = &postgres_update_lifetime; 2941 plugin->start = &begin_transaction; 2942 plugin->check_connection = &check_connection; 2943 plugin->verify_challenge_code = &postgres_verify_challenge_code; 2944 plugin->mark_challenge_code_satisfied = 2945 &postgres_mark_challenge_code_satisfied; 2946 plugin->test_challenge_code_satisfied = 2947 &postgres_test_challenge_code_satisfied; 2948 plugin->create_challenge_code = &postgres_create_challenge_code; 2949 plugin->mark_challenge_sent = &postgres_mark_challenge_sent; 2950 plugin->challenge_gc = &postgres_challenge_gc; 2951 plugin->record_truth_upload_payment = &postgres_record_truth_upload_payment; 2952 plugin->check_truth_upload_paid = &postgres_check_truth_upload_paid; 2953 plugin->record_challenge_payment = &postgres_record_challenge_payment; 2954 plugin->record_challenge_refund = &postgres_record_challenge_refund; 2955 plugin->check_challenge_payment = &postgres_check_challenge_payment; 2956 plugin->lookup_challenge_payment = &postgres_lookup_challenge_payment; 2957 plugin->update_challenge_payment = &postgres_update_challenge_payment; 2958 plugin->record_auth_iban_payment = &postgres_record_auth_iban_payment; 2959 plugin->test_auth_iban_payment = &postgres_test_auth_iban_payment; 2960 plugin->get_last_auth_iban_payment_row 2961 = &postgres_get_last_auth_iban_payment_row; 2962 return plugin; 2963 } 2964 2965 2966 /** 2967 * Shutdown Postgres database subsystem. 2968 * 2969 * @param cls a `struct ANASTASIS_DB_STATUS_Plugin` 2970 * @return NULL (always) 2971 */ 2972 void * 2973 libanastasis_plugin_db_postgres_done (void *cls); 2974 2975 /* declaration to fix compiler warning */ 2976 void * 2977 libanastasis_plugin_db_postgres_done (void *cls) 2978 { 2979 struct ANASTASIS_DatabasePlugin *plugin = cls; 2980 struct PostgresClosure *pg = plugin->cls; 2981 2982 GNUNET_PQ_disconnect (pg->conn); 2983 GNUNET_free (pg->currency); 2984 GNUNET_free (pg); 2985 GNUNET_free (plugin); 2986 return NULL; 2987 } 2988 2989 2990 /* end of plugin_anastasisdb_postgres.c */