/*---[ portfw.c ]-----------------------------------------------------
 * Copyright (C) 2001 Tomas Junnonen (majix@sci.fi)
 *
 * 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.
 *
 * Portforwarding functions
 *--------------------------------------------------------------------*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>

#include "portfw.h"
#include "parse.h"
#include "util.h"

/* [ detect_ipmasqadm ]
 * If using Linux 2.2 the program ipmasqadm must be present
 */
static gboolean
detect_ipmasqadm (void)
{
	GtkWidget *dialog;
	FILE *f;
	GtkWidget *button;

	/* ipmasqadm is not used on Linux 2.4 machines */
	if (detect_netfilter ())
		return TRUE;

	f = fopen ("`which ipmasqadm`", "r");
	if (f != NULL) {
		g_print ("ipmasqadm not found!\n");

		dialog = gnome_error_dialog (_(
			"The ipmasqadm program was not found on your system.\n"
			"On Linux 2.2 this program is necessary for portfwarding.\n"
			));

		button = gnome_href_new ("http://freshmeat.net/projects/ipmasqadm/",
			_("Download ipmasqadm now"));
		gtk_box_pack_start (GTK_BOX (GNOME_DIALOG (dialog)->vbox),
			button, FALSE, FALSE, 0);
		gtk_widget_show (button);

		gnome_dialog_run_and_close (GNOME_DIALOG (dialog));
		return FALSE;
	}

	return TRUE;
}


/* [ write_portfw_list ]
 * Parse the data from the clist into rules in the portfw file
 */
static void
write_portfw_list (GtkWidget *clist)
{
	FILE *f;
	int i, j, rows;
	gchar *entry[4];

	f = fopen (FIRESTARTER_RULES_DIR "/firestarter/portfw", "w");
	if (f == NULL) {
		/* Use perror for sane error messages */
		perror (FIRESTARTER_RULES_DIR "/firestarter/portfw");
		return;
	}

	fprintf (f, "#!/bin/sh\n");
	chmod (FIRESTARTER_RULES_DIR "/firestarter/portfw", 00700);

	/* Count the number of rows in the clist */
	rows = 0;
	for (i = 0; gtk_clist_get_text (GTK_CLIST (clist), i, 0, NULL); i++)
		rows++;

	/* Read the data from the clist, i=row index, j=column index */
	for (i = 0; i < rows; i++) {
		for (j = 0; j < 4; j++) {
			gtk_clist_get_text (GTK_CLIST (clist), i, j, &entry[j]);
		}
		/* Linux 2.4 case */
		if (detect_netfilter ()) {
		fprintf (f, "$IPT -A FORWARD -p %s -d %s --dport %s -j ACCEPT\n",
			entry[3], entry[2], entry[1]);
		fprintf (f, "$IPT -A PREROUTING -t nat -p %s -d $NET --dport %s -j DNAT --to %s:%s\n",
			entry[3], entry[0], entry[2], entry[1]);
		} else {
		/* Linux 2.2 case */
		fprintf (f, "$MSQADM portfw -a -P %s -L $IP %s -R %s %s\n",
			entry[3], entry[0], entry[2], entry[1]);
		}
	}

	fclose (f);
}

/* [ clist_row_select ]
 * Attach current line data to a clist
 */
static void
clist_row_select (GtkWidget *clist, gint row, gint column,
		  GdkEventButton *event, gpointer data)
{
	gint *clistrow = GINT_TO_POINTER (row);
	gtk_object_set_data (GTK_OBJECT (clist), "row", clistrow);
}

/* [ clist_row_unselect ]
 * Clear current line data from a clist
 */
static void
clist_row_unselect (GtkWidget *clist)
{
	gtk_object_set_data (GTK_OBJECT (clist), "row", NULL);
}

/* [ clist_remove_entry ]
 * Remove the currently selected clist row
 */
static void
clist_remove_entry (GtkWidget *clist)
{
	gint row = GPOINTER_TO_INT (gtk_object_get_data (GTK_OBJECT (clist), "row"));

	gtk_clist_remove (GTK_CLIST (clist), row);
}

/* [ clist_add_entry ]
 * Add a new entry to the end of the clist
 */
static void
clist_add_entry (GtkWidget *clist, gchar *field1, gchar *field2, gchar *field3, gchar *field4) {
	gchar *entry[4];

	if (strlen (field1) < 1 || strlen (field2) < 1 || strlen (field3) < 1 || strlen (field4) < 1) {
		g_print ("Required field empty, rule not created\n");
	} else {
		entry[0] = field1;
		entry[1] = field2;
		entry[2] = field3;
		entry[3] = field4;

		gtk_clist_append (GTK_CLIST (clist), entry);
	}
}

/* [ parse_portfw_list ]
 * Parse the rules from the portfw file into data in the clist
 * data[3] = packet type, data[0] = firewall port, data[1] = lan port, data[2] = lan machine
 */
static void
parse_portfw_list (GtkWidget *clist)
{
	gchar *data[4];
	char buf[512];
	FILE *f;

	f = fopen (FIRESTARTER_RULES_DIR "/firestarter/portfw", "r");
	if (f == NULL)
		return;

	gtk_clist_clear (GTK_CLIST (clist));

	printf ("REACHED\n");

	/* Linux 2.4 case */
	if (detect_netfilter ()) {
		while (fgets (buf, 512, f) != NULL) {
			if (strstr (buf, " PREROUTING ") == NULL)
				continue;

			data[3] = get_text_between (buf, "-p ", "-d");
			data[0] = get_text_between (buf, "--dport ", " -j");
			data[2] = get_text_between (buf, "--to ", ":");
			data[1] = get_text_between (buf, ":", "\n");
			
			printf ("%s %s %s %s", data[3], data[0], data[2], data[1]);
			printf ("REACHED2\n");
			
			if (data[0] != NULL)
				gtk_clist_append (GTK_CLIST (clist), data);
		}

	} else {
	/* Linux 2.2 case */
		while (fgets (buf, 512, f) != NULL) {
			if (strstr (buf, " portfw ") == NULL)
				continue;

			data[3] = get_text_between (buf, "-p ", "-d");
			data[0] = get_text_between (buf, "$IP ", " -R");
			data[2] = get_text_between (buf, "-R ", " ");
			data[1] = g_strjoin (NULL, data[2], " ", NULL);
			data[1] = get_text_between (buf, data[1], "\n");

			if (data[0] != NULL)
				gtk_clist_append (GTK_CLIST (clist), data);
		}
	}

	fclose (f);
}

/* [ create_rule_dialog ]
 * Simple dialog to enter the firewall port, lan port and lan machine data
 */
static void
create_rule_dialog (GtkWidget *clist)
{
	GtkWidget *dialog, *hbox, *vbox, *label, *entry1, *entry2, *entry3, *entry4;
	gint button;

	dialog = gnome_dialog_new (_("Add connection forwarding"),
			GNOME_STOCK_BUTTON_OK, GNOME_STOCK_BUTTON_CANCEL,
			NULL);

	hbox = gtk_hbox_new (FALSE, 4);
	gtk_box_pack_start (GTK_BOX (GNOME_DIALOG (dialog)->vbox), hbox,
		TRUE, TRUE, 0);

	vbox = gtk_vbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);

	label = gtk_label_new (_("Firewall port:"));
	gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);

	entry1 = gtk_entry_new ();
	gtk_widget_set_usize (entry1, 100, 20);
	gtk_box_pack_start (GTK_BOX (vbox), entry1, TRUE, TRUE, 0);
	gtk_widget_grab_focus (entry1);

	gnome_dialog_close_hides (GNOME_DIALOG (dialog), TRUE);
	gnome_dialog_set_default (GNOME_DIALOG (dialog), 0);
	gnome_dialog_editable_enters (GNOME_DIALOG (dialog), GTK_EDITABLE (entry1));


	vbox = gtk_vbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);

	label = gtk_label_new (_("LAN port:"));
	gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);

	entry2 = gtk_entry_new ();
	gtk_widget_set_usize (entry2, 100, 20);
	gtk_box_pack_start (GTK_BOX (vbox), entry2, TRUE, TRUE, 0);

	gnome_dialog_close_hides (GNOME_DIALOG (dialog), TRUE);
	gnome_dialog_set_default (GNOME_DIALOG (dialog), 0);
	gnome_dialog_editable_enters (GNOME_DIALOG (dialog), GTK_EDITABLE (entry1));


	vbox = gtk_vbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);

	label = gtk_label_new (_("LAN machine address:"));
	gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);

	entry3 = gtk_entry_new ();
	gtk_box_pack_start (GTK_BOX (vbox), entry3, TRUE, TRUE, 0);

	gnome_dialog_close_hides (GNOME_DIALOG (dialog), TRUE);
	gnome_dialog_set_default (GNOME_DIALOG (dialog), 0);
	gnome_dialog_editable_enters (GNOME_DIALOG (dialog), GTK_EDITABLE (entry1));

	vbox = gtk_vbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);

	label = gtk_label_new (_("Connection type (TCP / UDP):"));
	gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);

	entry4 = gtk_entry_new ();
	gtk_box_pack_start (GTK_BOX (vbox), entry4, TRUE, TRUE, 0);

	gnome_dialog_close_hides (GNOME_DIALOG (dialog), TRUE);
	gnome_dialog_set_default (GNOME_DIALOG (dialog), 0);
	gnome_dialog_editable_enters (GNOME_DIALOG (dialog), GTK_EDITABLE (entry1));


	gtk_widget_show_all (dialog);
	button = gnome_dialog_run_and_close (GNOME_DIALOG (dialog));

	if (button) {
		gtk_widget_destroy (dialog);
		return;
	}

	clist_add_entry (clist,
		gtk_entry_get_text (GTK_ENTRY (entry1)),
		gtk_entry_get_text (GTK_ENTRY (entry2)),
		gtk_entry_get_text (GTK_ENTRY (entry3)),
		gtk_entry_get_text (GTK_ENTRY (entry4)));

	gtk_widget_destroy (dialog);
}

void
show_portfw_window (void)
{
	static GtkWidget *window;
	GtkWidget *scrolled_win;
	GtkWidget *vbox;
	GtkWidget *hbox;
	GtkWidget *clist;
	GtkWidget *addbutton;
	GtkWidget *removebutton;
	GtkWidget *button;

	gchar *titles[4];
	gchar *entry[4];
	int i;

	if (window != NULL) {
		g_assert (GTK_WIDGET_REALIZED (window));
		gdk_window_show (window->window);
		gdk_window_raise (window->window);
		return;
	}

	titles[0] = _("Forward port ");
	titles[1] = _(" to port ");
	titles[2] = _(" on machine");
	titles[3] = _(" via");

	entry[0] = NULL;
	entry[1] = NULL;
	entry[2] = NULL;
	entry[3] = NULL;

	window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title (GTK_WINDOW (window), _("Configure Port Forwarding"));

	gtk_signal_connect_object (GTK_OBJECT (window), "destroy",
		GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (window));
	gtk_signal_connect (GTK_OBJECT (window), "destroy",
		GTK_SIGNAL_FUNC (gtk_widget_destroyed), &window);

	vbox = gtk_vbox_new (FALSE, 4);
	gtk_container_add (GTK_CONTAINER (window), vbox);

	scrolled_win = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
		GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	gtk_widget_set_usize (scrolled_win, 250, 200);
	gtk_box_pack_start (GTK_BOX (vbox), scrolled_win, FALSE, FALSE, 0);


	for (i = 0; i < 4;i++)
		titles[i] = _(titles[i]);
	clist = gtk_clist_new_with_titles (4, titles);
	gtk_signal_connect (GTK_OBJECT (clist), "select-row",
			    GTK_SIGNAL_FUNC (clist_row_select), NULL);
	gtk_signal_connect (GTK_OBJECT (clist), "unselect-row",
			    GTK_SIGNAL_FUNC (clist_row_unselect), NULL);
	gtk_container_add (GTK_CONTAINER (scrolled_win), clist);

	hbox = gtk_hbox_new (FALSE, 0);
	gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);

	addbutton = gtk_button_new_with_label (_("Add entry"));
	gtk_signal_connect_object (GTK_OBJECT (addbutton), "clicked",
		GTK_SIGNAL_FUNC (create_rule_dialog), GTK_OBJECT (clist));

	gtk_box_pack_start (GTK_BOX (hbox), addbutton, FALSE, FALSE, 0);

	removebutton = gtk_button_new_with_label (_("Remove entry"));
	gtk_signal_connect_object (GTK_OBJECT (removebutton), "clicked",
		GTK_SIGNAL_FUNC (clist_remove_entry), GTK_OBJECT (clist));
	gtk_box_pack_start (GTK_BOX (hbox), removebutton, FALSE, FALSE, 0);

	/* Stock ok button, writes the porfw file and destroys the window */
	button = gnome_stock_button (GNOME_STOCK_BUTTON_OK);
	gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
		GTK_SIGNAL_FUNC (write_portfw_list), GTK_OBJECT (clist));
	gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
	gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
		GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (window));
	gtk_signal_connect (GTK_OBJECT (button), "clicked",
		GTK_SIGNAL_FUNC (gtk_widget_destroyed), &window);

	/* Stock cancel button, destoys window */
	button = gnome_stock_button (GNOME_STOCK_BUTTON_CANCEL);
	gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
	gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
		GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (window));
	gtk_signal_connect (GTK_OBJECT (button), "clicked",
		GTK_SIGNAL_FUNC (gtk_widget_destroyed), &window);

	if (!detect_ipmasqadm ()) {
		gtk_widget_destroy (window);
	}

	parse_portfw_list (clist);

	gtk_widget_show_all (window);
}
