/*---[ firestarter.c ]------------------------------------------------
 * Copyright (C) 2000, 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.
 *
 * The main application file
 *--------------------------------------------------------------------*/

#include <config.h>
#include <gnome.h>
#include <status-docklet.h>
#include <sys/stat.h>
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <errno.h>
#include <popt.h>

#include "firestarter.h"
#include "util.h"
#include "logread.h"
#include "menus.h"
#include "parse.h"
#include "druid.h"
#include "preferences.h"
#include "modrules.h"
#include "sort-clist.h"

#include "xpm/statusrunning.xpm"
/*#include "xpm/statuscaught.xpm"*/
#include "xpm/statustopped.xpm"

gboolean NETFILTER;
gboolean CONSOLE;
gboolean NO_APPLET;

GtkWidget *window;
GtkWidget *panelvbox;
GtkWidget *panelpic;

static gint save_session (GnomeClient       *client,
			  gint               phase,
			  GnomeSaveStyle     save_style,
			  gint               is_shutdown,
			  GnomeInteractStyle interact_style,
			  gint               is_fast,
			  gpointer           client_data);

static void session_die  (GnomeClient        *client,
			  gpointer            client_data);

void set_panel_pic (gchar *pixmap[]);

/* [ appbar_update ]
 * Updates the application status bar with the state of the firewall,
 * returns the new state
 */
static int
appbar_update (char *state)
{
	static int currentstate;

	if (state == "stop") {
		if (!NO_APPLET) gnome_appbar_set_default (GNOME_APPBAR (appbar),
			_("Firewall stopped"));
		currentstate = 1;
	}
	else if (state == "start") {
		if (!NO_APPLET) gnome_appbar_set_default (GNOME_APPBAR (appbar),
			_("Firewall running"));
		currentstate = 2;
	}
	else if (state == "halt") {
		if (!NO_APPLET) gnome_appbar_set_default (GNOME_APPBAR (appbar),
			_("All traffic halted"));
		currentstate = 3;
	}

	return currentstate;
}

/* [ clist_row_select ]
 * Attach current line data to a clist. Uggly.
 */
static void
clist_row_select (GtkWidget *clist, gint row, gint column,
		  GdkEventButton *event, gpointer data)
{
	gint *clistrow = GINT_TO_POINTER (row+1);
	gint *clistcol = GINT_TO_POINTER (column+1);

	gtk_object_set_data (GTK_OBJECT (clist), "row", clistrow);
	gtk_object_set_data (GTK_OBJECT (clist), "col", clistcol);

	if (clist != hitlogclist)
		dynclist =  (gpointer *) clist;
	else return;

	if (clist != denyallclist)
		gtk_clist_unselect_all (GTK_CLIST (denyallclist));
	if (clist != allowallclist)
		gtk_clist_unselect_all (GTK_CLIST (allowallclist));
	if (clist != allowsmclist)
		gtk_clist_unselect_all (GTK_CLIST (allowsmclist));
	if (clist != allowsaclist)
		gtk_clist_unselect_all (GTK_CLIST (allowsaclist));
}

/* [ clist_row_unselect ]
 * Clear current line data from a clist
 */
static void
clist_row_unselect (GtkWidget *clist)
{
	if (clist == (GtkWidget *)dynclist)
		dynclist = NULL;

	gtk_object_set_data (GTK_OBJECT (clist), "row", NULL);
	gtk_object_set_data (GTK_OBJECT (clist), "col", NULL);
}

/* [ hide_window_cb ]
 * Hides the main window, either when "destroyed" or docklet clicked.
 */
static void
hide_window_cb (GtkWidget *window, GdkEventButton* bevent)
{
	if (bevent->button != 3) {
		if (GTK_WIDGET_VISIBLE (window))
			gtk_widget_hide (window);
		else
			if (GTK_WIDGET_VISIBLE (hitlogclist))
				gtk_widget_show (window);
			else
				gtk_widget_show_all (window);

		if (appbar_update ("query") == 2)
			set_panel_pic (statusrunning_xpm);
	}
}

/* [ root_alert ]
 * check if the users is root, if not display an alert message
 */
gint
root_alert (void)
{
	gint uid;

	if ((uid = getuid ())) {
		if (!CONSOLE)
		{
			GtkWidget *dialog=gnome_error_dialog (_(
				"You must be root to manipulate the firewall."));
			gnome_dialog_run_and_close (GNOME_DIALOG (dialog));
		}
		else
			g_print (_("You must be root to manipulate the firewall.\n"));
	}

	return uid;
}
	
/* [ stop_firewall ]
 * Flushes, zeroes and sets all policies to accept
 */
void
stop_firewall (void)
{
	/* gchar *soundfile; */
	gchar *sbin;
	gchar *command = NULL;

	if (root_alert ())
		return;

	if (gnome_config_get_bool ("/firestarter/Druid/locatesbins=FALSE"))
		if (NETFILTER)
			sbin = g_strdup ("`which iptables`");
		else
			sbin = g_strdup ("`which ipchains`");
	else
		if (NETFILTER)
			sbin = g_strdup ("/sbin/iptables");
		else
			sbin = g_strdup ("/sbin/ipchains");

	if (NETFILTER) {
		command = g_strconcat (sbin, " -F;",
				       sbin, " -X;",
				       sbin, " -Z;",
			     	       sbin, " -P INPUT ACCEPT;",
				       sbin, " -P FORWARD ACCEPT;",
			               sbin, " -P OUTPUT ACCEPT;", NULL);
		system (command);
	}
	else {
		command = g_strconcat (sbin, " -X;",
				       sbin, " -F;",
				       sbin, " -Z;",
				       sbin, " -P input ACCEPT;",
				       sbin, " -P forward ACCEPT;",
				       sbin, " -P output ACCEPT;", NULL);
		system (command);
	}

	g_free (sbin);
	g_free (command);

	g_print (_("Firewall stopped, network traffic is now flowing freely\n"));
	set_panel_pic (statustopped_xpm);
	appbar_update ("stop");

/*	if (gnome_config_get_bool ("/firestarter/Sounds/enablesounds=FALSE")) {
		soundfile = gnome_config_get_string (
			"/firestarter/Sounds/stopsoundfile=");
		gnome_sound_play (soundfile);
		g_free (soundfile);
	}
*/
}

/* [ restart_firewall ]
 * Executes the firewall script
 */
void
restart_firewall (void)
{
	gchar *commandline = g_strjoin (NULL, "/bin/sh ",
		FIRESTARTER_RULES_DIR "/firestarter/firewall.sh",
		NULL);
/*	gchar *soundfile; */

	if (root_alert ())
		return;

	system (commandline);
	g_print (_("Firewall script restarted\n"));

	set_panel_pic (statusrunning_xpm);
	g_free (commandline);
	appbar_update ("start");

/*	if (gnome_config_get_bool ("/firestarter/Sounds/enablesounds=FALSE")) {
		soundfile = gnome_config_get_string (
			"/firestarter/Sounds/restartsoundfile");
		gnome_sound_play (soundfile);
		g_free (soundfile);
	}
*/
}

/* [ halt_traffic ]
 * Flushes and sets all policies to deny
 */
void
halt_traffic (void)
{
	gchar *sbin;
	gchar *command;

	if (root_alert ())
		return;

	if (gnome_config_get_bool ("/firestarter/Druid/locatesbins=TRUE"))
		if (NETFILTER)
			sbin = g_strdup ("`which iptables`");
		else
			sbin = g_strdup ("`which ipchains`");
	else
		if (NETFILTER)
			sbin = g_strdup ("/sbin/iptables");
		else
			sbin = g_strdup ("/sbin/ipchains");

	if (NETFILTER) {
		command = g_strconcat (sbin, " -P INPUT DROP;",
				       sbin, " -P FORWARD DROP;",
				       sbin, " -P OUTPUT DROP;",
				       sbin, " -F;",
				       sbin, " -X;",
				       sbin, " -Z;", NULL);
		system (command);
	}
	else {
		command = g_strconcat (sbin, " -P input DENY;",
				       sbin, " -P forward DENY;",
				       sbin, " -P output DENY;",
				       sbin, " -X;",
				       sbin, " -F;",
				       sbin, " -Z;", NULL);
		system (command);
	}

	g_free (sbin);
	g_free (command);

	g_print (_("All traffic halted\n"));
	set_panel_pic (statustopped_xpm);
	appbar_update ("halt");
}

static void
tail_and_resize (GtkWidget *clist)
{
	gint i, width;

	/* Optimize the clists column widths */
	for (i = 0; i < 4; i++) {
		width = gtk_clist_optimal_column_width (GTK_CLIST (hitlogclist), i);
		gtk_clist_set_column_width (GTK_CLIST (hitlogclist), i, width);
	}

	/* Move to the end of the clist */
	while (gtk_clist_get_text (GTK_CLIST (hitlogclist), i, 0, NULL))
		i++;
	gtk_clist_moveto (GTK_CLIST (hitlogclist), i-1, 0, 0.0, 0.0);
	gtk_widget_queue_resize	(window);
}



/* [ clear_hitlist ]
 * Clears the log CList
 */
void
clear_hitlist (void)
{
	if (appbar_update ("get current") == 2)
		set_panel_pic (statusrunning_xpm);
	gtk_clist_clear (GTK_CLIST (hitlogclist));
	clist_row_unselect (hitlogclist);
}

/* [ reload_hitlist ]
 * Reads the entire log file into the hitlogclist
 */
void
reload_hitlist (gpointer data)
{
	gchar buf[512];
	gchar *logpath = gnome_config_get_string (
		"/firestarter/Files/logfile=/var/log/messages");

	FILE *f;

	f = fopen (logpath, "r");
	if (f == NULL) {
		perror(logpath);
		return;
	}

	gtk_clist_freeze (GTK_CLIST (hitlogclist));
	gtk_clist_clear (GTK_CLIST (hitlogclist));
	while (fgets (buf, 512, f) != NULL) {
		if (NETFILTER) {
			if (strstr (buf, "kernel: IN"))
				parse_hit_log (buf);
		} else
			if (strstr (buf, "DENY") || strstr (buf, "REJECT"))
				parse_hit_log (buf);
	}
	gtk_clist_thaw (GTK_CLIST (hitlogclist));
	tail_and_resize (hitlogclist);

	fclose (f);
}

/* [ show_about ]
 * Creates the about dialog
 */
void
show_about (GtkWidget *widget, gpointer data)
{
	static GtkWidget *dialog = NULL;

	if (dialog != NULL) {
		g_assert (GTK_WIDGET_REALIZED (dialog));
		gdk_window_show (dialog->window);
		gdk_window_raise (dialog->window);
	}
	else {
		const gchar *authors[] = {"Tomas Junnonen <majix@sci.fi> - Main Developer, Maintainer",
			"Paul Drain <pd@cipherfunk.org> - Developer",
			NULL};

		dialog = gnome_about_new (
			"Firestarter", VERSION,
			"(C) 2000-2002 Tomas Junnonen", authors,
			_("An all-in-one Linux firewall utility for GNOME.\n"
			"http://firestarter.sourceforge.net"),
			NULL);

		gtk_signal_connect (GTK_OBJECT (dialog), "destroy",
			GTK_SIGNAL_FUNC (gtk_widget_destroyed), &dialog);

		gtk_widget_show (dialog);
	}
}

/* [ set_panel_pic ]
 * Sets the picture on the docklet to *pixmap
 */
void
set_panel_pic (gchar *pixmap[])
{
	if( !NO_APPLET )
	{
		gtk_container_remove (GTK_CONTAINER (panelvbox), panelpic);
		panelpic = gnome_pixmap_new_from_xpm_d (pixmap);
		gtk_box_pack_end (GTK_BOX (panelvbox), panelpic, FALSE, FALSE, 2);
		gtk_widget_show (panelpic);
	}
}

/* [ status_docklet_build_plug ]
 * Builds the docklet plug
 */
static void
status_docklet_build_plug (StatusDocklet *docklet, GtkWidget *plug)
{
	GtkWidget *eventbox;

	g_return_if_fail (docklet != NULL);
	g_return_if_fail (IS_STATUS_DOCKLET (docklet));

	eventbox = gtk_event_box_new ();
	gtk_widget_set_events (eventbox, GDK_BUTTON_PRESS_MASK);
	gtk_signal_connect_object (GTK_OBJECT (eventbox), "button_press_event",
		GTK_SIGNAL_FUNC (hide_window_cb), GTK_OBJECT (window));
	gtk_container_add (GTK_CONTAINER (plug), eventbox);

	panelvbox = gtk_vbox_new (FALSE, 5);
	gtk_container_add (GTK_CONTAINER (eventbox), panelvbox);

	if (gnome_config_get_bool ("/firestarter/Program/shutdown=FALSE"))
		panelpic = gnome_pixmap_new_from_xpm_d (statustopped_xpm);
	else
		panelpic = gnome_pixmap_new_from_xpm_d (statusrunning_xpm);

	gtk_box_pack_start (GTK_BOX (panelvbox), panelpic, FALSE, FALSE, 2);

	install_docklet_menu (eventbox);

	gtk_widget_show_all (plug);
}

/* [ pack_clist ]
 * Convenience function to pack a clist into a scrolled window and box
 */
static void
pack_clist (GtkWidget *clist, GtkWidget *box)
{
	GtkWidget *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_container_add (GTK_CONTAINER (scrolled_win), clist);

	gtk_box_pack_start (GTK_BOX (box), scrolled_win, TRUE, TRUE, 0);
}

/* [ prepare_clist ]
 * Convinience function that installs popups, signals and packs the clists
 */
static void
prepare_clist (GtkWidget *clist, GtkWidget *vbox)
{
	install_clist_popup (clist);

	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);

	if (clist == hitlogclist)
		gtk_signal_connect (GTK_OBJECT (clist), "button_press_event",
				    GTK_SIGNAL_FUNC (popup_menu), hitpopup);
	else {
		gtk_signal_connect (GTK_OBJECT (clist), "button_press_event",
				    GTK_SIGNAL_FUNC (popup_menu), dynlistpopup);
		pack_clist (clist, vbox);
	}
}

/* [ exit_firestarter ]
 * Quit firestater
 */
void
exit_firestarter (void)
{
	if (gnome_config_get_bool ("/firestarter/Program/shutdown=FALSE"))
		stop_firewall ();
	gtk_main_quit ();
}

/* [ closewindow ]
 * Quit or hide, determined by the config files
 */
void
closewindow (void)
{
	if (gnome_config_get_bool ("/firestarter/Program/exitonclose=FALSE"))
		exit_firestarter ();
	else
		gtk_widget_hide (window);
}

/* [ main ]
 * The main function, this is where it all begins
 */
int
main (int argc, char* argv[])
{
	GnomeClient *client;
	GtkObject *status_dock;

	GtkWidget *notebook;
	GtkWidget *scrolled_win;
	GtkWidget *tablabel;

	gchar *hitlogtitles[4];

	GtkWidget *dynamicvbox;
	gchar *denyalltitles[1];
	gchar *allowtitles[1];
	gchar *allowsmtitles[1];
	gchar *allowsatitles[1];

	gchar hostname[40];
	gint i;
	CORBA_Environment ev;
	CORBA_ORB orb;

	/* command line stuff */
	poptContext ctx;
	gint opt_up, opt_down, opt_stop;
	
	const struct poptOption options[] =
	{
		{"noapplet", 'a', POPT_ARG_NONE, &NO_APPLET, 0, _("Startup without panel applet"), NULL},
		{"start", 'u', POPT_ARG_NONE, &opt_up, 0, _("Start the firewall"), NULL},
		{"stop", 'd', POPT_ARG_NONE, &opt_down, 0, _("Stop the firewall"), NULL},
		{"halt", 'h', POPT_ARG_NONE, &opt_stop, 0, _("Halt all network traffic"), NULL},
		{NULL, 0, 0, NULL, 0, NULL, NULL}
	};
	
	bindtextdomain (PACKAGE, GNOMELOCALEDIR);
	textdomain (PACKAGE);
	
	hitlogtitles[0] = _("Port");
	hitlogtitles[1] = _("Sent from");
	hitlogtitles[2] = _("Service");
	hitlogtitles[3] = _("Time");

	denyalltitles[0] = _("Deny all connections from");
	allowtitles[0] = _("Allow all connections from");
	allowsmtitles[0] = _("Open service to machine");
	allowsatitles[0] = _("Open service to anyone");

	NETFILTER = detect_netfilter ();

	opt_up = opt_down = opt_stop = 0;
	ctx = poptGetContext (NULL, argc, argv, options, 0);
	poptGetNextOpt( ctx );
	poptResetContext (ctx);

	if (opt_up || opt_down || opt_stop)
	{
		CONSOLE = TRUE;
		NO_APPLET = TRUE;

		if (opt_up)
			restart_firewall ();
		else if (opt_down)
			stop_firewall ();
		else if (opt_stop)
			halt_traffic ();
	
		return 0;
	}

	CORBA_exception_init (&ev);
	orb = gnome_CORBA_init_with_popt_table (PACKAGE, VERSION, &argc, argv, options, 0, &ctx, 0, &ev);
	
	/* Create the main window, append hostname to title */
	if (!gethostname (hostname, 39))
		window = gnome_app_new (PACKAGE, g_strconcat ("Firestarter ", hostname, NULL));
	else
		window = gnome_app_new (PACKAGE, "Firestarter");

	ttips = gtk_tooltips_new ();

/* Set up the session managment */
	client = gnome_master_client ();
	gtk_signal_connect (GTK_OBJECT (client), "save_yourself",
			    GTK_SIGNAL_FUNC (save_session), argv[0]);
	gtk_signal_connect (GTK_OBJECT (client), "die",
			    GTK_SIGNAL_FUNC (session_die), NULL);

/* Set up the docklet */
	if (!NO_APPLET)
	{
		status_dock = status_docklet_new ();
		gtk_signal_connect (GTK_OBJECT (status_dock), "build_plug",
				    GTK_SIGNAL_FUNC (status_docklet_build_plug), NULL);
		status_docklet_run (STATUS_DOCKLET (status_dock));
	}
	else
		status_dock = NULL;
/* Set up the window */
	gtk_signal_connect (GTK_OBJECT (window), "delete_event",
			    GTK_SIGNAL_FUNC (closewindow), NULL);

	gnome_window_icon_set_default_from_file (
		"/usr/share/pixmaps/firestarter.png");

	install_menus_and_toolbar (window);

	notebook = gtk_notebook_new ();
	gnome_app_set_contents (GNOME_APP (window), notebook);

/* Set up hitlogclist */
	scrolled_win = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win),
		GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);

	tablabel = gtk_label_new (_("Firewall hits"));
	gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled_win,
		tablabel);

	for (i = 0; i < 4;i++)
		 hitlogtitles[i] = _(hitlogtitles[i]);
	hitlogclist = gtk_clist_new_with_titles (4, hitlogtitles);
	prepare_clist (hitlogclist, NULL);
	gtk_container_add (GTK_CONTAINER (scrolled_win), hitlogclist);
	prepare_for_sorting (hitlogclist, 0);

/* Set up the clists on the dynamic rules page */
	tablabel = gtk_label_new (_("Dynamic rules"));
	dynamicvbox = gtk_vbox_new (FALSE, 0);
	gtk_notebook_append_page (GTK_NOTEBOOK (notebook), dynamicvbox,
		tablabel);

	allowtitles[0] = N_(allowtitles[0]);
	allowallclist = gtk_clist_new_with_titles (1, allowtitles);
	gtk_object_set_data (GTK_OBJECT (allowallclist), "path",
		FIRESTARTER_RULES_DIR "/firestarter/allow-all");
	prepare_clist (allowallclist, dynamicvbox);
/*	prepare_for_sorting (allowallclist, 2); */

	denyalltitles[0] = N_(denyalltitles[0]);
	denyallclist = gtk_clist_new_with_titles (1, denyalltitles);
	gtk_object_set_data (GTK_OBJECT (denyallclist), "path",
		FIRESTARTER_RULES_DIR "/firestarter/deny-all");
	prepare_clist (denyallclist, dynamicvbox);
/*	prepare_for_sorting (denyallclist, 1); */

	allowsmtitles[0] = N_(allowsmtitles[0]);
	allowsmclist = gtk_clist_new_with_titles (1, allowsmtitles);
	gtk_object_set_data (GTK_OBJECT (allowsmclist), "path",
		FIRESTARTER_RULES_DIR "/firestarter/allow-service-machine");
	prepare_clist (allowsmclist, dynamicvbox);
/*	prepare_for_sorting (allowsmclist, 3); */

	allowsatitles[0] = N_(allowsatitles[0]);
	allowsaclist = gtk_clist_new_with_titles (1, allowsatitles);
	gtk_object_set_data (GTK_OBJECT (allowsaclist), "path",
		FIRESTARTER_RULES_DIR "/firestarter/allow-service-all");
	prepare_clist (allowsaclist, dynamicvbox);
/*	prepare_for_sorting (allowsaclist, 4); */

	open_logfile (gnome_config_get_string (
		"/firestarter/Files/logfile=/var/log/messages"));

	mkdir (FIRESTARTER_RULES_DIR "/firestarter", 00700);

	/* populate the clists on the dynamic rules notebookpage*/
	parse_allow_and_deny_all ();
	parse_allow_service_machine ();
	parse_allow_service_all ();

	if (!gnome_config_get_bool ("/firestarter/Program/starthidden=FALSE"))
		gtk_widget_show_all (window);

	check_version ();

	if (gnome_config_get_bool ("/firestarter/Druid/firsttime=TRUE")) {
		rundruid ();
	}

	if (gnome_config_get_bool ("/firestarter/Program/startup=TRUE"))
		restart_firewall ();
	else
		appbar_update ("stop");

	gtk_main ();

	CORBA_exception_free (&ev);

	return 0;
}

/* [ save_session ]
 * Saves the current session for later revival
 */
static gint
save_session (GnomeClient       *client,
	      gint               phase,
	      GnomeSaveStyle     ave_style,
	      gint               is_shutdown,
	      GnomeInteractStyle interact_style,
	      gint               is_fast,
	      gpointer           client_data)
{
	gchar **argv;
	guint argc;

	argv = g_new0 (gchar*, 4);
	argc = 1;

	argv[0] = client_data;
	gnome_client_set_clone_command (client, argc, argv);
	gnome_client_set_restart_command (client, argc, argv);
	return TRUE;
}

/* [ session_die ]
 * Gracefully end the session
 */
static void
session_die (GnomeClient *client, gpointer client_data)
{
	exit_firestarter ();
}
