#
#   <<< Unix Csh-Like Command interpreter >>>
#
#   Bruce D. Lightner   Thu May 14 16:58:53 PDT 1998
#

$DEFAULT_PROMPT = '$p$g';		# standard MS-DOS prompt
$DEFAULT_PROMPT = '{$h}$d:$b[$i]%$s';	# favorite Unix-style prompt

$DEFAULT_TELNET_RC = 'telnet.rc';

open(STDERR, ">&STDOUT") || die "'can't dup stdout";

$| = 1;
select((select(STDOUT), $! = 1)[0]);   # unbuffer output
select((select(STDERR), $! = 1)[0]);   # unbuffer output

$show_esc  = 0;

$ctrl_break = 0;
sub dokill {
	print "detected ^C...($?)\n";
	$ctrl_break = 1;
	$SIG{'INT'} = 'dokill';
}

$SIG{'INT'} = 'dokill';

$punt = 1;	# Punt command to COMMAND?

if (!($dir=$ENV{'TELNET_DIR'})) {
	$dir = &get_pwd;
}
$dir =~ s=\\=/=g;
chdir($dir);

$last_pwd = &get_pwd;

$last_cmd = '';
$ix = 0;

if (!($file=$ENV{'TELNET_RC'})) {
	$file = $DEFAULT_TELNET_RC;
}
$file =~ s=\\=/=g;
if (-f $file) {
	&source_file($file, $ENV{'VERBOSE'});
}

&do_banner;
for (&prompt ;         ; &prompt)  {
	$cmd = &read_keys;
        last if ($cmd eq '');
	&process_cmd_line($cmd);
}

sub map_aliases {
	local($cmd) = @_;

	if ($cmd =~ /^\s*([\S]+)(.*)/) {
		if ($aliases{$1} ne '') {
			$cmd = $aliases{$1} . $2;
		}
	}
	while ($cmd =~ /^(.*)%([^%]+)%(.*)$/) {
		$cmd = $1 . $ENV{$2} . $3;
	}
	$cmd =~ s/%%/%/g;
	return $cmd;
}

sub clear {
	local($i);
	##print "\r\033[K";	# clear to EOL
	print "\r\033[J";	# clear to EOS
	##for ($i = 0; $i < 79; ++$i) { print ' '; }
	##print "\r";
}

sub reprompt {
	local($cmd, $off) = @_;
	local($i);

	--$ix; &clear; &prompt; print $cmd;
	for ($i = 0; $i < $off; ++$i) {
		print "\b";
	}
}

sub read_keys {
	local($ch, $s, $cmd, $cix, $s1, $s2, $off, $cix, $esc);

        printf("%c", 0x80);	# echo off
more:
	$off = 0;
	$cix = $ix;
	$esc = '';
	while (!$ctrl_break && read(STDIN,$ch,1)) {
		if ($ch eq "\n" || $ch eq "\r") {
			##printf("\\%c", ord($ch) + ord('@'));
			print $ch;
			$cmd .= $ch;
			last;
		}
		if ($ch eq "\b" || $ch eq "\177") {	# BS or DEL
			if ($off == 0) {
				print "\b \b" if (length($cmd) > 0);
				$cmd = substr($cmd, 0, length($cmd)-1);
			} elsif ($off < length($cmd)) {
				$s1 = substr($cmd, 0, length($cmd) - $off -1);
				$s2 = substr($cmd, length($cmd) - $off);
				$cmd = $s1 . $s2;
				&reprompt($cmd, $off);
			}
			next;
		}
		if ($ch eq "\003") {	# ^C
			print "^C\n";
			&reprompt($cmd, $off);
			next;
		}
		if ($ch eq "\022") {	# ^R
			print "\n";
			&reprompt($cmd, $off);
			next;
		}
		if ($ch eq "\025") {	# ^U
			$cmd = substr($cmd, length($cmd) - $off);
			&reprompt($cmd, $off);
			next;
		}
		if ($ch eq "\005") {	# ^E
			$off = 0;
			&reprompt($cmd, $off);
			next;
		}
		if ($ch eq "\032" || $ch eq "\004") {	# ^Z or ^D
			$cmd .= "\004";
			print "\n";
			($cmd, $edit) = &escapes($cmd);
			$off = 0;
			&reprompt($cmd, $off);
			next;
		}
###		if ($ch eq "\177") {	# DELETE
###			if ($off != 0) {
###				$s1 = substr($cmd, 0, length($cmd) - $off);
###				$s2 = substr($cmd, length($cmd) - $off + 1);
###				$cmd = $s1 . $s2;
###				&reprompt($cmd, $off);
###			}
###			next;
###		}
		if ($esc ne '') {
			if ($esc eq "\033" && $ch eq "\033") { # ESC-ESC
				$esc = '';
				$cmd .= $ch;
				($cmd, $edit) = &escapes($cmd);
				$off = 0;
				&reprompt($cmd, $off);
				next;
			}
			if ($esc eq "\033" && $ch eq 'O') {
				$esc .= $ch;
				next;
			}
			if ($esc eq "\033" && $ch eq '[') {	# map Unix term
				$esc .= 'O';
				next;
			}
			if ($esc eq "\033O" && $ch eq 'A') { # ^
				$esc = '';
				--$cix;
				$cix = 1 if ($cix < 1);
				$cmd = $history[$cix];
				$off = 0;
				&reprompt($cmd, $off);
				next;
			}
			if ($esc eq "\033O" && $ch eq 'B') { # v
				$esc = '';
				++$cix;
				$cix = $ix if ($cix > $ix);
				$cmd = $history[$cix];
				$off = 0;
				&reprompt($cmd, $off);
				next;
			}
			if ($esc eq "\033O" && $ch eq 'D') { # >
				if ($off < length($cmd)) {
					++$off;
					print "\b";
				}
				$esc = '';
				next;
			}
			if ($esc eq "\033O" && $ch eq 'C') { # <
				if ($off > 0) {
					--$off;
					print "\033[C";
				}
				$esc = '';
				next;
			}
			print $esc;
			$cmd .= $esc;
			$esc = '';
		}
		if ($ch eq "\033") {
			$esc = $ch;
			next;
		}
		##printf("ord(%s) = %d\n",$ch,ord($ch));
		if ($show_esc && ord($ch) < ord(' ')) {
			printf("\\%c", ord($ch) + ord('@'));
		} else {
			print $ch;
		}
		if ($off == 0) {
			$cmd .= $ch;
		} else {
			$s1 = substr($cmd, 0, length($cmd) - $off);
			$s2 = substr($cmd, length($cmd) - $off);
			$cmd = $s1 . $ch . $s2;
			&reprompt($cmd, $off);
		}
	}

	if ($cmd =~ /^\^/) {
		if ($cmd =~ /^\^([^^]+)\^(.*)/) {
			$edit = 1;
			$from = $1;
			$to = $2;
			$from =~ s/([^a-zA-Z])/\\\1/g;
			$to =~ s/\^*$//;
			$cmd = $last_cmd;
			#print "'$cmd' =~ s/$from/$to/\n";
			eval { $cmd =~ s/$from/$to/ ; };
			&reprompt($cmd, 0);
		} else {
			print "Modifier failed.\n";
			&reprompt($cmd, 0);
			goto more;
		}
	}
	while ($cmd =~ /!([0-9]+)/) {
		$edit = 1;
		$i = $1;
		$h = $history[$i];
		$cmd =~ s/!$i/$h/g;
	}
	if ($cmd =~ /!!/ || $cmd =~ /!\$/)  {
		$last_cmd = $history[$ix-1];
		$edit = 1;
		$extra = '';
		if ($cmd =~ /:p$/) {
			$extra = ':p';
			$cmd =~ s/:p$//;
		}
		$cmd =~ s/!!/$last_cmd/g;
		$n = @f = split(/\s+/, $last_cmd);
		$lastf = $f[$n-1];
		$cmd =~ s/!\$/$lastf/g;
		if ($extra eq '') {
			&reprompt($cmd, 0);
		}
		$cmd .= $extra;
	}
	if ($cmd =~ /^![?](.*)/ || $cmd =~ /^!(.*)/)  {
		$h = $1;
		$bol = ($cmd =~ /^![?]/) ? 0 : 1;
		$extra = '';
		if ($cmd =~ /:p$/) {
			$extra = ':p';
			$h =~ s/:p$//;
		}
		$cmd = '';
		for ($i = $ix; --$i; $i >= 0) {
			last if ($h eq '');
			if ($bol) {
				next if ($history[$i] !~ /^$h/);
			} else {
				next if ($history[$i] !~ /$h/);
			}
			$cmd = $history[$i];
			last;
		}
		$edit = 1;
		if ($cmd ne '') {
			if ($extra eq '') {
				&reprompt($cmd, 0);
				print "\n";
			}
			$cmd .= $extra;
		} else {
			print "$h: Event not found.\n";
			$cmd = ' ';
		}
	}
	if ($cmd =~ /:p$/) {
		$cmd =~ s/:p$//;
		chop $cmd while ($cmd =~ /[\n\r]$/);
		&reprompt($cmd, 0);
		$history[$ix] = $cmd;
		$cmd = '';
		print "\n";
		&prompt;
		goto more;
	}
        printf("%c", 0x81); # echo on
	return $cmd;
}

sub source_file {
	local($file, $verbose) = @_;
	local($ok, $cmds, @cmds, $s);

	$file =~ s=\\=/=g;
	if (open(F,"<$file")) {
		read(F, $cmds, 1000000);
		close(F);
		@cmds = split(/\n/, $cmds);
		foreach $s (@cmds)  {
			next if ($s =~ /^\s*$/);
			print "$s\n" if ($verbose != 0);
			$ok = &process_cmd_line($s);
			last if (!$ok);
		}
	} else {
		print "$file: No such file\n";
	}
}

sub fix_nonprinting {
	local($s) = @_;
	local($i, $r, $ch);

	for ($i = 0; $i < length($s); ++$i) {
		$ch = substr($s, $i, 1);
		if (ord($ch) < ord(' ')) {
			$r .= sprintf("\\%c", ord($ch) + ord('@'));
		} else {
			$r .= $ch;
		}
	}
	return $r;
}

sub process_cmd_line {
	local($cmd) = @_;
	local($save_cmd, $s);

	chop $cmd while ($cmd =~ /[\n\r]$/);

	if ($cmd =~ /^\s*$/i)  {	# ignore blank lines
		--$ix;
		return 1;
	}

	$history[$ix] = $cmd;	# save command in history list

	$cmd = &map_aliases($cmd);

	if ($cmd =~ /^exit$/i)  {
		exit;
	}
	elsif ($cmd =~ /^\s*[#]/i)  {	# comments are '#'
		;
	}
	elsif ($cmd =~ /^history$/i)  {
	        $last_cmd = $cmd;
		$ix1 = $ix - 100;
		$ix1 = 1 if ($ix1 < 1);
		$ix2 = $ix - 1;
		$ix2 = 1 if ($ix2 < 1);
		for ($i = $ix1; $i <= $ix2; ++$i) {
			printf(STDOUT "%4d  %s\n",
				$i, &fix_nonprinting($history[$i]));
		}
	}
	elsif ($cmd =~ /^set$/i)  {
	        $last_cmd = $cmd;
		$n = 0;
		foreach $s (keys %ENV) {
			$n = length($s) if (length($s) > $n);
		}
		$fmt = "%-$n"."s %s\n";
		foreach $s (sort (keys %ENV)) {
			printf(STDOUT $fmt, $s, $ENV{$s});
		}
	}
	elsif ($cmd =~ /^set\s*([^=]+)\s*=\s*(.*)$/i)  {
		$s = $1; $s =~ y/[a-z]/[A-Z]/;
		$ENV{$s} = "$2";
	}
	elsif ($cmd =~ /^pushd$/i)  {
	        $last_cmd = $cmd;
		$x = $pwd;
		$dir = $last_pwd;
		if (!chdir($dir)) {
			print "$dir: No such directory\n"
		} else {
			$pwd = &get_pwd;
			$last_pwd = $x;
			print STDOUT "$pwd $x\n";
		}
	}
	elsif ($cmd =~ /^pushd\b/i)  {
	        $last_cmd = $cmd;
		$x = $pwd;
		($cmd,$dir) = split(/\s+/,$cmd);
		$dir =~ s=\\=/=g;
		if (!chdir($dir)) {
			print "$dir: No such directory\n"
		} else {
			$pwd = &get_pwd;
			$last_pwd = $x;
		}
	}
	elsif ($cmd =~ /^echo\b/i)  {
	        $last_cmd = $cmd;
		$n = ($cmd,$s) = split(/\s+/,$cmd, 2);
		print "$s\n";
	}
	elsif ($cmd =~ /^alias\b/i)  {
	        $last_cmd = $cmd;
		$n = ($cmd,$s1,$s2) = split(/\s+/,$cmd, 3);
		if ($n == 1) {
			$n = 0;
			foreach $s (keys %aliases) {
				$n = length($s) if (length($s) > $n);
			}
			$fmt = "%-$n"."s %s\n";
			foreach $s (sort (keys %aliases)) {
				printf(STDOUT $fmt, $s, $aliases{$s})
					if ($aliases{$s});
			}
		} elsif ($n == 2) {
			printf(STDOUT "%s %s\n", $s1, $aliases{$s1});
		} else {
			$aliases{$s1} = $s2;
		}
	}
	elsif ($cmd =~ /^unalias\b/i)  {
	        $last_cmd = $cmd;
		$n = ($cmd,$s1,$s2) = split(/\s+/,$cmd);
		if ($n == 2) {
			$aliases{$s1} = '';
		}
	}
	elsif ($cmd =~ /^source\s+/i)  {
		($save_cmd, $file) = split(/\s+/,$cmd);
		&source_file($file, $ENV{'VERBOSE'});
	        $last_cmd = $save_cmd;
		$history[$ix] = $cmd;
	}
	elsif ($cmd =~ /^cd\b/i)  {
	        $last_cmd = $cmd;
		($cmd,$dir) = split(/\s+/,$cmd);
		$dir =~ s=\\=/=g;
		print "$dir: No such directory\n" if (!chdir($dir));
	}
	elsif ($cmd =~ /^[a-zA-Z]:$/i)  {
	        $last_cmd = $cmd;
		$dir = $cmd;
		print "$dir: No such directory\n" if (!chdir($dir));
	}
	elsif ($cmd =~ /^pwd$/i)  {
	        $last_cmd = $cmd;
		$pwd = &get_pwd;
		print STDOUT "$pwd\n";
	}
	elsif ($cmd =~ /^date$/i)  {
	        $last_cmd = $cmd;
		$s = &localtime(time);
		print STDOUT "$s\n";
	}
	elsif (substr($cmd,0,1) eq ".")  {
	        $last_cmd = $cmd;
		eval substr($cmd,1,999);
		print "\n";
	}
	else {
	        $last_cmd = $cmd;
		return &execute_cmd($cmd);
	}
	return 1;
}

sub do_banner {
	eval '$user = Win32::LoginName';
	eval '$domain = Win32::DomainName';
	eval '$host = Win32::NodeName';
	$host =~ y/A-Z/a-z/;
	eval '($string, $major, $minor, $build, $id) = Win32::GetOSVersion';
	$os = ("Win32", "Windows 95", "Windows NT")[$id];
	$build = sprintf("0x%x",$build);
	$ver = "(v$major.$minor build $build)" if ($build != 0);
	print "$os $ver\n";
	print "User: $user  Host: $host  Domain: $domain\n" if ($user ne '');
	print "\n";
}

sub execute_cmd {
	local($cmd) = @_;

	$cmd = &map_aliases($cmd);
	##if ($cmd =~ /[<>]/) {
	##	$result = `$cmd`;
	##	print "$result\n" if ($result ne '');
	##}
	system($cmd);
	$status = $?;
	$exit_status = $status / 256;
	if ($exit_status == 255) {
		if (!$punt) {
			$s = $cmd;
			$s =~ s/\s+.*//;
			print "$s: Command not found.\n";
			return 0;
		} else {
			print "." if ($ENV{'VERBOSE'} > 1);
			system("command /c $cmd");
			$status = $?;
			$exit_status = $status / 256;
			if ($exit_status == 255) {
				if ($cmd =~ /[<>|]/) {
					print ".." if ($ENV{'VERBOSE'} > 1);
					$result = `$cmd`;
					print "$result\n" if ($result ne '');
				} else {
					print "Can't run COMMAND\n";
				}
				return 0;
			}
		}
	}
	return 1;
}

sub get_pwd {
	#####$pwd = `cd`;
	#####chop($pwd);
	eval '$pwd = Win32::GetCwd';
	if ($@) { $pwd = `cd` ; chop $pwd ; }
	return $pwd;
}

sub prompt {
	local ($h, $p, $base);

	++$ix;
	$pwd = &get_pwd;
	$base = $pwd;
	$base =~ s/^.*[\/\\]//;
	$disk = $pwd;
	$disk =~ s/:.*//;
	$disk =~ tr/[a-z]/[A-Z]/;
	$h = "$ix";

	$p = $ENV{'PROMPT'};
	$p = $DEFAULT_PROMPT if ($p eq '');

	while (1) {
		if ($p =~ /^(.*)([\$]p)(.*)$/i) {
			$p = $1 . $pwd . $3;
		} elsif ($p =~ /^(.*)([\$]g)(.*)$/i) {
			$p = $1 . '>' . $3;
		} elsif ($p =~ /^(.*)([\$]b)(.*)$/i) {
			$p = $1 . $base . $3;
		} elsif ($p =~ /^(.*)([\$]h)(.*)$/i) {
			$p = $1 . $host . $3;
		} elsif ($p =~ /^(.*)([\$]d)(.*)$/i) {
			$p = $1 . $disk . $3;
		} elsif ($p =~ /^(.*)([\$]i)(.*)$/i) {
			$p = $1 . $h . $3;
		} elsif ($p =~ /^(.*)([\$]s)(.*)$/i) {
			$p = $1 . ' ' . $3;
		} else {
			last;
		}
	}
	print STDOUT "$p";
}

sub wildcard  {
	local($orig_name, $list) = @_;
	local($pat, $ix, $name, $bslash, $orig_dir, $i, $t, $f, $l, $n, $ok);

	$name = $orig_name;
	$bslash = ($name =~ s=([\\])=/=g) ? 1 : 0;
	#$name =~ s=/+$==;
	if ($name =~ /^(.*)[\/]([^\/]*)$/) {
		$orig_dir = $dirname = $1;
		$pat = $2;
	} elsif ($name =~ /^([a-z][:])(.*)$/) {
		$orig_dir = $dirname = $1;
		$pat = $2;
	} else {
		$orig_dir = '';
		$dirname = '.';
		$pat = $name;
	}

	$pat =~ y/A-Z/a-z/;
	$dirname = "$dirname/" if ($dirname =~ /^[a-z][:]$/);
	##print "Looking in $dirname for /$pat/\n";
	$match = '';
	if (opendir(D,"$dirname"))  {
		undef %files ;  # be safe
		$n = 0;
		$l = length($pat);
		while (defined($t=readdir(D)))  {
			next if (substr($t,0,1) eq '.');
			$f = substr($t,0,$l);
			$f =~ y/A-Z/a-z/;
			next if ($f ne $pat);
			$orig_t = $t;
			$t =~ y/A-Z/a-z/;
			$files{$t} = $orig_t;
			++$n;
		}
		closedir(D);

		##foreach $file (keys %files)  { print "\t$files{$file}\n"; }

		if ($n == 0) {
			return ($orig_name, 0);
		} elsif ($n == 1) {
			$match = (keys %files)[0];
			##$match = $files{$match};
			if ($list != 0) {
				print "$files{$match}\n";
			}
		} else {
			for ($i = length($pat);    ; ++$i) {
				$trial_pat = (keys %files)[0];
				$trial_pat = substr($trial_pat,0,$i);
				$ok = 1;
				foreach $file (keys %files)  {
					$f = substr($file,0,$i);
					#print "$i $file:'$f'=~/^$trial_pat/";
					if ($f eq $trial_pat) {
						#print "->ok=$ok\n";
						next;
					}
					$ok = 0;
					#print "->ok=$ok\n";
					last;
				}
				if ($ok == 0) {
					$match = substr($trial_pat,0,$i-1);
					last;
				}
			}
			if ($list != 0) {
				foreach $file (keys %files) {
					print "$files{$file}\t";
				}
				print "\n";
			}
		}
	}
	$dirname =~ s=/+$==;
	if ($orig_dir eq '') {
		$name = $match;
	} else {
		$name = "$dirname/$match";
	}
	$name =~ s=([/])=\\=g if ($bslash);
	return ($name, $n);
}

sub escapes
{
	local($cmd) = @_;

	if ($cmd =~ /\033$/) {		# ESC
		$show = 0;
	} elsif ($cmd =~ /\004$/) {	# ^D
		$show = 1;
	} elsif ($cmd =~ /\022$/) {	# ^R
		return ('', 2);
	} else {
		return ($cmd, 0);
	}
	chop($cmd);
	if ($cmd =~ /^(.*\s+)(\S*)$/) {
		$root = $1;
		$file = $2;
	} else {
		$root = '';
		$file = $cmd;
	}
	($s, $n) = &wildcard($file, $show);
	##print "($s, $n) = \&wildcard($file, $show)\n";
	return ($cmd, 0) if (!$n);
	return ($cmd, 2) if ($show);
	return ("$root$s", 2);
}

sub localtime {
	($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
localtime(@_[0]) ;
	$monx = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')[$mon];
	$wdayx = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat')[$wday];
	$tz = $ENV{'TZ'};
	if ($tz ne '') {
		$tz = substr($tz, 0, 1);
		if ($isdst) {
			$tz .= 'D';
		} else {
			$tz .= 'S';
		}
		$tz .= 'T';
		$tz =~ tr/[a-z]/[A-Z]/;
		$tz .= ' ';
	}
	# Thu May 14 16:09:57 PDT 1998
	return sprintf("%s %s %d %02d:%02d:%02d %s%d",
		$wdayx, $monx,  $mday, $hour, $min, $sec, $tz, $year+1900);
}
