/*
    Silky - A GTK+ client for SILC.
    Copyright (C) 2003,2004 Toni Willberg

    - SILC keys management functions

    This program 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 2 of the License, or
    (at your option) any later version.

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

    http://silky.sourceforge.net/

*/

#include <glib.h>
#include <gtk/gtk.h>
#include <glade/glade.h>
#include <silcincludes.h>
#include <silcclient.h>
#include <sys/types.h>
#include <dirent.h>

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif


#include "support.h"
#include "keys.h"
#include "files.h"
#include "buddylist.h"
#include "ui_console.h"
#include "gui.h"
#include "timer.h" /* silky_progressbar_update() */
#include "log.h"

#include "common.h"

extern silkyStruct *silky;

extern gchar *silkydir;

extern gchar *pubkey; /* own key */
extern gchar *privkey; /* own key */
extern gchar *silkypath; /* from main.c */
extern GladeXML *xmlmain;




/* (internal) Utility function to check if received public key is already stored.
 * Returns TRUE if key exists, FALSE if key is yet unknown.
 */
gboolean _silky_check_dir_for_key(const char * dir, SilcPublicKey pkey) {
  DIR *pubkeydir;
  struct dirent *keytypename = NULL;
  SilcPublicKey lkey;
  struct stat sb;
  int ret = 0;
  char fpath[256];
  gboolean try;

  debug("Checking for peer's existing SILC public key from '%s'...", dir);
	
  /* Open directory for reading */
  pubkeydir = opendir(dir);
  if (pubkeydir == NULL) {
    debug("no such dir");
    return FALSE;
  }

  /* Init keytypename so it's not NULL */
  keytypename = readdir(pubkeydir);

  /* Read whole directory */
  while (keytypename != NULL) {
    keytypename = readdir(pubkeydir);
    if (keytypename != NULL) {
      /* Create path to file */
      memset(fpath, 0, 256);
      g_snprintf(fpath, 256, "%s%c%s", dir, DIR_SEPARATOR, keytypename->d_name);

      debug("statting '%s'", fpath);

      /* Check filetype */
#ifdef SILKY_WIN32
      ret = stat(fpath, &sb);
#else
      ret = lstat(fpath, &sb);
#endif
      if (S_ISREG(sb.st_mode)) {
        /* Regular file, let's try to load a SILC public key */
        try = silc_pkcs_load_public_key(fpath, &lkey, SILC_PKCS_FILE_PEM);
        if (!try)
          try = silc_pkcs_load_public_key(fpath, &lkey, SILC_PKCS_FILE_BIN);

        if (try) {
	  debug("found key");
          /* Okay, it really was a SILC public key */
          if(silc_pkcs_public_key_compare(pkey, lkey)) {
            debug("Key %s matches", fpath);
            closedir(pubkeydir);
            return TRUE;
          } else {
	    debug("key %s doesn't match", fpath);
	  }
	  
        } else {
	  debug("didn't find key");
	}
	
      }
    }
  }

  closedir(pubkeydir);
  debug("Received unknown key");
  return FALSE;
}


/* (internal) Utility function to check if received public key is already stored.
 * Returns TRUE if key exists, FALSE if key is yet unknown.
 */
gboolean silky_known_server_key(SilcPublicKey pkey) {
  gboolean ret;
  char pkdir[256];
  memset(pkdir, 0, 256);
  snprintf(pkdir, 256, "%s%cserverkeys", silkydir, DIR_SEPARATOR);
  ret = _silky_check_dir_for_key(pkdir, pkey);
  return ret;
}

gboolean silky_known_client_key(SilcPublicKey pkey) {
  gboolean ret;
  char pkdir[256];
  memset(pkdir, 0, 256);
  snprintf(pkdir, 256, "%s%cclientkeys", silkydir, DIR_SEPARATOR);
  ret = _silky_check_dir_for_key(pkdir, pkey);
  return ret;
}


/* this is called when generating keys, as a separate thread */

void silky_keygen (SilkySilcKey *key) {

  gint ret;

  /* create the key */
  debug("silky_keygen [thread]");
  debug("calling silc_create_key_pair()");
  ret = silc_create_key_pair(key->cipher, key->length,
			     pubkey, privkey, /* global variables/pointers */
			     key->identifier, key->passphrase,
			     &silky->client->pkcs,
			     &silky->client->public_key,
			     &silky->client->private_key, FALSE);
  debug("returned from silc_create_key_pair()");

  if (ret) {
    debug("success");
  }
  else {
    debug("failed");
  }

  debug("setting key->ready = TRUE");
  key->ready = TRUE; /* tell the caller we are ready */


  debug("calling g_thread_exit()");

  /* commit suicide */
  g_thread_exit(&ret);
}


/*
  Asks user if he wants to accept public key,
  and saves it
*/

gboolean silky_save_public_key(SilcClient client, SilcClientConnection conn,
                                const char *name, SilcSocketType conn_type,
                                unsigned char *pk, SilcUInt32 pk_len,
                                SilcSKEPKType pk_type,
                                SilcVerifyPublicKey completion, void *context)
{
  const gchar *keyfile;

  GtkLabel *accept_server_key_label;
  GtkWidget *dialog;
  gchar *fingerprint = NULL;
  gchar *babbleprint = NULL;
  gint result;

  SilcPublicKey pubkey;
  unsigned char *encpk;
  char *keytypename = NULL;


  /*
    Verify the key type
  */
  if (pk_type != SILC_SKE_PK_TYPE_SILC) {
    printconsole(_("Received unknown key type."));
    if (completion) {
      completion(FALSE, context);
    }
    return FALSE;
  }


  /*
    Type of the key entity
  */
  if  (conn_type == SILC_SOCKET_TYPE_SERVER || conn_type == SILC_SOCKET_TYPE_ROUTER) {
    keytypename = strdup("server");
  }

  if (conn_type == SILC_SOCKET_TYPE_CLIENT) {
    keytypename = strdup("client");
  }

  debug("key type: %s (%d)", keytypename, conn_type);

  /* replace spaces with underscores */
  fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
  //  fingerprint = g_strdelimit(fingerprint, " ", '_');

  /* construct keyfile path */
  keyfile = gen_keyfile_path(fingerprint, keytypename);
  //  keyfile = g_strdup_printf("%s%c%skeys%c%skey_%s.pub",  silkydir, DIR_SEPARATOR, keytypename, DIR_SEPARATOR, keytypename, fingerprint);

  g_free(fingerprint);

  /* Check whether this key already exists */
  if ( !g_file_test(keyfile, G_FILE_TEST_IS_REGULAR)) { /* require a regular file, not symlink */
    /* no such file */

    /* Decode new key */
    if ( !silc_pkcs_public_key_decode(pk, pk_len, &pubkey) ) {
      printconsole(_("Could not decode received public key."));
      return FALSE;
    }

    /* Encode new key */
    encpk = silc_pkcs_public_key_encode(pubkey, &pk_len);
    if (!encpk) {
      printconsole(_("Could not encode received public key."));
      silc_pkcs_public_key_free(pubkey);
      return FALSE;
    }
    else {

      /* does the user accept the key, before saving? */

      /* create dialog */
      dialog = glade_xml_get_widget (xmlmain, "dialog_accept_new_key");

      /* add text to label */
      accept_server_key_label = GTK_LABEL(glade_xml_get_widget (xmlmain, "accept_new_key_text"));

      fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
      babbleprint = silc_hash_babbleprint(NULL, pk, pk_len);
      gtk_label_set_text(GTK_LABEL(accept_server_key_label), g_strdup_printf("%s\n%s\n", fingerprint, babbleprint) );
      g_free(fingerprint);
      g_free(babbleprint);

      /* show the dialog, blocking UI */
      debug("showing dialog...");

      gdk_threads_enter();
      result = gtk_dialog_run (GTK_DIALOG(dialog));
      gdk_threads_leave();

      debug("returned from the dialog");

      gtk_widget_hide (GTK_WIDGET(dialog));

      if (result == GTK_RESPONSE_OK) {

	debug("user accepted, saving '%s'", keyfile);
	silc_pkcs_save_public_key(keyfile, pubkey, SILC_PKCS_FILE_PEM);
	silc_pkcs_public_key_free(pubkey);

	printconsole(_("Key saved.")); /* was it? */

	buddy_set_keyverified_by_fp(fingerprint, 1); /* set buddylist parameter */
	return TRUE;
      }
      else {

	debug("user didn't accept the key, not saved");
	buddy_set_keyverified_by_fp(fingerprint, -1); /* set buddylist parameter */

	silc_pkcs_public_key_free(pubkey);
	return FALSE;
      }


    } /* if (!encpk) */


  }
  else {
    /* key exists, now verify it */

    /* compare existing keys */
      //      bool silc_pkcs_public_key_compare(SilcPublicKey key1, SilcPublicKey key2);

  
    debug("known key");
    printconsole(_("This key is already saved."));

    /* FIXME: verify that the file matches the key! */
    buddy_set_keyverified_by_fp(fingerprint, 1); /* set buddylist parameter */

    return TRUE;
  }
}


/*
 generates full path to a keyfile of given fingerprint
*/
const gchar *gen_keyfile_path(const gchar *fingerprint, const gchar *keytype) {
  const gchar *keyfile;
  gchar *_fingerprint = g_strdup(fingerprint); /* we need a local copy */
  
  g_strdelimit(_fingerprint, " ", '_');
  keyfile = g_strdup_printf("%s%c%skeys%c%skey_%s.pub",  silkydir, DIR_SEPARATOR, keytype, DIR_SEPARATOR, keytype, _fingerprint);
  debug("keyfile path: '%s'", keyfile);
  g_free(_fingerprint);

  return keyfile;
}


/*
  runs after user fills fields to the keygen form,
*/

gint silky_keygen_ui () {

  guint progress_timeout;
  GtkEntry *ekey_name;
  GtkEntry *ekey_email;
  gchar *key_passphrase1;
  GtkEntry *ekey_passphrase1;
  gchar *key_passphrase2;
  GtkEntry *ekey_passphrase2;
  GtkWidget *dialog_keygen_progressbar;
  GtkWidget *keygen_progressbar;
  GThread *thread_id;
  SilkySilcKey key;

  //  gtk_dialog_run(GTK_DIALOG(glade_xml_get_widget (xmlmain, "dialog_firstrun_manual")));
/* fixme; check ret and return -1 if user wants to cancel! */

  /* hide the dialogs */
  //  gtk_widget_hide(glade_xml_get_widget (xmlmain, "dialog_firstrun"));
  // gtk_widget_hide(glade_xml_get_widget (xmlmain, "dialog_firstrun_manual"));


  /* get user's data */
  ekey_name = GTK_ENTRY(glade_xml_get_widget (xmlmain, "fr_key_name"));
  key.name = strdup(gtk_entry_get_text(ekey_name));

  ekey_email = GTK_ENTRY(glade_xml_get_widget (xmlmain, "fr_key_email"));
  key.email = strdup(gtk_entry_get_text(ekey_email));

  ekey_passphrase1 = GTK_ENTRY(glade_xml_get_widget (xmlmain, "fr_key_passphrase1"));
  key_passphrase1 = strdup(gtk_entry_get_text(ekey_passphrase1));

  ekey_passphrase2 = GTK_ENTRY(glade_xml_get_widget (xmlmain, "fr_key_passphrase2"));
  key_passphrase2 = strdup(gtk_entry_get_text(ekey_passphrase2));

  if (!strcmp (key_passphrase1, key_passphrase2) == 0) {
    debug("pass wrong");
    errordialog(_("Passphrases don't match! Try again."));
    memset(key_passphrase1, 0, sizeof(key_passphrase1));
    memset(key_passphrase2, 0, sizeof(key_passphrase2));
    free(key_passphrase1); free(key_passphrase2);

    return 0; /* go back to idle mode, gtk will call us again */

  }
  else {
    debug("pass equals, continuing");
  }

  key.passphrase = g_strdup(key_passphrase1);

  memset(key_passphrase1, 0, sizeof(key_passphrase1));
  memset(key_passphrase2, 0, sizeof(key_passphrase2));
  free(key_passphrase1); free(key_passphrase2);


  /* generate identifier, 'RN=real name, HN=localhost, E=email@address' */
  key.identifier = g_strconcat("RN=", key.name, ", HN=localhost, E=", key.email , NULL);
  debug("Using identifier: '%s'", key.identifier);

  /* verify that the keys are there */
  if (!pubkey || !privkey) {
    g_error("can't find the key file names!");
    /* aborts */
  }


  /* show the progress dialog */
  keygen_progressbar = glade_xml_get_widget (xmlmain, "keygen_progressbar");
  progress_timeout = gtk_timeout_add(50, (GSourceFunc)silky_progressbar_update, keygen_progressbar); /* update the bar every X second */
  dialog_keygen_progressbar = glade_xml_get_widget (xmlmain, "dialog_keygen_progress");


  key.length = (gint)g_strtod(gtk_entry_get_text(GTK_ENTRY(glade_xml_get_widget (xmlmain, "fr_key_length"))), NULL);
  key.cipher = g_strdup("rsa"); /* fixme; put this into the ui */

  debug("calling g_thread_create(silky_keygen)");
  thread_id = g_thread_create( (GThreadFunc)silky_keygen, &key, TRUE, NULL);

  /* this will block until silky_keygen() enables the OK button in the dialog */
  debug("showing progress dialog");
  /* I18N This is a window title */
  gtk_window_set_title(GTK_WINDOW(dialog_keygen_progressbar), g_strdup_printf("Silky v%s : %s", SILKY_VERSION, _("Generating SILC keys...")));
  gtk_widget_show_all (dialog_keygen_progressbar);

  /* we sit here and wait */
  while (key.ready != TRUE) {
    gtk_main_iteration_do(FALSE); /* give GTK some CPU time, non-blocking call */
  }

  debug("key.ready was set TRUE");

  /* stop updating the bar */
  gtk_timeout_remove(progress_timeout);

  if (!GTK_IS_PROGRESS_BAR(keygen_progressbar)) {
    /* this happens if user destroys the progress dialog while we generate the key,
       quitting */
    debug("progress dialog destroyed by user, returning -1");
    return -1;
  }


  /* set text and fraction */
  gtk_progress_bar_set_text( GTK_PROGRESS_BAR(keygen_progressbar), N_("Ready! Click OK to continue."));
  gtk_progress_bar_set_fraction( GTK_PROGRESS_BAR(keygen_progressbar), 1 ); /* set 100% */
  gtk_widget_set_sensitive(glade_xml_get_widget (xmlmain, "button_keygen_ok"), TRUE);

  debug("now waiting for the user");
  while (gtk_events_pending ()) {
    gtk_main_iteration ();
  }

  /* lets wait until user presses OK button */
  gtk_dialog_run (GTK_DIALOG (dialog_keygen_progressbar));

  debug("dialog returned");


  /* hide the dialog window */
  gtk_widget_hide(dialog_keygen_progressbar); /* get rid of the window */


  return 1;
}


syntax highlighted by Code2HTML, v. 0.9.1