anastasis

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

anastasis-cli-redux.c (11574B)


      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 Lesser 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 cli/anastasis-cli-redux.c
     18  * @brief command line tool for our reducer
     19  * @author Christian Grothoff
     20  * @author Dennis Neufeld
     21  * @author Dominik Meister
     22  */
     23 
     24 #include "platform.h"
     25 #include <gnunet/gnunet_util_lib.h>
     26 #include <gnunet/gnunet_curl_lib.h>
     27 #include "anastasis_redux.h"
     28 #include <taler/taler_util.h>
     29 #include <taler/taler_error_codes.h>
     30 #include <taler/taler_json_lib.h>
     31 #include "anastasis_util_lib.h"
     32 #include "anastasis-cli-common.h"
     33 
     34 /**
     35  * Closure for #GNUNET_CURL_gnunet_scheduler_reschedule().
     36  */
     37 static struct GNUNET_CURL_RescheduleContext *rc;
     38 
     39 /**
     40  * Curl context for communication with anastasis backend
     41  */
     42 static struct GNUNET_CURL_Context *ctx;
     43 
     44 /**
     45  * Application ID to include in the user attributes.
     46  * (-a option).
     47  */
     48 char *application_id;
     49 
     50 /**
     51  * -b option given.
     52  */
     53 static int b_flag;
     54 
     55 /**
     56  * -r option given.
     57  */
     58 static int r_flag;
     59 
     60 /**
     61  * Input to -a option given.
     62  */
     63 static char *input;
     64 
     65 /**
     66  * Output filename, if given.
     67  */
     68 static char *output_filename;
     69 
     70 /**
     71  * JSON containing previous state
     72  */
     73 static json_t *prev_state;
     74 
     75 /**
     76  * JSON containing arguments for action
     77  */
     78 static json_t *arguments;
     79 
     80 /**
     81  * Handle to an ongoing action.
     82  */
     83 static struct ANASTASIS_ReduxAction *ra;
     84 
     85 /**
     86  * Return value from main.  Defaults to failure so that every path which shuts
     87  * down without an action having run to completion — a parse error, a bad
     88  * command line, or a signal — is reported to the caller's shell.
     89  */
     90 static int global_ret = 1;
     91 
     92 
     93 /**
     94  * Persist a json state, report errors.
     95  *
     96  * @param state to persist
     97  * @param filename where to write the state to, NULL for stdout
     98  * @return #GNUNET_OK if the state was written out
     99  */
    100 static enum GNUNET_GenericReturnValue
    101 persist_new_state (json_t *state,
    102                    const char *filename)
    103 {
    104   if (NULL != filename)
    105   {
    106     if (0 !=
    107         json_dump_file (state,
    108                         filename,
    109                         JSON_COMPACT))
    110     {
    111       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    112                   "Could not dump state to `%s'\n",
    113                   filename);
    114       return GNUNET_SYSERR;
    115     }
    116     return GNUNET_OK;
    117   }
    118   {
    119     char *state_str = json_dumps (state,
    120                                   JSON_COMPACT);
    121 
    122     if (NULL == state_str)
    123     {
    124       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    125                   "Could not serialize state to JSON\n");
    126       return GNUNET_SYSERR;
    127     }
    128     if (-1 >=
    129         fprintf (stdout,
    130                  "%s",
    131                  state_str))
    132     {
    133       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    134                   "Could not dump state to stdout\n");
    135       GNUNET_free (state_str);
    136       return GNUNET_SYSERR;
    137     }
    138     GNUNET_free (state_str);
    139     /* stdio may only report a full output device at flush time, and the
    140        implicit flush at exit has nowhere left to report it to. */
    141     if (0 != fflush (stdout))
    142     {
    143       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    144                            "fflush");
    145       return GNUNET_SYSERR;
    146     }
    147   }
    148   return GNUNET_OK;
    149 }
    150 
    151 
    152 /**
    153  * Function called with the results of #ANASTASIS_redux_action().
    154  *
    155  * @param cls closure
    156  * @param error_code Error code
    157  * @param result_state new state as result
    158  */
    159 static void
    160 action_cb (void *cls,
    161            enum TALER_ErrorCode error_code,
    162            json_t *result_state)
    163 {
    164   bool persisted = true;
    165 
    166   (void) cls;
    167   ra = NULL;
    168   if (NULL != result_state)
    169     persisted = (GNUNET_OK ==
    170                  persist_new_state (result_state,
    171                                     output_filename));
    172   /* A state we could not write out is a failure of this tool even when the
    173      reduction itself succeeded, so both conditions must hold. */
    174   if ( (TALER_EC_NONE == error_code) &&
    175        persisted)
    176     global_ret = 0;
    177   if (TALER_EC_NONE != error_code)
    178   {
    179     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    180                 "Redux failed with error %d: %s\n",
    181                 error_code,
    182                 TALER_ErrorCode_get_hint (error_code));
    183     json_dumpf (result_state,
    184                 stderr,
    185                 JSON_INDENT (2));
    186   }
    187   GNUNET_SCHEDULER_shutdown ();
    188 }
    189 
    190 
    191 /**
    192  * @brief Shutdown the application.
    193  *
    194  * @param cls closure
    195  */
    196 static void
    197 shutdown_task (void *cls)
    198 {
    199   (void) cls;
    200 
    201   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    202               "Shutdown initiated\n");
    203   if (NULL != ra)
    204   {
    205     ANASTASIS_redux_action_cancel (ra);
    206     ra = NULL;
    207   }
    208   ANASTASIS_redux_done ();
    209   if (NULL != ctx)
    210   {
    211     GNUNET_CURL_fini (ctx);
    212     ctx = NULL;
    213   }
    214   if (NULL != rc)
    215   {
    216     GNUNET_CURL_gnunet_rc_destroy (rc);
    217     rc = NULL;
    218   }
    219   json_decref (prev_state);
    220   prev_state = NULL;
    221   json_decref (arguments);
    222   arguments = NULL;
    223   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    224               "Shutdown complete\n");
    225 }
    226 
    227 
    228 /**
    229  * @brief Start the application
    230  *
    231  * @param cls closure
    232  * @param args arguments left
    233  * @param cfgfile config file name
    234  * @param cfg handle for the configuration file
    235  */
    236 static void
    237 run (void *cls,
    238      char *const *args,
    239      const char *cfgfile,
    240      const struct GNUNET_CONFIGURATION_Handle *cfg)
    241 {
    242   json_error_t error;
    243 
    244   (void) cls;
    245   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    246               "Starting anastasis-reducer\n");
    247   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    248                                  NULL);
    249   if (b_flag && r_flag)
    250   {
    251     GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    252                 "We cannot start backup and recovery at the same time!\n");
    253     global_ret = 1;
    254     GNUNET_SCHEDULER_shutdown ();
    255     return;
    256   }
    257   if (r_flag)
    258   {
    259     json_t *init_state;
    260 
    261     init_state = ANASTASIS_recovery_start (cfg);
    262     if (NULL == init_state)
    263     {
    264       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    265                   "Failed to create an initial recovery state!\n");
    266       global_ret = 1;
    267       GNUNET_SCHEDULER_shutdown ();
    268       return;
    269     }
    270     if (GNUNET_OK ==
    271         persist_new_state (init_state,
    272                            args[0]))
    273       global_ret = 0;
    274     json_decref (init_state);
    275     GNUNET_SCHEDULER_shutdown ();
    276     return;
    277   }
    278   if (b_flag)
    279   {
    280     json_t *init_state;
    281 
    282     init_state = ANASTASIS_backup_start (cfg);
    283     if (NULL == init_state)
    284     {
    285       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    286                   "Failed to create an initial backup state!\n");
    287       global_ret = 1;
    288       GNUNET_SCHEDULER_shutdown ();
    289       return;
    290     }
    291     if (GNUNET_OK ==
    292         persist_new_state (init_state,
    293                            args[0]))
    294       global_ret = 0;
    295     json_decref (init_state);
    296     GNUNET_SCHEDULER_shutdown ();
    297     return;
    298   }
    299 
    300   /* action processing */
    301   {
    302     const char *action = args[0];
    303 
    304     if (NULL == action)
    305     {
    306       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    307                   "You must specify an action as the first argument (or `-b' or `-r')\n");
    308       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    309                   "Example: anastasis-reducer back\n");
    310       global_ret = 1;
    311       GNUNET_SCHEDULER_shutdown ();
    312       return;
    313     }
    314     args++;
    315     if (NULL != input)
    316     {
    317       arguments = json_loads (input,
    318                               JSON_DECODE_ANY,
    319                               &error);
    320       if (NULL == arguments)
    321       {
    322         GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    323                     "Failed to parse arguments on line %u:%u: %s!\n",
    324                     error.line,
    325                     error.column,
    326                     error.text);
    327         global_ret = 1;
    328         GNUNET_SCHEDULER_shutdown ();
    329         return;
    330       }
    331     }
    332     if (NULL != args[0])
    333     {
    334       prev_state = json_load_file (args[0],
    335                                    JSON_DECODE_ANY,
    336                                    &error);
    337       args++;
    338     }
    339     else
    340     {
    341       prev_state = ANASTASIS_CLI_json_load_capped (stdin,
    342                                                    &error);
    343     }
    344     if (NULL == prev_state)
    345     {
    346       GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
    347                   "Failed to parse initial state on line %u:%u: %s!\n",
    348                   error.line,
    349                   error.column,
    350                   error.text);
    351       global_ret = 1;
    352       GNUNET_SCHEDULER_shutdown ();
    353       return;
    354     }
    355     output_filename = args[0];
    356     /* initialize HTTP client event loop */
    357     ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    358                             &rc);
    359     rc = GNUNET_CURL_gnunet_rc_create (ctx);
    360     ANASTASIS_redux_init (ctx);
    361     /* Expand identity_attributes if -a is given explicitly and we
    362        are at the respective step of the reduction */
    363     if ( (0 == strcasecmp (action,
    364                            "enter_user_attributes")) &&
    365          (NULL != application_id) &&
    366          (NULL != arguments) )
    367     {
    368       json_t *attr = json_object_get (arguments,
    369                                       "identity_attributes");
    370       if (NULL != attr)
    371         GNUNET_assert (0 ==
    372                        json_object_set_new (attr,
    373                                             "application-id",
    374                                             json_string (application_id)));
    375     }
    376     ra = ANASTASIS_redux_action (prev_state,
    377                                  action,
    378                                  arguments,
    379                                  &action_cb,
    380                                  cls);
    381   }
    382 }
    383 
    384 
    385 int
    386 main (int argc,
    387       char *const *argv)
    388 {
    389   /* the available command line options */
    390   struct GNUNET_GETOPT_CommandLineOption options[] = {
    391     GNUNET_GETOPT_option_string ('A',
    392                                  "application",
    393                                  "ID",
    394                                  "set the application ID",
    395                                  &application_id),
    396     GNUNET_GETOPT_option_string ('a',
    397                                  "arguments",
    398                                  "JSON",
    399                                  "pass a JSON string containing arguments to reducer",
    400                                  &input),
    401     GNUNET_GETOPT_option_flag ('b',
    402                                "backup",
    403                                "use reducer to handle states for backup process",
    404                                &b_flag),
    405     GNUNET_GETOPT_option_flag ('r',
    406                                "restore",
    407                                "use reducer to handle states for restore process",
    408                                &r_flag),
    409     GNUNET_GETOPT_OPTION_END
    410   };
    411   enum GNUNET_GenericReturnValue ret;
    412 
    413   ret = GNUNET_PROGRAM_run (ANASTASIS_project_data (),
    414                             argc,
    415                             argv,
    416                             "anastasis-reducer",
    417                             "This is an application for using Anastasis to handle the states.\n",
    418                             options,
    419                             &run,
    420                             NULL);
    421   GNUNET_free (application_id);
    422   if (GNUNET_SYSERR == ret)
    423     return 3;
    424   if (GNUNET_NO == ret)
    425     return 0;
    426   return global_ret;
    427 }
    428 
    429 
    430 /* end of anastasis-cli-redux.c */