/* OpenVAS-Client
 * $Id$
 * Description: LSC Credentials management - module to create and unlink key
 *              files.
 *
 * Authors:
 * Felix Wolfsteller <felix.wolfsteller@intevation.de>
 *
 * Copyright:
 * Copyright (C) 2008 Intevation GmbH
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2,
 * or, at your option, any later version as published by the Free
 * Software Foundation
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * In addition, as a special exception, you have
 * permission to link the code of this program with the OpenSSL
 * library (or with modified versions of OpenSSL that use the same
 * license as OpenSSL), and distribute linked combinations including
 * the two. You must obey the GNU General Public License in all
 * respects for all of the code used other than OpenSSL. If you
 * modify this file, you may extend this exception to your version
 * of the file, but you are not obligated to do so. If you do not
 * wish to do so, delete this exception statement from your version.
 */

#include "openvas_ssh_login.h"
#include "openvas_ssh_key_create.h"
#include "file_utils.h"
#include "nessus_i18n.h"
#include "error_dlg.h"
#include <glib/gstdio.h>
#include "context.h"
#include "includes.h" // For stdio (function: remove)

/**
 * @brief Creates a private key for local checks.
 * 
 * Forks and creates a key for local checks by calling
 * "openssl pkcs8 -topk8 -v2 des3 -in filepath -passin pass:passphrase -out
 *          filepath.p8 -passout pass:passphrase"
 * Directories within privkey_file will be created if they do not exist.
 * 
 * @param pubkey_file Path to file of public key (a trailing .pub will be stripped).
 * @param privkey_file Name of private key file to be created.
 * 
 * @param passphrase_pub The passphrase for the public key.
 * @param passphrase_priv Passhprase for the private key.
 * 
 * @return TRUE if successfull, FALSE otherwise.
 */
static
gboolean openvas_ssh_privkey_create(char* pubkey_file, char* privkey_file,
                                    char* passphrase_pub, char* passphrase_priv)
{
  gchar* astdout = NULL;
  gchar* astderr = NULL;
  GError* err    = NULL;
  gint exit_status;
  gchar* dir = NULL;
  gchar* pubkey_stripped = NULL;

  /* Sanity-check essential parameters */
  if(!passphrase_pub || !passphrase_priv)
    {
      show_error(_("Error creating private key file:\nPlease provide all information."));
      return FALSE;
    }

  /* Sanity check files */
  if(check_exists(pubkey_file) != 1)
    {
      show_error(_("Error creating private key file:\nPublic key %s not found."), pubkey_file);
      return FALSE;
    }
  if(check_exists(privkey_file) != 0 )
    {
      show_error(_("Error creating private key file:\nFile already exists."));
      return FALSE;
    }
  dir = g_path_get_dirname(privkey_file);
  if(file_utils_ensure_dir(dir) != TRUE)
    {
      show_error(_("Error creating private key file:\nfolder %s not accessible."), dir);
      g_free (dir);
      return FALSE;
    }
  g_free (dir);

  // Strip ".pub" of public key filename, if any.
  if (g_str_has_suffix(pubkey_file, ".pub") == TRUE)
    {
      pubkey_stripped = g_malloc (strlen(pubkey_file) -
                                  strlen(".pub") +1); /* RATS: ignore, string literal is nul-terminated */
      g_strlcpy (pubkey_stripped, pubkey_file, strlen(pubkey_file) -
                 strlen(".pub") + 1); /* RATS: ignore, string literal is nul-terminated */
    }
  else
    pubkey_stripped = g_strdup(pubkey_file);

  /* Fire openssl */
  const gchar* command = g_strconcat ("openssl pkcs8 -topk8 -v2 des3 -in ", pubkey_stripped,
                                     " -passin pass:", passphrase_pub, " -out ",
                                     privkey_file, " -passout pass:",
                                     passphrase_priv, NULL);
  g_free (pubkey_stripped);

  if (g_spawn_command_line_sync(command, &astdout, &astderr, &exit_status, &err) == FALSE
      || exit_status != 0 )
    {
      show_error (_("Error creating private key file.\nFor further information consult your shell."));
      printf ("Error creating private key file.");
      printf ("\tSpawned openssl process returned with %d.\n", exit_status);
      printf ("\t\t stdout: %s\n", astdout);
      printf ("\t\t stderr: %s\n", astderr);
      return FALSE;
    }

  return TRUE;
}

/**
 * Forks and creates a key for local checks by calling
 * "ssh-keygen -t rsa -f filepath -C comment -P passhprase -q"
 * A directory will be created if it does not exist.
 * 
 * @param comment Comment to use (will be freed).
 * @param passphrase The passphrase for the key (will be freed), must be longer
 *                   than 4 characters (+nul).
 * @param filepath Path to file of public key (a trailing .pub will be stripped).
 * 
 * @return TRUE if successfull, FALSE otherwise.
 */
static
gboolean openvas_ssh_pubkey_create (const char* comment, char* passphrase,
                                    char* filepath)
{
  gchar* astdout = NULL;
  gchar* astderr = NULL;
  GError* err = NULL;
  gint exit_status;
  gchar* dir;
  gchar* file_pubstripped;

  /* Sanity-check essential parameters */
  if (!comment || comment[0] == '\0')
    {
      show_error (_("Error creating public key file:\ncomment has to be set."));
      return FALSE;
    }
  if (!passphrase || strlen(passphrase) < 5)
    {
      show_error (_("Error creating public key file:\npassword must be longer than 4 characters."));
      return FALSE;
    }
  /* Sanity check files */
  dir = g_path_get_dirname (filepath);
  if (file_utils_ensure_dir(dir) != TRUE)
    {
      show_error (_("Error creating public key file:\n%s is not accessable."), filepath);
      g_free (dir);
      return FALSE;
    }
  g_free (dir);
  if (check_exists(filepath) == 1)
  {
    show_error (_("Error creating public key file:\n%s already exists."), filepath);
    return FALSE;
  }

  // Strip ".pub" of filename, if any.
  if (g_str_has_suffix(filepath, ".pub") == TRUE)
    {
      file_pubstripped = g_malloc(strlen(filepath) -
                                  strlen(".pub") +1); /* RATS: ignore, string literal is nul-terminated */
      g_strlcpy (file_pubstripped, filepath, strlen(filepath) -
                 strlen(".pub") + 1); /* RATS: ignore, string literal is nul-terminated */
    }
  else
    file_pubstripped = g_strdup(filepath);

  /* Fire ssh-keygen */
  const char* command = g_strconcat("ssh-keygen -t rsa -f ", file_pubstripped, " -C ",
                                    comment, " -P ", passphrase, NULL);
  g_free (file_pubstripped);

  if (g_spawn_command_line_sync(command, &astdout, &astderr, &exit_status, &err) == FALSE
      || exit_status != 0 )
    {
      show_error (_("Error creating public key file.\nFor further information consult your shell."));
      printf ("Error creating public key file.\n");
      printf ("\tSpawned key-gen process returned with %d.\n", exit_status);
      printf ("\t\t stdout: %s", astdout);
      printf ("\t\t stderr: %s", astderr);
      return FALSE;
    }
  return TRUE;
}


/**
 * @brief Creates the public and private key files.
 * 
 * @param loginfo.
 * @return TRUE if things went good, FALSE if things went bad.
 */
gboolean openvas_ssh_key_create(openvas_ssh_login* loginfo)
{
  /* Create pubkey */
  gboolean success = openvas_ssh_pubkey_create (loginfo->comment, 
                                                loginfo->ssh_key_passphrase,
                                                loginfo->public_key_path);

  /* Eventually report failure */
  if (success == FALSE)
    return FALSE;

  /* Create private key */
  success = openvas_ssh_privkey_create (loginfo->public_key_path,
                                        loginfo->private_key_path,
                                        loginfo->ssh_key_passphrase,
                                        loginfo->ssh_key_passphrase);
  return success;
}

/**
 * @brief Unlinks pub. and private key files + identity file.
 * 
 * @param loginfo Login of which to unlink files.
 */
void
openvas_ssh_key_create_unlink_files (openvas_ssh_login* loginfo)
{
  char* identity_file = NULL;

  if (loginfo == NULL)
    return;

  // Create identity file path
  if (loginfo->public_key_path != NULL)
    {
      int len = (strlen(loginfo->public_key_path) -
                 strlen (".pub") + 1); /* RATS: ignore, string literal is nul-terminated */
      if (len > 0)
        {
          identity_file = emalloc (len);
          g_strlcpy (identity_file, loginfo->public_key_path, len);
        }

      // Delete all the files
      unlink (identity_file);
      unlink (loginfo->private_key_path);
      unlink (loginfo->public_key_path);
    }

  efree (&identity_file);
}

/**
 * @brief Unlinks the pub and private key + identity files, removes
 *        Global->sshkeys entry for a single login and frees the memory.
 * 
 * If you want to 'harden' this delete, make sure that you rewrite the .login
 * file.
 * 
 * @param loginfo Login of which to unlink files and free memory.
 */
void
openvas_ssh_key_create_undo (openvas_ssh_login* loginfo)
{
  if (loginfo == NULL)
    return;

  openvas_ssh_key_create_unlink_files (loginfo);
  // Free associated memory.
  g_hash_table_remove (Global->sshkeys, loginfo->name);
}
