#!/usr/bin/env perl
# ctrlproxy configuration editor
# (C) 2003 By Kyoshiro @ #ctrlproxy

use strict;
use IO::File;
use POSIX qw/strftime/;
use Data::Dumper;

# TODO
# - add maxlines handling to reprint screen when number of lines printed is over
# - add maxcolumns handling to auto-cut printed text between words and adjust
#   title bar
# - add per network nickserv support
# - add import config from file support
# - improve exit message with available ports to connect to
# - add more checks for IRC stuff
# - add checks/replace for XML write

my $maxcolumns = 80;
my $spool = $ENV{'HOME'}."/.ctrlproxy";

my %defs = (
    modules => {
        'admin'     => {
            autoload    => 1,
            desc        => "allows you to configure ctrlproxy via IRC",
			parameters	=> {
				tata	=> {
					default => "plop",
					}
			},
        },
        'antiflood' => {
            autoload    => 1,
            desc        => "prevents you from getting kicked from the server for 'Excess flooding' by queueing messages"
        },
        'auto-away' => {
            autoload    => 1,
            desc        => "sets you away after a certain amount of time",
            options     => {
                message => {
                    default => "I'm away, please leave me a message",
                    type    => "text",
                    desc    => "allows you to specify your away message when auto-away sets you away.",
                    parameters  => {
                        only_noclient   => {
                            default => 0,
                            type    => "bool",
                            desc    => "tells ctrlproxy whether to set away only if no client is attached or not",
                        },
                        time    => {
                            default => 600,
                            type    => "text",
                            desc    => "tells ctrlproxy the max idle time before setting away.",
							allowed	=> '^\d+$',
                        },
                    },
                },
            },
        },
        'ctcp'  => {
            autoload    => 1,
            desc        => "replies to various CTCP requests like VERSION",
        },
        'linestack_'    => {
            suffix      => "memory",
            autoload    => 1,
            desc        => "allows you to choose where backlogs will be recorded",
            allowed     => {
                'memory'    => "writes replication data into memory (faster but requires memory or/and swap)",
                'file'      => "writes replication data into files",
            },
        },
        'log_'  => {
            suffix      => "irssi",
            autoload    => 1,
            desc        => "allows you to log discussions into files.",
            allowed     => {
                'irssi'     => "log into irssi IRC client format",
                'custom'    => "log into a user-defined format",
            },
            options     => {
                logfile => {
                    default => "$spool/logs/",
                    desc    => "tells ctrlproxy where to store log files",
                    type    => "text",
                },
            },
        },
        'motd_file' => {
            autoload    => 0,
            desc        => "adds a Message Of The Day to ctrlproxy's server emulation",
        },
        'nickserv'  => {
            autoload    => 1,
            desc        => "handles NickServ's identification",
        },
        'noticelog' => {
            autoload    => 1,
            desc        => "sends ctrlproxy's output as notices to attached clients",
        },
        'repl_' => {
            suffix      => "none",
            autoload    => 1,
            desc        => "replicates messages and events which occured when you were not connected to ctrlproxy. None doesn't replicate anything, highlight only replicates events involving certain words, simple replicates everything on the chan or private since last time you said something on the channel or you connected to the proxy.",
            allowed     => {
                'none'      => "No backlogs",
                'simple'    => "All backlogs since last moment you said something",
                'command'   => "Backlogs on demand (when running specific command)",
                'highlight' => "Backlogs of all lines matching certain conditions",
            },
        },
        'report_time'   => {
            autoload    => 0,
            desc        => "prints time periodically to have an idea of chronology in backlog replication",
            options     => {
                interval    => {
                    default => "300",
                    desc    => "tells ctrlproxy the frequency of time report in backlog
s",
                    type    => "text",
                },
                format      => {
                    default => '%h:%m',
                    desc    => "allows you to specify a format for timestamps reported"
,
                    type    => "text",
                }
            },
        },
        'stats' => {
            autoload    => 0,
            desc        => "generates a file with stats containing class of words or re
gexes",
        },
        'strip' => {
            autoload    => 1,
            desc        => "avoids to have a reply for a command requested by a client into other clients connected to the same network",
        },
        'socket' => {
            autoload    => 1,
            desc        => "allows you to connect to a server",
            needed      => 1,
            options     => {
                sslcertfile     => {
                    default => "$spool/ctrlproxy.pem",
                    desc    => "tells ctrlproxy where to find certificate for SSL listening",
                    type    => "text",
                },
                sslkeyfile      => {
                    default => "$spool/ctrlproxy.pem",
                    desc    => "tells ctrlproxy where to find key for SSL listening",
                    type    => "text",
                },
            },
        },
    },
	channels	=> {
		name	=> {
			desc	=> "is the channel name, for example #foo",
			type	=> "text",
			needed	=> 1,
			allowed	=> '^[#&$%][^\s\n\r,]+$'
		},
		autojoin	=> {
			desc	=> "tells if ctrlproxy has to connect automatically to network",
			type	=> "bool",
		},
		key			=> {
			desc	=> "allows you to specify a key to join the channel",
			type	=> "text",
		},
	},
    networks    => {
        nickname    => {
            desc    => "is your nickname on IRC",
            type    => "text",
            needed  => 1,
			allowed	=> '^[a-zA-Z][-\[\]\\`^{}a-zA-Z0-9]*$',
        },
        username    => {
            desc    => "is the name used for user/ident",
            type    => "text",
            default => $ENV{'USER'},
			allowed	=> '^\S+$',
        },
        fullname    => {
            desc    => "is the name shown in whois reply",
            type    => "text",
            default => "CtrlProxy User",
        },
        password    => {
            desc    => "is the password needed to connect to server",
            type    => "text",
        },
        client_pass => {
            desc    => "is the password needed for a client to attach",
            type    => "text",
        },
        autoconnect => {
            desc    => "says if ctrlproxy should connect automatically or not",
            type    => "bool",
            default => "1",
        },
        ignore_first_nick   => {
            desc    => "says if ctrlproxy should change nick when a new client sends NICK for the first time",
            type    => "bool",
            default => "1",
        },
    },
);

###########################################################################
#         And the code begins.... edit below at your own risk ;)          #
###########################################################################


###
# Initialisations
#$SIG{'INT'} = \&goback;
my $clear_string = `clear`;
my $level;
my $modified = 0;
my %levels;
my %conf;
my $title;


CheckSpool();
LoadDefaultConf(1);
main();

sub CheckSpool {
	if (-e $spool) {
	    if (! -d $spool) {
	        print "$spool already exists and is not a directory !!
	Please move this file to another place and try again.\n";
	        exit(1);
	    }
	} else {
	    mkdir $spool;
	}
}

sub bool2text {
    my ($bool) = @_;
    return ($bool eq '<undefined>' ? $bool : ($bool ? "Yes" : "No"));
}

sub getchar {
    system "stty", '-icanon', 'eol', "\001";
    system "stty", '-echo';
    my $key = getc(STDIN);
    system "stty", 'icanon', 'eol', '^@'; # ASCII null
    system "stty", 'echo';
#print "\n" unless ($key eq "\n");
    return $key;
}

sub getline {
    my $line = <STDIN>;
    chomp($line);
    return $line;
}

sub printsleep {
    my ($line) = @_;
    chomp($line);
    print "$line\n";
    sleep 1;
}

sub goback {
    my $ref = $levels{$level};
    if (ref($ref)) {
        &{$ref}();
    } else {
        exit(0);
    }
}

sub newScreen {
    print $clear_string;
    print "\033[7;40m CtrlProxy setup - $title\n\033[0;0m\n";
}

sub Pause {
    my ($title) = @_;
    print "\n\nPress a key to continue...\n";
    getchar();
    newScreen();
}

sub getOption {
	# verbose mode deactivated... 
	my ($text,$default,$needed,$type,$allowed,$notallowed,$verbose,$redraw) = @_;
	my ($value,$end);
	chomp($text);
	chomp($default);
	
	if ($type eq "text") {
		while (!$end) {
			newScreen() if ($redraw);
			print $text;
			print ' ['.$default.']' if ($default);
			print ": ";
			$value = getline();
			if (!$value and $default) {
				$end = 1;
				next;
			}
			if ($value) {
				if ($notallowed) {
					next if (exists $notallowed->{$value});
				}
				if ($allowed) {
					$end = 1 if ($value =~ /$allowed/i);
					next;
				} else {
					$end = 1;
					next;
				}
			} else {
				$end = 1 if (!$needed);
			}
		}
		if (!$value and $default) {
			$value = $default;
			print "\t(default value used)\n";
		}
#		print "\t$value\n" if ($verbose);
	} elsif ($type eq "bool") {
		while (!$end) {
			newScreen() if ($redraw);
			my $optionstext;
			print $text;
			if (defined $default) {
				$optionstext = ($default ? ' [Y/n]' : ' [y/N]');
				print $optionstext;
			}
			print ": ";
			$value = getchar();
			if ($value !~ /[yn]/i) {
				$value = $default if (defined $default);
				$end = 1;
				next;
			} else {
				if ($value =~ /y/i) {
					$value = 1;
					$end = 1;
					next;
				} else {
					$value = 0;
					$end = 1;
					next;
				}
			}
		}
		print " ".bool2text($value);# if ($verbose);
		print "\n";
	} elsif ($type eq "list") {
		while (!$end) {
			newScreen() if ($redraw);
			my $optionslist = join(', ',sort keys %{$allowed});
			print "Allowed values list: $optionslist\n";
			print $text;
			print ' ['.$default.']' if ($default);
			print ": ";
			$value = getline();
			if (!$value and $default) {
				$end = 1;
				next;
			}
			if (!$value and !$needed) {
				$end = 1;
				next;
			} elsif (!$value and $needed) {
				next;
			} else {
				if (defined $allowed->{$value}) {
					$end = 1;
					next;
				} else {
					next;
				}
			}
		}
		if (!$value and $default) {
			$value = $default;
			print "\t(default value used)\n";
		}
#		print "\t$value\n" if ($verbose);
	} else {
		newScreen() if ($redraw);
		die "No such type $type";
	}

	return $value;
}

sub main {
    while (1)
    {
		$title = "Main menu";
        newScreen();
        print <<EOI;
This program will help you with the configuration of ctrlproxy. It will ask you
some questions on what modules you want to use, networks to add, and then it
will generate a configuration file for you.

1. Select modules
2. Configure networks
3. Generate SSL Certificate/Key
4. Write configuration
5. Load basic modules configuration (soon !)
6. Load recommended modules configuration
7. Load configuration from file (soon !)
8. Wipe configuration
0. Exit
EOI
        my $char = getchar();
        if ($char eq '1') {
            MenuModules();
        } elsif ($char eq '2') {
            MenuNetworks();
        } elsif ($char eq '3') {
            GenerateSSL();
        } elsif ($char eq '4') {
            WriteConfig();
        } elsif ($char eq '5') {
        } elsif ($char eq '6') {
			LoadDefaultConf();
        } elsif ($char eq '7') {
        } elsif ($char eq '8') {
			EraseConf();
        } elsif ($char eq '0') {
			printCtrlproxyUsage();
            exit;
        }
    }
}

sub EraseConf {
	$title = "Wipe Configuration";
	newScreen();
	my $key = getOption("Completely erase configuration",0,1,"bool",undef,undef,0);
	if ($key) {
		%conf = ();
		printsleep "Configuration has been deleted.";
	} else {
		printsleep "Configuration has *not* been deleted.";
	}
}

sub LoadDefaultConf {
	my ($auto) = @_;
	my $key;

	if (!$auto) {
		$title = "Load Configuration";
		newScreen();
		$key = getOption("Load default modules configuration",0,1,"bool",undef,undef,0);
	}
	if ($key or $auto) {
		$conf{'modules'} = {};
		foreach my $option (keys %{$defs{'modules'}}) {
			$conf{'modules'}->{$option}->{'autoload'} =
				$defs{'modules'}->{$option}->{'autoload'};
			$conf{'modules'}->{$option}->{'suffix'} =
				$defs{'modules'}->{$option}->{'suffix'};
			if (defined $defs{'modules'}->{$option}->{'parameters'}) {
				foreach my $param (keys %{$defs{'modules'}->{$option}->{'parameters'}}) {
					$conf{'modules'}->{$option}->{'parameters'}->{$param} = {};
					$conf{'modules'}->{$option}->{'parameters'}->{$param}->{'value'} =
						$defs{'modules'}->{$option}->{'parameters'}->{$param}->{'default'};
				}
			}
			if (defined $defs{'modules'}->{$option}->{'options'}) {
				foreach my $opt (keys %{$defs{'modules'}->{$option}->{'options'}}) {
					$conf{'modules'}->{$option}->{'options'}->{$opt} = { 
						CopyOptions($defs{'modules'}->{$option}->{'options'}->{$opt}) };
				}
			}
		}
		printsleep "Default modules configuration has been loaded." unless $auto;
	} else {
		printsleep "Configuration kept.";
	}
}

sub CopyOptions {
	my ($h) = @_;
	my %copy = ();

	$copy{'value'} = $h->{'default'} if exists $h->{'default'};
	$copy{'suffix'} = $h->{'suffix'} if exists $h->{'suffix'};
	foreach my $param (keys %{$h->{'parameters'}}) {
		$copy{'parameters'}->{$param} = {};
		$copy{'parameters'}->{$param}->{'value'} =
			$h->{'parameters'}->{$param}->{'default'};
	}
	$copy{'options'} = CopyOptions($h->{'options'}) if exists $h->{'options'};
	return %copy;
}

sub printCtrlproxyUsage {
	$title = "Thanks for using ctrlproxy's configuration editor !";
    newScreen();
    print <<EOI;
Please report any bug to ctrlproxy\@vernstok.nl.
If you generated a configuration file, you can now launch ctrlproxy
and enjoy ;)
EOI
}

sub MenuNetworks {
    while (1) 
    {
		$title = "Networks - main menu";
        newScreen();
        print <<EOI;
This menu will help you to add networks and configure them.

1. Print networks
2. Add network
3. Delete network
4. Configure network
0. Return to main menu
EOI
        my $char = getchar();
        if ($char eq '1') {
            PrintNetworks();
        } elsif ($char eq '2') {
            AddNetwork(); 
        } elsif ($char eq '3') {
            DelNetwork();
        } elsif ($char eq '4') {
            ConfigureNetwork();
        } elsif ($char eq '0') {
            return 0;
        }
    }
}           

sub ConfigureNetwork {
    my ($network) = @_;
            
    $network = SelectNetwork() unless $network;
    return -1 unless $network;
	$conf{'networks'}->{$network} = () unless defined $conf{'networks'}->{$network};

    while (1)
    {
		$title = "Networks - $network";
        newScreen();
        print <<EOI;
This menu will help you to configure $network network.
        
1. Configure global options
2. Configure listeners
3. Configure servers
4. Configure channels
5. Configure nickserv
6. Print network
0. Return to network menu
EOI
        my $char = getchar();
        if ($char eq '1') {
			$title = "Networks - $network - global options";
			newScreen();
			$conf{'networks'}->{$network}->{'parameters'} = {}
				if (!defined $conf{'networks'}->{$network}->{'parameters'});
			HandleParameters($conf{'networks'}->{$network}->{'parameters'},$defs{'networks'});
        } elsif ($char eq '2') {
            ConfigureNetworkListen($network);
        } elsif ($char eq '3') {
            ConfigureNetworkServers($network);
        } elsif ($char eq '4') {
            ConfigureNetworkChans($network);
        } elsif ($char eq '5') {
            #ConfigureNetworkNS($network);
        } elsif ($char eq '6') {
            PrintNetwork($network);
        } elsif ($char eq '0') {
            return 0;
        }
    }
}

sub ConfigureNetworkChans {
    my ($network) = @_;
	return -1 unless $network;

    while (1)
    {
		$title = "Networks - configure $network\'s chans";
        newScreen();
        print <<EOI;
This menu will help you to configure $network\'s channel list.

1. Print channels
2. Add channel
3. Delete channel
0. Return to network configure menu
EOI
        my $char = getchar();
        if ($char eq '1') {
			PrintChans($network);
        } elsif ($char eq '2') {
            AddChan($network);
        } elsif ($char eq '3') {
            DelChan($network);
        } elsif ($char eq '0') {
            return 0;
        }
    }
}

sub PrintChans {
	my ($network) = @_;
	
	$title = "Networks - configure $network\'s channels";
    newScreen();
    if (scalar keys %{$conf{'networks'}->{$network}->{'channels'}} eq 0) {
		$title = "ERROR";
        printsleep "No channel has been defined yet !";
        return 0;
    }
	
	foreach my $chan (sort keys %{$conf{'networks'}->{$network}->{'channels'}}){
		my $ch = $conf{'networks'}->{$network}->{'channels'}->{$chan};
		print "\t$chan";
		print " (".$ch->{'key'}.")" if (defined $ch->{'key'});
		print " noauto" if (!isYes($ch,'autojoin'));
		print "\n";
	}
    Pause();
}
sub AddChan {
	my ($network) = @_;
	my ($chan, $aj, $key);
	
	$title = "Networks - add a channel to $network";
    newScreen();
	$chan = getOption("Channel name",
			undef,0,"text",$defs{'channels'}->{'name'}->{'allowed'},
			$conf{'networks'}->{$network}->{'channels'},0,0);
	return -1 unless $chan;
    $key = getOption("Channel key",undef,0,"text",undef,undef,0,0);
	$aj = getOption("Autojoin channel on connect ?",1,1,"bool",undef,undef,0,0);

	$conf{'networks'}->{$network}->{'channels'}->{$chan} = {};
	$conf{'networks'}->{$network}->{'channels'}->{$chan}->{'key'} = $key if
		($key);
	$conf{'networks'}->{$network}->{'channels'}->{$chan}->{'autojoin'} = $aj;
}

sub SelectChan {
	my ($network) = @_;
	
    if (scalar keys %{$conf{'networks'}->{$network}->{'channels'}} eq 0) {
		$title = "ERROR";
        newScreen();
        printsleep "No channel has been defined yet !";
        return 0;
    }

	return getOption("Please choose a channel in the list",undef, 0,"list",
			$conf{'networks'}->{$network}->{'channels'}, undef, 0,1);
}

sub DelChan {
	my ($network) = @_;

	my $chan = SelectChan($network);
	return -1 unless $chan;
	my $key = getOption("Are you sure you want to delete $chan from $network",
			0, 1,"bool",undef,undef,0,1);
	return 0 unless $key;
    delete $conf{'networks'}->{$network}->{'channels'}->{$chan};
    printsleep "Channel $chan deleted from $network.";
}

sub ConfigureNetworkServers {
    my ($network) = @_;
	return -1 unless $network;

    while (1)
    {
		$title = "Networks - $network - servers";
        newScreen();
        print <<EOI;
This menu will help you to configure $network\'s server list.

1. Print servers
2. Add server
3. Delete server
0. Return to network configure menu
EOI
        my $char = getchar();
        if ($char eq '1') {
			$title = "Networks - $network - servers";
            newScreen();
			my $ret = PrintIPList($conf{'networks'}->{$network}->{'servers'},"server",\&PrintServer);
            if ($ret) {
                sleep 1;
            } else {
                Pause();
            }
        } elsif ($char eq '2') {
            AddServer($network);
        } elsif ($char eq '3') {
            DelServer($network);
        } elsif ($char eq '0') {
            return 0;
        }
    }
}

sub ConfigureNetworkListen {
    my ($network) = @_;
    return -1 unless $network;

    while (1)
    {
		$title = "Networks - $network - listeners";
        newScreen();
        print <<EOI;
This menu will help you to configure $network\'s listeners.

1. Print listeners
2. Add listener
3. Delete listener
0. Return to network configure menu
EOI
        my $char = getchar();
        if ($char eq '1') {
			$title = "Networks - $network - listeners";
            newScreen();
			my $ret =
				PrintIPList($conf{'networks'}->{$network}->{'listeners'},"listener",\&PrintListen);
            if ($ret) {
                sleep 1;
            } else {
                Pause();
            }
        } elsif ($char eq '2') {
            AddListener($network);
        } elsif ($char eq '3') {
            DelListener($network);
        } elsif ($char eq '0') {
            return 0;
        }
    }
}

sub MenuModules
{
    while (1)
    {
		$title = "Modules - main menu";
        newScreen();
        print <<EOI;
This menu allows you to configure and select modules.
(work in progress. Changing a module's options is not possible atm)

1. Configure and select modules
2. Configure and select a specific module
3. Print selection and configuration of modules
4. Print configuration of a specific module
0. Return to main menu
EOI

        my $char = getchar();
        if ($char eq '1') {
            foreach my $module (sort keys %{$defs{'modules'}}) {
                my $ret = ConfigureModule($module);
                last if ($ret);
            }
        } elsif ($char eq '2') {
            ConfigureModule();
        } elsif ($char eq '3') {
            foreach my $module (sort keys %{$defs{'modules'}}) {
                my $ret = DumpModule($module);
                last if ($ret);
            }
        } elsif ($char eq '4') {
            DumpModule();
        } elsif ($char eq '0') {
            return 0;
        }
    }
}

sub GenerateSSL {
	$title = "SSL - generate a certificate";
    newScreen();
    my $HOSTNAME = getOption("Short hostname touse","localhost",1,"text",'^\S+$',undef,0);
    my $FQDN = getOption("FQDN to use","localhost.localdomain",1,"text",'^\S+$',undef,0);
    my $file = "$spool/ctrlproxy.pem";
    if (-e $file)
    {
        rename $file,"$file.old";
        print "An existing certificate was found, and renamed to $file.old\n";
    }

    my $fh = new IO::File;
    my $tmpssl = "$spool/.tmpssl";
    $fh->open(">$tmpssl");
    print $fh <<EOS;
.
.
.
ControlProxy IRC BNC
$HOSTNAME
$FQDN
$ENV{'USER'}\@$HOSTNAME
EOS

    print "Generating SSL certificate...\n";
    system("openssl req -new -x509 -days 365 -nodes -out $file -keyout $file <$tmpssl >/dev/null 2>&1");
    unlink $tmpssl;

    if (-e $file)
    {
        print "SSL certificate generated into $spool/ctrlproxy.pem...\n";
    } else {
        print "An error occured during certificate generation...\n";
    }

    Pause();
}

sub AddNetwork {
    my ($name,$key);
 
	$title = "Networks - add network";
    newScreen();
	# $text,$default,$needed,$type,$allowed,$notallowed,$verbose
	$name = getOption("Enter network name to add (no space)",undef,0,"text",'^\S+$',$conf{'networks'},0,0);
    return -1 unless $name;
        
    $conf{'networks'}->{$name} = {};
    $key = getOption("Configure $name network now",1,1,"bool",undef,undef,0,0);
    ConfigureNetwork($name) if ($key);
    return 0;
}

sub SelectNetwork {
    if (scalar keys %{$conf{'networks'}} eq 0) {
		$title = "ERROR";
        newScreen();
        printsleep "No network has been defined yet !
Please choose Add network option in menu.\n";
        return 0;
    }

	return getOption("Please choose a network in the list",undef, 0,"list", $conf{'networks'}, undef, 0,1);
}

sub SelectModule {
	return getOption("Please choose a module in the list",undef,0,"list", $defs{'modules'}, undef, 0, 1);
}

sub DelNetwork {
    my ($network) = @_;

    $network = SelectNetwork() unless $network;
    return -1 unless $network;

	$title = "Networks - delete a network";
    newScreen();
	my $key = getOption("Are you sure you want to delete $network network ?",0,1,"bool",$conf{'networks'},undef,0,0);
    if ($key) {
        delete $conf{'networks'}->{$network};
        printsleep "\n\nNetwork has successfully been deleted.\n";
    }
}

sub PrintIPList {
    my ($hashref,$name,$funref) = @_;
    
    if (scalar keys %{$hashref} eq 0) {
        print "No $name has been defined yet !\n";
        return -1;
    }
    
    foreach my $key (sort keys %{$hashref}) {
        &{$funref}($key, $hashref->{$key});
    }
}   

sub AddServer {
    my ($network) = @_;
    my ($port,$ssl,$host,$proto);

	$title = "Networks - $network - add a server";
	newScreen();

	$proto = getOption("IP protocol (IPv)",4,1,"text",'^[46]$',undef,1,0);
	$port = getOption("Port to connect to",6667,1,"text",'^\d{0,5}$',undef,0,0);
	$host = getOption("Host/IPv$proto to connect to",undef,1,"text",'^\S+$',undef,0,0);
	$ssl = getOption("Use SSL",0,1,"bool",undef,undef,1,0);
	Pause();

	my $next = 1;
	foreach my $key (keys %{$conf{'networks'}->{$network}->{'servers'}}) {
		$next =  ($key >= $next ? $key+1 : $next);
	}
	$conf{'networks'}->{$network}->{'servers'} = {}
		unless defined $conf{'networks'}->{$network}->{'servers'};
	$conf{'networks'}->{$network}->{'servers'}->{$next} = {}
		unless defined $conf{'networks'}->{$network}->{'servers'}->{$next};
    $conf{'networks'}->{$network}->{'servers'}->{$next}->{'ssl'} = $ssl;
    $conf{'networks'}->{$network}->{'servers'}->{$next}->{'host'} = $host;
    $conf{'networks'}->{$network}->{'servers'}->{$next}->{'proto'} = $proto;
    $conf{'networks'}->{$network}->{'servers'}->{$next}->{'port'} = $port;

}

sub AddListener {
    my ($network) = @_;
    my ($port,$ssl,$host,$proto);

	$title = "Networks - $network - add a listener";
	newScreen();

	$proto = getOption("IP protocol (IPv)",4,1,"text",'^[46]$',undef,1,0);
	$port = getOption("Port to listen to",undef,1,"text",'^\d{0,5}$',undef,0,0);
	$host = getOption("Host/IPv$proto to bind to",($proto eq 4 ? '0.0.0.0' : 
					                '::'),1,"text",'^\S+$',undef,0,0);
	$ssl = getOption("Use SSL",0,1,"bool",undef,undef,1,0);
	Pause();

	my $next = 1;
	foreach my $key (keys %{$conf{'networks'}->{$network}->{'listeners'}}) {
		$next =  ($key >= $next ? $key+1 : $next);
	}
	$conf{'networks'}->{$network}->{'listeners'} = {}
		unless defined $conf{'networks'}->{$network}->{'listeners'};
	$conf{'networks'}->{$network}->{'listeners'}->{$next} = {}
		unless defined $conf{'networks'}->{$network}->{'listeners'}->{$next};
    $conf{'networks'}->{$network}->{'listeners'}->{$next}->{'ssl'} = $ssl;
    $conf{'networks'}->{$network}->{'listeners'}->{$next}->{'host'} = $host;
    $conf{'networks'}->{$network}->{'listeners'}->{$next}->{'proto'} = $proto;
    $conf{'networks'}->{$network}->{'listeners'}->{$next}->{'port'} = $port;
}


sub DelServer {
    my ($network) = @_;
    my $net = $conf{'networks'}->{$network};
    my $line;
    
	$title = "Networks - $network - delete a server";
	newScreen();
	my $ret = PrintIPList($net->{'servers'},"server",\&PrintServer);
	if ($ret) {
		sleep 1;
		return 0;
	}
	my $line = getOption("Please choose a server in the list",undef, 0,"text",
			'('.join('|',keys %{$net->{'servers'}}).')', undef, 0,0);
    return 0 unless $line;

    delete $net->{'servers'}->{$line};
    printsleep "Server $line deleted.\n";
}       

sub DelListener {
    my ($network) = @_;
    my $net = $conf{'networks'}->{$network};
    my $line;
    
	$title = "Networks - $network - delete a listener";
	newScreen();
	my $ret = PrintIPList($net->{'listeners'},"listener",\&PrintListen);
	if ($ret) {
		sleep 1;
		return 0;
	}
	my $line = getOption("Please choose a listener in the list",undef, 0,"text",
			'('.join('|',keys %{$net->{'listeners'}}).')', undef, 0,0);
    return 0 unless $line;

    delete $net->{'listeners'}->{$line};
    printsleep "Listener $line deleted.\n";
}

sub PrintNetworks {
    if (scalar keys %{$conf{'networks'}} eq 0) {
		$title = "Networks - networks information";
        newScreen();
        printsleep "No network has been defined yet !\n";
        return -1;
    }       
                
    foreach my $net (sort keys %{$conf{'networks'}}) {
        PrintNetwork($net);
    }           
}       

sub PrintListen {
    my ($listen,$listenhash) = @_;

    next unless defined $listenhash->{'port'};
    my $port = $listenhash->{'port'};
    my $ssl = ((defined($listenhash->{'ssl'}) and $listenhash->{'ssl'} eq 1)
            ? '(SSL)' : '');
    my $proto = $listenhash->{'proto'};
    my $bind = (defined $listenhash->{'host'} ? $listenhash->{'host'} :
                ($proto eq 4 ? '0.0.0.0' : '::' ));
    print "$listen. IPv$proto\://$bind\:$port $ssl\n";
}

sub PrintServer {
    my ($serv,$servhash) = @_;

    next unless defined $servhash->{'host'};
    my $port = (defined($servhash->{'port'}) ? $servhash->{'port'}
            : 6667);
    my $ssl = ((defined($servhash->{'ssl'}) and $servhash->{'ssl'} eq 1)
            ? '(SSL)' : '');
    my $proto = (defined($servhash->{'proto'}) ? $servhash->{'proto'}
            : '4');
    my $host = $servhash->{'host'};
    print "$serv. IPv$proto\://$host\:$port $ssl\n";
}

sub DumpModule {
    my ($module) = @_;

    $module = SelectModule() unless $module;
    return -1 unless $module;

	$title = "Modules - $module";
    newScreen();
    print "Module $module :\n";
    print Dumper($conf{'modules'}->{$module});
    Pause();
    return 0;
}

sub PrintNetwork {
    my ($net) = @_;

    $net = SelectNetwork() unless $net;
    return -1 unless $net;

    my $nethash = $conf{'networks'}->{$net}->{'parameters'};
	$title = "Networks - $net - information";
	newScreen();
    print "$net network :\n";

    foreach my $param (keys %{$defs{'networks'}}) {
        my $value = (defined $nethash->{$param}->{'value'}
				? $nethash->{$param}->{'value'}
				: '<undefined>');

        if ($defs{'networks'}->{$param}->{'type'} eq "bool") {
            print "\t$param\: ".bool2text($value)."\n";
        } elsif ($defs{'networks'}->{$param}->{'type'} eq "text") {
            print "\t$param\: ".$value."\n";
        }
    }

    print "\nChannels to join (* password protected,  no autojoin):\n";
    if (defined $conf{'networks'}->{$net}->{'channels'}) {
		my $start = 1;
		foreach my $chan (sort keys %{$conf{'networks'}->{$net}->{'channels'}}) {
			if (!$start) {
				print ", ";
			} else {
				$start = 0;
			}
			print "*" if defined
				$conf{'networks'}->{$net}->{'channels'}->{$chan}->{'key'};
			print "" unless 
				isYes($conf{'networks'}->{$net}->{'channels'}->{$chan},
						'autojoin');
			print $chan;
		}
	} else {
        print "\tNo channels defined yet !\n";
    }
	Pause();

    print "Servers :\n";
    PrintIPList($conf{'networks'}->{$net}->{'servers'},"server",\&PrintServer);
    print "Listeners :\n";
    PrintIPList($conf{'networks'}->{$net}->{'listeners'},"listener",\&PrintListen);
	Pause();
}

sub isYes {
	my ($hash,$key) = @_;

	return (defined($hash->{$key}) ? ($hash->{$key} eq 1 ? 1 : 0) : 0);
}

sub ConfigureModule {
    my ($module) = @_;
    my $changed = 0;

    $module = SelectModule() unless $module;
    return -1 unless $module;
    
	$title = "Modules - $module";
    newScreen();
    print "This module ".$defs{'modules'}->{$module}->{'desc'}."\n\n";
    
    if (isYes($defs{'modules'}->{$module},'needed')) {
        print "This module is needed, thus you cannot deactivate it.\n";
		$conf{'modules'}->{$module}->{'autoload'} = 1;
    } else {
		$conf{'modules'}->{$module}->{'autoload'} =
			 getOption("Activate this module", $defs{'modules'}->{$module}->{'autoload'},1,"bool",undef,undef,1,0);
	}

	if ($conf{'modules'}->{$module}->{'autoload'} eq 0) {
		Pause();
		return 0;
	}

	if (defined $defs{'modules'}->{$module}->{'allowed'}) {
		$conf{'modules'}->{$module}->{'suffix'} = 
			getOption("Please select a type for this module",
				$defs{'modules'}->{$module}->{'suffix'},1,"list",
				$defs{'modules'}->{$module}->{'allowed'},undef,0,0);
		Pause();
	}
	
    if (defined $defs{'modules'}->{$module}->{'options'}) {
		$title = "Modules - configure $module\'s options";
		$conf{'modules'}->{$module}->{'options'} = {}
			if (!defined $conf{'modules'}->{$module}->{'options'});
        HandleOptionsTree($conf{'modules'}->{$module}->{'options'},
				$defs{'modules'}->{$module}->{'options'});
    } else {
        print "This module does not have any options.\n";
    }
	Pause();
	return 0;
}

sub HandleOptionsTree {
	my ($hash,$defaults) = @_;

	foreach my $option (sort keys %{$defaults}) {
		newScreen();
		my $default = (defined $hash->{$option}->{'value'}
			? $hash->{$option}->{'value'}
			: (defined $defaults->{$option}->{'default'}
				? $defaults->{$option}->{'default'}
				: undef));
		my $h = $defaults->{$option};

		print "The $option option ".$h->{'desc'}."\n";
		$hash->{$option}->{'value'} = getOption($option, $default, 
			$h->{'needed'}, $h->{'type'}, $h->{'allowed'}, undef,
			($h->{'type'} eq "bool" ? 1 : 0 ),0);

		if (defined $h->{'parameters'}) {
			$hash->{$option}->{'parameters'} = {}
				if (!defined $hash->{$option}->{'parameters'});
			HandleParameters($hash->{$option}->{'parameters'},$h->{'parameters'});
		}

		if (defined $h->{'options'}) {
			$hash->{$option}->{'options'} = {}
				if (!defined $hash->{$option}->{'options'});
			HandleOptionsTree($hash->{$option}->{'options'},$h->{'options'});
		}

	}
}

sub HandleParameters {
    my ($config,$defaults) = @_;

    foreach my $param (sort keys %{$defaults}) {
		my $default = (defined $config->{$param}->{'value'}
			? $config->{$param}->{'value'}
			: (defined $defaults->{$param}->{'default'}
				? $defaults->{$param}->{'default'}
				: undef));
		my $h = $defaults->{$param};

		print "The $param parameter ".$h->{'desc'}."\n";
		$config->{$param}->{'value'} = getOption($param, $default,
			$h->{'needed'}, $h->{'type'}, $h->{'allowed'}, undef,
			($h->{'type'} eq "bool" ? 1 : 0 ),0);
	}
}

sub PrintXMLOptions {
	my ($h) = @_;
	my $text;

	foreach my $option (sort keys %{$h}) {
		$text .= "        <$option";
		if (defined $h->{$option}->{'parameters'}) {
			$text .= PrintXMLParams($h->{$option}->{'parameters'});
		}
		if (defined $h->{$option}->{'options'} or defined
				$h->{$option}->{'value'}) {
			$text .= ">";
			$text .= "\n" unless defined $h->{$option}->{'value'};
			$text .= PrintXMLOptions($h->{$option}->{'options'}) if defined
				$h->{$option}->{'options'};
			$text .= $h->{$option}->{'value'} if defined
				$h->{$option}->{'value'};
			$text .= "        " unless defined $h->{$option}->{'value'};
			$text .= "</$option>\n";
		} else {
			$text .= "/>\n";
		}
	}
	return $text;
}

sub PrintXMLParams {
	my ($h) = @_;
	my $text;

	foreach my $param (sort keys %{$h}) {
		$text .= ' '.$param.'="'.$h->{$param}->{'value'}.'"' if defined
			$h->{$param}->{'value'};
	}
	return $text;
}

sub WriteConfig {
	my ($file,$fh);
	$title = "Write configuration file";
	newScreen();

	$file = getOption("Configuration file","$ENV{'HOME'}/.ctrlproxyrc",1,"text",undef,undef,0);

	if (-e $file) {
		my $time = strftime('%s',localtime);
		rename $file,"$file.$time";
		print "Warning: $file already existed and was renamed to $file.$time\n\n";
	}
	
	$fh = new IO::File;
	if (!$fh->open(">$file")) {
		printsleep "Unable to write into $file !";
		return -1;
	}
	print $fh "<?xml version=\"1.0\"?>\n<ctrlproxy>\n";
	print $fh "  <plugins>\n";

	foreach my $module (sort keys %{$conf{'modules'}}) {
		my $autoload = (isYes($conf{'modules'}->{$module},'autoload') ? 1 : 0);
		print $fh '    <plugin autoload="'.$autoload.'" file="lib'.$module;
		print $fh $conf{'modules'}->{$module}->{'suffix'} if defined
			$conf{'modules'}->{$module}->{'suffix'};
		if (scalar keys %{$conf{'modules'}->{$module}->{'options'}}) {
			print $fh "\">\n";
			print $fh PrintXMLOptions($conf{'modules'}->{$module}->{'options'});
			print $fh "    </plugin>\n";
		} else {
			print $fh "\"/>\n";
		}
	}

	print $fh "  </plugins>\n  <networks>\n";

	foreach my $network (sort keys %{$conf{'networks'}}) {
		my $net = $conf{'networks'}->{$network};
		print $fh '    <network';
		print $fh PrintXMLParams($net->{'parameters'});
#		foreach my $param (sort keys %{$net->{'parameters'}}) {
#			print $fh "$param=\"".$net->{'parameters'}->{$param}->{'value'}."\" ";
#		}
		print $fh ">\n\n";
		
		print $fh "      <listen>\n";
		foreach my $listen (sort keys %{$net->{'listeners'}}) {
			my $listener = $net->{'listeners'}->{$listen};
			my ($host,$ssl);
			next unless (defined $listener->{'proto'} and defined $listener->{'port'});
			$host = 'bind="'.$listener->{'host'}.'" ' if defined $listener->{'host'};
			$ssl = 'ssl="1" ' if isYes($listener,'ssl');
			print $fh '        <ipv'.$listener->{'proto'}.' '.$host.$ssl.
				'port="'.$listener->{'port'}.'"/>'."\n";
		}
		print $fh "      </listen>\n\n";
			
		print $fh "      <servers>\n";
		foreach my $serv (sort keys %{$net->{'servers'}}) {
			my $server = $net->{'servers'}->{$serv};
			my ($port,$ssl);
			next unless (defined $server->{'proto'} and defined $server->{'host'});
			$port = (defined $server->{'port'} ? $server->{'port'} : '6667');
			$ssl = 'ssl="1" ' if isYes($server,'ssl');
			print $fh '        <ipv'.$server->{'proto'}.' host="'.$server->{'host'}.
				'" '.$ssl.'port="'.$port.'"/>'."\n";
		}
		print $fh "      </servers>\n\n";
		
		foreach my $ch (sort keys %{$net->{'channels'}}) {
			my $hash = $net->{'channels'}->{$ch};
			my ($aj,$key);
			$aj = (isYes($hash,'autojoin') ? 'autojoin="1" ' : 'autojoin="0" ');
			$key = 'key="'.$hash->{'key'}.'" ' if defined $hash->{'key'};
			print $fh '      <channel name="'.$ch.'" '.$aj.$key.'/>'."\n";
		}
	
		print $fh '    </network>'."\n";
	}

	print $fh "  </networks>\n";
	print $fh "</ctrlproxy>\n";

	$fh->close();
	print "Config has been written in $file sucessfully.\n";
	Pause();
}
