anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

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 */