package VPS;

use strict;
use Tk;
use Tk::Menu;
use Exporter;

@VPS::ISA = qw(Exporter);
@VPS::EXPORT    = qw(initialize openWindow windowOpen closeWindow parseConfigFile saveConfigFile getCurrentDir saveCurrentDir currentlyOpenFile currentlyOpenFilename newTunnelCommand modifyTunnelCommand deleteTunnelCommand message fileDirty buildMainString buildKeyLabelString buildTunnelRouteString tunnelAddRoute tunnelModifyRoute updateTunnel);

my %windowhash;
my $fileDirty = 0;
local $VPS::debug;
local @VPS::data;
local $VPS::defaultkeydir;
local $VPS::keydir;
local $VPS::globalrefdata;
local $VPS::routenum;
local $VPS::VPSversion;
local $VPS::defaultFilename;
local $VPS::currentFile;
local $VPS::currentDir;

sub initialize {
    $VPS::debug = 0;
    @VPS::data = { };
    $VPS::defaultkeydir = '/etc/vps';
    $VPS::keydir = $VPS::defaultkeydir;
    $VPS::globalrefdata = \@VPS::data;
    $VPS::routenum;
    $VPS::VPSversion = '2.0b2';
    $VPS::defaultFilename = '/etc/vps/tunnel.conf';
    $VPS::currentFile = $VPS::defaultFilename;
    if (-d '/etc/vps/') {
	$VPS::currentDir = '/etc/vps';
    }
    else {
	$VPS::currentDir = '/etc';
    }
}
sub openWindow {
    my ($id) = @_;

    return 1 if ((defined($windowhash{$id})) && ($windowhash{$id}));
    if ($VPS::mainWindow::root) {
	$VPS::mainWindow::root->configure(-cursor => 'watch');
    }
    $windowhash{$id} = 1;
    return 0;
}

sub windowOpen {
    my ($id) = @_;

    return 1 if ((defined($windowhash{$id})) && ($windowhash{$id}));
    return 0;
}

sub closeWindow {
    my ($id) = @_;

    if ((defined($windowhash{$id})) && ($windowhash{$id})) {
	if ($VPS::mainWindow::root) {
	    $VPS::mainWindow::root->configure(-cursor => 'arrow');
	}
	undef ($windowhash{$id});
	return 0;
    }
    return 1;
}
    
sub getCurrentDir {
    if (!$VPS::currentDir) {
	my $pwd = `pwd`;
	chop $pwd;
	$VPS::currentDir = $pwd if (!$VPS::currentDir);
	$VPS::currentDir = '.' if (!$VPS::currentDir);
    }
    return $VPS::currentDir;
}

sub saveCurrentDir {
    my ($file) = @_;

    if ($file) {
	$VPS::currentDir = `dirname $file`;
	chop $VPS::currentDir;
	$file = $VPS::currentDir . '/' . `basename $file`;
	chop $file;
    }
    $VPS::currentFile = $file;
    return $file;
}

sub makeErrorString {
    my ($error,$fdata,$pointer,$printerror) = @_;

    $printerror =~ s/^(.*)/\1/; # Truncate it to one line
    my $truncfile = substr($fdata,0,$pointer); #Find out on what line the error occurred
    my $line = split("\n",$truncfile);
    print "$error at or around line $line: $printerror\n";
    return "$error at or around line $line: $printerror\n";
}
    
sub parseConfigFile {
    my ($file) = @_;

    my @mydata;
    my $index = 0;
    my $state = 0;
    my $error;
    my $localroutenum;
    my $remoteroutenum;
    my $newkeydir;
    my $tmpdest;
    my $tmpmask;
    $fileDirty = 0;
    $VPS::currentFile = $file;
    if (!open(IN,"<$VPS::currentFile")) {
	return "Could not open $VPS::currentFile!";
    }
    my @file = <IN>;
    close(IN);
    my $fdata = join("",@file);
    my $length = length($fdata);
    my ($token,$pointer);
    while ((!$error) && $pointer < $length) {
	($token,$pointer,$error) = nextToken($fdata,$pointer,$length);
	if ((!$error) && $pointer <= $length) {
	    if ($VPS::debug) { 
		print "\"$token\"\n";
	    }
	}
	if ($error) {
	    return makeErrorString($error,$fdata,$pointer,$token);
	}
	if ($state == 0) { # We're at the beginning of a tunnel directive
	    if (lc($token) eq "keydir") {
		if ($newkeydir ne "") {
		    return makeErrorString("More than one KEYDIR directive",$fdata,$pointer,$token);
		}
		$state = 4;
		next;
	    }
	    elsif (lc($token) eq "tunnel") {
		$localroutenum = 0; # We don't have any routes set for this tunnel yet
		$remoteroutenum = 0; # We don't have any routes set for this tunnel yet
		$state = 1; # We're ready to read in tunnel name
		next;
	    }
	    else {
		return makeErrorString("Invalid command",$fdata,$pointer,$token);
	    }
	}
	if ($state == 1) { # Ready to read in tunnel name
	    $mydata[$index]{'tunnelName'} = $token;
	    $state = 2; # Ready to read in addressing and route info
	    next;
	}
	if ($state == 2) { # Ready to read in Address or route info
	    if (lc($token) eq "local") { # Read in the local info next
		if ($mydata[$index]{'tunnelLocalAddr'} ne "") {
		    return makeErrorString("Attempt to define more than one Local Address for Tunnel \"$mydata[$index]{'tunnelName'}\"",$fdata,$pointer,$token);
		}
		$state = 21;
		next;
	    }
	    if (lc($token) eq "remote") { # Read in the remote info next
		if ($mydata[$index]{'tunnelRemoteAddr'} ne "") {
		    return makeErrorString("Attempt to define more than one Remote Address for Tunnel \"$mydata[$index]{'tunnelName'}\"",$fdata,$pointer,$token);
		}
		$state = 22;
		next;
	    }
	    if (lc($token) eq "gateway") { # Read in the gateway info next
		if ($mydata[$index]{'tunnelGatewayAddr'} ne "") {
		    return makeErrorString("Attempt to define more than one Gateway for Tunnel \"$mydata[$index]{'tunnelName'}\"",$fdata,$pointer,$token);
		}
		$state = 24;
		next;
	    }
	    if (lc($token) eq "localroute") { # Read in the route info next
		$state = 3;
		next;
	    }
	    if (lc($token) eq "remoteroute") { # Read in the route info next
		$state = 5;
		next;
	    }
	    if (lc($token) eq "keyfile") { # Read in the file info
		if ($mydata[$index]{'keyFile'} ne "") {
		    return makeErrorString("Attempt to define more than one Key File for Tunnel \"$mydata[$index]{'tunnelName'}\"",$fdata,$pointer,$token);
		}
		$state = 23;
		next;
	    }
	    if (lc($token) eq "tunnel") { # Ready to read in the next tunnel?
		if ($mydata[$index]{'tunnelName'} eq "") { # No tunnel name
		    return makeErrorString("Syntax error: No tunnel name",$fdata,$pointer,$token);
		}
		if ($mydata[$index]{'tunnelLocalAddr'} eq "") {
		    return makeErrorString("Syntax error: No Local Address for Tunnel \"$mydata[$index]{'tunnelName'}\"",$fdata,$pointer,$token);
		}
		if ($mydata[$index]{'tunnelRemoteAddr'} eq "") {
		    return makeErrorString("Syntax error: No Remote Address for Tunnel \"$mydata[$index]{'tunnelName'}\"",$fdata,$pointer,$token);
		}
		$index++;
		$VPS::routenum = 0;
		$state = 1;
		next;
	    }
	    if ($token != -1) {
		return makeErrorString("Syntax error: Unknown Command \"$token\"",$fdata,$pointer,$token);
	    }
	}
	if ($state == 21) { # Next item is the Local Address
	    $mydata[$index]{'tunnelLocalAddr'} = $token;
	    $state = 2; # Ready to read in addressing and route info
	    next;
	}
	if ($state == 22) { # Next item is the Remote Address
	    $mydata[$index]{'tunnelRemoteAddr'} = $token;
	    $state = 2; # Ready to read in addressing and route info
	    next;
	}
	if ($state == 23) { # Next item is the Key File
	    $mydata[$index]{'keyFile'} = $token;
	    $state = 2; # Ready to read in addressing and route info
	    next;
	}
	if ($state == 24) { # Next item is the Gateway Address
	    $mydata[$index]{'tunnelGatewayAddr'} = $token;
	    $state = 2; # Ready to read in addressing and route info
	    next;
	}
	if ($state == 3) { # Next item is the Route network address
	    $tmpdest = $token;
	    $state = 31; # Ready to read in addressing and route info
	    next;
	}
	if ($state == 31) { # Next item is the Route network address
	    $tmpmask = $token;
	    my $string = buildTunnelRouteString($tmpdest,$tmpmask);
	    $mydata[$index]{'tunnelLocalRoute'}[$localroutenum] = $string;
	    $localroutenum++;
	    $state = 2; # Ready to read in more tunnel info
	    next;
	}
	if ($state == 4) { # Ready to read in Key Directory
	    if ($token !~ m#/$#) {
		$newkeydir = $token . '/';
	    }
	    else {
		$newkeydir = $token;
	    }
	    $state = 0; # Ready to read in tunnel
	    next;
	}
	if ($state == 5) { # Next item is the Route network address
	    $tmpdest = $token;
	    $state = 51; # Ready to read in addressing and route info
	    next;
	}
	if ($state == 51) { # Next item is the Route network address
	    $tmpmask = $token;
	    my $string = buildTunnelRouteString($tmpdest,$tmpmask);
	    $mydata[$index]{'tunnelRemoteRoute'}[$remoteroutenum] = $string;
	    $remoteroutenum++;
	    $state = 2; # Ready to read in more tunnel info
	    next;
	}
    }
    if ($error) {
	return $error;
    }
    # Otherwise let's finish up the last tunnel
    if ($mydata[$index]{'tunnelName'} eq "") { # No tunnel name
	return makeErrorString("Syntax error: No tunnel name",$fdata,$pointer,$token);
    }
    if ($mydata[$index]{'tunnelLocalAddr'} eq "") {
	return makeErrorString("Syntax error: No Local Address for Tunnel \"$mydata[$index]{'tunnelName'}\"",$fdata,$pointer,$token);
    }
    if ($mydata[$index]{'tunnelRemoteAddr'} eq "") {
	return makeErrorString("Syntax error: No Remote Address for Tunnel \"$mydata[$index]{'tunnelName'}\"",$fdata,$pointer,$token);
    }
    if ($mydata[$index]{'tunnelGatewayAddr'} eq "") {
	return makeErrorString("Syntax error: No Gateway Address for Tunnel \"$mydata[$index]{'tunnelName'}\"",$fdata,$pointer,$token);
    }

    # OK, we're committed to this.  Let's erase the old data and replace it.
    
    # delete old data
    my $oldindex = $VPS::mainWindow::lbTunnel->index("end");
    my $i;
    for ($i=0;$i<=$oldindex;$i++) {
	$VPS::mainWindow::lbTunnel->delete(0);
    }
    undef @VPS::data;
    @VPS::data = @mydata;
    $VPS::globalrefdata = \@VPS::data;
    if ($newkeydir eq "") {
	$newkeydir = '/etc/vps/keys/';
    }
    $VPS::keydir = $newkeydir;
    return ("Opened $file.",$VPS::globalrefdata);
}

sub nextToken { # Give the next valid token in the string
    my ($fdata,$start,$length) = @_;
    my $token = "";
    my ($char,$special,$i);
    for ($i=$start;$i<$length;$i++) {
	($char,$i,$special) = getTokenChar($fdata,$i);
	if ($special) {
	    $token .= $char;
	    $special = 0;
	    next;
	}
	if ($char =~ /\s/) { # Whitespace
	    if ($token) {
		return ($token,$i,0); # Success!
	    }
	    else { # Otherwise get another character
		next;
	    }
	}
	if ($char eq '#') { # Everything from here to the  end of the line 
	                    # is a comment
	    my $end = index($fdata,"\n",$i+1);
	    if ($end == -1) { # This shouldn't really happen
		if ($VPS::debug) {
		    print "Eek!";
		}
		next; 
	    }
	    $i = $end; # Move the pointer to the beginning of the new line
	    next;
	}
	if ($char eq '"') { # Read everything in up to the next '"'
	    $char = '';
	    while (($i < $length) && (!((!$special) && ($char eq '"')))) { 
                # Until we get an unescaped '"', read on
		$i++;
		($char,$i,$special) = getTokenChar($fdata,$i);
		if (($special) || ($char ne '"')) { 
		    $token .= $char;
		}
	    }
	    if ($i >= $length) { # Error - mismatched '"'!
		return ('"' . $token,($length-length($token))-1,"Mismatched double quote");
	    }
	    return ($token,$i+1,0); # Success!
	}
	if ($char eq '\'') { # Read everything in up to the next '\''
	    $char = '';
	    while (($i < $length) && (!((!$special) && ($char eq '\'')))) { 
                # Until we get an unescaped '"', read on
		$i++;
		($char,$i,$special) = getTokenChar($fdata,$i);
		if (($special) || ($char ne '\'')) { 
		    $token .= $char;
		}
	    }
	    if ($i >= $length) { # Error - mismatched '''!
		return ('\'' . $token,($length-length($token))-1,"Mismatched single quote");
	    }
	    return ($token,$i+1,0); # Success!
	}
	$token .= $char;
    }
    if ($i >= $length) {
	if ($token) {
	    return ($token,$length,0);
	}
	else {
	    return (-1,$length,0);
	}
    }
}	    

sub getTokenChar { # Return the next valid token characters (including special
                   # characters, like "\n" and "\t" and the like 
    my ($fdata,$i) = @_;

    my $char = substr($fdata,$i,1);
    if ($char eq "\\") { # The next character is a special char
	my $nextchar = substr($fdata,$i+1,1);
	if ($nextchar eq '"') {
	    $char = '"';
	    $i += 1;
	    return($char,$i,1);
	}
	if ($nextchar eq "\'") {
	    $char = '\'';
	    $i += 1;
	    return($char,$i,1);
	}
	if ($nextchar eq "\\") {
	    $char = "\\";
	    $i += 1;
	    return($char,$i,1);
	}
	if ($nextchar eq "t") {
	    $char = "\t"; # A tab
	    $i += 1;
	    return($char,$i,1);
	}
	if ($nextchar eq "n") {
	    $char = "\n"; # A newline
	    $i += 1;
	    return($char,$i,1);
	}
	$char .= $nextchar;
	$i += 1;
	return($char,$i,0);
    }
    else {
	return ($char,$i,0);
    }
}

sub quotify { # Escape any wierd characters in a string
    my ($string) = @_;

    my $length = length($string);
    my $needquotes = 0;
    my $ret = "";
    my $i;
    for ($i=0;$i<$length;$i++) {
	my $char = substr($string,$i,1);
	if ($char eq "\n") {
	    $ret .= '\\n';
	    next;
	}
	if ($char eq "\t") {
	    $ret .= '\\t';
	    next; 
	}
	if ($char eq "\"") {
	    $ret .= '\\"';
	    next;
	}
	if ($char eq "\\") {
	    $ret .= '\\\\';
	    next;
	}
	if ($char =~ /\s/) {
	    $needquotes = 1;
	}
	$ret .= $char;
    }
    if ($needquotes) {
	substr($ret,0,0) = '"';
	$ret .= '"';
    }
    return $ret;
}

sub saveConfigFile {
    my ($file,$refdata) = @_;

    $VPS::currentFile = $file;
    if ((windowOpen('tunnelBuilder')) || (windowOpen('route_ui')) || (windowOpen('simpleAlert'))) {
	message("Function currently inaccessable.");
	return 0;
    }
    if (!$refdata) {
	return ("No data to save!  Aborting...");
    }
    if ($$refdata[0]{'tunnelName'}) {
	my $index = $#$refdata; 
	open(OUT,">$file") or return ("Could not open $file for save!");
	print OUT "keydir " . quotify($VPS::keydir) . "\n";
	my $i;
	for ($i=0;$i<=$index;$i++) {
	    print OUT "tunnel " . quotify($$refdata[$i]{'tunnelName'}) . "\n";
	    print OUT "\tlocal " . quotify($$refdata[$i]{'tunnelLocalAddr'}) . "\n";
	    print OUT "\tremote " . quotify($$refdata[$i]{'tunnelRemoteAddr'}) . "\n";
	    print OUT "\tgateway " . quotify($$refdata[$i]{'tunnelGatewayAddr'}) . "\n";
	    if ($$refdata[$i]{'keyFile'}) {
		print OUT "\tkeyfile " . quotify($$refdata[$i]{'keyFile'}) . "\n";
	    }
	    if ($$refdata[$i]{'tunnelLocalRoute'}) {
		my $totalroutes = scalar @{ $$refdata[$i]{'tunnelLocalRoute'} };
		my $j;
		for ($j=0;$j<$totalroutes;$j++) {
		    my $routestring = $$refdata[$i]{'tunnelLocalRoute'}[$j];
		    my ($dest,$mask) = split (' ', $routestring);
		    print OUT "\tlocalroute " . quotify($dest) . " " . quotify($mask) . "\n";
		}
	    }
	    if ($$refdata[$i]{'tunnelRemoteRoute'}) {
		my $totalroutes = scalar @{ $$refdata[$i]{'tunnelRemoteRoute'} };
		my $j;
		for ($j=0;$j<$totalroutes;$j++) {
		    my $routestring = $$refdata[$i]{'tunnelRemoteRoute'}[$j];
		    my ($dest,$mask) = split (' ', $routestring);
		    print OUT "\tremoteroute " . quotify($dest) . " " . quotify($mask) . "\n";
		}
	    }
	}
	close(OUT);
    }
    else {
	return ("No data to save!  Aborting...");
    }
    $fileDirty = 0;
    if ($VPS::debug) {
	print "Saved $file\n";
    }
    return "Saved $file";
}

sub currentlyOpenFile {
    if ($VPS::debug) {
	print "Currently Open File: $VPS::currentFile\n";
    }
    return $VPS::currentFile;
}

sub currentlyOpenFilename {
    my $file;
    $VPS::currentFile = $VPS::defaultFilename if (!$VPS::currentFile);
    
    $file = `basename $VPS::currentFile`;
    chop $file;
    if ($VPS::debug) {
	print "Currently Open Filename: $file\n";
    }
    return $file;
}

sub message {
    my ($msg) = @_;
    
  VPS::mainWindow::displayMessage($msg);
}

sub newTunnelCommand {
    my ($lbref) = @_;

    if (((windowOpen("fileSaveWindow")) || windowOpen('tunnelBuilder'))) {
	message("Function currently inaccessable.");
	return 0;
    }
    $VPS::globalrefdata = \@VPS::data;
    &VPS::tunnelBuilder::tunnelBuilder(1,'end',$VPS::globalrefdata);
}

sub modifyTunnelCommand {
    my ($lbref) = @_;

    my $index;
    if (((windowOpen("fileSaveWindow")) || windowOpen('tunnelBuilder'))) {
	message("Function currently inaccessable.");
	return 0;
    }
    $index = $lbref->curselection if (defined($lbref));
    if (length($index) == 0) {
      VPS::simpleAlert::simpleAlert($VPS::mainWindow::root,"No Tunnel Selected.");
	return 1;
    }
    my $string = $lbref->get($index,$index);
    my ($tunnelName,$localIP,$remoteIP,$gateway) = split(' ',$string);
    $VPS::globalrefdata = \@VPS::data;
    &VPS::tunnelBuilder::tunnelBuilder(2,$index,$VPS::globalrefdata);
}

sub deleteTunnelCommand {
    my ($lbref) = @_;

    my $index;
    if (windowOpen('tunnelBuilder')) {
	message("Function currently inaccessable.");
	return 0;
    }
    $index = $lbref->curselection if (defined($lbref));
    if (length($index) == 0) {
      VPS::simpleAlert::simpleAlert($VPS::mainWindow::root,"No Tunnel Selected.");
	return 1;
    }
    my $tunnelName = $VPS::data[$index]{'tunnelName'};
    my $localIP = $VPS::data[$index]{'tunnelLocalAddr'};
    my $remoteIP = $VPS::data[$index]{'tunnelRemoteAddr'};
    my $gateway = $VPS::data[$index]{'tunnelGatewayAddr'};
    
    my $string = $lbref->get($index,$index);
    if (VPS::simpleAlert::simpleAlert($VPS::mainWindow::root,"Delete \"" . $tunnelName . "\"?")) {
	my $lastindex = $lbref->index("end");
	my $i;
	for ($i=$index;$i<$lastindex-1;$i++) {
	    $VPS::data[$i] = $VPS::data[$i+1];
	}
	undef($VPS::data[$lastindex-1]);
	$lbref->delete($index);
	fileDirty(1);
	message("\"" . $tunnelName . "\" deleted.");
    }
    else {
	message("Command cancelled.");
    }
}

sub buildMainString {
    my ($tunnelName,$localIPAddr,$remoteIPAddr,$gateway) = @_;
    
    my $string = $tunnelName . (" " x (40-length($tunnelName))) . $localIPAddr . (" " x (20-length($localIPAddr))) . $remoteIPAddr . (" " x (20 - length($remoteIPAddr))) . $gateway . (" " x (30 - length($gateway)));
    return $string;
}

sub buildKeyLabelString {
    my ($tunnelName,$keyDir,$keyFile) = @_;
    
    if ($keyFile ne "") {
	$keyFile = $keyDir . $keyFile;
    }
    else {
	$keyFile = "Currently Unassigned";
    }
    my $string = $tunnelName . (" " x (40-length($tunnelName))) . $keyFile . (" " x (40-length($keyFile)));
    return $string;
}

sub buildTunnelRouteString {
    my ($dest,$mask) = @_;

    my $string = $dest . " " x (40 - length($dest)) . $mask;
    return $string;
}

sub tunnelAddRoute {
    my ($dest,$mask,$index,$lbref) = @_;

    my $string = buildTunnelRouteString($dest,$mask);
    $lbref->insert($index,$string);
}

sub tunnelModifyRoute {
    my ($dest,$mask,$index,$lbref) = @_;

    my $string = buildTunnelRouteString($dest,$mask);
    $lbref->delete($index);
    $lbref->insert($index,$string);
}

sub fileDirty {
    my ($flag) = @_;
    $fileDirty = 1 if ($flag);
    return $fileDirty;
}

sub verifyTunnel {
    my ($win) = @_;

}

sub updateTunnel {
    my ($index,$refData,$command,%info) = @_;

    my $realindex = $VPS::mainWindow::lbTunnel->index($index);
    $$refData[$realindex]{'tunnelName'} = $info{'tunnelName'};
    $$refData[$realindex]{'tunnelLocalAddr'} = $info{'tunnelLocalAddr'};
    $$refData[$realindex]{'tunnelRemoteAddr'} = $info{'tunnelRemoteAddr'};
    $$refData[$realindex]{'tunnelGatewayAddr'} = $info{'tunnelGatewayAddr'};
    $$refData[$realindex]{'tunnelLocalRoute'} = $info{'tunnelLocalRoute'};
    $$refData[$realindex]{'tunnelRemoteRoute'} = $info{'tunnelRemoteRoute'};
    my $string = buildMainString($info{'tunnelName'},$info{'tunnelLocalAddr'},$info{'tunnelRemoteAddr'},$info{'tunnelGatewayAddr'});
    if ($command == 1) { # New Tunnel
	$VPS::mainWindow::lbTunnel->insert("end",$string);
    }
    else {
	$VPS::mainWindow::lbTunnel->delete($index);
	$VPS::mainWindow::lbTunnel->insert($index,$string);
    }
    fileDirty(1);
    message("Updated Tunnel Information.");
}

1;
