/*
 * otpCalc - A one time password calculator.
 *
 * Copyright (C) 2001 Anthony D. Urso
 * Copyright 2006-2021 Gentoo Authors
 *
 * 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, 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.
 *
 */

#include <gtk/gtk.h>

#include "window.h"
#include "callbacks.h"
#include "crypto.h"
#include "macros.h"
#include "utility.h"

unsigned short hash;
unsigned short newline = 0;

struct _OtpcalcWindow
{
	GtkApplicationWindow parent_instance;

	GtkWidget *te_challenge;
	GtkWidget *te_passwd;
	GtkWidget *te_response;

	gboolean autopaste;
};

G_DEFINE_TYPE (OtpcalcWindow, otpcalc_window, GTK_TYPE_APPLICATION_WINDOW)

static void
calculate_cb (OtpcalcWindow *self)
{
	void (*hashes[5]) (unsigned char *, unsigned int);

	gchar *challenge, *passwd, *message, *response;
	struct tokens *set;
	unsigned short i;
	unsigned char results[9];


	hashes[SKEY] = md5lite;
	hashes[MD4] = md4lite;
	hashes[MD5] = md5lite;
	hashes[RMD160] = rmd160lite;
	hashes[SHA1] = sha1lite;

	challenge = gtk_editable_get_chars (GTK_EDITABLE (self->te_challenge),
					    0, -1);
	set = g_malloc0 (sizeof (struct tokens));
	set->seed = g_malloc (strlen (challenge));

	if (!parse (challenge, set)) {
		gtk_entry_set_text (GTK_ENTRY (self->te_response),
				    "invalid challenge");
		g_free (challenge);
		g_free (set->seed);
		g_free (set);
		return;
	}

	g_free (challenge);

	passwd = gtk_editable_get_chars (GTK_EDITABLE (self->te_passwd), 0, -1);
	if (set->alg != SKEY) {
		gchar *tmp;

		tmp = g_utf8_strdown (set->seed, -1);
		g_free (set->seed);
		set->seed = tmp;
	}
	message = g_strconcat (set->seed, passwd, NULL);

	g_free (set->seed);
	g_free (passwd);

	(*hashes[set->alg]) ((unsigned char *)message, strlen (message));
	memcpy (results, message, 8);
	g_free (message);

	for (i = 0; i < set->seq; i++)
		(*hashes[set->alg]) (results, 8);

	g_free (set);

	results[8] = parity (results);

	response = g_malloc (31);
	sixwords (results, response);
	gtk_entry_set_text (GTK_ENTRY (self->te_response), response);
	g_free (response);
	gtk_editable_select_region (GTK_EDITABLE (self->te_response), 0, -1);
}

static void
clear_cb (OtpcalcWindow *self)
{
	gtk_editable_delete_text (GTK_EDITABLE (self->te_challenge), 0, -1);
	gtk_editable_delete_text (GTK_EDITABLE (self->te_passwd), 0, -1);
	gtk_editable_delete_text (GTK_EDITABLE (self->te_response), 0, -1);
}

static gboolean
focus_in_cb (OtpcalcWindow *self,
             GdkEvent      *event,
             gpointer       data)
{
	static GdkAtom targets;

	if (!self->autopaste)
		return FALSE;

	targets = gdk_atom_intern ("STRING", FALSE);

	gtk_selection_convert (GTK_WIDGET (self),
			       GDK_SELECTION_PRIMARY,
			       targets,
			       GDK_CURRENT_TIME);

	return FALSE;
}

static void
selector_cb (OtpcalcWindow *self,
	     GtkSelectionData *selection,
	     GtkWidget *widget)
{
	struct tokens *set;
	unsigned short parsed;

	if (gtk_selection_data_get_length (selection) < 1)
		return;

	set = g_malloc (sizeof (struct tokens));
	set->seed = g_malloc (gtk_selection_data_get_length (selection));

	parsed = parse ((const char *)gtk_selection_data_get_data (selection),
			set);

	g_free (set->seed);
	g_free (set);

	if (!parsed)
		return;

	gtk_entry_set_text (GTK_ENTRY (self->te_challenge),
			    (const gchar *)gtk_selection_data_get_data (selection));
}


OtpcalcWindow *
otpcalc_window_new (void)
{
	return g_object_new (OTPCALC_TYPE_WINDOW,
			     "application", g_application_get_default (),
			     NULL);
}

static void
otpcalc_window_class_init (OtpcalcWindowClass *klass)
{
}

static void
otpcalc_window_init (OtpcalcWindow *self)
{
	GtkWidget *gr_top, *la_challenge, *bu_calc, *la_passwd,
		  *bu_clear, *mb_top, *mi_file, *me_file, *mi_file_quit,
		  *mi_sett, *me_sett, *mi_sett_rmd160, *mi_sett_sha1,
		  *mi_sett_md5, *mi_sett_md4, *mi_sett_skey, *mi_help,
		  *me_help, *mi_help_about;
	GtkAccelGroup *accel_group;
	GSList *hashes = NULL;

	self->autopaste = TRUE;

	/* Setup the main window... */
	gtk_window_set_title (GTK_WINDOW (self), "otpCalc");
	gtk_window_set_resizable (GTK_WINDOW (self), FALSE);
	gtk_window_set_role (GTK_WINDOW (self), "Calc");

	g_signal_connect (self,
			  "focus-in-event",
			  G_CALLBACK (focus_in_cb),
			  NULL);

	g_signal_connect (self,
			  "selection-received",
			  G_CALLBACK (selector_cb),
			  NULL);
	/* Done. */


	/* Setup the accelrator group... */
	accel_group = gtk_accel_group_new ();
	gtk_window_add_accel_group (GTK_WINDOW (self), accel_group);
	/* Done. */


	/* Setup the main container... */
	gr_top = gtk_grid_new ();
	gtk_container_add (GTK_CONTAINER (self), gr_top);
	gtk_widget_show (gr_top);
	/* Done. */


	/* Setup the file menu... */
	me_file = gtk_menu_new ();
	mi_file_quit = gtk_menu_item_new_with_label ("Quit");
	gtk_menu_shell_append (GTK_MENU_SHELL (me_file), mi_file_quit);
	g_signal_connect_swapped (G_OBJECT (mi_file_quit),
				  "activate",
				  G_CALLBACK (g_application_quit),
				  g_application_get_default ());

	gtk_widget_add_accelerator (mi_file_quit, "activate", accel_group,
				    0x071, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);

	gtk_widget_show (mi_file_quit);
	/* Done. */


	/* Setup the hash menu... */
	me_sett = gtk_menu_new ();

	mi_sett_sha1 = gtk_radio_menu_item_new_with_label (hashes, "sha1");
	gtk_menu_shell_append (GTK_MENU_SHELL (me_sett), mi_sett_sha1);
	g_signal_connect_swapped (G_OBJECT (mi_sett_sha1), "activate",
				  G_CALLBACK (sethash), (void *)SHA1);
	hashes = gtk_radio_menu_item_get_group (
		GTK_RADIO_MENU_ITEM (mi_sett_sha1));
	gtk_widget_show (mi_sett_sha1);

	mi_sett_rmd160 = gtk_radio_menu_item_new_with_label (hashes, "rmd160");
	gtk_menu_shell_append (GTK_MENU_SHELL (me_sett), mi_sett_rmd160);
	g_signal_connect_swapped (G_OBJECT (mi_sett_rmd160), "activate",
				  G_CALLBACK (sethash), (void *)RMD160);
	hashes = gtk_radio_menu_item_get_group (
		GTK_RADIO_MENU_ITEM (mi_sett_rmd160));
	gtk_widget_show (mi_sett_rmd160);

	mi_sett_md5 = gtk_radio_menu_item_new_with_label (hashes, "md5");
	gtk_menu_shell_append (GTK_MENU_SHELL (me_sett), mi_sett_md5);
	g_signal_connect_swapped (G_OBJECT (mi_sett_md5), "activate",
				  G_CALLBACK (sethash), (void *)MD5);
	hashes = gtk_radio_menu_item_get_group (
		GTK_RADIO_MENU_ITEM (mi_sett_md5));
	gtk_widget_show (mi_sett_md5);

	gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mi_sett_md5),
					TRUE);

	mi_sett_md4 = gtk_radio_menu_item_new_with_label (hashes, "md4");
	gtk_menu_shell_append (GTK_MENU_SHELL (me_sett), mi_sett_md4);
	g_signal_connect_swapped (G_OBJECT (mi_sett_md4), "activate",
				  G_CALLBACK (sethash), (void *)MD4);
	hashes = gtk_radio_menu_item_get_group (
		GTK_RADIO_MENU_ITEM (mi_sett_md4));
	gtk_widget_show (mi_sett_md4);

	mi_sett_skey = gtk_radio_menu_item_new_with_label (hashes, "s/key");
	gtk_menu_shell_append (GTK_MENU_SHELL (me_sett), mi_sett_skey);
	g_signal_connect_swapped (G_OBJECT (mi_sett_skey), "activate",
				  G_CALLBACK (sethash), (void *)SKEY);
	hashes = gtk_radio_menu_item_get_group (
		GTK_RADIO_MENU_ITEM (mi_sett_skey));
	gtk_widget_show (mi_sett_skey);
	/* Done. */


	/* Setup the help menu... */
	me_help = gtk_menu_new ();
	mi_help_about = gtk_menu_item_new_with_label ("About...");
	gtk_menu_shell_append (GTK_MENU_SHELL (me_help), mi_help_about);
	g_signal_connect (G_OBJECT (mi_help_about), "activate",
			  G_CALLBACK (about), self);
	gtk_widget_show (mi_help_about);
	/* Done. */


	/* Setup the menu bar... */
	mb_top = gtk_menu_bar_new ();
	/*gtk_menu_bar_set_shadow_type (GTK_MENU_BAR (mb_top), GTK_SHADOW_NONE);*/
	gtk_grid_attach (GTK_GRID (gr_top), mb_top, 0, 0, 3, 1);
	gtk_widget_show (mb_top);

	mi_file = gtk_menu_item_new_with_label ("File");
	gtk_widget_show (mi_file);
	gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi_file), me_file);
	gtk_menu_shell_append (GTK_MENU_SHELL (mb_top), mi_file);

	mi_sett = gtk_menu_item_new_with_label ("Settings");
	gtk_widget_show (mi_sett);
	gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi_sett), me_sett);
	gtk_menu_shell_append (GTK_MENU_SHELL (mb_top), mi_sett);

	mi_help = gtk_menu_item_new_with_label ("Help");
	gtk_widget_show (mi_help);
	gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi_help), me_help);
	gtk_menu_shell_append (GTK_MENU_SHELL (mb_top), mi_help);
	/* Done. */


	/* Setup the "Challenge:" label... */
	la_challenge = gtk_label_new ("Challenge:");
	gtk_grid_attach (GTK_GRID (gr_top), la_challenge, 0, 1, 1, 1);
	g_object_set (la_challenge, "margin", 5, NULL);
	gtk_widget_show (la_challenge);
	/* Done. */


	/* Setup the "Password:" label... */
	la_passwd = gtk_label_new ("Password:");
	gtk_grid_attach (GTK_GRID (gr_top), la_passwd, 0, 2, 1, 1);
	g_object_set (la_passwd, "margin", 5, NULL);
	gtk_widget_show (la_passwd);
	/* Done. */


	/* Setup the server challenge text entry... */
	self->te_challenge = gtk_entry_new ();
	gtk_grid_attach (GTK_GRID (gr_top), self->te_challenge, 1, 1, 1, 1);
	g_object_set (self->te_challenge, "margin", 5, NULL);
	gtk_widget_set_hexpand (self->te_challenge, TRUE);
	gtk_widget_show (self->te_challenge);
	/* Done. */


	/* Setup the "Calculate" button... */
	bu_calc = gtk_button_new_with_label ("Calculate");
	gtk_button_set_relief (GTK_BUTTON (bu_calc), GTK_RELIEF_HALF);
	gtk_grid_attach (GTK_GRID (gr_top), bu_calc, 2, 1, 1, 1);
	g_object_set (bu_calc, "margin", 5, NULL);
	gtk_widget_show (bu_calc);

	g_signal_connect_object (bu_calc,
				 "clicked",
				 G_CALLBACK (calculate_cb),
				 self,
				 G_CONNECT_SWAPPED);
	/* Done. */


	/* Setup the password text entry... */
	self->te_passwd = gtk_entry_new ();
	gtk_entry_set_visibility (GTK_ENTRY (self->te_passwd), FALSE);
	gtk_grid_attach (GTK_GRID (gr_top), self->te_passwd, 1, 2, 1, 1);
	g_object_set (self->te_passwd, "margin", 5, NULL);
	gtk_widget_set_hexpand (self->te_passwd, TRUE);
	gtk_widget_show (self->te_passwd);

	g_signal_connect (G_OBJECT (self->te_passwd), "activate",
			  G_CALLBACK (click), bu_calc);
	/* Done. */


	/* Setup the "Clear" button... */
	bu_clear = gtk_button_new_with_label ("Clear");
	gtk_button_set_relief (GTK_BUTTON (bu_clear), GTK_RELIEF_HALF);
	gtk_grid_attach (GTK_GRID (gr_top), bu_clear, 2, 2, 1, 1);
	g_object_set (bu_clear, "margin", 5, NULL);
	gtk_widget_show (bu_clear);

	g_signal_connect_object (bu_clear,
				 "clicked",
				 G_CALLBACK (clear_cb),
				 self,
				 G_CONNECT_SWAPPED);
	/* Done. */


	/* Setup the response text display... */
	self->te_response = gtk_entry_new ();
	gtk_grid_attach (GTK_GRID (gr_top), self->te_response, 0, 3, 3, 2);
	gtk_widget_set_hexpand (self->te_response, TRUE);
	g_object_set (self->te_response, "margin", 5, NULL);
	gtk_editable_set_editable (GTK_EDITABLE (self->te_response), FALSE);
	gtk_widget_show (self->te_response);
	/* Done. */
}
