#!/usr/bin/perl -w
# ^^^^^^^^^^^^^^^^ Insert *YOUR* perl path above
# If you think you need to tailor any other part of this code, please email me
$vers = "brc0.26";

#- #, and add an "s" after the "-w" if you don't have Getopts -- in this case,
#- # set slag to 1
#- $sflag = 0;
# Copyright (c) Piete Brooks <pb@cl.cam.ac.uk> 1995
# This code may be freely copied so long as this message is included,
# you don't sell it, you update it when it expires, and you don't tweak the
# expiry date.
# The author is not liable for any effects of this code.
# Bug reports, improvements, etc greatfully accepted.

# Keep -w happy
$opt_v if 0; $opt_h if 0; &try_addr if 0; $TIMEOUT if 0; $fails if 0;

$defservers	= "sksp.brute.cl.cam.ac.uk";
$defackservers	= "sksp-ack.brute.cl.cam.ac.uk";
$defkeyservers	= "sksp-key.brute.cl.cam.ac.uk";
$defport	= "19957";
$defackport	= $defport;
$defkeyport	= $defport;
$defbrtype	= "sslck";
$expirydate	= 812127631;	# *** See Copyright above ***
$version	= 1;		# version of the protocol (HELO)
$numargs	= $#ARGV;	# Remember the number of args early on ...
$is_irix =1 if -f "/unix";	# IRIX has problems !
$use_syswrite = 1 if $is_irix;	# use syswrite to avoid I+O on stdio ...
$seek_stdio = 1 if 0;		# usee seek to avoid I+O on stdio problems.

eval "require 'sys/socket.ph';";# Many sites don't have this ...
$AF_INET = 2 unless defined($AF_INET);
$PF_INET = 2 unless defined($PF_INET);
# THIS IS A GUESS !  SunOS 5 uses 2 but most systems use 1 .... we try both!
$SOCK_STREAM = 1 + ((-d "/kernel") ? 1 : 0) unless defined($SOCK_STREAM);
# process command line args as well as possible ...
# 
# I don't believe this !!!!!
# perl 5.001 core dumps if you "require 'getopts.pl'" and then call it with
# certain args, BUT it works if you inline the same code !
#
#- unless (eval "require 'getopts.pl'; &Getopts('a:Ac:C:dhHi:klLn:pP:s:S:t:u:U:Vva') || exit(&do_help(0));") {
#-     printf STDERR "\n\t*** As Getopts is not available, edit $0 to add the 's' flag\n\n"
#- 	unless $sflag;
#- };
#
# VERBATIM copy of our /usr/lib/gnu/perl5/getopts.pl ... <<<<<<<<<<<<<<<<<<<<<
;# getopts.pl - a better getopt.pl

;# Usage:
;#      do Getopts('a:bc');  # -a takes arg. -b & -c not. Sets opt_* as a
;#                           #  side effect.

sub Getopts {
    local($argumentative) = @_;
    local(@args,$_,$first,$rest);
    local($errs) = 0;
    local($[) = 0;

    @args = split( / */, $argumentative );
    while(@ARGV && ($_ = $ARGV[0]) =~ /^-(.)(.*)/) {
	($first,$rest) = ($1,$2);
	$pos = index($argumentative,$first);
	if($pos >= $[) {
	    if($args[$pos+1] eq ':') {
		shift(@ARGV);
		if($rest eq '') {
		    ++$errs unless @ARGV;
		    $rest = shift(@ARGV);
		}
		eval "\$opt_$first = \$rest;";
	    }
	    else {
		eval "\$opt_$first = 1";
		if($rest eq '') {
		    shift(@ARGV);
		}
		else {
		    $ARGV[0] = "-$rest";
		}
	    }
	}
	else {
	    print STDERR "Unknown option: $first\n";
	    ++$errs;
	    if($rest ne '') {
		$ARGV[0] = "-$rest";
	    }
	    else {
		shift(@ARGV);
	    }
	}
    }
    $errs == 0;
}

1;
# VERBATIM copy of our /usr/lib/gnu/perl5/getopts.pl ... >>>>>>>>>>>>>>>>>>>>>
$exit_duffargs = 2;
&Getopts('a:Ac:C:dEhHi:klLn:N:pP:s:S:t:Tu:U:Vva') || exit(&do_help($exit_duffargs+30));
# HACK -- if -s is set when Getopts is available, need to do this too ...
$opt_A = $A if $A;
$opt_d = $d if $d;
$opt_E = $E if $E;
$opt_h = $h if $h;
$opt_H = $H if $H;
$opt_k = $k if $k;
$opt_l = $l if $l;
$opt_L = $L if $L;
$opt_p = $p if $p;
$opt_P = $P if $P;
$opt_v = $v if $v;
$opt_V = $V if $V;

open(STDERR, ">&STDOUT") if $opt_E;
select((select(COMMAND), $| = 1)[0]) if $opt_E;

# Keep this proc near to Getopts so as to ensure that they are kept in step ...
# Systems with Getopts can set key/value pairs (-x:), but systems using
# 'perl -s' can only use flags (-x) and have to pass in values using
# environment variables (BRx). The following args are recognised:
sub do_help {
	($command = $0) =~ s+.*/++;
	print STDERR "				[$vers]

key/value pairs (-x:) can be enetered on the command line, or by using
environment variables (BRx). The following args are recognised:

flag	ENV variable	use:
----	------------	----
-a:	BRACK		text generated by a run of bruterc4, brutessl, etc
	BRRACK		raw ACK info				(experts only)
-A			read stdin for an ACK, e.g. run of bruterc4, brutessl,
-c:	BRCLIENT	client 'free format text' name (eg. Piete Brooks) BRNAME
-C:	BRCOMMENT	comment to be passed to server (logged on server)
-E			send stderr and stdout to the same place.
-d			read back the data / config information
-h			Get this help info
-H			Get help from the server
-i:	BRID		ID to send in (e.g. pb\@cl.cam.ac.uk)
-l			Loop if server returns SLEEP
-L			Loop if server times out
-k			request some keyspace
-n:	BRNUMBER	Number of segments to request in keyspace
-N:	BRNSERV		Ask for the list of Names for servers for this type.
-p			list the current projects
-P	BRPROJ		Seleect a specific project
-s:	BRSERVERS	Servers to ask				(experts only)
	BRKEYSERVERS	Servers to ask to allocate keys		(experts only)
	BRACKSERVERS	Servers to ask to ACK keys		(experts only)
	BRKEYPORT	port to ask to allocate keys		(experts only)
	BRACKPORT	port to ask to ACK keys			(experts only)
-S:	BRSTATE		State in which the project should be	(experts only)
-t:	BRTYPE		Type of request: rc4, ssl, sslck or rc4ck
-T			display the project info for the selected type
-u:	USER		User id to send in (e.g. pb)
-U:	BRUNIXCMD	UNIX command to run to process keys and config file
-v			Sent verbose messages to STDERR		(HACKers only)
-V			Sent interactions with server to STDERR	(HACKers only)

sh  usage: BRID=pb\@cl.cam.ac.uk BRCLIENT='Piete Brooks'; export BRID BRCLIENT
csh usage: setenv BRID pb\@cl.cam.ac.uk; setenv BRCLIENT 'Piete Brooks'

	# sh one sh liner to do a test ssl search which should succeeed
	$command -dktsslck | (read a;brutessl - \$a) | $command -AVtsslck

	# sh one liner to do do some real work on the 'ssl' project
	($command -dklLtssl|(read a;nice brutessl - \$a)|$command -ALtssl)2>>log

	# Split up the separate functions to do multiple ssl searches
	$command -dtssl > datafile			# read a datafile
   		keys=\`$command -klLtssl\`  		# aquire some keys
   		result=\`nice brutessl datafile \$keys\`	# do the work
   		$command -lLtssl -a\"\$result\"		# return the result

";
#-	# sh command without needing Getopts
#-	BRTYPE=sslck perl -s brclient -d -k | (read a; nice brutessl - \$a) |
#-	BRTYPE=sslck perl -s brclient -A -V
#-
	return $_[0];
}

$sockaddr = 'S n a4 x8';	# format for packing socket info
$| = 1;				# Flush at all times ...
$def_open_timeout = 30+5+(5*5);	# Total time for a call ....
		# T/O of server + slop + (listen queue * average call duration)
# $reading_timeout = 5;
$timeout_msg	= "Timeout ...\n";# Message used if a call times out
$timeoutsleep	= 5;

# Set parameters: User command line flags, then ENV variables, then defaults
$verbose	= $opt_v;
$Verbose	= $opt_V;
$user		= $ENV{'USER'};
$user		= $ENV{'LOGNAME'} unless $user;
$user		= $opt_u if $opt_u;
$user		= "unknown" unless $user;
$id		= $ENV{'BRID'};
$id		= "$user\@".&fqdn unless $id;
$id		= $opt_i if $opt_i;
$brclient	= $ENV{'BRCLIENT'};
$brclient	= $ENV{'BRNAME'} unless $brclient;
$brclient	= (getpwuid($<))[6] unless $brclient;
$brclient	= "" if $brclient eq "WWW";
$brclient	= $opt_c if $opt_c;
$brclient	= " $brclient" if $brclient;
$brcomment	= $ENV{'BRCOMMENT'};
$brcomment	= $opt_C if $opt_C;
$brcomment	= ": $brcomment" if $brcomment;
$brcomment	= "" unless $brcomment;
$brtype		= $ENV{'BRTYPE'};
$brtype		= $defbrtype unless $brtype;
$brtype		= $opt_t if $opt_t;
$brstate	= $ENV{'BRSTATE'};
$brstate	= "running" unless $brstate;
$brstate	= $opt_S if $opt_S;
$brproj		= $ENV{'BRPROJ'};
$brproj		= $opt_P if $opt_P;
$brunixcmd	= $ENV{'BRUNIXCMD'};
$brunixcmd	= $opt_U if $opt_U;
$number		= $ENV{'BRNUMBER'};
$number		= 16 unless $number;;
$number		= $opt_n if defined($opt_n);
# HACK: getopts will chop opt_a at the first \n :-((
push(@brackr, split(/[\r\n]/, $ENV{'BRACK'})) if $ENV{'BRACK'};
push(@brackr, split(/[\r\n]/, $opt_a)) if $opt_a;
$defport	= $ENV{'BRPORT'} if $ENV{'BRPORT'};
$defackport	= $ENV{'BRPORT'} if $ENV{'BRPORT'};
$defackport	= $ENV{'BRACKPORT'} if $ENV{'BRACKPORT'};
$defkeyport	= $ENV{'BRPORT'} if $ENV{'BRPORT'};
$defkeyport	= $ENV{'BRKEYPORT'} if $ENV{'BRKEYPORT'};
$defport	= $defkeyport if ($opt_k || $brunixcmd);
$defport	= $defackport if ($opt_A || $#brackr >= $[);
$defackservers	= $ENV{'BRACKSERVERS'} if $ENV{'BRACKSERVERS'};
$defkeyservers	= $ENV{'BRKEYSERVERS'} if $ENV{'BRKEYSERVERS'};
$servers	= $ENV{'BRSERVERS'};
$servers	= ($opt_A || $#brackr >= $[) ? $defackservers :
		  ($opt_k || $brunixcmd) ? $defkeyservers :
		  $defservers unless $servers;
$servers	= $opt_s if $opt_s;
$brnserv	= $ENV{'BRNSERV'};
$brworkstart	= $ENV{'BRWORKSTART'};
$brworkstart	= time unless defined($brworkstart);
$exit_OK = 0;
#exit_duffargs
$exit_callfailed = 3;
$exit_noinput = 4;
$exit_duffinput = 5;
$exit_stop = 6;
$exit_sleep = 7;
$exit_noproj = 8;
$exit_stop_host = 9;

$id =~ s/\n$//;		# If "hostname" or "uname" was used, it may have a \n

# Try to be good and use CRLF ...
$cr		= "\r";		# Edit this to remove all CRs !
$crlf		= "$cr
";

if (time > $expirydate) {
	print STDERR "

*** This code may be out of date. Please pick up a new version from
*** http://www.brute.cl.cam.ac.uk/brute/ or ftp.brute.cl.cam.ac.uk:pub/brute/


";
	sleep 2;
} elsif ($verbose) {
	($sec,$min,$hour,$mday,$mon,$year,$junk,$junk,$junk) = gmtime($expirydate);
	$mon++;
	print STDERR "\t\t[ This code expires $year/$mon/$mday $hour:$min:$sec ]\n";
}

# -h -- give help.
exit (&do_help(exit_OK)) if $opt_h;

if ($numargs < 0 && !$opt_A && !$opt_d && !$opt_E && !$opt_h && !$opt_H && !$opt_k
    && !$opt_l && !$opt_L && !$opt_p && !$opt_P && !$opt_v && !$opt_V
    && !$opt_N && !$brnserv && !$opt_t && !$opt_T) {
	print STDERR "\tNo args supplied. Try \"$0 -h\" for more info\n";
	exit($exit_duffargs + 10);
}

$opt_k = $opt_d = 1 if defined($brunixcmd);

# no flag, defaults to "allocate keys" ...
exit(&do_help($exit_duffargs+20))unless $opt_k||$#brackr >= $[||$opt_d||$opt_H||$opt_p||$opt_A||$opt_N||$brnserv||$opt_T;

for ($i = $[; $i <= $#brackr; $i++) {
	$_ = $brackr[$i];
	if (/^comm /i) {
		$comm[$#brack +1] = $_;
	} elsif (/^user /i) {
		$user[$#brack +1] = $_;
	} else {
		&split_ack($_);
		push(@brack, $_);
	}
}

if ($opt_A) {
	$! = $exit_noinput;
	while(<>) {
		chop;
		$_ || die("Null input when expecting an ACK line\n");
		if (/^comm /i) {
			$comm[$#brack +1] = $_;
		} elsif (/^user /i) {
			$user[$#brack +1] = $_;
		} else {
			&split_ack($_);
			push(@brack, $_);
		}
	}
	die("No input when expecting an ACK line\n") if $#brack < $[;
}

sub split_ack {
	# [bruterc4 V0.1] 11fa 11fb 0000 1 0000001234
	# [brutessl V0.1] 11fa 11fb 0000 1 no
	# 11fa FOUND 11fb 0000 1
	# 11fa SEARCHED 11fb 0000 1
	# 11fa REJECTED 11fb 0000 1
	$_[$[] =~ s/^brute[^\s]+\s+[^\s]+\s+//;
	($proj, $cksum, $start, $len, $found) = $_[$[] =~
/^([0-9a-f][0-9a-f][0-9a-f][0-9a-f]) ([0-9a-f][0-9a-f][0-9a-f][0-9a-f]) ([0-9a-f][0-9a-f][0-9a-f][0-9a-f]) (\d+) ([^\s]+)$/;
	if (defined($found) && ($found eq "no" || $found =~ /[0-9a-f]+/)) {
		if ($found eq "no") {
			$bracktype = "SEARCHED";
		} else {
			$bracktype = "FOUND";
			$start = $found;
			$len = 0;
			$cksum = "0000";
		}
	} else
	{	($proj, $bracktype, $cksum, $start, $len) = $_[$[] =~
/^([0-9a-f][0-9a-f][0-9a-f][0-9a-f]) ([^\s]+) ([0-9a-f][0-9a-f][0-9a-f][0-9a-f]) ([0-9a-f][0-9a-f][0-9a-f][0-9a-f]+) (\d+)$/;
	$! = $exit_duffinput;
	die "Input should be 'xxxx xxxx xxxx d+ [ xxxxxxxxxx | no ]' or 'xxxx [SEARCHED|FOUND|REJECTED] xxxx xxxx+ d+' not $_[$[]\n"
		unless defined($len) && ($len ne "");
	}
}
$req = " [";
$req .= "a" if ($#brack >= $[);
$req .= "A" if ($opt_A);
$req .= "aa" if ($brrack);
$req .= "c" if ($opt_c);
$req .= "C" if ($opt_C);
$req .= "d" if ($opt_d);
$req .= "h" if ($opt_h);
$req .= "H" if ($opt_H);
$req .= "i" if ($opt_i);
$req .= "l" if ($opt_l);
$req .= "L" if ($opt_L);
$req .= "k" if ($opt_k);
$req .= "n" if ($opt_n);
$req .= "N" if ($opt_N);
$req .= "N" if ($brnserv);
$req .= "p" if ($opt_p);
$req .= "P" if ($opt_P);
$req .= "s" if ($opt_s);
$req .= "S" if ($opt_S);
$req .= "t" if ($opt_t);
$req .= "T" if ($opt_T);
$req .= "u" if ($opt_u);
$req .= "U" if ($opt_U);
$req .= "v" if ($opt_v);
$req .= "V" if ($opt_V);
$req .= "]";
# Do bits which don't change per call ...
($junk, $junk, $proto) = getprotobyname('tcp');
@servers = split(',', $servers);
$attempt = 0;
$attempts = 1;
$attempts = 5 if $#brack >= $[;

while(1) {
    # Open a socket to each of the servers in turn, within a fire wall.
    foreach $serv (@servers) {
	# Split the "host/port" arg
	local ($server,$portn) = split('/', $serv);
	# Initialise to suitable defaults
	$open_timeout = $def_open_timeout;
	$portn = $defport unless defined $portn;
	# Boring old "set up a socket" code ......
	if ($portn =~ /^\d+$/) {
		$port = $portn;
	} else {
		($junk,$junk,$port)=getservbyname($portn,'tcp');
		unless (defined($port)) {
			print STDERR "Error: port $portn unknown\n";
			next;
		}
	}
	($junk, $junk, $junk, $junk, @addrs) = gethostbyname($server);
	if ($#addrs < 0) {
		print STDERR "Error: host $server unknown\n";
		next;
	}
	print STDERR "Try 0 $#addrs $server/$port ($serv)\n" if $verbose;

	# now try each A RR -- there may be more than one!
	foreach $thataddr (@addrs) {
		$address = join('.', unpack('C4',$thataddr));
		print STDERR "Try 1 $server/$port ($address $serv)\n" if $verbose;
		while (1) {
			$slept = 0;
			undef($termmsg);
			$res=eval "&try_addr(\"$serv\")" ; 
			alarm(0);	# just in case !
			close(COMMAND);
			$res =~ s/[\r]*\n// if defined($res);
			$@ =~ s/[\r]*\n// if defined($@);
			$@ =~ s/[\r]*\n// if defined($@);
			print STDERR "Try 2 [$@|$!]\n" if $verbose;
			if ($res) {
				print STDERR "Worked($exit_OK): $res\n" if $verbose;
				exit $exit_OK;
			}
			elsif (defined($termmsg)) {
				print STDERR "$termmsg ($@)\n";
			}
			else { print STDERR "Failed: $@\n"; };
			last unless ($opt_l || $opt_L) && $slept;
			sleep($slept -1);
		}
		exit($exit_duffinput)if $@ =~ /^515 Incorrect checksum return/;
		exit($exit_duffinput)if $@ =~ /^514 Parameter out of range/;
		exit($exit_stop_host)if $@ =~ /^60. STOP [^ .]+[^ ]+$/i;
		exit($exit_stop)     if $@ =~ /^60. STOP/i;
		exit($exit_sleep)    if $@ =~ /^60. SLEEP/i;
	}
    }
    # get here ==> Failed
    $attempt++;
    exit($exit_noproj)	 if $@ =~ /^510 No such project/;
    $! = $exit_callfailed;
    die(" +++ FAILED after $attempt attempts\n") if $attempt >= $attempts && $attempt > 1;
    exit($exit_callfailed) if $attempt >= $attempts;
    sleep (15 * $attempt);
}

# EITHER: add some commands to be performed later (e.g. to read back ack's)
# OR:     if needed, do synchronously  :-(
sub expect {
	print STDERR "	       + $_[0]\n" if $verbose;
	$pend_cmd .= "$_[0]\n";
}

# Catch up on all the pending commands.
sub expect_catchup {
	return unless $pend_cmd;
	print STDERR "Expect catchup: <<$pend_cmd>>\n" if $verbose;
	undef($@);
	$evrc = eval "$pend_cmd; 1";
	print STDERR "eval gave $evrc, \$\@='$@'\n" if $verbose && $evrc != 1 && $@ ne '';
	die $@ if !defined($evrc) && $@ ne '';
	$pend_cmd = '';
}

# Try making a call to an address. This is evaled so can timeout, die, etc
sub try_addr {
	local ($serv) = $_[0];
	$workout = "";
	die("gethostbyname($server) failed: $!\n") unless defined $thataddr;
	$that = pack($sockaddr, $AF_INET, $port, $thataddr);
	# Guessing time folks ! Is SOCK_STREAM 1 or 2 ??
	socket(COMMAND, $PF_INET, $SOCK_STREAM, $proto) ||
	socket(COMMAND, $PF_INET, $SOCK_STREAM = 3 - $SOCK_STREAM, $proto)
		|| die "create socket for $server: $!\n";
	select((select(COMMAND), $| = 1)[0]);
	$SIG{"ALRM"} = "TIMEOUT";
	alarm($open_timeout);
	$starttime = time;
	$brprojt = $brproj;
	connect(COMMAND, $that) || die "connect to $server: $!\n";

	print STDERR "Connected, now read the HELO\n" if $verbose;
	$brinterval = "";
	$brinterval = sprintf(" T=%d", time - $brworkstart) if $brworkstart;
	$need_p = $opt_T || (! $brprojt && ($opt_k || $opt_d));
	$pend_cmd = '';

	&expect('($c,$t) = &read_resp; die "$code $t\n" unless $c == 221;');
	&raw_send_cmd("HELO $version $id$brclient", 251);
	&expect('($c,$t) = &raw_read_cmd(251);die "$c\n" if!$t;');
	&raw_send_cmd("COMM $vers$req$brcomment$brinterval", 250);
	&expect('($c,$t) = &raw_read_cmd(250);die "$c\n" if!$t;');
	if ($need_p) {
		&raw_send_cmd("INFO", 311);
		# We *NEED* this data before we can issune later command
		&expect_catchup;
		($c,$t) = &raw_read_cmd(311); die "$c\n" unless $t;
		@projects = split("\n", $t);
		foreach $project (@projects) {
			@F = split(":", $project);
			print STDERR "$F[0]/$F[1]/$F[3]:$project\n" if $verbose;
			if ($F[1] eq $brtype && $F[3] eq $brstate) {
				$brprojt = $F[0];
				print "$project\n" if $opt_T;
				last;
			}
		}
		die "no suitable project\n" unless $brprojt;
	}
	if ($brnserv) {
		$arg = ($brprojt) ? $brprojt :
			($proj)   ? $proj :
			($brtype) ? $brtype : "unknown";
		&raw_send_cmd("SERV $brnserv $arg", 250);
		&expect('($c,$t) = &raw_read_cmd(250); die "$c\n" unless $t; print "$t\n";');
	}
	if ($opt_N) {
		$arg = ($brprojt) ? $brprojt :
			($proj)   ? $proj :
			($brtype) ? $brtype : "unknown";
		&raw_send_cmd("SERV $opt_N $arg", 250);
		&expect('($c,$t) = &raw_read_cmd(250); die "$c\n" unless $t; print "$t\n";');
	}
	if ($opt_p) {
		&raw_send_cmd("INFO", 311);
		&expect('($c,$t) = &raw_read_cmd(311); die "$c\n" unless $t; print "$t\n";');
	}
	if ($opt_d) {
		&raw_send_cmd("WORK $brprojt", 310);
		&expect('($c,$t) = &raw_read_cmd(310); die "$c\n" unless $t; if ($opt_k) { $workout=$t; } else { print "$t\n"; }');
	}
	if ($#brack >= $[) {
		$fails = "";
		for ($i = $[; $i <= $#brack; $i++) {
			if ($user[$i]) {
				&raw_send_cmd($user[$i], 250);
				&expect('($c,$t) = &raw_read_cmd(250); $fails .= "$c for \"$user[$i]\". \n" if!$t;');
			}
			if ($comm[$i]) {
				&raw_send_cmd($comm[$i], 250);
				&expect('($c,$t) = &raw_read_cmd(250); $fails .= "$c for \"$comm[$i]\". \n" if!$t;');
			}
			&split_ack($brack[$i]);
			&raw_send_cmd("ACK $proj $bracktype $cksum $start $len", 250);
			&expect('($c,$t) = &raw_read_cmd(250); $fails .= "$c for \"$brack[$i]\". \n" if!$t;');
		}
		&expect('die $fails if $fails;');
		&expect('print "$t\n";');
	}
	if ($brrack) {
		&raw_send_cmd("ACK $brrack", 250);
		&expect('($c,$t) = &raw_read_cmd(250); die "$c\n" unless $t; print "$t\n";');
	}
	if ($opt_H) {
		&raw_send_cmd("HELP", 312);
		&expect('($c,$t) = &raw_read_cmd(312); die "$c\n" unless $t; print "$t\n";');
	}
	if ($opt_k) {
		# Not a requirement, but better not to issue a KEYS command
		# if there might have been an earlier error.
		&expect_catchup;
		($c,$t) = &send_cmd("KEYS $brprojt $number", 210);
		die "$c\n" unless $t;
		printf "%s%s %s%s\n", ($brunixcmd) ? "$brunixcmd - " : "",
			 $brprojt, $t, ($brunixcmd) ? " <<'EOF'" : "";
	}
	if ($workout) {
		print "$workout\n";
		print "EOF\n" if $brunixcmd;
	}
	&raw_send_cmd("QUIT completed", 220);
	&expect('($c,$t) = &raw_read_cmd(220); die "$c\n" unless $t;');
	&expect_catchup;
	while (<COMMAND>) { print STDERR "Trail: $_"; }
	close(COMMAND);
	$! = 0; # '';
	return "OK";
}

sub TIMEOUT { $! = 99 unless $!;
	$slept = $timeoutsleep +1;
	$timeoutsleep *= 2;
	$termmsg = "Server not responding: Timed out";
	die($timeout_msg);
}

# send a command, then await and check reply
sub send_cmd {
	&raw_send_cmd(@_);
	&raw_read_cmd($_[1]);
}

sub raw_send_cmd {
	$interval = sprintf("%02d", time - $starttime);
	print STDERR "\t =$interval=> $_[1] $_[0]\n" if $Verbose;
	seek(COMMAND, 0, 1) if ($seek_stdio);
	if ($use_syswrite) {
		syswrite(COMMAND, "$_[0]$crlf", length("$_[0]$crlf"));
	} else { print COMMAND "$_[0]$crlf"; }
	seek(COMMAND, 0, 1) if ($seek_stdio);
}

sub raw_read_cmd {
	($code, $text) = &read_resp;
	if ($verbose) {
		if (defined($code)) {
			if (defined($text)) {print STDERR "<$code> [$text]\n"; }
			else { print STDERR "Error: <$code>\n"; }
		} else { print STDERR "[no result]\n"; }
	}
	return ($code, $text) if defined($code)&&defined($text)&&$code eq $_[0];
	if (defined($code)) {
		$text = "<No DATA>" unless defined($text);
	} else { return "<NoResponse>"; }
	if ($code eq "600" && $text =~ /^SLEEP/i) {
		$cmd if 0;
		($cmd, $delay) = split(' ', $text);
		print STDERR "sleep $delay\n" if $verbose;
		$slept = $delay +1;
		&raw_send_cmd("QUIT asked to sleep", 220);
		$termmsg = "Server suggestion: $text";
	}
	if (($code eq 601 || $code eq 600) && $text =~ /^STOP/i) {
		($cmd, $newhost, $rest) = split(' ', $text, 3);
		if ($newhost && !$rest) {
			print STDERR "asked to $cmd and move to $newhost\n" if $verbose;
			&raw_send_cmd("QUIT asked to stop to $newhost", 220);
			$termmsg = "Server request: $cmd and move to $newhost";
		} else {
			print STDERR "asked to $text\n" if $verbose;
			&raw_send_cmd("QUIT asked to stop", 220);
			$termmsg = "Server request: $text";
		}
	}
	if ($opt_L && $code eq 421 && $text =~ /you have been timed out/i) {
		print STDERR "TIMEOUT: $text\n" if $verbose;
		$slept = $timeoutsleep +1;
		$timeoutsleep *= 2;
		$termmsg = "Server timing problem: $text";
	}
	return "$code $text\n";
}

# read back a response. Process multi-line responses "nnn-text" and "."
sub read_resp {
	return undef unless $_ = <COMMAND>;

	# fix CR, CRLF and LF
	$_ .= $end unless ($end = chop) eq "\n";
	$_ .= $end unless ($end = chop) eq "\r";

	$interval = sprintf("%02d", time - $starttime);
	print STDERR "\t<=$interval=  $_\n" if $Verbose;
	($top, $mid, $low, $cont, $rest) = /^(\d)(\d)(\d)([- ])(.*)$/;
	return $_ unless defined $rest;
	$rc = "$top$mid$low";

	# Handle "-" multi line responses
	while ($cont eq "-") {
		return undef unless $_ = <COMMAND>;

		$_ .= $end unless ($end = chop) eq "\n";
		$_ .= $end unless ($end = chop) eq "\r";
		$interval = sprintf("%02d", time - $starttime);
		print STDERR "\t<=$interval=  $_\n" if $Verbose;
		($top, $mid, $low, $cont, $more) = /^(\d)(\d)(\d)([- ])(.*)$/;
		return "$rc $rest\n$_" unless defined $more;
		$rest .= "\n$more";
	}

	# Handle "." multi line responses
	if ($rc == 350 || $rc == 310 || $rc == 311 || $rc == 312) {
		$rest = "";
		while (<COMMAND>) {
			$_ .= $end unless ($end = chop) eq "\n";
			$_ .= $end unless ($end = chop) eq "\r";
			$interval = sprintf("%02d", time - $starttime);
			print STDERR "\t<=$interval=  $_\n" if $Verbose;
			last if /^\.$/;
			s/^\.//;
			$rest .= "$_\n";
		}
		chop $rest;
		return "$rc $rest" unless defined $_;
	}
	# All done -- it worked.
	return ($rc, $rest);
}

# Try to work out the FQDN of this host ....
sub fqdn {
	$pqdn = "";

	# Try things which *MIGHT* return a FQDN. Save the PQDN if any.
	if (open(CMD, "hostname|")) {
		$_ = <CMD>;
		$_ .= $end unless ($end = chop) eq "\n";
		close(CMD);
		print STDERR "[hostname gave $_]\n" if $verbose;
		return $_ if /.\../;
		$pqdn = $_ if $_ ne "";
	}
	if (open(CMD, "uname -n|")) {
		$_ = <CMD>;
		$_ .= $end unless ($end = chop) eq "\n";
		close(CMD);
		print STDERR "[uname -n gave $_]\n" if $verbose;
		return $_ if /.\../;
		$pqdn = $_ if $_ ne "";
	}

	# Try to find the domain name
	if ($pqdn) {
		($name, @addrs) = gethostbyname($pqdn);
		if (defined($name)) {
			print STDERR "[gethostbyname gave $name]\n" if $verbose;
			return $name if $name =~ /.\../;
		}
	}
	$pqdn .= "." if $pqdn;
	if (open(CMD, "domainname|")) {
		$_ = <CMD>;
		$_ .= $end unless ($end = chop) eq "\n";
		close(CMD);
		print STDERR "[domainname gave $_]\n" if $verbose;
		return "$pqdn$_" if /.\../;
	}
	if (open(CMD, "/etc/resolv.conf")) {
		while(<CMD>) {
			@F = split;
			if (@F[$[] =~ /^domain$/) {
				print STDERR "[resolv.conf gave $F[$[+1]]\n" if $verbose;
				close(CMD);
				return "$pqdn$F[$[+1]";
			}
		}
		close(CMD);
	}
	return "UnKnown";
}
