Subject: ANNOUNCE: "shelltalk" -- multi-user shell conferencing utility
From: [email-masked] (Eric Arnold)
Date: 11 Jun 1995 17:51:38 GMT
Newsgroups: comp.lang.perl.announce,comp.lang.perl.misc,comp.lang.perl

Here's a little (well, not-so-little) utility people might like.

-Eric

#
#                             "shelltalk"  
#
#      (Shell/socket multiplexor, multi-user conferencing utility)
#
#
# This has been tested on SunOS4.x and Solaris2.3-5 with Perl4.036/5.001.
#
# It has no warranty or support (it's Free!), but on the other hand, I'm
# interested in how it's used and how well it works so I can fix and
# improve it.
# Ergo, suggestions and/or comments are welcome.
#
#      Email: [email-masked] or [email-masked]
#
#
# What it is:
#
#   I wanted to have a way to conference a few remote users inside an
#   (optionally) active shell.  It's *very* useful to watch what people
#   are doing, keystroke by keystroke, when you are
#   supporting/training/learning from them.  There is a big difference
#   between what they say they are doing, and reality.
#
#   I soon decided that it could have a more generalized scope because a
#   utility which can accept and initiate multiple network connections,
#   and copy data between all of them, is a versatile beast, whence came
#   all the various options.  (Although an alternative explanation is that
#   the instant gratification of developing in Perl took over my brain.)
#
#   It makes a nice showcase for many features of Perl programming,
#   i.e. network/socket programming, asynchronous file handle IO,
#   terminal modes (stty, raw, char-at-a-time), pseudo-tty handling, 
#   mostly portable ioctl, and lots of Perl idioms.  It also demonstrates
#   for any non-believers that Perl can tackle non-trival Unix programming
#   tasks.
#
#
# Features (see also "-h" or "print_usage()" for detailed descriptions):
#
#   - It can start a shell in a pseudo-tty, and allows you to share your
#     shell with other people running "shelltalk" on other machines.
#     The pseudo-tty is a full terminal driver, so "vi", "emacs", etc.
#     work normally.  Local window size changes are propogated via SIGWINCH.
#
#   - It works well as a multi-user "talk" facility.  You can have two or
#     more people talking in an single window.  It is pretty intelligent
#     about keeping straight who is typing what, and keeps auto-refreshing
#     buffers for each user.  If you would rather, you can start processes
#     in several windows, each containing the output from a single
#     person.  If you do start it in a couple windows, and use the -notify
#     option, it has all the major features of split screen style BSD
#     "talk", plus lots more.
#
#     In talk mode, almost everything is sent across, including 
#     commands, error messages, etc.  This allows you to help the remote
#     user use shelltalk itself (and if you have the user set "-trust", you
#     can actually drive his shelltalk process for him).
#
#   - You can use it to create ad hoc background server processes.
#     To illustrate, you could make a daemon which copies files to you, a
#     little like rcp/rsh.  Say you make a script called "givefile" containing:
#	system "stty -echo raw";
#	while($fname=<STDIN>){
#	  open(FH,$fname); while(read(FH,$buf,10000)){print $buf}; close(FH);
#	  print "\000DIE\000"; }
#     Then you could start a server daemon:
#       shelltalk -daemon -lit -l -sh givefile
#     and retrieve files from a remote system using:
#       echo filename.remote | shelltalk -quiet -lit -ex "\000DIE\000" \
#		-o somehost > filename.local
#     
#   - You can use it like "telnet <host> <port>".  E.g. connect to login:
#     shelltalk -o somehost 23
#
#   - The -eval is actually a real Perl "eval()", but  the default
#     is off for security reasons.  You could probably have a lot of fun
#     with downloaded "stored procedures", even rewrite a remote script
#     from the inside out while it's running.
#
#   - You can use it to analyze protocols if you wedge it between a client
#     and a server.  For example, I used it to snoop the telnet options
#     negotiation protocol by:
#       $ shelltalk -tr -lit -l 5050 -ctl	# in the first window
#       $ telnet myhost 5050			# in the second window
#       command>o myhost 23			# in the first window
#     Then everything I typed in the second/telnet window was scanned in
#     the first, and I got to peep all the control chars and see how telnet
#     worked.
#     
#   - I've included an answering machine script at the end.  That way,
#     you can leave a shelltalk process running in the background, and it
#     can greet people, take calls for you, and page you on your console.
#
# Misfeatures:
#
#   - Be careful if you turn on the "-trust" flag.  (See "Features", above :-)
#   - It's big.
#   - Not very performance tuned.  (E.g. each char sent in talk mode does
#     a lot of stuff, but you can cut/paste a whole screenfull for the same
#     cost, so it's fast for bulk.)
#     Each talk char/packet has a lot of header info, which may be felt over
#     slow modems.
#
# Bugs/problems:
#
#   - It can be confusing connecting to/from a user on a gateway, since 
#     you don't easily know which hostname to connect to (E.g. a single 
#     machine can have two or more hostnames.."somehost" or "somehost-bb" 
#     if that host has two ethernet ports).  The "listen" option doesn't
#     have parameter to specify which ethernet to listen on.
#   - There are some rare pathological race cases in the auto-connect feature.
#   - There are some rare cases where it will die mysteriously in comm'print,
#     maybe when the socket was newly opened, or ?
#
# Enhancements:
#
#   - invite/reject functions
#   - handle telnet better?  It currently only does enough to get Sun's telnet
#     to respond with a login prompt.  Maybe just better to start a telnet
#     via "-sh telnet host", if you want true telnet features.
#   - There is no "utmp" entry.  If you want that, maybe rlogin
#     back into your host.  You can use "-sh login" on SunOS, but Solaris
#     won't allow it -- nasty goodie-goodie Solaris.
#   - The window spawn mode doesn't work with autoconnect, because it closes
#     connections as soon as it spawns a window for them.  Thus, it's a
#     little more work to get a conference going with > 2 people, since
#     each person has to explicitly connect to every user he wants to talk to.
#
# 09/11/94 07:04:49 PM;  eric:	Now works on Solaris2.3 (and SunOS4.x)
# 09/12/94 01:22:54 AM;  eric:	client/server/local-command modes
# 09/12/94 07:13:21 AM;  eric:	talk mode is out-of-band for shell
# 09/12/94 03:03:21 PM;  eric:	added SIGWINCH handling
# 09/12/94 10:02:15 PM;  eric:	receiving side now does talk prompting
# 09/13/94 06:44:05 AM;  eric:	shell output armored. Solaris wait(WNOHANG)
# 09/13/94 08:52:59 AM;  eric:	-daemon, -I, -lit, -ctl
# 09/13/94 06:43:39 PM;  eric:	blocking, conferencing/auto-connect
# 09/14/94 02:09:26 AM;  eric:	chasing race conditions with auto-connect
# 09/14/94 03:01:07 PM;  eric:	command mode is now non-blocking, and can be
#				seen remotely in talk mode
# 09/15/94 02:10:20 AM;  eric:	added ^Z suspend emulation
# 09/15/94 01:57:09 PM;  eric:  added hub mode, eval, exit_tag
# 09/16/94 06:27:39 PM;  eric:	cleaned up input editing & now input lines accum
# 09/17/94 03:58:56 PM;  eric:  -trace and better passive analyzing
# 09/18/94 03:24:32 PM;  eric:	-notify
# 09/19/94 04:24:47 PM;  eric:	smarter buffering on out accum.s
# 09/21/94 12:27:12 AM;  eric:	cleaner talk packet address scheme
# 09/22/94 09:11:40 PM;  eric:	Better host info scheme. Detect loops.
# 09/22/94 10:26:34 PM;  eric:	added window_spawn
# 10/10/94 03:48:22 PM;  eric:	TERM and size checking.  Line wrap

eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
& eval 'exec perl -S $0 $argv:q'
if 0;

($dirname = $0) =~ s:/[^/]+$::;
push(@INC, $dirname );

print "Perl Version $]\n";

require "comm_utils.pl" unless defined &comm'init;

# I've included a copy of "shellwords" at the end.  It's important to use
# a newer version of "shellwords", since the old ones loop badly in some
# cases.
require "shellwords.pl" unless defined &shellwords;

&telnet_defines;

if ( -f "/vmunix" ) {
  $OS_type = "BSD";
} else {
  $OS_type = "SVR4"; }

( $My_prog_name = $0 ) =~ s:.*/::g;

$Version="shelltalk2.7";
$Default_port = $Start_port= 5050;
$Stop_port = 5060;
$Command_char = "\035";	# ^]  same as telnet
$Shell_cmd="/bin/sh";
$NO_PORT="NO_PORT";
$Prompt_char = ">";
#$Command_prompt = "cmd$Prompt_char";
$Command_prompt = "command$Prompt_char";
$Window_spawn_cmd = 'cmdtool -Wh 20 -c "shelltalk -client_window -t -o $host $port; exit" ';
$Proxy_host = "";	# special hack

@My_window_size=();	# make perl5 happy

$Debug = 0;
$Ctlecho = 0;
$Is_literal = 0;
$Is_hub = 1;
$Is_shell_proc = 0;
$Is_listen = 0;
$Is_talk_mode = 1;
$Do_real_evals = 0;
$Do_opt_evals = 0;
$Is_auto_wrap = 1;

$My_user = (getpwuid($>))[0];
chop( $My_host = `uname -n` );
$Listen_port = $NO_PORT;

$My_addr = &make_my_addr;
$Longest_prompt = length($My_addr);
$Prompt_aliases{ $My_user } = $My_addr;

$SIG{"WINCH"}="do_sig_winch";
&do_sig_winch;

sub do_sig_usr1{ $Debug = 1 }
$SIG{"USR1"}="do_sig_usr1";

$|=1;

# This is simple minded, since telnet uses characters in this range, as
# might other things like Japanese double-byte chars.  But generally, the
# user won't be able to enter these, and we won't use \377, the telnet
# command char.

$BOP="\376";	# Beginning of packet
$BOT="\375";	# Talk mode packet
$BOS="\374";	# Shell output packet
$BOE="\373";	# Eval packet
$BOA="$BOP$BOT$BOS$BOE";

$Host_info{ $My_addr, "version" } = $Version;
$Host_info{ $My_addr, "user" } = $My_user;
$Host_info{ $My_addr, "port" } = $Listen_port; #? not needed
$Host_info{ $My_addr, "conn_time" } = time();
$date=&get_date;
$Host_info{ $My_addr, "conn_date" } = "[$date]";

&comm'init;
unless ( grep(/-daemon/, @ARGV ) ){# chicken and egg problem, so pre-check
  open(TTY_FH, "+>/dev/tty") || print STDERR "Error opening /dev/tty:$!\r\n"; }

while( @ARGV ){
  unless( &do_option_list( *ARGV ) ){	# do_option nibbles off *ARGV
    print STDERR " Invalid arguments:(@ARGV)\r\n";
    &print_usage("short");
    exit 1;
  }
}

sub print_usage{
  local($verbose)=@_;

  grep( (print STDERR "$_\r\n"), split(/\n/, <<EOF ) );

  Usage: 
    Brief:
	shelltalk -l            # start a process listening on host1
	shelltalk -o host1	# connect to host1's process from host2
				# To quit: type control- ]  and then "q"
    Detailed:
	shelltalk
		*[-l(isten) [<port> [<stop port>]]] 
		 [-o(pen) <host> [<port> [<stop port>]] [b(lock)] ]
		*[-b(lock) [<host>|all]]
		 [-sh(ell) [<shell|command>]]
		 [-I(input) <input to shell>] 
		 [-e(scape) <char>|"\oct"|^Char] | [-comm(and_char) <char>]
  		*[-t(alk mode)]
		*[-lit(eral)]
		*[-ctl(echo)]
		*[-log <file>]
		*[-hub]
		*[-a(utoconnect)]
		 [-ev(al) [<address>] [opt "<options>" | <perl string>] ]
		 [-ex(it_tag) <string>]
		 [-k(ill) [<signal>] ]
		*[-tr(ace) [<source>] ]
		*[-trust [perl]]
		 [-notify [<user>|<pattern>|console] ]
		*[-q(uiet)]
		*[-d(ebug)]
		 [-dae(mon)]
		 [-h(elp)]
  		 [-st(atus) [rem(ote)|<user>|<user\@host...>] ]
		 [-p(rint) "string"]
		 [-w(wap)]
		*[-win(dow_spawn) ["<window_command>"] ]
		* = toggle
	    In command mode:
		 (any option)
		 h(elp) for detailed help
		 q(uit)
		 z or ^Z		# suspend
		 (empty line to exit command mode)

EOF

  grep( (print STDERR "$_\r\n"), split(/\n/, <<EOF ) ) if $verbose eq "verbose";

  Details:

  -listen tries to listen on <port>, or on one of the next few
	until <stop port> if <port> is busy.
  -shell mode starts a shell/command.  It only makes sense for one
	person in a connection set should do this, or you will 1) not
	see output from someone else's shell, or 2) see simultaneous
	output from other shells.  You can give an alternative to
	/bin/sh as a parameter.
  -open will attempt a connection to the given <host> and <port>.
	You can block input from a connection, making it read-only from you.
	If you give a <stop port>, it will seek for an active listener.
  -block makes your process readonly for others.  You can block "all",
	or everything from a specific host/connection.
  -talk mode tags your data with "user\@host>", and is transparent
	to any running shells.  Unless a shell is running you won't see
	anything with talk mode off.  Default is on.
  -hub toggles the rebroadcast all incoming data (default on).
	Be very careful not to set up a loop.  Only connect to more than
	one host if you know the new connection isn't connected to anywhere
	else.
  -autoconnect will try to connect to all hosts connected to hosts you
	connect to. It sometimes opens/closes connections a few times, but
	it usually ends up correct.  Hub mode is usually preferable.
  -daemon mode redirects STDIN and STDOUT to /dev/null, so you can run
	as a background process.
  -debug mode (ewww!)
  -Input should be used after -sh, or the shell won't be alive yet.
  -literal doesn't send or check for special chars.  Note that if you're
	not in talk mode or literal mode, other people won't see you typing.
  -log creates a session log
  -quiet shushes STDERR, which suppresses almost all messages. Used mostly
	for -daemon mode.  
  -escape or -command_char is used to change the character which escapes
	to command mode.  The default is control-right-bracket (^]), as with
	"telnet".  You can enter the octal equivalent for a character using
	(For example, if you want a real escape char):
	  esc "\033"
  -eval evaluates the given command(s) on the specified host.  The Default
	is to disallow evals.  Use the "-trust" flag to allow things like:
	  ev user\@host#pid opt t		    # toggle remote talk mode
	  ev user\@host#pid opt '-sh -I "who am i"'  # start a shell on somehost 
						# be careful to close the quotes
	"-trust perl" allows any Perl string to be eval'd.
	You can eval the contents of a file:
	  ev user\@host#pid [port] < file  # if "-trust perl" is set on host
	  ev < file  to eval locally	# eval into your own process
	You can eval a string locally like:
	  ev '\@a=split(//,"somestuff")'
  -kill sends the shell a default signal of -1.
  -trace provides debug information about where data are coming from.
	Options for source include:
	  all|shell|from_shell|to_shell|remote|from_remote|to_remote
	Options add together.
  -trust allows other people to send evals to you.  "-trust perl" allows
	them to download Perl code to you.
  -exit_tag will cause the process to exit when it receives the
	given string.  This is just a hack to help background daemons.
  -notify does a Unix "write" to all instances of the given <user>,
	when somebody connects.  You can also specify a regex pattern
	instead of a user (it does a "who | grep pattern").
  -status alone gives you your info.  [rem(ote)|<user>|<user\@host...>] ]
	Will cause all users, or a particular user, to print status.
	You will only see it if the remote user is in talk mode.
  -print prints the given string to all connections (and shell).  It's
	useful because you can send control characters like "\033[...".
  -wrap allows status info to be automatically wrapped.  Default on.
  -window_spawn will start a new window when somebody connects to you.
	The default command to spawn is:
	  cmdtool -I "shelltalk -client_window -o $host ; exit"

  All options can be set interactively via the command-character/command-mode.
  Command mode is patterned after the way "telnet"'s command mode.  You
  will get a command prompt after typing control-right-bracket (control ] )
  and then you can give commands/options until you type an empty newline.

  Options may be abbreviated, but tie breaking is arbitrary (E.g. "-li" is
  "-listen", not "-literal").  The leading "-" is optional, but if you
  give more than one option on a line, you should use "-" before
  subsequent options.

  Examples:
    - One shell server,
	shelltalk -sh -listen			# on <somehost> (start first)
      and one or more clients:
	shelltalk -open somehost		# first client on <otherhost1>

    - Two people talking in "talk" mode:
	 somehost% shelltalk -l -b all	# start the listener first
	otherhost% shelltalk -o somehost

      And optionally a second set of windows so that each person can
      have their own window so they can't type over each other
      (unless they're in a bellicose mood). With two or more windows,
      it's a little more like split screen BSD "talk".

	 somehost% shelltalk -o otherhost
	otherhost% shelltalk -l -b all	# start first
      
    - Or have it start the second set of windows for you:

	 somehost% shelltalk -l -win		# start first
	otherhost% shelltalk -l -win -o somehost

    - Initialize a connection to "somehost", start a shell over there, and
      send it some input:
	shelltalk -o somehost -ev somehost "opt \"-sh -I 'echo zounds'\""

    - With -notify, if you are logged in remotely or have only one
      available window, and you're busy with other stuff, you can start
      a background shelltalk which will field incoming connections:
	somehost% shelltalk -q -daemon -notify eric -l 
      You can then connect to that background daemon and talk to the
      other person there.  E.g.
	somehost% shelltalk -o somehost

  Options are processed in the order given.  For example,
      shelltalk -l -sh -I "date" -daemon
    will print start up information, while this will not:
      shelltalk -daemon -l -sh -I "date"

    Suggested order is:
      -quiet -debug -daemon -shell -I -talk -auto -listen -hub -open -ev 

  See also the header on this script.

  Misc: notes:
    - You can "telnet <host> <port>" to a shelltalk session, but you
      should set the telnet "mode char" option, and the shelltalk
      "literal" option, since different control chars are used by each.

EOF

}

&option( "stat" );
print STDOUT &make_prompt( $My_addr );

&stty_raw unless $Is_daemon;

# Some notes:
#
# - Have to handle any fragment of data, regardless of line breaks, from
#   handles.
# 
# - In general, the assumption is made that we may read more than
#   one packet on a handle at once, thus we may encounter more than
#   one $BOT,$BOP,etc.
#
# - You mustn't assume any symmetry in connections (ie. user1<-->user2,
#   user1<-->user3, but not user2<-->user3 ) .  For example, the
#   data-tags/prompts/accumulator-buffers that show which input comes from
#   what source are handled by the receiving process, because the sending
#   process cannot know who might be connected to the receiving process,
#   thereby interjecting unaccounted data into the conversation.

while ( 1 )
{
  $inp=""; #shut up -w
  @handles=();
  if ( $Is_listen ){
    push(@handles, $Listen_handle ) }
  if ( $Is_shell_proc ){
    push(@handles, $Proc_handle ) }
  if ( !$Is_daemon && !$Stdin_dead ){
    push(@handles, STDIN ) }
  push( @handles, keys( %Remote_handles ) );

  unless($found){
    # Do one autocon per pass; better maybe.  Try to give it a chance to
    # get the host info for a new connection, so it will spend less time
    # thrashing.
    local(@tmp)=(keys %Auto_conn );
    &do_option_list( *tmp );
  }

  $found=0;
  @read_handles = &comm'select_it( 1, @handles );
  for $read_handle ( @read_handles )
  {
    $found++;
    #print "ready=$read_handle\r\n";
    if ( $Is_listen && $read_handle eq $Listen_handle )
    {
      ($rem_handle,$host) = &comm'accept_it( $Listen_handle );

      $msg = "[New connection from ($host)]\007";
      &print_stdout_remote( "\r\n$msg\r\n" );

      &send_host_info ($rem_handle );
      $Remote_handles{ $rem_handle } = "$host:$NO_PORT";

      if ( $Notify eq "console" ){
	system "echo '$msg to ($My_host,$Listen_port)' `date`>/dev/console ";
      }elsif ( $Notify ){
	for $i ( grep( /$Notify/i, `who` ) ){
	  chop $i;
	  $i =~ s/^\s+//;
	  ($user, $tty) = split(/\s+/,$i);
	  # SunOS write fails if STDOUT not a tty?
	  system "echo 'Shelltalk: $msg to ($My_host,$Listen_port)' `date`>/dev/$tty ";
	  #system "echo '$msg to ($My_host,$Listen_port)' `date`| " .
		  #"write $user $tty 2>/dev/null";
	}
      }
    }
    elsif ( $Is_shell_proc && $read_handle eq $Proc_handle )
    {
      #if ( (kill 0, $Proc_pid) && &comm'sysread( $Proc_handle, $inp, 10000 ) ) 
      if ( &comm'sysread( $Proc_handle, $inp, 10000 ) ) 
      {

	if ( $Exit_tag ) {
	  if ( $inp =~ s/$Exit_tag$// ){
	    $Exiting = 1; }
	}

	if ( $Ctlecho ){
	  $inp =~ s/[\0-\011\013\014\016-\037\177-\377]/&ctlecho($&)/ge; }
	if ( $Is_trace_mode ){
	  if ( $Is_trace_mode =~ /all/ ||
	       $Is_trace_mode =~ /(,shell|^shell)/ ||
	       $Is_trace_mode =~ /from_shell/
	     )
	  {
	    local( $tmp ) = $inp;
	    $tmp =~ s/[\0-\037\177-\377]/&ctlecho($&)/ge;
	    $tmp = "[$Shell_cmd]--->[$tmp]" if $tmp ne "";
	    &print_stdout( STDIN, "\r\n$tmp\r\n" );
	  }
	}
	print STDOUT $inp;
	print LOG $inp if $Log_file;
	unless ( $Is_literal ){
	  $inp = "$BOS$inp"; }
	&print_remote_handles( *Remote_handles, $inp );
      }
      else {
	&print_stdout_remote( "\r\n[proc died on read]\r\n" );
	&close_proc;
      }
    }
    #----------------------------BEGIN STDIN----------------------------------
    elsif ( $read_handle eq "STDIN" )
    {
      # I'm (maybe too often) making assumptions that editing stuff will
      # come in a packet by itself.  It's a lot easier that way, though
      # it could get confused if you were to dump a bunch of lines into
      # the window, with embedded backspaces and stuff.

      unless ( sysread( $read_handle, $inp, 10000 ) ) {
	# If we are piping to the process, we can get:
	&print_stdout_remote( "\r\n[STDIN EOF]\r\n" );
	$Stdin_dead=1;
	&my_exit unless $Exit_tag;
      }

      if ( $Exit_tag ) {
	if ( $inp =~ s/$Exit_tag$// ){
	  $Exiting = 1; }
      }

      # It's best to get the Command_prompt into the accum., so it's seen by
      # everybody everywhere.  Put the newline in to flush out any junk.
      if ( $inp =~ s/$Command_char/\n$Command_prompt/ ){
	$Is_command_mode = 1;
	$Out_accum = "";
      }

      local( $unharmed ) = $inp;	# doesn't keep command char

      if ( $Is_command_mode ){
	if ( ( $last_accum eq "" || $last_accum eq $Command_prompt ) &&
		$inp =~ /^[\r\n]$/ ) {
	  $Is_command_mode = 0;
	}
	elsif ( $inp =~ /^(.*)[\r\n]+$/ ) {
	  $last_accum .= $1;	# talk processing will kill all up to \n
	}
	elsif ( $inp =~ /^\n?$Command_prompt(.*)[\r\n]+$/ ) {
	  $last_accum .= $1;	# talk processing will kill all up to \n
	}
	# This bit is sorta buggy in one rare case: if we receive a packet
	# that has "stuff\n\035command" or "command\nstuff\n", it will
	# trash the command.
      }

      if ( $Is_talk_mode || $Is_command_mode )
      {
	# process STDIN as if it came from any remote handle:
	($junk,$out) = &process_talk_data(
	      &pack_my_talk_data( "$inp" ),
	      STDIN );
	&print_stdout( STDIN, $out );
      }

      # shell echo will take care of bouncing STDIN to everywhere, so
      # don't send remote
      if ( $Is_shell_proc && !$Is_talk_mode && !$Is_command_mode )
      {
	&print_to_proc( $unharmed );
      }
      else
      {
	unless ( $Is_literal ){
	  if ( $Is_talk_mode ) {
	    # talk mode is sorta out-of-band data
	    $inp = &pack_my_talk_data($inp);
	  }else{
	    $inp = "$BOP$inp";}	# normal packet
	}
	if ( $Is_command_mode ){
	  $inp = "" unless ( $Is_talk_mode );
	}
	&print_remote_handles( *Remote_handles, $inp ) if $inp ne "";
      }

      # Do this last so that we can process normally the keystrokes before
      # we see the output from do_command.

      if ( $Is_command_mode )
      {
	if ( $unharmed =~ /\032/ )	 	# ^Z
	{
	  &print_stdout_remote( "^Z" );
	  &do_suspend;
	  &print_stdout( STDIN, $Command_prompt );
	}
	elsif ( $unharmed =~ /[\r\n]$/ )
	{
	  if ( $last_accum ){
	    &do_command($last_accum);
	    if ( $Is_command_mode ){	# shell mode changes it
	      &print_stdout_remote( $Command_prompt );
	      $Out_accum = $Command_prompt;
	    }
	    $last_accum = "";
	  }
	}
	else{
	  # Need to keep around last_accum, because newline flushes
	  # Out_accum in &process_talk_data
	  $last_accum = $Out_accum;
	}
      }

    }
    # ----------------------------END STDIN-----------------------------------
    # ----------------------------BEGIN REMOTE--------------------------------
    elsif ( $Remote_handles{$read_handle} )
    {
      if ( &comm'sysread( $read_handle, $inp, 10000 ) ) {

	if ( $Block_all || $Blocked_handles{ $read_handle } ){
	  next; }

	if ( $Exit_tag ) {
	  if ( $inp =~ s/$Exit_tag$// ){
	    $Exiting = 1; }
	}

	$rebroadcast = $inp;

	if ( $Is_trace_mode ){
	  if ( $Is_trace_mode =~ /all/ ||
	       $Is_trace_mode =~ /(,remote|^remote)/ ||
	       $Is_trace_mode =~ /from_remote/
	     )
	  {
	    local( $tmp ) = $inp;
	    $tmp =~ s/[\0-\037\177-\377]/&ctlecho($&)/ge;
	    $tmp = "[" . &who_from_handle($read_handle) . "]--->[$tmp]";
	    &print_stdout( STDIN, "\r\n$tmp\r\n" );
	  }
	}

	unless ( $Is_literal ){

	  $inp =~ s/\000//g ;	# telnet is weird, it ends all newlines with \0

	  # the following works for Sun telnet, but not free BSD telnet?
	  if ( $inp =~ /$TEL_IAC$TEL_DO$TELOPT_TTYPE/ ) {
	    # telnet, waiting for terminal type negotiation
	    $Is_telnet_mode = 1;
	    $Is_literal = 1;    # don't send our $BOP header, amongst others
	    $Is_talk_mode = 0;  # talk just confuses telnet, because
                                # it can't strip off the addr, like we do 
	    &print_remote_handle( $read_handle,
				"$TEL_IAC$TEL_WONT$TELOPT_TTYPE" );
	  }

	  $inp =~ s/$TEL_IAC..//g;	# we hate telnet options chatter

	  $inp = &strip_eval_info( $inp, $read_handle );

	  # strip out talk data into $out so it we don't send it to the shell
	  ($inp,$out) = &process_talk_data( $inp, $read_handle );

	  &print_stdout( $read_handle, $out ) if $out ne "";
	}

	if ( $Is_shell_proc ){
	  unless ( $Is_literal ){
	    $inp =~ s/$BOS[^$BOA]*//g; 		# strip shell data
	    $inp =~ s/[$BOP]//g;
	  }
	  &print_to_proc( $inp );
	}
	else{
	  unless ( $Is_literal ){
	    $inp =~ s/$BOP[^$BOA]*//g; 	# strip normal packets so we only
					# see echo from a shell
	    $inp =~ s/[$BOA]//g;	# clean any of our control chars
	  }
	  if ( $Ctlecho ){
	    $inp =~ s/[\0-\011\013\014\016-\037\177-\377]/&ctlecho($&)/ge; }
	  &print_stdout( $read_handle, $inp ) if $inp ne "";
	}

	if ( $Is_hub ){
	  &print_remote_handles( *Remote_handles, $rebroadcast, $read_handle );}

      }
      else {
	local( $out ) = &who_from_handle( $read_handle );
	&close_remote_handle( $read_handle );
	&print_stdout_remote( "\r\n[($out) disconnected on read]\r\n" );
	&my_exit if ( $Is_window_client && !%Remote_handles );
      }
    } # ----------------------------END REMOTE--------------------------------

    &my_exit if $Exiting;

  }# for .. select

  # If we've been idle for a second, or so
  if ( !@read_handles || $Last_time_to_stdout + 1 < time() ){
    for $addr ( keys %Out_accums_flush )
    {
      print STDOUT "\r\n" . &make_prompt( $addr ) . $Out_accums{$addr};
      delete $Out_accums_flush{$addr};

      # if we change Last_addr_to_stdout, we must swap out accum. 
      # else we will push onto the wrong accum. later.
      $Out_accums{$Last_addr_to_stdout} = $Out_accum;
      $Out_accum = $Out_accums{$addr};
      $Last_addr_to_stdout = $addr;
      $Last_time_to_stdout = time();
    }
  }
}# while 1

&my_exit;

sub my_exit{
  &print_remote_handles( *Remote_handles, 
	&pack_eval( qq{&host_exited("$My_addr")}), STDIN );
  &stty_sane unless $Is_daemon;
  kill 15, $Proc_pid if $Proc_pid;
  &comm'close( $Listen_handle, keys( %Remote_handles ) );
  print "\n";
  exit;
}

sub print_remote_handles{
  local( *handles, $inp, $source_handle )=@_;
  local( $host, $port );

  #$Debug1=1;
  #local( $i ) = $inp;
  #$i =~ s/[\0-\037\177-\377]/&ctlecho($&)/ge;
  #print "[$i]" if $Debug1;

  for $handle ( keys %handles ){
    if ( $handle eq $source_handle ){
      next;
    }
    &print_remote_handle( $handle, $inp );
  }
}

sub print_remote_handle{
  local( $handle, $inp )=@_;

  if ( $Is_trace_mode ){
    if ( $Is_trace_mode =~ /all/ ||
	 $Is_trace_mode =~ /(,remote|^remote)/ ||
	 $Is_trace_mode =~ /to_remote/
       )
    {
      local( $tmp ) = $inp;
      $tmp =~ s/[\0-\037\177-\377]/&ctlecho($&)/ge;
      $tmp = "[" . &who_from_handle($handle) . "]<---[$tmp]";
      &print_stdout( STDIN, "\r\n$tmp\r\n" );
    }
  }

  unless ( &comm'print($handle, $inp) ){
    local($out)=&who_from_handle($handle);
    &close_remote_handle( $handle );	# do this first!
    &print_stdout_remote( "\r\n[($out) disconnected on write]\r\n" );
    &my_exit if ( $Is_window_client && !%Remote_handles );
  }
}

sub close_remote_handle{
  local( $handle ) = @_ ;
  &delete_Host_info( $Addr_for_handle{$handle} );
  delete $Remote_handles{ $handle }; 
  &comm'close( $handle );
}

sub delete_Host_info{
  local( $addr ) = @_;

  return unless $addr ne "";
  for $key ( keys %Host_info )
  {
    if ( $key =~ /^$addr/ )
    {
      delete $Host_info{$key};
      &print_stdout( STDIN, "delete ($key)", $Host_info{$key}, "\r\n" ) if $Debug;
    }
  }
}

sub print_to_proc{
  local( $inp )=@_;

  &wait_nohang;

  if ( $OS_type eq "SVR4" ){
    $kill = kill 0, $Proc_pid;
  }else{
    # This kill 0 has a weird effect on the shell (probly causing EINTRs)
    # Investigate...
    $kill = kill 0, $Proc_pid;
    #$kill = 1;
  }

  if ( $Is_trace_mode ){
    if ( $Is_trace_mode =~ /all/ ||
	 $Is_trace_mode =~ /(,shell|^shell)/ || $Is_trace_mode =~ /to_shell/
       )
    {
      local( $tmp ) = $inp;
      $tmp =~ s/[\0-\037\177-\377]/&ctlecho($&)/ge;
      $tmp = "[shell]<---[$tmp]";
      &print_stdout( STDIN, "\r\n$tmp\r\n" );
    }
  }

  unless( $kill && &comm'print( $Proc_handle, $inp) ){
    &print_stdout_remote( "\r\n[proc died on write]\r\n" );
    &close_proc;
  }
}

sub close_proc{
    &comm'close($Proc_handle);
    $Proc_handle = 0;
    $Is_shell_proc = 0;
    $Proc_pid = 0;
}

sub print_stdout_remote{
  local( @stdout )=@_;
  local( $stdout ) = join("", @stdout );
  return if $Quiet;
  $Last_addr_to_stdout = $My_addr;
  #&print_out( STDERR, STDIN, $stdout );
  &print_out( STDOUT, STDIN, $stdout );
  if ( $Is_talk_mode ){
    &print_remote_handles(
	*Remote_handles, &pack_my_talk_data($stdout)
	);
  }
}

sub print_stdout{
  $Last_time_to_stdout = time();
  &print_out( STDOUT, @_ );
}

# used for STDOUT or STDERR only:
sub print_out{
  local( $out_handle, $source_handle, @stdout )=@_;
  print $out_handle @stdout;
  print LOG @stdout if $Log_file;
}

sub process_talk_data{
  local( $inp, $source_handle )=@_;
  local( $out, $tmp, $addr, $addr_to )=("","","", "");

  local($not_for_shell) = undef; 

  return $inp if ( $Is_literal && !$Is_command_mode );

  #$Debug1=1;
  local( $i ) = $inp;
  $i =~ s/[\0-\037\177-\377]/&ctlecho($&)/ge;
  print "[$i]" if $Debug1;

  # format should be:
  # ${BOT}fromhost,tohost;data
  # host format should be:
  #   user|user@host|user@host:port
  #
  # The idea is that each host prepends its talk-mode packets with the
  # $BOT char and the addr with uniquely identifying information.
  # (The default is just the user name, but it will add host/port if
  # it receives an identical addr from someone else.)  This is necessary
  # since with -hub mode, we might get packets from several hops away,
  # and we can't keep track of it by incoming handle.  
  #
  # Next, if we think that data are flowing uninterrupted from a user
  # because we are seeing unchanging headers/addrs, 
  # then the data should be printed on the same line.
  # So, we need to remove the addr, print the data, and store the data
  # in an accumulator associated with the addr.  That way, if a user is
  # interrupted, we can restore the line he was in the middle of typing
  # when he starts typing again.
  #
  # All this effort is so that we can uniquely identify a talk mode
  # packet that was sent from several hops away, where we have no reliable
  # socket/port information to add to the packet.

  while ( $inp =~ s/^$BOT([^$BOA]*)// )
  {
    $not_for_shell = $1; 
    next if ( $not_for_shell eq "" );   # compare to "", or will fail on "0"

    ($not_for_shell, $addr, $addr_to ) = &strip_addr( $not_for_shell );

    next unless $addr;

    print "not=($not_for_shell),addr=($addr)\r\n" if $Debug1;

    if ( $addr eq $My_addr && $source_handle ne STDIN ){
      &print_stdout_remote( "\r\n[eek I($My_addr) see a loop, this handle!]\r\n" );
      &close_remote_handle( $source_handle );
      return undef;
    }

    if ( $addr_to && $addr_to ne $My_addr ){
      print STDERR "Skipping packet not for me($My_addr): " .
		 "[$addr,$addr_to;$not_for_shell] \r\n" if $Debug;
      next;
    }

    $prompt = &make_prompt( $addr );
    if ( $Is_command_mode && $addr eq $My_addr ){
      $prompt =""; }

    # Decide whether we need to put the addr back on
    if ( $Last_addr_to_stdout ne $addr )
    {
      # buffer it for later, if STDOUT has been written to recently, 
      # unless it's from STDIN, which takes priority, or
      # it contains a newline, and that's too complicated to buffer, so
      # flush it out.
      if (
	   $Last_time_to_stdout + 1 >= time() &&
	   $source_handle ne "STDIN" && $not_for_shell !~ /[\r\n]/
	 )
      {
	#print "[h=($source_handle)skipping($not_for_shell)]";

	#print "[\$Out_accums{$addr} .= ($not_for_shell)]\r\n";
	$Out_accums{$addr} .= $not_for_shell;
	$Out_accums_flush{$addr} = 1;
	$not_for_shell = "";
	next;
      }
      {
	# indicate that current line has been interrupted, and
	# will continue later,  by adding a backslash marker to the end
	if ( $Out_accum ne "" ){
	  $out .= "\\"; }

	# Now switch contexts, refresh to whatever the last speaker was in the
	# middle of typing:
	$Out_accums{$Last_addr_to_stdout} = $Out_accum;
	$Out_accum = $Out_accums{$addr};

	$out .= "\r\n" . $prompt;
	if ( $Out_accum ne "" ){
	  # indicate that line continues from old;
	  # tack an eraseable marker onto onto the front of accum
	  $Out_accum =~ s/^[^\\]/\\$&/;
	}
	$out .= $Out_accum;
	delete $Out_accums_flush{$addr};
	$Last_addr_to_stdout = $addr;
      }
    }

    if ( 0 && $not_for_shell =~ /^(\r\n|\r|\n)$/ ){
      $Out_accum = "";
    }
    else
    {
      # it is possible that a packet will contain several lines of
      # data (i.e. the user cut/pasted something).
      for $tmp ( split(/(\r\n|\r|\n)/,  $not_for_shell ) )
      {
	if ( $tmp =~ /^(\r\n|\r|\n)$/ ){
	  $Out_accum = "";
	  $out .= "\r\n" . $prompt;
	  next;
	}
	$Out_accum .= $tmp;
	print "accum=($Out_accum)\n" if $Debug1;
	( $Out_accum, $out1 ) = &expand_bksp( $Out_accum );
	$tmp =~ tr/\0-\037\177//d;
	$out .= $tmp . $out1;
      }
    }
  }
  return ($inp,$out);
}

sub pack_my_talk_data{
  local( $s, $to ) = @_;
  local( $ret ) = "$BOT$My_addr;";
  $ret = "$BOT$My_addr,$to;" if $to;
  return $ret . $s;
}

sub strip_addr{
  local( $inp ) = @_;
  local( @addrs ) = ();
  local( $addr ) = "";

  #if ( $inp =~ s/^([\r\n]*[^$Prompt_char]+$Prompt_char$Command_prompt)// ) {
  if ( $inp =~ s/^([^;]+);// )
  {
    $addr = $1;
    @addrs = split(/,/, $addr );
  }
  return ( $inp, @addrs );
}

# Remember that the address that a remote host thinks it is isn't necessary
# what we can calculate from the $handle/%Host_info, so don't use it as such.
sub who_from_handle{
  local ( $handle ) = @_;
  local ( $host, $alt_host, $port, $addr ) = ("","","","");

  #$Debug1 = 1;
  print "in who_from_handle\r\n" if $Debug1;
  #( $host ) =  split(/:/, $Remote_handles{$read_handle} );

  if ( $Addr_for_handle{ $handle} ) {
    return $Addr_for_handle{ $handle } ; }

  return $Remote_handles{$handle};
}

sub make_my_addr{
  print "in make_my_addr\r\n" if $Debug1;
  return $My_user . "@" . $My_host . "#$$";
}

sub make_prompt{
  local( $addr ) = @_;
  local( $user ) = ( $addr =~ /^([^@]+)/ );
  local( $prompt );
  local( $prompt ) = $addr . $Prompt_char;

  if ( $user )
  {
    if ( $Prompt_aliases{ $user } ){
      if ( $Prompt_aliases{ $user } eq $addr ){
	$prompt = $user . $Prompt_char; }
    }
    else
    {
      $Prompt_aliases{ $user } = $addr;
      $prompt = $user . $Prompt_char;
    }
  }
  $Longest_prompt = length($addr) if $Longest_prompt < length($addr) ;
  $prompt #Return
}

sub expand_bksp{
  local( $accum ) = @_;
  local( $expanded_out ) = ("");
  local( $prompt ) = ("");

  # Refuse to back over a command prompt:
  $prompt = $1 if $accum =~ s/^(.*$Command_prompt)//;	# ick!

  $accum =~ tr/\0-\007\011\013\014\016-\024\026\030-\037//d;
  while ( $accum =~ s/.[\010\177]// ){	# ^H and ^?
    $expanded_out .= "\010 \010"; }
  while ( $accum =~ s/^([^\025]+)\025// ){		# ^U
    $expanded_out .= "\010 \010" x length($1); }
  while ( $accum =~ s/([\w]+|\W+)\027// ){		# ^W
    $expanded_out .= "\010 \010" x length($1); }
  $accum =~ tr/\0-\011\013\014\016-\037\177//d; #leave new lines
  return ( $prompt . $accum, $expanded_out );
}

sub ctlecho{
  local($c)=@_;
  local($char) = unpack("C",$c);
  return sprintf("\\%3.3o",$char);
}

sub ctlecho1{
  local($c)=@_;
  local($char) = unpack("C",$c);
  return "^" . pack("C", $char + 64 ) . sprintf("=\\%3.3o",$char);
}

sub stty_raw{
  # works for both SVR4 and SUNOS?BSD:
  #system "stty raw -echo -icanon eol '^a' " if -t STDIN;
  &comm'stty_raw(STDIN);
}

sub stty_sane{
  ##system "stty -raw echo icanon eol '^-' " if -t STDIN;
  # works for both SVR4 and SUNOS?BSD:
  #system "stty sane erase '^H' " if -t STDIN;
  &comm'stty_sane(STDIN);
}

sub do_command{
  local( $inp ) = @_;

  $inp =~ s/$Command_prompt//;
  return unless ($inp );
  print STDERR "In do_command($inp)\r\n" if $Debug;

  unless( &option( $inp ) )
  {
    &print_stdout_remote( "Invalid command($inp)\r\n" );
    #&print_commands;
    &print_usage();
  }
}

# Takes a list, and pulls of the first option, and any parameters, from it,
# leaving a shorter list.
#
# Returns 0 if there were invalid options or parameters.
#

sub option {
  local( $opt ) = @_;
  #$opt =~ s/"[^"]+"/&doubleback($&)/ge;	#attempt hack for shellwords
  local( @args ) = &shellwords ( $opt );
  print "option ($Opt) shellword \$#args=$#args=(", join(",",@args),")\r\n" if $Debug;
  if ( $opt && "@args" eq "" ){
    ## bloody shellwords has failed probably due to embedded \
    print STDERR "Parsing has failed.  Try \\\\ instead of \\ \r\n";
    return 0;
  }
  while ( @args ){
    return 0 unless &do_option_list( *args ); }
  return 1;
}

sub doubleback{
  local( $s ) = @_;
  $s =~ s/\\/\\\\/g;
  return $s;
}

sub do_option_list {
  local( *args ) = @_;
  local( $handle, $filename );

  return 0 unless @args;
  print STDERR "In do_option_list (@args)#=$#args\r\n" if $Debug;

  for $arg ( @args ) {
    $arg = "INVALID_NULL" if $arg eq "";
  }
  return 0 unless $arg=shift(@args);

  # We will accept options without leading "-" signs, but it is safer
  # to have them to keep some options from consuming parameters blindly
  # and eating other options.

  # Note that the order of the cases is important using this scheme of
  # abbreviation 

  # keep symbols like | ( ) , etc from confusing regexp.s down below:
  $arg =~ s/[^a-zA-Z0-9]/\\$&/g;

  "-help" =~ /^-?$arg/ && do {
      &print_usage("verbose");
      if ( $Is_command_mode ){
      }else{
	&my_exit;}
      return 1;
    };

  "-autoconn" =~ /^-?$arg/ && do {
      if ( $Autoconn ){
	$Autoconn = 0;
	&print_stdout_remote( "Autoconn mode off\r\n" );
      }else{
	$Autoconn = 1;
	&print_stdout_remote( "Autoconn mode on\r\n" );
      }
      return 1;
    };

  "-block" =~ /^-?$arg/ && do {
      if ( !@args ){
	&print_stdout_remote( "Block all mode off\r\n" );
	$Block_all = 0;
	return 1;
      }
      return 0 if ( !@args || $args[0] =~ /^-/ );
      $host = shift(@args);
      if ( $host eq "all" ){
	&print_stdout_remote( "Blocking all hosts\r\n" );
	$Block_all = 1;
	return 1;
      }
      &print_stdout_remote( "(un)blocking selected host(s)\r\n" );
      $Block_all = 0;
      $port = 0;
      if ( @args && $args[0] =~ /^\d+$/ ){
	$port = shift(@args);
      }

      $found=0;
      for $handle (keys %Remote_handles ){
	$addr = $Addr_for_handle{$handle};
	( $host1 ) =  split(/:/, $Remote_handles{$handle} );
	next unless ( $host eq $host1 ||
		$host eq $Host_info{ $addr, "alt_host" } );
	$port1 = $Host_info{ $addr, "port" };
	next if ( $port && ( $port != $port1 ) );

	$found=1;
	if ( $Blocked_handles{ $handle } ){
	  $Blocked_handles{ $handle } = 0;
	  &print_stdout_remote( "$host $port1 unblocked\r\n" );
	} else{
	  $Blocked_handles{ $handle } = 1;
	  &print_stdout_remote( "$host $port1 blocked\r\n" );
	}
      }
      &print_stdout_remote( "host $host port $port not found\r\n" ) unless $found;

      return 1;
    };

  "-open" =~ /^-?$arg/ && do {
      return 0 if ( !@args || $args[0] =~ /^-/ );
      $host = shift(@args);
      $port = $NO_PORT;
      if ( @args && $args[0] =~ /^\d+$/ ){
	$port = shift(@args);
	if ( @args && $args[0] =~ /^\d+$/ ){
	  $Start_port = $port;
	  $Stop_port = shift(@args); 
	  $port = $NO_PORT;
	}
      }
      $block=0;
      if ( @args && $args[0] =~ /^b/ ){		#block
	$block=1; shift;}

      ($rem_handle,$port) = &open_port1( $host, $port );

      if ( $rem_handle ){
	&send_host_info ($rem_handle);
	print "In open: \$Remote_handles{ $rem_handle } = $host;\r\n" if $Debug;
	$Remote_handles{ $rem_handle } = "$host:$port";
	if( $block ){
	  $Blocked_handles{ $rem_handle } = 1;
	  &print_stdout_remote( "$host $port blocked\r\n" );
	}
      }
      return 1;
    };

  "-close" =~ /^-?$arg/ && do {
      $host = "";
      $host = shift(@args) if @args;
      $port = 0;
      if ( @args && $args[0] =~ /^\d+$/ ){
	$port = shift(@args); }
      if ( @args && $args[0] eq $NO_PORT ){
	$port = shift(@args); }
      $found_one = 0;
      local($addr)="";
      for $handle ( keys %Remote_handles )
      {
	$addr = $Addr_for_handle{$handle};
	( $rem_host, $port1 ) =  split(/:/, $Remote_handles{$handle} );
	if ( $host eq "" || $host eq $rem_host ||
		$host eq $Host_info{ $addr, "alt_host" } )
	{
	  next if ( $port && $port1 != $port );
	  &print_stdout_remote( "Closing: " );
	  if ( $addr ){
	    &print_host_info( $addr );
	  }else{
	    &print_stdout_remote( "  ", $Remote_handles{$handle}, "\r\n" );
	  }
	  &close_remote_handle( $handle );
	  $found_one = 1;
	}
      }
      &print_stdout_remote( "Host/port not found\r\n" ) unless $found_one;
      return 1;
    };

  "-ctlecho" =~ /^-?$arg/ && do {
      if ( $Ctlecho ){
	$Ctlecho = 0;
	&print_stdout_remote( "Ctlecho mode off\r\n" );
      }else{
	$Ctlecho = 1;
	&print_stdout_remote( "Ctlecho mode on\r\n" );
      }
      return 1;
    };

  "-talk" =~ /^-?$arg/ && do {
      if ( $Is_talk_mode ){
	&print_stdout_remote( "Talk mode off\r\n" );
	$Is_talk_mode = 0;
      }else{
	$Is_talk_mode = 1;
	&print_stdout_remote( "Talk mode on\r\n" );
      }
      #if ( $Is_shell_proc && $Is_talk_mode ) {
	#print "Turning off talk mode for shell server mode\r\n";
	#$Is_talk_mode = 0;
      #}
      return 1;
    };

  "-trace" =~ /^-?$arg/ && do {
      if ( $Is_trace_mode && ( !@args || $args[0] =~ /^-/ ) ){
	&print_stdout_remote( "Trace mode off\r\n" );
	$Is_trace_mode = undef;
      }else{
	if ( @args && $args[0] !~ /^-/ ){
	  $arg = shift(@args);
	  if ( $arg !~
	    /^(all|shell|from_shell|to_shell|remote|from_remote|to_remote)$/
	  ){
	    return 0; }
	  $Is_trace_mode .= "," if $Is_trace_mode ne "";
	  $Is_trace_mode .= $arg;
	}
	else{
	  $Is_trace_mode = "all"; }
	&print_stdout_remote( "Trace mode on ($Is_trace_mode) \r\n" );
      }
      return 1;
    };

  #"-trust" =~ /^-?$arg/ && do {
  ( "trust" eq $arg || "-trust" eq $arg ) && do { #make 'em spell it out
      if ( $args[0] eq "perl" ){
	shift(@args);
	if ( $Do_real_evals ){
	  &print_stdout_remote( "Perl code evals not OK \r\n" );
	  $Do_real_evals = 0;
	}else{
	  $Do_real_evals = 1;
	  &print_stdout_remote( "Perl code evals OK \r\n" );
	}
      }else{
	if ( $Do_opt_evals ){
	  &print_stdout_remote( "Option evals not OK \r\n" );
	  $Do_opt_evals = 0;
	}else{
	  $Do_opt_evals = 1;
	  &print_stdout_remote( "Option evals OK \r\n" );
	}
      }
      return 1;
    };

  "-shell" =~ /^-?$arg/ && do {
      if ( $Is_pass_thru ){
        &print_stdout_remote( "Cannot start local shell in passthru mode\r\n" );        return 1; 
      }    
      if ( $Is_shell_proc ){
	&print_stdout_remote( "Already have a shell\r\n" );
	return 1;
      }
      if ( @args && $args[0] !~ /^-/ ){
	$Shell_cmd = shift(@args) ; }
      ($Proc_handle,$Proc_tty_handle,$Proc_pid) = &comm'open_proc($Shell_cmd);
      print STDERR "Opened pid $Proc_pid\r\n" if $Debug;
	#sysread($pty_handle,$buf,10000);
	#&comm'sysread( $Proc_handle, $inp, 10000 );
	#print STDOUT "got from fh($Proc_handle)($inp)\r\n";
      &print_stdout_remote( "Started a shell pid=$Proc_pid\r\n" );
      if ( $Shell_cmd =~ /sh$/ ){
	&comm'system( $Proc_tty_handle, "stty sane erase '^h' " ); }
      &do_sig_winch;
      $Is_shell_proc = 1;
      $Is_talk_mode = 0;
      $Is_command_mode = 0;
      return 1;
    };

  "-status" =~ /^-?$arg/ && do {
      if ( @args && $args[0] !~ /^-/ ){
	if ( ( $arg = shift(@args) ) =~ /^rem/ ){
	  &print_remote_handles( *Remote_handles, 
			&pack_eval( qq{&option("stat")} )  );
	}else{
	  &print_remote_handles( *Remote_handles, 
			&pack_eval( qq{&option("stat")}, $arg )  );
	  #&print_stdout_remote("Invalid status option ($arg)\r\n" );
	}
	return 1;
      }
      local( $out ) = "Status: address($My_addr) ";
      ( $i = $Command_char ) =~ s/[\0-\037\177-\377]/&ctlecho1($&)/ge;
      $out .=  "command_char($i) ";
      $out .=  "window($ENV{TERM},@My_window_size) ";
      $out .=  "daemon "			if $Is_daemon;
      $out .=  "ctlecho "			if $Ctlecho;
      $out .=  "trace($Is_trace_mode) "		if $Is_trace_mode;
      $out .=  "literal "			if $Is_literal;
      $out .=  "debug "				if $Debug;
      $out .=  "talk_mode "			if $Is_talk_mode;
      $out .=  "shell($Shell_cmd)($Proc_pid) "	if $Is_shell_proc;
      $out .=  "log($Log_file) " 		if $Log_file;
      $out .=  "listen($Listen_port)[$Start_port..$Stop_port] " if $Listen_port ne $NO_PORT;
      $out .=  "autoconn "			if $Autoconn;
      $out .=  "hub "				if $Is_hub;
      $out .=  "trust_option_evals " 		if $Do_opt_evals;
      $out .=  "trust_code_evals "		if $Do_real_evals;
      $out .=  "notify($Notify) "		if $Notify;
      $out .=  "block_all "			if $Block_all;
      $out .=  "\r\n";
      &print_stdout_remote( &wrap( $out ) );
      local( %printed_addrs ) = ();
      if ( %Remote_handles ){
	&print_stdout_remote( "Connections:\r\n" );
	for $handle ( keys %Remote_handles )
	{
	  if ( $addr = $Addr_for_handle{$handle} ){
	    &print_host_info( $addr );
	    $printed_addrs{$addr} = 1;
	  }else{
	    &print_stdout_remote( "  ", $Remote_handles{$handle}, "\r\n" );
	  }
	}
      }
      local( $addr1 ) = "";
      local( %print_addrs ) = ();
      for $key ( keys %Host_info )
      {
	( $addr1 = $key ) =~ s/$;.*//;
	next if $printed_addrs{$addr1};
	next if $addr1 eq $My_addr;
	$print_addrs{$addr1} = 1;
      }
      if ( %print_addrs ){
	&print_stdout_remote( "Others on:\r\n" );
	for $addr ( keys %print_addrs ){
	  &print_host_info( $addr ); }
      }
      return 1;
    };

  "-kill" =~ /^-?$arg/ && do {
      unless ( $Is_shell_proc ){
	&print_stdout_remote( "No shell to kill\r\n" );
	return 1;
      }
      local( $sig ) = -1;
      if ( @args && $args[0] =~ /^-?\d+$/ ){
	$sig = shift(@args ); }
      kill $sig, $Proc_pid;
      &print_stdout_remote( "kill $sig,$Proc_pid\r\n" );
      sleep 2;
      if ( kill 0, $Proc_pid ){
	&print_stdout_remote( "Shell ($Proc_pid) would not die.\r\n" );
      }else{
	&print_stdout_remote( "Shell ($Proc_pid) killed.\r\n" );
	&close_proc;
      }
      return 1;
    };

  "-listen" =~ /^-?$arg/ && do {
      if ( $Is_listen ){
	&comm'close($Listen_handle);
	&print_stdout_remote( "Listen off\r\n" );
	for $handle ( keys %Remote_handles ){
	  &send_host_info ( $handle ); }
	$Is_listen = 0;
	$My_addr = &make_my_addr;
      }else{
	$Is_listen = 1;
	$Listen_port=$NO_PORT;
	undef $Listen_handle;
	if ( @args && $args[0] =~ /^\d+$/ ){
	  $Listen_port = shift(@args); 
	  if ( @args && $args[0] =~ /^\d+$/ ){
	    $Start_port = $Listen_port;
	    $Stop_port = shift(@args); 
	    $Listen_port = $NO_PORT;
	  }
	}
	if ( $Listen_port ne $NO_PORT )
	{
	  $Listen_handle = &comm'open_listen( $Listen_port );
	} else {
	  &print_stdout_remote( "Trying to listen on " );
	  for( $port = $Start_port ; $port <= $Stop_port ; $port++ )
	  {
	    &print_stdout_remote( "$port " );
	    if ( $Listen_handle = &comm'open_listen( $port ) )
	    {
	      $Listen_port = $port;
	      last;
	    }
	    &print_stdout_remote( ".. " );
	  }
	  &print_stdout_remote( "\r\n" );
	}
	if ( !defined( $Listen_handle) ) {
	  &print_stdout_remote( "couldn't get a port\r\n" );
	  $Listen_port = $NO_PORT;
	  $Is_listen = 0;
	  return 1;
	}
	&print_stdout_remote( "Listening on port $Listen_port\r\n");
	$My_addr = &make_my_addr;
	for $handle ( keys %Remote_handles ){
	  &send_host_info ( $handle ); }
      }

      return 1;
    };

  "-literal" =~ /^-?$arg/ && do {
      if ( $Is_literal ){
	$Is_literal = 0;
	&print_stdout_remote( "Is_literal mode off\r\n" );
      }else{
	$Is_literal = 1;
	&print_stdout_remote( "Is_literal mode on\r\n" );
      }
      return 1;
    };

  "-log" =~ /^-?$arg/ && do {
      if ( $Log_file ){
	&print_stdout_remote( "Closing old log file \r\n" );
	$Log_file = "";
	close(LOG);
	return 1;
      }
      return 0 if ( !@args || $args[0] =~ /^-/ );
      $Log_file = shift(@args) ;
      open(LOG,">>$Log_file");
      &print_stdout_remote( "Opening log file $Log_file in pwd ",`pwd` );
      select(LOG);$|=1;select(STDOUT);
      return 1;
    };

  ( "-command_char" =~ /^-?$arg/ ) || ( "-escape" =~ /^-?$arg/ ) && do {
      return 0 if ( !@args || $args[0] =~ /^-/ );
      local($c) = shift(@args);
      if ( length( $c ) > 1 )
      {
	if ( $c =~ s/^\\*(\d+)$/$1/ ){	# e.g. \033
	  $c = pack("C", oct($c) );
	  #eval qq{\$c = "$c"};
	} elsif ( $c =~ s/^\136*([A-Z])$/$1/ ){	# e.g. ^H
	  $c = unpack("C", $c );
	  $c = pack("C", $c - 64 );
	} elsif ( $c =~ s/^\136*([a-z])$/$1/ ){	# e.g. ^h
	  $c = unpack("C", $c );
	  $c = pack("C", $c - 96 );
	}else{
	  return 0; }
      }
      $Command_char=$c;
      return 1;
    };

  "-exit_tag" =~ /^-?$arg/ && do {
      return 0 if ( !@args || $args[0] =~ /^-/ );
      $Exit_tag=shift(@args);
      return 1;
    };

  "-eval" =~ /^-?$arg/ && do {
      return 0 if ( !@args || $args[0] =~ /^-/ );
      local( $addr ) = shift(@args);
      if ( $addr =~ s/^<// ){
	if ( $addr ){
	  $filename = $addr;
	}else{
	  return 0 if ( !@args || $args[0] =~ /^-/ );
	  $filename = shift(@args);
	}
	do $filename;
	return 1;
      }
      elsif ( $addr !~ /^\w+@.*#\d+/ ){
	print STDERR "Evaling ($addr)\r\n" if $Debug;
	eval $addr;
	return 1;
      }
      return 0 if ( !@args || $args[0] =~ /^-/ );
      local( $eval_out, $need_paren ) = ("","");

      $eval_out = shift( @args );

      # use q{} in case we are in real/trusted eval mode
      # they more safely quote stuff
      if ( $eval_out =~ s/^opt\w*/&option(q{/ ){
	return 0 if ( !@args );
	$eval_out .= shift( @args );
	$eval_out .= '})';
      }
      elsif ( $eval_out =~ s/^<// ){
	if ( $eval_out ){				# eval host <file
	  $filename = $eval_out;
	}else{
	  return 0 if ( !@args || $args[0] =~ /^-/ );
	  $filename = shift(@args);			# eval host < file
	}
	local($old) = $/; undef $/;
	open(EV_FH, $filename ); $eval_out = <EV_FH> ; close(EV_FH);
	$/ = $old;
	print STDERR "Eval(<$filename)=($eval_out)\r\n" if $Debug;
      }

      &print_remote_handles( *Remote_handles, 
		&pack_eval("$eval_out", $addr ) );

      return 1;
    };

  "-print" =~ /^-?$arg/ && do {
      return 0 if ( !@args || $args[0] =~ /^-/ );
      $i = shift(@args);
      eval qq{\$i = "$i"};	# expand stuff like \377
      print STDERR "sending($i)\r\n" if $Debug;
      &print_remote_handles( *Remote_handles, $i );
      if ( $Is_shell_proc ){
	&print_to_proc( $i ); }
      return 1;
    };

  "-Input" =~ /^-?$arg/ && do {
      return 0 if ( !@args || $args[0] =~ /^-/ );
      $i = shift(@args);
      if ( $Is_shell_proc ){
	&print_to_proc( "$i\n" ); }
      return 1;
    };

  "-hub" =~ /^-?$arg/ && do {
      if ( $Is_hub ){
	$Is_hub = 0;
	&print_stdout_remote( "Hub mode off\r\n" );
      }else{
	$Is_hub = 1;
	&print_stdout_remote( "Hub mode on\r\n" );
      }
      return 1;
    };

  if ( $Is_command_mode ){
    "-quit" =~ /^-?$arg/ && &my_exit; }

  "-quiet" =~ /^-?$arg/ && do {
      if ( $Quiet ){
	$Quiet = 0;
	&print_stdout_remote( "Quiet mode off\r\n" );
      }else{
	$Quiet = 1;
	&print_stdout_remote( "Quiet mode on\r\n" );	# ha!
      }
      return 1;
    };

  "-debug" =~ /^-?$arg/ && do {
      if ( $Debug ){
	$Debug = 0;
	&print_stdout_remote( "Debug mode off\r\n" );
      }else{
	$Debug = 1;
	&print_stdout_remote( "Debug mode on\r\n" );
      }
      return 1;
    };

  "-daemon" =~ /^-?$arg/ && do {
      close(STDIN);close(STDOUT);
      # I don't know why, but if STDIN/STDOUT aren't open, the child
      # shell process dies
      open(STDIN,"</dev/null");
      open(STDOUT,">/dev/null");
      $Is_daemon = 1; 
      return 1;
    };

  "-notify" =~ /^-?$arg/ && do {
      if ( $Notify ){
	$Notify = 0;
	&print_stdout_remote( "Notify mode off\r\n" );
      }else{
	return 0 if ( !@args || $args[0] =~ /^-/ );
	$Notify = shift(@args);
	&print_stdout_remote( "Notify $Notify with new connections\r\n" );
      }
      return 1;
    };

  "-window_spawn" =~ /^-?$arg/ && do {
      if ( $Is_window_spawn ){
	$Is_window_spawn = 0;
	&print_stdout_remote( "Window spawn mode off\r\n" );
      }else{
	if ( @args && $args[0] !~ /^-/ ){
	  $Window_spawn_cmd = shift(@args); }
	$Is_window_spawn = 1;
	&print_stdout_remote( "Window spawn mode on\r\n" );	# ha!
      }
      return 1;
    };

  "-client_window" =~ /^-?$arg/ && do {
      if ( $Is_window_client ){
	$Is_window_client = 0;
	&print_stdout_remote( "Window client mode off\r\n" );
      }else{
	$Is_window_client = 1;
	&print_stdout_remote( "Window client mode on\r\n" );	# ha!
      }
      return 1;
    };

  "-wrap" =~ /^-?$arg/ && do {
      if ( $Is_auto_wrap ){
	$Is_auto_wrap = 0;
	&print_stdout_remote( "Auto wrap mode off\r\n" );
      }else{
	$Is_auto_wrap = 1;
	&print_stdout_remote( "Auto wrap mode on\r\n" );
      }
      return 1;
    };

  $arg eq "z" && do {
      &do_suspend;
      return 1;
    };

  unshift(@args,$arg);
  return undef;
}

sub open_port1{
  local( $host, $port ) = @_;
  local( $rem_handle );

  local( $is_me ) = 0;
  local($addr1) = (gethostbyname($My_host))[4];
  local($addr2) = (gethostbyname($host))[4];
  $is_me = 1 if ( $addr1 eq $addr2 );

  if ( $host !~ /^\d+\.\d+\.\d+\.\d+$/ &&
       `ypmatch $host hosts 2>/dev/null` eq "" &&
       `grep $host /etc/hosts 2>/dev/null` eq "" &&
       !( @tmp = gethostbyname($host) ) ){
    ( $rem_handle = &comm'open_port( $Proxy_host, 3666 ) ) || 
			die "couldn't get a port on $Proxy_host\n";
    $Is_pass_thru = 1;
    if( $Is_shell_proc ){
      &print_stdout_remote( "Killing shell for pass-thru mode\r\n" );
      &close_proc;
    }
    &print_remote_handle( $rem_handle, "$host $port\r\n");
    #print STDERR "No such host $host $port\n" ; return undef;
  }
  else{
    if ( $port == 0 ){
      &print_stdout_remote( "Trying to connect to $host " );
      for ( $port1 = $Start_port ; $port1 <= $Stop_port ; $port1++){
	if ( $is_me && $port1 == $Listen_port ){
	  &print_stdout_remote( "me=$port1 " );
	  next;
	}
	&print_stdout_remote( "$port1 " );

	if ( $rem_handle = &comm'open_port( $host, $port1 ) ){
	  last;
	}
      }
      &print_stdout_remote( "\r\n" );
    }else{ 
      $port1=$port;
      if ( $rem_handle = &comm'open_port( $host, $port1 ) )
      {
      }
    }
  }

  if ( $rem_handle ){
    &print_stdout_remote( "Connected to $host, $port1\r\n" );
  }else{
    &print_stdout_remote( "Failed connection to $host\r\n" );
    return undef;
  }
  return ($rem_handle, $port1);
}

sub send_host_info{
  local($rem_handle)=@_;
  local($handle,$host,$port,$out,@conns, $port1, $addr);
  return if ( $Is_literal );
  for $handle ( keys %Remote_handles ){
    ( $host, $port ) =  split(/:/, $Remote_handles{$handle} );
    #print "send_host_info:host=$host,$port\n";
    if ( $addr = $Addr_for_handle{$handle} ){
      #print "send_host_info:addr=$addr\n";
      if ( $port1 = $Host_info{ $addr, "port" } ){
	$port = $port1;
	#print "send_host_info:port1=$port1\n";
      }
    }
    push(@conns, $host, $port );
  }
  local( $rem_hosts ) =join(",", @conns);
  $out =  "&host_info('" .
      join(";", 
	"version=$Version",
	"user=$My_user",
	"host=$My_host",
	"addr=$My_addr",
	"port=$Listen_port",
	"hub=$Is_hub",
	"window_client=$Is_window_client",
	"window_size=@My_window_size",
	"term=$ENV{TERM}",
	"rem_hosts=$rem_hosts"
      ) .
    "')";

  print STDERR "Sent my info: $out\r\n"  if $Debug;
  &print_remote_handle( $rem_handle, &pack_eval("$out\r\n") );
}

sub pack_eval{
  local( $s, $to ) = @_;
  return "$BOE$My_addr,$to;$s";
}

sub strip_eval_info{
  local($inp, $read_handle)=@_;
  local( $addr_from, $addr_to ) = ("","");

  return $inp unless $inp =~ s/$BOE([^$BOA]*)//;
  local($eval_inp) = $1;

  ($eval_inp, $addr_from, $addr_to ) = &strip_addr( $eval_inp );
  print "Eval info: my addr=($My_addr),from=($addr_from),to=($addr_to),inp=($eval_inp)\r\n" if $Debug;

  if ( $addr_to ){
    return unless ( $addr_to eq $My_addr ); }

  if ( $addr_from eq $My_addr ){
    &print_stdout_remote( "\r\n[eek I($My_addr) see a loop, this handle!]\r\n" );
    &close_remote_handle( $read_handle );
    return undef;
  }

  print STDERR "Received eval :(fh=$read_handle)($eval_inp)\r\n" if $Debug;

  $Eval_handle = $read_handle;

  if ( $eval_inp =~ s/^\&option\("stat"\)// )
  {
    &print_stdout( STDIN, "\r\nEvaling stat from $addr_from \r\n" );
    &send_host_info ($read_handle );
    &option("stat");
  }
  elsif ( $eval_inp =~ s/^\&host_info\(['"]// )
  {
    # Host info not actually eval'd here
    $eval_inp =~ s/['"]\)[;\s]*$//;
    &host_info($eval_inp);
  }
  elsif ( $eval_inp =~ s/^\&host_exited\(['"]// )
  {
    $eval_inp =~ s/['"]\)[;\s]*$//;
    &host_exited($eval_inp);
  }
  elsif ( $Do_real_evals )
  {
    &print_stdout_remote ( "Eval code ($eval_inp) from ($addr_from)\r\n" );
    local( $ret ) = eval $eval_inp;
    &print_stdout_remote( "eval returned=($ret),err=($@)\r\n" );
  }
  elsif ( $Do_opt_evals )
  {
    if ( $eval_inp =~ s/^\&option\(q{// ){
      $eval_inp =~ s/}\)$//;
      &print_stdout_remote ( "Eval option ($eval_inp) from ($addr_from)\r\n" );
      unless ( &option($eval_inp) ){
	&print_stdout_remote( "Eval option failed from ($addr_from)\r\n" ); }
    }else{
      &print_stdout_remote ( "Eval denied ($eval_inp) from ($addr_from)\r\n" ); }
  } else{
    &print_stdout_remote ( "Eval denied ($eval_inp) from ($addr_from)\r\n" ); }

  return $inp;
}

sub host_exited{
  local( $addr ) = @_;
  local( $user, $addr1 );

  $user = $Host_info{ $addr, "user" };
  $addr1 = $Prompt_aliases{ $user };

  if ( $addr1 && $addr1 eq $addr ){
    delete $Prompt_aliases{ $user }; }

  &delete_Host_info( $addr );
}

sub host_info{
  local($host_inp )=@_;
  local(@other, $port, $rem_host, $rem_port, @tmp, 
	$host, $host1, $port1, $alt_host, $handle, $arg, %opt, $ok,
	$opt_name, $opt_val, $version, $user,$alt_host, $handle
       );

  return unless $host_inp;

  print STDERR "Received host info:(fh=$Eval_handle)($host_inp)\r\n" if $Debug;

  for $arg ( split(/[;\n]/,$host_inp) )
  {
    ( $opt_name, $opt_val ) = split( /\=/, $arg );
    $opt{$opt_name} = $opt_val;
    print STDERR $opt_name," = ",$opt{$opt_name},"\r\n" if $Debug;
  }

  $alt_host = $opt{"alt_host"};
  $port = $opt{"port"} ? $opt{"port"} : $NO_PORT ;
  $host = $opt{"host"};
  # Try to get the hostname with the full domain suffix, which we may not
  # get via "host_info".
  for $handle ( keys %Remote_handles )
  {
    ( $host1, $port1 ) = split(/:/, $Remote_handles{$handle} );
    if ( $port1 == $port && $host1 =~ /^$host/ ){
      $host = $host1; }
  }

  if ( $Is_window_spawn && $opt{"window_client"} != 1 )
  {
    print STDERR "Closing non-window-client\r\n" if $Debug;
    &close_remote_handle( $Eval_handle );

    unless (fork) {
      local( $cmd ) = $Window_spawn_cmd;
      $cmd =~ s/\$host/$host/g;
      $cmd =~ s/\$port/$port/g;
      &print_stdout_remote( "\r\n[Running: $cmd]\r\n" );
      exec $cmd;
      print STDERR "Exec ($cmd) failed!\r\n";
      exit;
    }
  }
  else
  {

    # Here, we may have accepted a redundant connection without out knowing
    # it until now, when the remote process identifies itself uniquely.

    # if old eq $NO_PORT , it could be that the remote process isn't
    # listening on a port, or that we accepted a socket but just now
    # received the host-info

    if ( $port ne $NO_PORT ){
      if ( $handle = &handle_for_host_port( $Eval_host, $port ) ){
	if ( $handle ne $Eval_handle )
	{
	  print STDERR "Closing duplicate($Eval_host,$port)\r\n" if $Debug;
	  &close_remote_handle( $Eval_handle );
	  return $inp;
	}
      }
    }

    $version = $opt{"version"};
    if ( $version ne $Version ){
      &print_stdout_remote( 
	  "Warning:  my version is $Version, $host is $version\r\n" ); }

    if ( $opt{"term"} ne $ENV{TERM} ){
      &print_stdout_remote( 
	  "Warning:  my term is $ENV{TERM}, $host is $opt{term}\r\n" );
    }

    if ( $opt{"window_size"} ne "@My_window_size" ){
      &print_stdout_remote( 
	"Warning:  my window size is @My_window_size, $host is $opt{window_size}\r\n" );
    }

    $user = $opt{"user"} ? $opt{"user"} : "[user unknown]";

    local( $addr ) = $opt{"addr"};

    ( $host1, $port1 ) =  split(/:/, $Remote_handles{$Eval_handle} );
    if ( $host1 eq $opt{"host"} && $port1 eq $opt{"port"} ){
      # The first one to respond on a handle gets to put its information
      # on that handle (it should always be the process connected to anyway).
      unless ( $Addr_for_handle{$Eval_handle} ){
	$Addr_for_handle{$Eval_handle} = $addr ; }
    }

    $Host_info{ $addr, "host" } = $opt{"host"};
    $Host_info{ $addr, "version" } = $opt{"version"};
    $Host_info{ $addr, "user" } = $user;
    $Host_info{ $addr, "addr" } = $opt{"addr"};
    $Host_info{ $addr, "port" } = $port;
    $Host_info{ $addr, "conn_time" } = time();

    $date = &get_date;

    $Host_info{ $addr, "conn_date" } = "[$date]";
    if ( $alt_host ne $Eval_host ){
      $Host_info{ $addr, "alt_host" } = $alt_host; }
    $Host_info{ $addr, "hub" } = $opt{"hub"};

  }# end not Is_window_spawn

  if ( $Autoconn && !$Is_daemon ){
    local( @rem_info ) = split(",", $opt{"rem_hosts"} );
    OTHER: while ( ( $rem_host, $rem_port ) = splice( @rem_info,0,2 ) ) 
    {
      next if ( $rem_port eq $NO_PORT );
      next unless ( $rem_host && $rem_port );
      if ( &handle_for_host_port( $rem_host, $rem_port ) ){
	print STDERR "Already connected to ($rem_host,$rem_port)\r\n" if $Debug;
	next;
      }
#      print STDERR "($Eval_host,$port)->($rem_host,$rem_port)->you? ";
#      &stty_sane;
#      $reply = <STDIN>;
#      if ( $reply =~ /^y/i ){
	print STDERR "Setting connection to ($rem_host,$rem_port)\r\n" if $Debug;
	$Auto_conn{ "open", $rem_host, $rem_port } = 1 if $ok;
#      }
#      &stty_raw;
    }
  }
  return $inp;
}

sub handle_for_host_port{
  local( $host, $port ) = @_;
  local( $handle, $host1, $port1, $alt_host1 )=("","","","");
  local( $addr );

  if ( $host eq $My_host && $port == $Listen_port ) {
    return STDIN;
  }

  for $handle ( keys %Remote_handles )
  {
    ( $host1, $port1 ) =  split(/:/, $Remote_handles{$handle} );
    $addr = $Addr_for_handle{ $handle };
    $alt_host1 = $Host_info{ $addr, "alt_host" };

    if ( ( $host eq $host1 || $host eq $alt_host1 ) &&
         ( $port == $port1 ) ) {
	return $handle;
    }
  }
  return undef;
}

sub print_host_info{
  local($addr)=@_;
  local($alt_host,$blocked)=("","");

  #$blocked = "(input blocked)" if $Host_info{ $addr, "blocked" };

  $alt_host = "(" . $Host_info{ $addr, "alt_host" } . ")";
  $alt_host = "" if ( $alt_host ne $host);

  &print_stdout_remote( &wrap( 
    "  ",
    join(" ",
      $Host_info{ $addr, "host" },
      $alt_host,
      $Host_info{ $addr, "port"},
      #$Host_info{ $addr, "user" },
      $Host_info{ $addr, "addr" },
      $Host_info{ $addr, "conn_date" },
      $blocked,
      $Host_info{ $addr, "hub" } ? "hub" : "not_hub",
      #"window(" . $Host_info{$addr,"term"} . "," .
		  #$Host_info{$addr,"window_size"} . ")",
      $Host_info{ $addr, "version" }
      ),
    "\r\n" ) );

}

# This just makes a meager attempt to keep the screen clutter down
# by nicely wrapping commonly sent stuff like status messages.
# We must wrap on the sending side, since the receiving side doesn't
# know the difference between status messages, and something that
# is supposed to be longer than the screen width, though it would be
# better to do it the other way around.

sub wrap{
  local(@in_lines)=@_;
  local(@lines, $s1);
  local($s) = join("", @in_lines );

  return @in_lines unless $Is_auto_wrap;

  local($leader)="";
  if ( $s =~ /^(\s+)/ ){
    $leader = $1; }

  while( $s =~ s/^(\s*\S+\s*)// )
  {
    if ( length($s1) + length($1) >=
         ( $My_window_size[1] - $Longest_prompt ) )
    {
      $s1 .= "\\\r\n";
      push(@lines,$s1);
      $s1 = $leader . "    ";
    }
    $s1 .= $1;
  }
  push(@lines,$s1);
  return @lines;
}

sub do_sig_winch{
  local( $sig ) = @_;
  local( $i );

  if ( $] >= 5 ){
    if ( $OS_type eq "SVR4" ){
      $i = `stty -a 2>&1 </dev/tty `;
      @My_window_size = ( $i =~ /rows = (\d+); columns = (\d+)/ );
    }else{
      $i = `stty -a 2>&1 >/dev/tty `;
      @My_window_size = ( $i =~ /(\d+) rows, (\d+) column/ );
    }
    return;
  }

  $SIG{"WINCH"}="do_sig_winch";

  $! = 0;
  #$winsize=pack("p",$winsize);
  #unless ( syscall( 54, fileno(TTY_FH), 0x40087468, $winsize ) ==0){	# XXX s/b &TIOCGWINSZ 

  if ( $OS_type eq "SVR4" ){
    $TIOCGWINSZ = 0x5468; #TIOCGWINSZ(21608)(5468)
    $TIOCSWINSZ = 0x5467; #TIOCSWINSZ(21607)(5467)
  }else{
    $TIOCGWINSZ = 0x40087468; 
    $TIOCSWINSZ = 0x80087467;
  }

  #unless ( ioctl( TTY_FH, $TIOCGWINSZ, $winsize ) ){
  # The Solaris ioctl is hosed:
  #Return value overflowed string at ./shelltalk line 821.

  $winsize=pack("p",$winsize);
  unless ( syscall( 54, fileno(TTY_FH), $TIOCGWINSZ, $winsize ) ==0){
    print STDERR "ioctl TIOCGWINSZ failed, $!\r\n" if $Debug;
    return;
  }

  #struct winsize {
  #        unsigned short  ws_row;         /* rows, in characters */
  #        unsigned short  ws_col;         /* columns, in characters */
  #        unsigned short  ws_xpixel;     /* horizontal size, pixels-not used */
  #        unsigned short  ws_ypixel;      /* vertical size, pixels-not used */
  #};

  $winsize_t = "ssss";          # 4 shorts
  @array = unpack( $winsize_t, $winsize );
  #print "array=(@array)\r\n";

  $! = 0;
  if ( $Proc_tty_handle ){
    unless ( &comm'ioctl( $Proc_tty_handle, $TIOCSWINSZ, $winsize ) ){
      print STDERR "ioctl TIOCSWINSZ failed, $!\r\n" if $Debug;
    }
  }
  print STDERR "sig_winch: array=", join(",",@array), "\r\n" if $Debug;

  # Keep the windows from dueling when remote windows can resize us and
  # visa versa.
  $time = time();
  if ( ( $Winch_recv_time + 2 ) >= $time ) {
    if ( $Winch_retries++ > 10 ){
      print STDERR "WINCH looping, turning off\r\n";
      $SIG{"WINCH"}="DEFAULT";
    }
    return ;
  }
  $Winch_retries = 0;
  $Winch_send_time = $time;

  @My_window_size = @array[0,1];
  &print_stdout_remote( "\r\n[Window size now @My_window_size]\r\n" );

  $row = $array[0] + 1;
  $col = $array[1] + 5;

  # Grrr.  They still haven't fixed this since SunOS3!  You can change
  # the size of the window with this string, but the resulting row/col
  # size will vary depending on the window font.
  #&print_stdout_remote( "\033[8;${row};${col}t" );  # row col
  ##print $Tty_handle "\033[4;${array[3]};${array[2]}t";	 # height width
  $Ignore_resize = 1;

}

sub get_date{
  local($date);
  chop( $date=`date '+%T %D' ` );
  return $date;
}

# "Bring out your deaddddddd"
sub wait_nohang{
  if ( $OS_type eq "SVR4" ) {
    # syscall 7 == wait/wait4 for BSD
    # syscall 107 == waitsys for Solaris, which seems to be waitid?
    # int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
    #define WNOHANG         0100/* non blocking form of wait    */
    #define WEXITED         0001/* wait for processes that have exited  */
    # See: <sys/procset.h> and <sys/wait.h>
    # Arguments: 7=P_ALL=idtype_t, 64=\100=WNOHANG | 1=W

    syscall(107,7,0,0,64|1);
  }
  else{
    # No need, since the SunOS4.x version of Perl does an implicit
    # wait4, apparently.
    syscall(7,0,1,0);
  }
}

sub do_suspend{
  $SIG{"CONT"}="do_cont";
  &print_stdout_remote( "[Suspended]\r\n" );
  &stty_sane;
  if ( $OS_type eq "SVR4" ){
    kill 23,  0;		# I ran into copies of Perl where the symbolic
  }else{			# symbol names were wrong.
    kill 17,  0;}
  $SIG{"CONT"}="DEFAULT";
  &print_stdout_remote( "[Resumed]\r\n" );
}

sub do_cont{
  &stty_raw;
}

sub telnet_defines{

  # Values taken from /usr/include/arpa/telnet.h
  # Note: need double quotes for correct interpolation in future double quotes
  $TEL_IAC     = "\377"             ;#/* interpret as command: */
  $TEL_DONT    = "\376"             ;#/* you are not to use option */
  $TEL_DO      = "\375"             ;#/* please, you use option */
  $TEL_WONT    = "\374"             ;#/* I won't use option */
  $TEL_WILL    = "\373"             ;#/* I will use option */
  $TELOPT_TTYPE    = "\030"         ;#/* terminal type */
  $TELOPT_ECHO     = "\001"         ;#/* echo */

  # Could use decimal values, but then would have to do:
  #E.g.&comm'print( $rem_handle, pack("C*", $IAC, $WILL, $ECHO ) );
  #&comm'print ($rem_handle, pack("C*", $IAC, $WILL, $ECHO ) );
  #&comm'print ($rem_handle, pack("C*", $IAC, $DONT, $ECHO ) );

}

# This is normally in the Perl distribution, but I'll include it anyway.

;# shellwords.pl
;#
;# Usage:
;#	require 'shellwords.pl';
;#	@words = &shellwords($line);
;#	or
;#	@words = &shellwords(@lines);
;#	or
;#	@words = &shellwords;		# defaults to $_ (and clobbers it)

sub shellwords {
    package shellwords;
    local($_) = join('', @_) if @_;
    local(@words,$snippet,$field);

    s/^\s+//;
    while ($_ ne '') {
	$field = '';
	for (;;) {
	    if (s/^"(([^"\\]|\\.)*)"//) {
		#($snippet = $1) =~ s#\\(.)#$1#g;
		($snippet = $1) =~ s/\\"/"/g;
	    }
	    elsif (/^"/) {
		print "Unmatched double quote: $_\n";
		return undef;
	    }
	    elsif (s/^'(([^'\\]|\\.)*)'//) {
		#($snippet = $1) =~ s#\\(.)#$1#g;
		($snippet = $1) =~ s/\\'/'/g;
	    }
	    elsif (/^'/) {
		print "Unmatched single quote: $_\n";
		return undef;
	    }
	    elsif (s/^\\(.)//) {
		$snippet = $1;
	    }
	    elsif (s/^([^\s\\'"]+)//) {
		$snippet = $1;
	    }
	    else {
		s/^\s+//;
		last;
	    }
	    $field .= $snippet;
	}
	push(@words, $field);
    }
    @words;
}
1;

# Here is an example of an answering machine.
# It loops through a given set of messages, and will also display
# personalized messages for particular users.

#
#eval 'exec perl -S $0 "$@"'
#  if $running_under_some_shell; 
#
#$|=1;
#require "open3.pl";
#
#$pid = &open3( WTR, RDR, RDR, 'shelltalk -notify "console|smcc" -log answer.out -t -l ');
## open2 would work also
#
#select WTR;$|=1;select STDOUT;
#open(LOG1, ">>answer.out1");
#select LOG1;$|=1;select STDOUT;
#
#&make_msgs;
#
#@fight_msgs = split(/ +/, "Press } to exit fight mode.\n");
##push(@fight_msgs, "\n");
#
#$*=1;
##while(<RDR>)
#{
#  sysread(RDR, $_, 10000);
#  print LOG1 $_;
#  /New connection from \((\w+)/ && do {
#      $host = $1;
#      $host =~ s/\..*//g;
#      @msgs = @msgs1 unless @msgs;
#      $msg = shift(@msgs);
#      sleep 1;
#      print WTR $msg;
#      push(@msgs1, $msg );
#      print WTR `date`;
#    };
#  /(\w+)[>@]/ && do {
#      $user = $1;
#      unless ( $Printed_personal{ $user } )
#      {
#	$Printed_personal{ $user  } = 1;
#	$msg = $Personal{ $user };
#	print WTR $msg;
#      }
#    };
#  /\((\w+)[@>].*disconnected/ && do {
#      $user = $1;
#      $Printed_personal{ $user  } = 0;
#    };
#  /{/ && do { $fight = 1; };
#  /}/ && do { $fight = 0; };
#
#  if ( $fight ){
#    @fight_msgs = @fight_msgs1 unless @fight_msgs;
#    $msg = shift(@fight_msgs);
#    print WTR "$msg ";
#    push(@fight_msgs1, $msg );
#  }
#
#  redo;
#}
#
#print STDERR "exited loop!\n";
#
#sub make_msgs{
#
#  $msgs =<<EOF;
#
#Hello.  You've reached the office of Eric Arnold.
#Nice of you to drop by.  If you'd like to leave a
#message, go right ahead!  Logging is on.
#
#This has been a recording.
#
#EOM
#
#Your call is very important to us.  Please stay on the
#line ... 
#
#This message will repeat.  (Meanwhile, type in yours.)
#
#EOF
#
#  @msgs=split(/EOM/,$msgs);
#
#  $personal = <<EOF;
#eric
#
#Greetings, Eric.  I hope you enjoyed making this thing.
#
#EOF
#
#  for $l ( split(/EOM/,$personal) )
#  {  
#    ( $user, $msg ) = split( /\n/, $l, 2 );
#    $Personal{ $user } = $msg;
#  }
#
#}

#                         "comm_utils.pl"
#
# This is a library of IPC goodies.  There is no warrenty, but I'd
# be happy to get ideas for improvements.
#
# 		- [email-masked]
#
# It's been tested with Perl4/Perl5 and SunOS4.x and Solaris2.3 - 2.5.
# It's normally put into a file and "require"'d, but can also be simply
# concatinated to the end of some other perl script.
#
# A lot was borrowed from "chat2.pl", and then diverged as its goals became 
# generalized client/server IPC, support for SVR4/Solaris, and to facilitate
# my "shelltalk" program.
#
# Function summary:
#
#   &init();
#
#   $handle = &comm'open_port($host, $port, $timeout);
#   $handle = &comm'open_listen( $port );
#   ( $Proc_handle, $Proc_tty_handle, $Proc_pid ) = &comm'open_proc($Shell_cmd);
#
#   ( $new_handle, $rem_host ) = &comm'accept_it( $handle );
#   @ready_handles = &comm'select_it( $timeout, $handle1, $handle2, ..... );
#
#   $string = &comm'gets( $handle );
#   &comm'sysread( $handle, $buf, $num_bytes );
#   &comm'print( $handle, $buf );
#   &comm'system( $Proc_tty_handle, $command );
#
#   &comm'close_noshutdown( $handle );
#   &comm'close( $handle )
#
#   &comm'ioctl( $Proc_tty_handle, $ioctl_command, $var );
#   &comm'stty_sane( $handle );
#   &comm'stty_raw( $handle );
#   &comm'get_ioctl_from_stty( $stty_cmd );
#   &comm'dump_ioctl( $stty_cmd );
#
#
# See the end for example programs demonstrating usage.
#
# Bugs:
#   - SOCK_STREAM (see comm_utils.pl) is different for SVR4/Solaris,
#     but if you use a perl compiled under SunOS, then the old value is
#     needed.  How do I tell whether I'm running a SunOS4.x perl or Solaris2?
#     I finally gave up and it first tries 1 for SOCK_STREAM, and if that 
#     fails, then it tries 2.
#
# 09/11/94 07:03:04 PM;  eric:	fixed for Solaris and /dev/tty
# 09/14/94 02:11:19 AM;  eric:	close correct file handle in open_listen
# 09/15/94 03:33:31 AM;  eric:	added sock'system 
# 09/19/94 10:48:11 AM;  eric:	added cheapo/easy ioctl dump/do
# 10/11/94 11:07:14 AM;  eric:	added I_POP to clear stream on pty
# 11/08/94 03:03:19 PM;  eric:	changed to first try SOCK_STREAM=1, then =2
# 02/28/95 12:53:22 PM;  eric:	found the right place to set SO_LINGER!
# 03/18/95 08:19:46 PM;  eric:	added timeout arg to open_port
# 05/07/95 10:56:25 PM;  eric:	fixed shutdown/close order bug in close()
#				added close_noshutdown
# 06/08/95 01:06:03 PM;  eric:	fixed Sol2.4 problem with string literal
#				as last arg to syscall($SYS_ioctl

package comm;

&init;

sub init{
  *Debug = *main'Debug;

  if ( -f "/vmunix" )
  {
    $OS_type = "BSD";
    $SOCK_STREAM=1;
  }
  else
  {
    $OS_type = "SVR4";
    if ( $] >= 5 ){
      $SOCK_STREAM=1;
    }else{
      $SOCK_STREAM=2;}
  }

  print STDERR "OS_type=$OS_type\n" if $Debug;

  chop( $thishost = `uname -n ` );

  $next_handle="stuff000000";
  $sockaddr = 'S n a4 x8';

  $thisaddr = (gethostbyname($thishost))[4];
  $thisproc = pack($sockaddr, 2, 0, $thisaddr);

  $SYS_ioctl = 54;

  if ( $OS_type eq "SVR4" ){
    # These must be syscalls because Perl's ioctl doesn't know about I_PUSH
    #syscall($SYS_ioctl, fileno($_TTY), $I_PUSH, "ptem" );
    #syscall($SYS_ioctl, fileno($_TTY), $I_PUSH, "ldterm");

    # from /usr/include/sys/termios.h
    $tIOC    	=( unpack("C", 't') << 8);
    $TIOCGETP       =($tIOC|8);
    $TIOCSETP       =($tIOC|9);

    $TIOC     	=( unpack("C", 'T' ) <<8);
    $TCGETS         =($TIOC|13);
    $TCSETS         =($TIOC|14);
    $TCSANOW        =(( unpack("C",'T')<<8)|14); #/* same as TCSETS */
    $TCGETA  	=($TIOC|1);
    $TCSETA  	=($TIOC|2);

    # From /usr/include/sys/stropts.h
    $STR =             ( unpack("C", "S") <<8 );
    $I_PUSH =          ($STR|02);	#$I_PUSH = 21250;
    $I_POP =           ($STR|03);
    $I_LOOK =          ($STR|04);
    #define I_FLUSH         (STR|05)
    
  }else{
    $TIOCGETP=0x40067408;	#d(1074164744)
    $TIOCSETP=0x80067409;	#d(-2147060727)
  }

  $SOL_SOCKET      =0xffff          ;#/* options for socket level */
  $SO_DEBUG        =0x0001          ;#* turn on debugging info recording */
  $SO_ACCEPTCONN   =0x0002          ;#* socket has had listen() */
  $SO_REUSEADDR    =0x0004          ;#* allow local address reuse */
  $SO_KEEPALIVE    =0x0008          ;#* keep connections alive */
  $SO_DONTROUTE    =0x0010          ;#* just use interface addresses */
  $SO_BROADCAST    =0x0020          ;#* permit sending of broadcast msgs */
  $SO_USELOOPBACK  =0x0040          ;#* bypass hardware when possible */
  $SO_LINGER       =0x0080          ;#* linger on close if data present */
  $SO_OOBINLINE    =0x0100          ;#* leave received OOB data in line */

}

sub open_port{

  local( $server, $port, $timeout ) = @_;
  local( $new_handle ) = ++$next_handle;
  local( %saveSIG, $ret );

  if ($server =~ /^(\d+)+\.(\d+)\.(\d+)\.(\d+)$/) {
	  $serveraddr = pack('C4', $1, $2, $3, $4);
  } else {
	  local(@x) = gethostbyname($server);
	  return undef unless @x;
	  $serveraddr = $x[4];
	  printf( "server=$server,serveraddr=%x\n", $serveraddr) if $Debug;
  }

  $serverproc = pack($sockaddr, 2, $port, $serveraddr);
  print STDERR "\$serverproc = pack($sockaddr, 2, $port, ",
    join(".", unpack("C*", $serveraddr)), ");\n" if $Debug;

  unless (socket( $new_handle, 2, 1, 6)) {
    unless (socket( $new_handle, 2, 2, 6)) {
	  # XXX hardwired $AF_SOCKET, $SOCK_STREAM, 'tcp'
	  # but who the heck would change these anyway? (:-)
	  ($!) = ($!, close( $new_handle)); # close new_handle while saving $!
	  #die "socket failed";
	  print STDERR "Socket error $!\n" if $Debug;
	  return undef;
    }
  }
  unless (bind( $new_handle, $thisproc)) {
	  ($!) = ($!, close( $new_handle)); # close new_handle while saving $!
	  #die "bind failed";
	  print STDERR "bind error $!\n" if $Debug;
	  return undef;
  }

  %saveSIG=%SIG;
  if ( $timeout )
  {
    $SIG{ALRM} = "timedout";
    alarm($timeout);
  }
  eval { $ret = connect( $new_handle, $serverproc) };

  if ( !$ret || ($@ =~ /^timedout/) ) {
	  ($!) = ($!, close( $new_handle)); # close new_handle while saving $!
	  #die "connect failed, $!";
	  print STDERR "connect error eval=($@)$!\n" if $Debug;
	  if ( $@ =~ /^timedout/ ) {
	    $! .= ", timeout after $timeout seconds";}
	  return undef;
  }

  if ( $timeout )
  {
    %SIG = %saveSIG;
    alarm(0);
  }

  select((select( $new_handle), $| = 1)[0]);

  return $new_handle;
}

sub timedout {
  die "timedout";
}

sub open_listen{
  local( $port ) = @_;

  ( $thisproc_local ) = pack( $sockaddr, 2, $port, $thisaddr );
  #print "( $thisproc_local ) = pack( $sockaddr, 2, $port, $thisaddr );\n";

  $new_handle = ++$next_handle;

  unless (socket( $new_handle, 2, 1, 6)) {
    unless (socket( $new_handle, 2, 2, 6)) {
	  # XXX hardwired $AF_SOCKET, $SOCK_STREAM, 'tcp'
	  # but who the heck would change these anyway? ( :- )
	  #die "socket failed: $!";
	  print STDERR "socket failed: $!\n" if $Debug;
	  ($!) = ($!, close($new_handle)); # close S while saving $!
	  return undef;
    }
  }

  # We want it it release the socket for immediate reuse if the server is
  # shutdown/restarted.  It seems that SO_LINGER and SO_REUSEADDR are most
  # pertinant, but SO_KEEPALIVE seems like it might be nice too, for
  # notification of peer disappearance.
  $linger = pack("II", 0, 0 );	# linger is a C struct in socket.h
  setsockopt( $new_handle, $SOL_SOCKET, $SO_LINGER, $linger);
  setsockopt( $new_handle, $SOL_SOCKET, $SO_KEEPALIVE, 1);
  setsockopt( $new_handle, $SOL_SOCKET, $SO_REUSEADDR, 1);

  unless ( bind( $new_handle, $thisproc_local )) {
	  #die "bind failed: $!";
	  print STDERR "bind failed: $!\n" if $Debug;
	  ($!) = ($!, close($new_handle)); # close S while saving $!
	  return undef;
  }
  unless ( listen( $new_handle, 1 )) {
	  #die "listen failed: $!";
	  print STDERR  "listen failed: $!\n" if $Debug;
	  ($!) = ($!, close($new_handle)); # close S while saving $!
	  return undef;
  }

  select( (select( $new_handle ), $| = 1 )[0] ); 
  local( $family, $port, @myaddr ) = unpack( "S n C C C C x8", 
		getsockname( $new_handle ));

  #print " local( family, port, myaddr ) = local( $family, $port, @myaddr) \n"; 
  return  $new_handle; 
}

sub accept_it{ local( $handle ) = @_; local( $addr, $af, $port,
  $inetaddr, $acceptaddr ) = ();

  $new_handle = ++$next_handle;

  unless( ( $addr = accept( $new_handle, $handle ) ) ) {
	  print STDERR "accept failed: $!";
  }

  ( $af, $port, $inetaddr ) = unpack( $sockaddr, $addr );
  @inetaddr = unpack( 'C4', $inetaddr );
  #print "accept: $af $port @inetaddr(", join('.', @inetaddr), ")\n";

  #print "host=", gethostbyaddr( join('.', @inetaddr), 2 ), "\n";
  #print "host=", gethostbyaddr( join(' ', @inetaddr), 2 ), "\n";
  ($name, $aliases, $type, $len, $acceptaddr) =
	gethostbyaddr( pack( 'C4', @inetaddr ), 2 );
  #print "accept: host=$name, port=$port\n";

  select( ( select( $new_handle ), $| = 1 )[0] );

  $name = join(".", @inetaddr ) unless $name;
  return $new_handle,$name;
}

sub select_it { 
  local( $timeout, @handles ) = @_;

  local( @ready ) = ( );
  local( $rout, $rmask, $handle, $eout, $emask );

  for $handle ( @handles ) {
    vec( $rmask, fileno( $handle ), 1 ) = 1;
    vec( $emask, fileno( $handle ), 1 ) = 1;
  }
  ( $nfound, $timeleft ) = select( $rout=$rmask, undef, $eout=$emask, $timeout );

  print "nfound=$nfound\n" if $DEBUG;
  if ( $nfound < 1 ){
    if ( $nfound < 0 ){
      print "error=$!\n" if $DEBUG; }
    return @ready;
  }

  # You could also do:
  #   @bit = split(//,unpack('b*',$rout));
  #   if ($bit[fileno(STDIN)] == 1){ ... };

  for $handle ( @handles ) { 
    if ( vec( $rout, fileno( $handle ), 1 ) == 1 ) {
      print "fh=$handle is ready\n" if $DEBUG;
      push( @ready, $handle ); }
    if ( vec( $eout, fileno( $handle ), 1 ) == 1 ) {
      print "Exception on read_handle=$handle\n" if $DEBUG; }
  }

  return @ready;
}

1;

## $handle = &chat'open_proc("command","arg1","arg2",...);
## opens a /bin/sh on a pseudo-tty

sub open_proc { ## public
	local(@cmd) = @_;

	#local(*TTY,*PTY);	# PTY must not die when sub returns
	local( $pty_handle, $tty_handle );

	$pty_handle = "proc" . ++$next_handle;
	*PTY = $pty_handle;
	$tty_handle = "proc" . ++$next_handle;
	*TTY = $tty_handle;

	local($pty,$tty) = &_getpty(PTY,TTY);

	die "Cannot find a new pty" unless defined $pty;
	local($pid) = fork;
	die "Cannot fork: $!" unless defined $pid;
	unless ($pid) {
		close STDIN; close STDOUT; close STDERR;
		if ( $OS_type eq "SVR4" ){
		  syscall(39,3); #* setsid():: syscall(39,3)
		}else{
		  #syscall(175);#setsid , doesn't seem to work well
		  setpgrp(0,$$); 
		  # this ioctl is necessary for "isig" to work right,
		  # and otherwise "csh" freaks out and hangs:
		  if (open(DEVTTY, "/dev/tty")) {
		      ioctl(DEVTTY,0x20007471,0);	# XXX s/b &TIOCNOTTY
		      close DEVTTY;
		  }
		}
		open(STDIN,"<$tty");
		#open(STDIN,"<&TTY");	# fails to assign controlling tty!
		if ( $OS_type eq "BSD" ){
		  # doesn't seem to be necessary if open by filename
		  #TIOCSCTTY,d(536900740)0x(20007484)
		  #syscall(54, fileno(STDIN), 0x20007484,  1 );
		}

		open(STDOUT,">$tty");
		open(STDERR,">&STDOUT");
		die "Oops" unless fileno(STDERR) == 2;	# sanity
		close(PTY) || print "error closing master handle:$!\n";

		exec @cmd;
		die "Cannot exec @cmd: $!";
	}
	#close(TTY);
	return ($pty_handle,$tty_handle,$pid); # return symbol for switcharound

}

sub _getpty { ## private
	local($_PTY,$_TTY)=@_;
	local($pty,$tty);
	$_PTY =~ s/^([^']+)$/(caller)[$[]."'".$1/e;
	$_TTY =~ s/^([^']+)$/(caller)[$[]."'".$1/e;
	for $bank (112..127) {
		next unless -e sprintf("/dev/pty%c0", $bank);
		for $unit (48..57) {
			$pty = sprintf("/dev/pty%c%c", $bank, $unit);
			open($_PTY,"+>$pty") || next;
			select((select($_PTY), $| = 1)[0]);
			($tty = $pty) =~ s/pty/tty/;
			# some stupid magic says I can't use a variable
			# name in the open for a TTY open
			open($_TTY,"+>$tty") || next;
			select((select($_TTY), $| = 1)[0]);
			if ( $OS_type eq "SVR4" )
			{
			  local( $pop ) = pack( "p", $pop );
			  syscall($SYS_ioctl, fileno($_TTY), $I_POP, 0 );
			  syscall($SYS_ioctl, fileno($_TTY), $I_POP, 0 );
			  #syscall($SYS_ioctl, fileno($_TTY), $I_LOOK, $pop );
			  #print "looked: len=", length($pop),"($pop)\n";
			  #$pop = pack( "p", $pop );
			  #syscall($SYS_ioctl, fileno($_TTY), $I_LOOK, $pop );
			  #print "looked: len=", length($pop),"($pop)\n";

			  local($tmp);
			  # $tmp needed because Solaris2.4,2.5 complains:
			  # Modification of a read-only value attempted at 
			  # comm_utils.pl line  ...
			  # if you use a string literal instead
			  syscall($SYS_ioctl, fileno($_TTY), $I_PUSH, $tmp="ptem" );
			  syscall($SYS_ioctl, fileno($_TTY), $I_PUSH, $tmp="ldterm");
			}
			system "stty nl>$tty <$tty";
			return ($pty,$tty);
		}
	}
	undef;
}

sub gets{
  local(*FH)=shift;
  scalar(<FH>);
}

sub sysread{
  local(*FH)=shift;
  sysread(FH, $_[0], $_[1]);
}

sub ioctl{
  local($fh)=shift;
  syscall( 54, fileno($fh), @_ ) == 0;
}

sub print{
  local($fh)=shift;
  local($ret)= print $fh @_;
  unless ( $ret ){
    print STDERR "Error printing to fh($fh),$!\n"; }
  return $ret;
}

# This is useful when there a parent and child share the file descriptor,
# because the shutdown affects both.
sub close_noshutdown{
  for (@_){
    next unless $_;
    close( $_ );
  }
}

sub close{
  for (@_){
    next unless $_;
    unless ( /^proc/ ){
      print STDERR "Doing shutdown on $_\n" if $DEBUG;
      shutdown($_,2) ;	# must happen before close
    }
    #print "comm'close: $_\n";
    #local( *fh ) = $_;	# some god-aweful magic
    #close( $fh );	# some god-aweful magic
    close( $_ );
  }
}

# This is so you can run commands in the pty process without routing
# through the shell, which is ugly

sub system{
  local( $handle, @args ) = @_;
  unless ( fork() ){
    close(STDIN);close(STDOUT);
    open(STDIN,"<&$handle" );
    open(STDOUT,">&$handle" );
    exec ( @args );
    exit;
  }
}

sub stty_sane{
  local( $handle ) = @_;
  local( $packed ) = ();
  #&dump_ioctl( "stty sane erase '^H' " );
  if ( $OS_type eq "SVR4" ){
    $packed = pack("C*", 37,38,0,5,5,173,138,59,0,3,28,8,21,4 );
    ioctl( $handle, $TCSETA, $packed);
  }else{
    $packed = pack("C*", 15,15,8,21,0,216 );
    #$packed = &get_ioctl_from_stty( "stty sane erase '^H' " );
    ioctl( $handle, $TIOCSETP, $packed);
  }
}

sub stty_raw{
  local( $handle ) = @_;
  local( $packed ) = ();
  #&dump_ioctl( "stty raw -echo -icanon eol '^a'" );
  if ( $OS_type eq "SVR4" ){
    $packed = pack("C*", 0,0,0,4,4,189,138,48,0,3,28,8,21,1,1,0,0,22 );
    ioctl( $handle, $TCSETA, $packed);
  }else{
    $packed = pack("C*", 15,15,8,21,0,240 );
    #$packed = &get_ioctl_from_stty( "stty raw -echo -icanon eol '^a'" );
    ioctl( $handle, $TIOCSETP, $packed);
  }
}

sub get_ioctl_from_stty{
  local( $stty_cmd ) = @_;
  local( $ioctl_struct, $get_cmd, $set_cmd, $out, $ret ) = ();

  #print $stty_cmd,"\n";
  system $stty_cmd;

  # These only return 4 bytes.  Why?
  # $p = pack("p", $ioctl_struct );
  #$ret = syscall($SYS_ioctl, fileno(STDIN), $TIOCGETP, $p);
  #$ret = syscall($SYS_ioctl, fileno(STDIN), $TCGETA, $p);

  if ( $OS_type eq "SVR4" ){
    $get_cmd = $TCGETA;
  }else{
    $get_cmd = $TIOCGETP;
  }

  $!=0;
  $ret = ioctl(STDIN, $get_cmd, $ioctl_struct );

  return $ioctl_struct;
  #return ( $ioctl_struct, $ret );	# blows up $ioctl_struct on the stack
}

sub dump_ioctl{
  local( $stty_cmd ) = @_;
  local( $ioctl_struct, $c, $out ) = ();

  $ioctl_struct = &get_ioctl_from_stty( $stty_cmd );
  for $c ( unpack("C*", $ioctl_struct) ){
    #$out .= sprintf("0x%2.2x,", $c );
    $out .= sprintf("%d,", $c );
  }
  print "$stty_cmd = $out \n";
  # I don't know off hand how much of the returned buffer is actually
  # significant; certainly less than the full 256 bytes.
  return $ioctl_struct;
}

1;

__END__

#--------------------------------Example server---------------------------------
#
# Allows multiple client connections, and rebroadcasts data between them.

eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
& eval 'exec perl -S $0 $argv:q'
if 0;

require "comm_utils.pl" unless defined &comm'init;

$Listen_port = 5050;
$Listen_port = $ARGV[0] if $ARGV[0];  

$SIG{'HUP'} = "my_exit";
$SIG{'INT'} = "my_exit";
$SIG{'QUIT'} = "my_exit";

$DEBUG = 1;
$|=1;

&comm'init;

if(1)
{
  $Listen_handle = &comm'open_listen( $Listen_port );
  die "open_listen failed on port $Listen_port" unless $Listen_handle;
}
else
{
  # This is optional; it can be useful to use a range of ports
  # if your sockets don't always release a port right away when you kill
  # a process.  However, the "setsockopt()" calls should release the ports
  # for you, so this should no longer be necessary.
  $start_port = $Listen_port;
  {
    if ( ! ( $Listen_handle = &comm'open_listen( $Listen_port ) ) )
    {
      redo unless ( ++$Listen_port <= $start_port + 10 );
      die "open_listen failed on port $Listen_port";
    }
  }
}

print "Listening on port $Listen_port\n" if $DEBUG;

while (1)
{
  @ready_handles = &comm'select_it(1, keys(%Client_handles), $Listen_handle );
  print "Handles ready: @ready_handles\n" if $DEBUG && @ready_handles;
  
  foreach $handle (@ready_handles)
  {  
    if ($handle eq $Listen_handle)
    { 
      ($new_handle, $rem_host) = &comm'accept_it($handle);
      $Client_handles{$new_handle} = $rem_host;
      print "New connection from $rem_host\n" if $DEBUG;
    }
    else
    {
      #print &comm'gets($handle);	# if new-line oriented
      if ( &comm'sysread($handle, $buf, 10000) )
      {
        $buf = $Client_handles{$handle} . ": $buf";
	$buf =~ s/[\n]*$/\n/;
        print $buf;
	# rebroadcast data to all clients:
	for $client_handle ( keys %Client_handles ) {
	  &comm'print( $client_handle, $buf ); }
      }
      else
      {
	print "Closing handle $handle, host $rem_host\n";
        &comm'close( $handle );
        delete $Client_handles{ $handle };
      }
    }
  }
}

sub my_exit 
{
  &comm'close( $Listen_handle );
  print "Closing listen port\n" if $DEBUG;
  exit;
}

#--------------------------------Example client---------------------------------

# Connect to a server, and send STDIN data to it.
# Usage:  tstclient <host> <port>

eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}'
& eval 'exec perl -S $0 $argv:q'
if 0;

require "comm_utils.pl" unless defined &comm'init;

$Server_port = 5050;
$Server_host = "serverhost.domain";

( $Server_host, $Server_port ) = @ARGV if @ARGV;

$SIG{'HUP'} = "my_exit";
$SIG{'INT'} = "my_exit";
$SIG{'QUIT'} = "my_exit";

$|=1;
$DEBUG = 1;

&comm'init;

if ( ! ( $Server_handle = &comm'open_port($Server_host, $Server_port, 5) ) )
{
  die "open_port failed on host $Server_host, port $Server_port";
}

print "Connected to host $Server_host, port $Server_port\n" if $DEBUG;

while (1)
{
  @ready_handles = &comm'select_it(1, $Server_handle, STDIN);
  
  foreach $handle (@ready_handles)
  {  
    if ($handle eq "STDIN")
    {
      $buf = <STDIN>;
      &comm'print( $Server_handle, $buf );
    }
    else	# server
    {
      unless ( &comm'sysread($handle, $buf, 1000) )
      {
        print "Server connection broken\n";
        &my_exit;
      }
      print $buf;
    }
  }
}

sub my_exit 
{
  &comm'close($HANDLE);
  exit;
}