/*
     This file is part of GNUnet.
     Copyright (C) 2013, 2018, 2019 GNUnet e.V.

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

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */
/**
 * @file identity/gnunet-identity.c
 * @brief IDENTITY management command line tool
 * @author Christian Grothoff
 *
 * Todo:
 * - add options to get default egos
 */
#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_identity_service.h"


/**
 * Return value from main on timeout.
 */
#define TIMEOUT_STATUS_CODE 40

/**
 * Handle to IDENTITY service.
 */
static struct GNUNET_IDENTITY_Handle *sh;

/**
 * Was "list" specified?
 */
static int list;

/**
 * Was "monitor" specified?
 */
static int monitor;

/**
 * Was "private" specified?
 */
static int private_keys;

/**
 * Was "verbose" specified?
 */
static unsigned int verbose;

/**
 * Was "quiet" specified?
 */
static int quiet;

/**
 * Was "eddsa" specified?
 */
static int type_eddsa;

/**
 * -W option
 */
static char *write_msg;

/**
 * -R option
 */
static char *read_msg;

/**
 * -C option
 */
static char *create_ego;

/**
 * -D option
 */
static char *delete_ego;

/**
 * -P option
 */
static char *privkey_ego;

/**
 * -k option
 */
static char *pubkey_msg;

/**
 * -s option.
 */
static char *set_ego;

/**
 * -S option.
 */
static char *set_subsystem;

/**
 * Operation handle for set operation.
 */
static struct GNUNET_IDENTITY_Operation *set_op;

/**
 * Handle for create operation.
 */
static struct GNUNET_IDENTITY_Operation *create_op;

/**
 * Handle for delete operation.
 */
static struct GNUNET_IDENTITY_Operation *delete_op;

/**
 * Private key from command line option, or NULL.
 */
struct GNUNET_IDENTITY_PrivateKey pk;

/**
 * Value to return from #main().
 */
static int global_ret;


/**
 * Task run on shutdown.
 *
 * @param cls NULL
 */
static void
shutdown_task (void *cls)
{
  if (NULL != set_op)
  {
    GNUNET_IDENTITY_cancel (set_op);
    set_op = NULL;
  }
  if (NULL != create_op)
  {
    GNUNET_IDENTITY_cancel (create_op);
    create_op = NULL;
  }
  if (NULL != delete_op)
  {
    GNUNET_IDENTITY_cancel (delete_op);
    delete_op = NULL;
  }
  if (NULL != set_ego)
  {
    GNUNET_free (set_ego);
    set_ego = NULL;
  }
  GNUNET_IDENTITY_disconnect (sh);
  sh = NULL;
}


/**
 * Test if we are finished yet.
 */
static void
test_finished (void)
{
  if ( (NULL == create_op) &&
       (NULL == delete_op) &&
       (NULL == set_op) &&
       (NULL == set_subsystem) &&
       (NULL == write_msg) &&
       (NULL == read_msg) &&
       (! list) &&
       (! monitor))
  {
    if (TIMEOUT_STATUS_CODE == global_ret)
      global_ret = 0;
    GNUNET_SCHEDULER_shutdown ();
  }
}


/**
 * Deletion operation finished.
 *
 * @param cls pointer to operation handle
 * @param emsg NULL on success, otherwise an error message
 */
static void
delete_finished (void *cls,
                 const char *emsg)
{
  struct GNUNET_IDENTITY_Operation **op = cls;

  *op = NULL;
  if (NULL != emsg)
    fprintf (stderr, "%s\n", gettext (emsg));
  test_finished ();
}


/**
 * Creation operation finished.
 *
 * @param cls pointer to operation handle
 * @param pk private key of the ego, or NULL on error
 * @param emsg error message, NULL on success
 */
static void
create_finished (void *cls,
                 const struct GNUNET_IDENTITY_PrivateKey *pk,
                 const char *emsg)
{
  struct GNUNET_IDENTITY_Operation **op = cls;

  *op = NULL;
  if (NULL == pk)
  {
    fprintf (stderr,
             _ ("Failed to create ego: %s\n"),
             emsg);
    global_ret = 1;
  }
  else if (verbose)
  {
    struct GNUNET_IDENTITY_PublicKey pub;
    char *pubs;

    GNUNET_IDENTITY_key_get_public (pk, &pub);
    pubs = GNUNET_IDENTITY_public_key_to_string (&pub);
    if (private_keys)
    {
      char *privs;

      privs = GNUNET_IDENTITY_private_key_to_string (pk);
      fprintf (stdout, "%s - %s\n", pubs, privs);
      GNUNET_free (privs);
    }
    else
    {
      fprintf (stdout, "%s\n", pubs);
    }
    GNUNET_free (pubs);
  }
  test_finished ();
}


/**
 * Function called by #GNUNET_IDENTITY_set up on completion.
 *
 * @param cls NULL
 * @param emsg error message (NULL on success)
 */
static void
set_done (void *cls, const char *emsg)
{
  set_op = NULL;
  if (NULL != emsg)
  {
    fprintf (stderr, _ ("Failed to set default ego: %s\n"), emsg);
    global_ret = 1;
  }
  test_finished ();
}


/**
 * Encrypt a message given with -W, encrypted using public key of
 * an identity given with -k.
 */
static void
write_encrypted_message (void)
{
  struct GNUNET_IDENTITY_PublicKey recipient;
  if (GNUNET_IDENTITY_public_key_from_string (pubkey_msg, &recipient) !=
      GNUNET_SYSERR)
  {
    struct GNUNET_CRYPTO_EcdhePublicKey message_key;
    size_t msg_len = strlen (write_msg);
    ssize_t res = GNUNET_IDENTITY_encrypt (write_msg,
                                           msg_len,
                                           &recipient,
                                           &message_key,
                                           write_msg);
    if (-1 != res)
    {
      char *keystr;
      char *serialized_msg;
      keystr = GNUNET_STRINGS_data_to_string_alloc (&message_key,
                                                    sizeof(struct
                                                           GNUNET_CRYPTO_EcdhePublicKey));
      serialized_msg = GNUNET_STRINGS_data_to_string_alloc (write_msg,
                                                            msg_len);
      fprintf (stdout,
               "%s.%s\n",
               keystr, serialized_msg);
      GNUNET_free (keystr);
      GNUNET_free (serialized_msg);
    }
    else
    {
      fprintf (stderr, "Error during encryption.\n");
      global_ret = 1;
    }
  }
  else
  {
    fprintf (stderr, "Invalid recipient public key.\n");
    global_ret = 1;
  }
}


/**
 * Decrypt a message given with -R, encrypted using public key of @c ego
 * and ephemeral key given with -k.
 *
 * @param ego ego whose private key is used for decryption
 */
static void
read_encrypted_message (struct GNUNET_IDENTITY_Ego *ego)
{
  // message contains ECDHE key and ciphertext divided by ".", so split up first
  char delim[2] = ".";
  char *key_msg = strtok (read_msg, delim);
  char *cipher;
  if (NULL == key_msg)
  {
    fprintf (stderr, "Invalid message format.\n");
    global_ret = 1;
    return;
  }
  cipher = strtok (NULL, delim);
  if (NULL == cipher)
  {
    fprintf (stderr, "Invalid message format, text missing.\n");
    global_ret = 1;
    return;
  }

  if (NULL != strtok (NULL, delim))
  {
    fprintf (stderr,
             "Invalid message format, expecting only key and cipher components.\n");
    global_ret = 1;
    return;
  }

  struct GNUNET_CRYPTO_EcdhePublicKey message_key;
  if (GNUNET_OK == GNUNET_STRINGS_string_to_data (key_msg, strlen (
                                                    key_msg),
                                                  &message_key,
                                                  sizeof(message_key)))
  {
    char *deserialized_msg;
    size_t msg_len;
    if (GNUNET_OK == GNUNET_STRINGS_string_to_data_alloc (cipher, strlen (
                                                            cipher),
                                                          (void **) &
                                                          deserialized_msg,
                                                          &msg_len))
    {
      ssize_t res = GNUNET_IDENTITY_decrypt (deserialized_msg,
                                             msg_len,
                                             GNUNET_IDENTITY_ego_get_private_key (
                                               ego),
                                             &message_key,
                                             deserialized_msg);
      if (-1 != res)
      {
        fprintf (stdout,
                 "%s\n",
                 deserialized_msg);
      }
      else
      {
        fprintf (stderr, "Failed to decrypt message.\n");
        global_ret = 1;
      }
      GNUNET_free (deserialized_msg);
    }
    else
    {
      fprintf (stderr, "Invalid message format.\n");
      global_ret = 1;
    }
  }
  else
  {
    fprintf (stderr, "Invalid message ephemeral key.\n");
    global_ret = 1;
  }
}


/**
 * If listing is enabled, prints information about the egos.
 *
 * This function is initially called for all egos and then again
 * whenever a ego's identifier changes or if it is deleted.  At the
 * end of the initial pass over all egos, the function is once called
 * with 'NULL' for 'ego'. That does NOT mean that the callback won't
 * be invoked in the future or that there was an error.
 *
 * When used with 'GNUNET_IDENTITY_create' or 'GNUNET_IDENTITY_get', this
 * function is only called ONCE, and 'NULL' being passed in 'ego' does
 * indicate an error (for example because name is taken or no default value is
 * known).  If 'ego' is non-NULL and if '*ctx' is set in those callbacks, the
 * value WILL be passed to a subsequent call to the identity callback of
 * 'GNUNET_IDENTITY_connect' (if that one was not NULL).
 *
 * When an identity is renamed, this function is called with the
 * (known) ego but the NEW identifier.
 *
 * When an identity is deleted, this function is called with the
 * (known) ego and "NULL" for the 'identifier'.  In this case,
 * the 'ego' is henceforth invalid (and the 'ctx' should also be
 * cleaned up).
 *
 * @param cls closure
 * @param ego ego handle
 * @param ctx context for application to store data for this ego
 *                 (during the lifetime of this process, initially NULL)
 * @param identifier identifier assigned by the user for this ego,
 *                   NULL if the user just deleted the ego and it
 *                   must thus no longer be used
 */
static void
print_ego (void *cls,
           struct GNUNET_IDENTITY_Ego *ego,
           void **ctx,
           const char *identifier)
{
  struct GNUNET_IDENTITY_PublicKey pk;
  char *s;
  char *privs;

  if ( (NULL != set_ego) &&
       (NULL != set_subsystem) &&
       (NULL != ego) &&
       (NULL != identifier) &&
       (0 == strcmp (identifier, set_ego)))
  {
    set_op = GNUNET_IDENTITY_set (sh,
                                  set_subsystem,
                                  ego,
                                  &set_done,
                                  NULL);
    GNUNET_free (set_subsystem);
    set_subsystem = NULL;
    GNUNET_free (set_ego);
    set_ego = NULL;
  }
  if ( (NULL == ego) &&
       (NULL != set_ego) &&
       (NULL != set_subsystem) )
  {
    fprintf (stderr,
             "Could not set ego to `%s' for subsystem `%s', ego not known\n",
             set_ego,
             set_subsystem);
    GNUNET_free (set_subsystem);
    set_subsystem = NULL;
    GNUNET_free (set_ego);
    set_ego = NULL;
  }
  if ( (NULL == ego) &&
       (NULL != set_ego) &&
       (NULL != read_msg) )
  {
    fprintf (stderr,
             "Ego `%s' is not known, cannot decrypt message.\n",
             set_ego);
    GNUNET_free (read_msg);
    read_msg = NULL;
    GNUNET_free (set_ego);
    set_ego = NULL;
  }
  if ((NULL == ego) && (! monitor))
  {
    list = 0;
    test_finished ();
    return;
  }
  if (! (list | monitor) && (NULL == read_msg))
    return;
  if ( (NULL == ego) ||
       (NULL == identifier) )
    return;
  if ( (NULL != set_ego) &&
       (0 != strcmp (identifier,
                     set_ego)) )
    return;
  GNUNET_IDENTITY_ego_get_public_key (ego, &pk);
  s = GNUNET_IDENTITY_public_key_to_string (&pk);
  privs = GNUNET_IDENTITY_private_key_to_string (
    GNUNET_IDENTITY_ego_get_private_key (ego));
  if ((NULL != read_msg) && (NULL != set_ego))
  {
    // due to the check above, set_ego and the identifier are equal
    read_encrypted_message (ego);
    GNUNET_free (read_msg);
    read_msg = NULL;
  }
  else if ((monitor) || (NULL != identifier))
  {
    if (quiet)
    {
      if (private_keys)
        fprintf (stdout, "%s - %s\n", s, privs);
      else
        fprintf (stdout, "%s\n", s);
    }
    else
    {
      if (private_keys)
        fprintf (stdout, "%s - %s - %s - %s\n",
                 identifier, s, privs,
                 (ntohl (pk.type) == GNUNET_IDENTITY_TYPE_ECDSA) ?
                 "ECDSA" : "EdDSA");
      else
        fprintf (stdout, "%s - %s - %s\n",
                 identifier, s,
                 (ntohl (pk.type) == GNUNET_IDENTITY_TYPE_ECDSA) ?
                 "ECDSA" : "EdDSA");

    }
  }
  GNUNET_free (privs);
  GNUNET_free (s);
}


/**
 * Main function that will be run by the scheduler.
 *
 * @param cls closure
 * @param args remaining command-line arguments
 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
 * @param cfg configuration
 */
static void
run (void *cls,
     char *const *args,
     const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *cfg)
{
  if ((NULL != set_subsystem) && (NULL == set_ego))
  {
    fprintf (stderr, "Option -s requires option -e to be specified as well.\n");
    return;
  }

  if ((NULL != read_msg) && (NULL == set_ego))
  {
    fprintf (stderr,
             "Option -R requires options -e to be specified as well.\n");
    return;
  }

  if ((NULL != write_msg) && (NULL == pubkey_msg))
  {
    fprintf (stderr, "Option -W requires option -k to be specified as well.\n");
    return;
  }
  sh = GNUNET_IDENTITY_connect (cfg,
                                (monitor | list) ||
                                (NULL != set_ego) ||
                                (NULL != set_subsystem)
                                ? &print_ego
                                : NULL,
                                NULL);
  if (NULL != write_msg)
  {
    write_encrypted_message ();
    GNUNET_free (write_msg);
    write_msg = NULL;
  }
  // read message is handled in ego callback (print_ego)
  if (NULL != delete_ego)
    delete_op =
      GNUNET_IDENTITY_delete (sh,
                              delete_ego,
                              &delete_finished,
                              &delete_op);
  if (NULL != create_ego)
  {
    if (NULL != privkey_ego)
    {
      GNUNET_STRINGS_string_to_data (privkey_ego,
                                     strlen (privkey_ego),
                                     &pk,
                                     sizeof(struct
                                            GNUNET_IDENTITY_PrivateKey));
      create_op =
        GNUNET_IDENTITY_create (sh,
                                create_ego,
                                &pk,
                                0, // Ignored
                                &create_finished,
                                &create_op);
    }
    else
      create_op =
        GNUNET_IDENTITY_create (sh,
                                create_ego,
                                NULL,
                                (type_eddsa) ?
                                GNUNET_IDENTITY_TYPE_EDDSA :
                                GNUNET_IDENTITY_TYPE_ECDSA,
                                &create_finished,
                                &create_op);
  }
  GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
                                 NULL);
  test_finished ();
}


/**
 * The main function.
 *
 * @param argc number of arguments from the command line
 * @param argv command line arguments
 * @return 0 ok, 1 on error
 */
int
main (int argc, char *const *argv)
{
  struct GNUNET_GETOPT_CommandLineOption options[] = {
    GNUNET_GETOPT_option_string ('C',
                                 "create",
                                 "NAME",
                                 gettext_noop ("create ego NAME"),
                                 &create_ego),
    GNUNET_GETOPT_option_string ('D',
                                 "delete",
                                 "NAME",
                                 gettext_noop ("delete ego NAME "),
                                 &delete_ego),
    GNUNET_GETOPT_option_string ('P',
                                 "privkey",
                                 "PRIVATE_KEY",
                                 gettext_noop (
                                   "set the private key for the identity to PRIVATE_KEY (use together with -C)"),
                                 &privkey_ego),
    GNUNET_GETOPT_option_string ('R',
                                 "read",
                                 "MESSAGE",
                                 gettext_noop (
                                   "Read and decrypt message encrypted for the given ego (use together with -e EGO)"),
                                 &read_msg),
    GNUNET_GETOPT_option_string ('W',
                                 "write",
                                 "MESSAGE",
                                 gettext_noop (
                                   "Encrypt and write message for recipient identity PULBIC_KEY, (use together with -k RECIPIENT_PUBLIC_KEY)"),
                                 &write_msg),
    GNUNET_GETOPT_option_flag ('X',
                               "eddsa",
                               gettext_noop (
                                 "generate an EdDSA identity. (use together with -C) EXPERIMENTAL"),
                               &type_eddsa),
    GNUNET_GETOPT_option_flag ('d',
                               "display",
                               gettext_noop ("display all egos"),
                               &list),
    GNUNET_GETOPT_option_flag ('q',
                               "quiet",
                               gettext_noop ("reduce output"),
                               &quiet),
    GNUNET_GETOPT_option_string (
      'e',
      "ego",
      "NAME",
      gettext_noop (
        "set default identity to NAME for a subsystem SUBSYSTEM (use together with -s), restrict results to NAME (use together with -d) or read and decrypt a message for NAME (use together with -R)"),
      &set_ego),
    GNUNET_GETOPT_option_string ('k',
                                 "key",
                                 "PUBLIC_KEY",
                                 gettext_noop (
                                   "The public key of the recipient (with -W)"),
                                 &pubkey_msg),
    GNUNET_GETOPT_option_flag ('m',
                               "monitor",
                               gettext_noop ("run in monitor mode egos"),
                               &monitor),
    GNUNET_GETOPT_option_flag ('p',
                               "private-keys",
                               gettext_noop ("display private keys as well"),
                               &private_keys),
    GNUNET_GETOPT_option_string (
      's',
      "set",
      "SUBSYSTEM",
      gettext_noop (
        "set default identity to EGO for a subsystem SUBSYSTEM (use together with -e)"),
      &set_subsystem),
    GNUNET_GETOPT_option_verbose (&verbose),
    GNUNET_GETOPT_OPTION_END
  };
  int res;

  if (GNUNET_OK !=
      GNUNET_STRINGS_get_utf8_args (argc, argv,
                                    &argc, &argv))
    return 4;
  global_ret = TIMEOUT_STATUS_CODE; /* timeout */
  res = GNUNET_PROGRAM_run (argc,
                            argv,
                            "gnunet-identity",
                            gettext_noop ("Maintain egos"),
                            options,
                            &run,
                            NULL);
  GNUNET_free_nz ((void *) argv);

  if (GNUNET_OK != res)
    return 3;
  return global_ret;
}


/* end of gnunet-identity.c */
