/*
  This file is part of TALER
  Copyright (C) 2014-2025 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file util/donau-secmod-rsa.c
 * @brief Standalone process to perform private key RSA operations
 * @author Christian Grothoff
 *
 * Key design points:
 * - EVERY thread of the exchange will have its own pair of connections to the
 *   crypto helpers.  This way, every thread will also have its own /keys state
 *   and avoid the need to synchronize on those.
 * - auditor signatures and master signatures are to be kept in the exchange DB,
 *   and merged with the public keys of the helper by the exchange HTTPD!
 * - the main loop of the helper is SINGLE-THREADED, but there are
 *   threads for crypto-workers which do the signing in parallel, one per client.
 * - thread-safety: signing happens in parallel, thus when REMOVING private keys,
 *   we must ensure that all signers are done before we fully free() the
 *   private key. This is done by reference counting (as work is always
 *   assigned and collected by the main thread).
 */
#include "donau_config.h"
#include <sys/stat.h>
#include <taler/taler_util.h>
#include "donau_util.h"

/* LSB-style exit status codes */
#ifndef EXIT_INVALIDARGUMENT
/**
 * Command-line arguments are invalid.
 * Restarting useless.
 */
#define EXIT_INVALIDARGUMENT 2
#endif

#ifndef EXIT_NOTCONFIGURED
/**
 * Key configuration settings are missing or invalid.
 * Restarting useless.
 */
#define EXIT_NOTCONFIGURED 6
#endif


/**
 * Set to true if the configuration is invalid.
 */
static bool config_invalid;

/**
 * Configuration we use.
 */
static const struct GNUNET_CONFIGURATION_Handle *my_cfg;

/**
 * Checks the donau configuration section settings.
 * denomination_alias.
 *
 * @param cls must point to a `struct TALER_SECMOD_Options *`
 * @param denomination_alias name of the denomination's section in the configuration
 */
static void
load_denominations (void *cls,
                    const char *denomination_alias)
{
  struct TALER_SECMOD_Options *opts = cls;
  struct GNUNET_TIME_Relative r;

  if (0 != strncasecmp (denomination_alias,
                        opts->cprefix,
                        strlen (opts->cprefix)))
    return; /* not a denomination type definition */

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (my_cfg,
                                           denomination_alias,
                                           "DURATION_WITHDRAW",
                                           &r))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               denomination_alias,
                               "DURATION_WITHDRAW");
    opts->global_ret = EXIT_NOTCONFIGURED;
    return;
  }
  if (GNUNET_TIME_relative_cmp (r,
                                !=,
                                GNUNET_TIME_UNIT_YEARS))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               denomination_alias,
                               "DURATION_WITHDRAW",
                               "Must be exactly 1 year for Donau");
    opts->global_ret = EXIT_NOTCONFIGURED;
    return;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (my_cfg,
                                           denomination_alias,
                                           "ANCHOR_ROUND",
                                           &r))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               denomination_alias,
                               "ANCHOR_ROUND");
    opts->global_ret = EXIT_NOTCONFIGURED;
    return;
  }
  if (GNUNET_TIME_relative_cmp (r,
                                !=,
                                GNUNET_TIME_UNIT_YEARS))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               denomination_alias,
                               "ANCHOR_ROUND",
                               "Must be exactly 1 year for Donau");
    opts->global_ret = EXIT_NOTCONFIGURED;
    return;
  }
}


/**
 * Wrapper around #TALER_SECMOD_rsa_run() that checks that the
 * configuration abides by the Donau-constraints.
 *
 * @param cls must point to a `struct TALER_SECMOD_Options *`
 * @param args remaining command-line arguments
 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
 * @param cfg configuration
 */
static void
donau_rsa_run (void *cls,
               char *const *args,
               const char *cfgfile,
               const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  struct TALER_SECMOD_Options *opts = cls;
  char *secname;
  struct GNUNET_TIME_Relative overlap_duration;

  my_cfg = cfg;
  GNUNET_asprintf (&secname,
                   "%s-secmod-rsa",
                   opts->section);
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (cfg,
                                           secname,
                                           "OVERLAP_DURATION",
                                           &overlap_duration))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               secname,
                               "OVERLAP_DURATION");
    opts->global_ret = EXIT_NOTCONFIGURED;
    GNUNET_free (secname);
    return;
  }
  if (! GNUNET_TIME_relative_is_zero (overlap_duration))
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               secname,
                               "OVERLAP_DURATION",
                               "must be zero for Donau");
    opts->global_ret = EXIT_NOTCONFIGURED;
    GNUNET_free (secname);
    return;
  }
  GNUNET_free (secname);
  GNUNET_CONFIGURATION_iterate_sections (cfg,
                                         &load_denominations,
                                         opts);
  if (config_invalid)
  {
    opts->global_ret = EXIT_NOTCONFIGURED;
    return;
  }
  TALER_SECMOD_rsa_run (cls,
                        args,
                        cfgfile,
                        cfg);
}


/**
 * The entry point.
 *
 * @param argc number of arguments in @a argv
 * @param argv command-line arguments
 * @return 0 on normal termination
 */
int
main (int argc,
      char **argv)
{
  struct TALER_SECMOD_Options opts = {
    .max_workers = 16,
    .section = "donau",
    .cprefix = "doco_"
  };
  struct GNUNET_GETOPT_CommandLineOption options[] = {
    TALER_SECMOD_OPTIONS (&opts),
    GNUNET_GETOPT_OPTION_END
  };
  enum GNUNET_GenericReturnValue ret;

  /* Restrict permissions for the key files that we create. */
  (void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH);
  opts.global_now_tmp
    = opts.global_now
      = GNUNET_TIME_timestamp_get ();
  ret = GNUNET_PROGRAM_run (DONAU_project_data (),
                            argc, argv,
                            "taler-exchange-secmod-rsa",
                            "Handle private RSA key operations for a Donau",
                            options,
                            &donau_rsa_run,
                            &opts);
  if (GNUNET_NO == ret)
    return EXIT_SUCCESS;
  if (GNUNET_SYSERR == ret)
    return EXIT_INVALIDARGUMENT;
  return opts.global_ret;
}
