Path: usenet.cis.ufl.edu!usenet.eel.ufl.edu!spool.mu.edu!howland.reston.ans.net!news.sprintlink.net!psgrain!nntp.teleport.com!usenet
From: [email protected] (Eric Arnold)
Newsgroups: comp.lang.perl.announce,comp.lang.perl.misc,comp.lang.perl
Subject: ANNOUNCE:  "shelltalk" -- multi-user shell conferencing utility
Date: 11 Jun 1995 17:51:38 GMT
Organization: Sun Microsystems
Lines: 3230
Approved: [email protected] (comp.lang.perl.announce)
Message-ID: <[email protected]>
NNTP-Posting-Host: linda.teleport.com
Xref: usenet.cis.ufl.edu comp.lang.perl.announce:30 comp.lang.perl.misc:563 comp.lang.perl:51869


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 protected] or [email protected]
#
#
# 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 protected]
#
# 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;
}