#!/bin/sh -- # A comment mentioning perl, to prevent perl from looping. Indented to work with bash. eval 'exec perl -S $0 ${1+"$@"}' if 0; #========================== Server Portion ========================= chop($program = `basename $0`); # this program's name. $Local = ''; &initialize_variables(); # initialize some global information. $Usage = &usage_statement(); while (@ARGV) { # loop over command line args. $_ = shift @ARGV; CASE: { /^-p$|^-port$/ && ($Port = shift, last CASE); /^-r$/ && ($Remote_Machine_Name = shift, last CASE); /^-s$|^-service$/ && ($Service = shift, last CASE); /^-sysv$/ && ($IS_SYSV = shift, last CASE); /^-limit$/ && ($Limit = shift, last CASE); /^-local$/ && ($Local = shift, last CASE); /^-bytes$/ && ($Bytes = shift, last CASE); /^-binary$/ && ($Bytes = $Bytes_Default, last CASE); /^-proxy$/ && ($Proxy = 1, last CASE); /^-c$|^-connect_only$/ && ($Connect_Only = 1, last CASE); /^-firewall$/ && ($Firewall = 1, last CASE); /^-firewall_cmd$/ && ($Firewall = 1, $Firewall_Command = shift, last CASE); /^-fw_perl$/ && ($Firewall_Perl = shift, last CASE); /^-pre_cmd$/ && ($Pre_Cmd = shift, last CASE); /^-ksockets$|^-k$/ && ((&spit_ksockets), exit 0, last CASE); /^-d$/ && ($Debug = 1, last CASE); /^-q$/ && ($Quiet = 1, last CASE); /^-l$/ && ($Log_File = shift, last CASE); /^-h.*/ && ( (&help), exit 0, last CASE); /^--$/ && (last LOOP); # -- means end of switches /^-(-.*)$/ && (unshift(@ARGV, $1), last CASE); if ( /^-(..+)$/ ) { # split bundled switches: local($y, $x) = ($1, ''); foreach $x (reverse(split(//, $y))) { unshift(@ARGV,"-$x") }; last CASE; } /^-./ && ((print "$Usage\n$_ is not an option, Bye!\n"), exit 1, last CASE); } } $Firewall_Command =~ s/%Firewall_Perl/$Firewall_Perl/g; &Use_Ksockets(); # Start appending to log file. &open_log_file(); # Make unbuffered filehandles select(LOG); $| = 1; select(STDOUT); select(STDOUT); $| = 1; select(STDOUT); if ( $Connect_Only ) { # if we are to just make a connection e.g. we are the firewall command &connect_remote_to_stdio(); exit 0; } # Set up the domains we limit access to (if any). @Access_Array = &domain_limits($Limit); $Server = &Server($Port); # get the Ksockets server socket. &die($Server) if &SocketError($Server); &Log("\n$A_Line\n"); if ( $Proxy ) { $Service = '-1'; # proxying service port and remote host # determined on the fly. } else { &initialize_remote_host(); } $SIG{'INT'} = 'do_shutdown'; # used for quitting. Doesn't work. sub do_shutdown { # shutdown($Server, 2) if $Server; # close($Server) if $Server; exit 0; } local($Connection); local($Address, $date); # Loop "forever" listening for connections. for($Connection = 1; ; $Connection++) { &Log(">> Listening for connection ${Connection} on port $Port ...\n"); $Address = ''; $C_port = ''; $Client = ''; ($Client, $Address, $C_port) = &AcceptConnection($Server); chop( $date = `date`); local($con) = $Connection; if($Address eq '' || &SocketError($Client)) { &Log("$con: Accept failed for connection $con at $date.\n"); &Log("$con: Error msg: $Client\n"); &Log("$con: Skipping connection and sleeping a bit.\n"); sleep (1); next; } local($access_ok) = &check_access(*Access_Array, $Address); if ($access_ok) { &Log("$con: $Address Passes test: $access_ok\n"); } else { print $Client "$Denied\n"; close($Client); &Log("$con: Date: $date, $Denied: $Address\n"); sleep(1); next; } &Log("$con: Got connection on $date, forking off child...\n"); if (!fork) { # Fork off child for connection unless (fork) { # Double fork to avoid zombies... # setpgrp(0, $$); # Insulate ourselves from server # # signals, etc. setpgrp(0, 0); # POSIX... close($Server); &Log("$con: fork ok\n"); # we are child's child &Log("$con: connected to: $Address on port $C_port\n"); $Global_Connection = $Connection; # goto client portion if ( $Proxy ) { &proxy_connect($Client); } else { local($ok_to_connect); $ok_to_connect = 1; if ( $Pre_Cmd ne '' ) { local($cmd) = $Pre_Cmd; $cmd =~ s/%ADDR/$Address/g; $cmd =~ s/%CPORT/$C_port/g; $cmd =~ s/%SPORT/$Port/g; &Log("$con: Pre_Cmd: $Pre_Cmd ->\n"); &Log("$con: Pre_Cmd: $cmd\n"); local($output); $output = `$cmd`; $ok_to_connect = 0 unless $? == 0; &Log("$con: Pre_Cmd: ok: $ok_to_connect, output: $output\n"); } if ( $ok_to_connect ) { &tcp_connect($Client, $Remote_Machine, $Service); } } close($Client); exit 0; } exit 0; # this exit happens quickly... } wait; # ...so the 1st child is harvested quickly. # The server no longer needs filehandle to connection close($Client); # on to the next connection ... } exit 0; sub help { if ( $ENV{'PAGER'} ne '' && open(PAGER, "|$ENV{'PAGER'}") ) { print PAGER "$Usage"; close(PAGER); } elsif ( open(LESS, "|less") ) { print LESS "$Usage"; close(LESS); } elsif ( open(MORE, "|more") ) { # open pipeline to "more" print MORE "$Usage"; close(MORE); } else { # wot no more? print STDOUT "$Usage"; } } #========================== Connect Only Portion ========================= sub connect_remote_to_stdio { # this does a one shot connection # of our stdin and stdout to remote host. local($remote_host, $remote_port) = @_; $remote_host = $Remote_Machine_Name unless $remote_host; $remote_port = $Service unless $remote_port; $Connect_Only = 1; # probably true already. &tcp_connect('', $remote_host, $remote_port); } #========================== Proxy Portion ========================= sub proxy_connect { local($client_FH) = @_; local(@client_request, $error); while (<$client_FH>) { push(@client_request, $_); last if /^\n|^\r\n/; } local($first_line) = $client_request[0]; $first_line =~ s/\r\n$//; $first_line =~ s/\n$//; local($method, $item) = split(/\s+/, $first_line, 2); local($protocol, $host, $port, $the_rest) = &null(4); local($new_first, $data) = &null(2); local($gc) = ''; if ( defined ($Global_Connection) ) { $gc = $Global_Connection; } else { $gc = '?'; } if ( $item =~ m,^([^:]+)://([^/\s]+)([/\s].*)$, ) { $protocol = $1; $host = $2; $the_rest = $3; if ( $host =~ /^([^:]+):(\d+)$/ ) { $host = $1; $port = $2; } if ( $port eq '' ) { if ( $protocol =~ /^http$/i ) { $port = '80'; } elsif ( $protocol =~ /^gopher$/i ) { $port = '70'; } else { $error = "Error: $gc: proxy_connect: cannot determine port: \n

\t\"$first_line\"\n"; &proxy_error($client_FH, $error); return $error; } } if ( $protocol =~ /^gopher$/i || $port == '70' ) { $method = 'GOPHER'; } } elsif ( $item =~ m,^([^:]+):(\d+)([/\s].*)$, ) { $host = $1; $port = $2; $the_rest = $3; # might be able to do it... } else { $error = "Error: $gc: proxy_connect: cannot yet handle request: \n

\t\"$first_line\"\n"; &proxy_error($client_FH, $error); return $error; } if ( $method =~ /GET|POST|HEAD|OPTIONS|PUT|DELETE|TRACE/ ) { $new_first = "$method $the_rest"; $client_request[0] = $new_first."\n"; $data = join('', @client_request); &Log("$gc: proxy_connect:method: $method:\n"); &Log("$gc: proxy_connect:from: \"$host\"\n"); &Log("$gc: proxy_connect:port: \"$port\"\n"); &Log("$gc: proxy_connect:request: \"$new_first\"\n"); &tcp_connect($client_FH, $host, $port, $data); } elsif ( $method =~ /GOPHER/ ) { $the_rest =~ s,\s+HTTP/[\d\.]+$,,; $the_rest =~ s,^/\d*(\d.*)$,$1,; $data = $the_rest."\n"; &Log("$gc: proxy_connect:method: $method:\n"); &Log("$gc: proxy_connect:from: \"$host\"\n"); &Log("$gc: proxy_connect:port: \"$port\"\n"); &Log("$gc: proxy_connect:request: \"$the_rest\"\n"); print $client_FH "HTTP/1.0 200 Connection established\n"; print $client_FH "Content-Type: text/gopher\n\n"; &tcp_connect($client_FH, $host, $port, $data); } elsif ( $method =~ /CONNECT/ ) { $Service = 'binary'; &Log("$gc: proxy_connect:method: $method:\n"); &Log("$gc: proxy_connect:from: \"$host\"\n"); &Log("$gc: proxy_connect:port: \"$port\"\n"); print $client_FH "HTTP/1.0 200 Connection established\n"; print $client_FH "Proxy-agent: $ProxyName/$Version_num\n\n"; local($oldenv) = $ENV{TCP_B_BINARY}; $ENV{TCP_B_BINARY} = 1; &tcp_connect($client_FH, $host, $port); $ENV{TCP_B_BINARY} = $oldenv; } else { $error = "Error: $gc: proxy_connect: cannot yet handle method: \n

\t\"$first_line\"\n"; &proxy_error($client_FH, $error); return $error; } return ''; } sub proxy_error { local($handle, $error_msg) = @_; local($text) = <<"END_OF_TEXT"; Error

Error

A $ProxyName error has occurred.


$error_msg
$ProxyName $Version_url END_OF_TEXT print $handle $text; } #========================== Client Portion ========================= sub tcp_connect { # child child handles actual connection local($Client_FH, $remote_host, $remote_port, $start_data) = @_; local($verbose, $local_ip, $remote_ip) = &null(3); $start_data = '' if !defined($start_data); $verbose = 1 if $Debug; # for extra logging $verbose = 1 if $ENV{'TCP_B_VERBOSE'}; # local($Client_FH_Reader, $Client_FH_Writer); # local($Remote_FH_Reader, $Remote_FH_Writer, $Remote_FH); $Client_FH_Reader = $Client_FH; # general case is a socket. $Client_FH_Writer = $Client_FH; if ( $Connect_Only || $Client_FH eq '' ) { $Client_FH_Reader = 'STDIN'; # special case stdio $Client_FH_Writer = 'STDOUT'; } local($gc) = ''; if ( defined ($Global_Connection) ) { $gc = $Global_Connection; } else { $gc = 'ONCE'; } if ( $Firewall ) { ($local_ip, $remote_ip, $Remote_FH_Reader, $Remote_FH_Writer) = &FirewallConnect($remote_host, $remote_port, $Firewall_Command); } else { ($Remote_FH, $remote_ip, $local_ip) = &Connect($remote_host, $remote_port); $Remote_FH_Reader = $Remote_FH; # general case is a socket. $Remote_FH_Writer = $Remote_FH; } if ( ! $Firewall || $Firewall_Command =~ /^TCP:/ ) { if ( !defined($remote_ip) || $remote_ip eq '' || &SocketError($Remote_FH_Reader) ) { &die($Remote_FH_Reader); } } &Log("$gc: tcp_connect:connect_ok: $local_ip ==> $remote_ip:$remote_port\n"); &Log("$gc: tcp_connect:Client_FH_Reader: $Client_FH_Reader\n") if $verbose; &Log("$gc: tcp_connect:Client_FH_Writer: $Client_FH_Writer\n") if $verbose; &Log("$gc: tcp_connect:Remote_FH_Reader: $Remote_FH_Reader\n") if $verbose; &Log("$gc: tcp_connect:Remote_FH_Writer: $Remote_FH_Writer\n") if $verbose; # Picture so far: # processes: Joe Blow <-------------> Us <-----------> Remote Server # filehandles: Client_FH Remote_FH local($xfer_method, $from, $i_do, $nbytes) = &null(4); if ($Service eq '23' || $Service eq 'telnet' || $Service eq 'binary' || $ENV{TCP_B_CHAR_AT_A_TIME} ne '') { # Hack to do character at a time for telnet login. # Man this is SLOW! Kinda like 2400 baud over ethernet ;-) # Now (Sat Jun 6 00:11:53 PDT 1998) do larger blksize. $xfer_method = 1 unless $xfer_method; if ( $Bytes > 0 ) { $xfer_method = $Bytes; } else { $xfer_method = $Bytes_Default; } if ( $xfer_method == 1 ) { &Log("$gc: tcp_connect: character at a time transmission\n"); } } else { $xfer_method = ''; } if ( $Bytes > 0 ) { $xfer_method = $Bytes; } elsif ( $ENV{TCP_B_BINARY} ne '' ) { $xfer_method = $Bytes_Default; } if($child = fork) { # Handles Remote Server -----> reader $i_do = "LocalClient <<== RemoteServer:$remote_port"; &Log("$gc: $i_do, pid: $$\n"); $SIG{'INT'} = 'dokill'; # used for killing sub dokill { local($sig) = 'INT'; &Log("$Global_Connection: killing ($sig) \"$child\" (I am $$)\n") if $Debug; kill $sig, $child if $child; exit 0; } if ( ! $xfer_method ) { # line at a time while(<$Remote_FH_Reader>) { # Send what Remote Server says to Joe Blow &Log("$gc: S:$_") if $verbose; print $Client_FH_Writer $_; } } else { if ( $xfer_method == 1 ) { # getc while(! eof($Remote_FH_Reader) ) { # Send what Remote Server says to Joe Blow if ( $xfer_method == 1 ) { $from = getc($Remote_FH_Reader); } else { $nbytes = read($Remote_FH_Reader, $from, $xfer_method); # $nbytes = sysread($Remote_FH_Reader, $from, $xfer_method); } &Log("$gc: S[$nbytes]:$from:") if $verbose; print $Client_FH_Writer $from; } } else { # select + sysread + syswrite &transfer($Remote_FH_Reader, $Client_FH_Writer, $xfer_method, 'R->C'); } } close $Remote_FH_Reader; close $Remote_FH_Writer; close $Client_FH_Reader; close $Client_FH_Writer; &short_sleep('0.15'); # sleep 1; # see if it makes a difference &dokill();# kill our sibling when Remote Server quits. } else { # Handles Original Client -----> Remote Server $i_do = "LocalClient ==>> RemoteServer:$remote_port"; &Log("$gc: $i_do, pid: $$\n"); $SIG{'INT'} = 'client_read_shutdown'; sub client_read_shutdown { &Log("$Global_Connection: client_read_shutdown: (I am $$)\n") if $Debug; close($Remote_FH_Reader); close($Remote_FH_Writer); close($Client_FH_Reader); close($Client_FH_Writer); &Log("$Global_Connection: client_read_shutdown: closed $Client_FH_Reader (I am $$)\n") if $Debug; exit 0; } if ( $start_data ne '' ) { print $Remote_FH_Writer $start_data; } if ( ! $xfer_method ) { # line at a time while (<$Client_FH_Reader>) { # Send what Joe Blow types to Remote Server &Log("$gc: C:$_") if $verbose; print $Remote_FH_Writer $_; } } else { if ( $xfer_method == 1 ) { # getc while(! eof($Client_FH_Reader) ) { # Send what Remote Server says to Joe Blow if ( $xfer_method == 1 ) { $from = getc($Client_FH_Reader); } else { $nbytes = read($Client_FH_Reader, $from, $xfer_method); # $nbytes = sysread($Client_FH_Reader, $from, $xfer_method); } &Log("$gc: C[$nbytes]:$from:") if $verbose; print $Remote_FH_Writer $from; } } else { # select + sysread + syswrite &transfer($Client_FH_Reader, $Remote_FH_Writer, $xfer_method, 'C->R'); } } close $Client_FH_Writer; close $Client_FH_Reader; close $Remote_FH_Writer; close $Remote_FH_Reader; } # Final entry in Ship's log. &Log("$gc: Closing PID $$ ($i_do)\n"); &Log("$gc: $A_Line\n"); } sub transfer { local($in,$out,$blksize,$lab) = @_; local($LineMode) = 0; if ( ! $LineMode ) { &select_transfer($in,$out,$blksize,$lab); return; } while (<$in>) { print $out $_; } } sub select_transfer { local($in,$out,$blksize,$lab) = @_; &Log("$gc: select_transfer, $lab\n") if $verbose; local($nf, $rchk, $len, $buf, $quit, $offset, $bytes, $written); $RIN = $WIN = $EIN = ''; $ROUT = ''; vec($RIN,fileno($in),1) = 1; vec($WIN,fileno($in),1) = 1; $EIN = $RIN | $WIN; LOOP: while (1) { $nf = 0; while (! $nf ) { &Log("$gc: $lab: select...\n") if $Debug; ($nf, $rchk) = &ready_to_read(); &Log("$gc: $lab: nf: $nf, " . &bitview($rchk) . "\n") if $Debug; } &Log("$gc: $lab: sysread...\n") if $Debug; $len = sysread($in ,$buf, $blksize); if ( !defined($len) ) { next if $! =~ /^Interrupted/; die "$lab: System read error: $!\n"; } elsif ($len == 0) { local($quit) = 0; if ( eof($in) ) { $quit = 1; &Log("$gc: $lab: input is EOF\n"); } last if $quit; } if ( $Debug ) { local($str) = substr($buf, 0, 12); $str .= " ..." if $len > 13; $str =~ s/\r\n//g; $str =~ s/\n//g; # $str = $buf; if ( $ENV{TCP_B_ALLSTR} ne '' ) { $str = $buf; } &Log("$gc: $lab: len($len) buf=\"$str\"\n"); } $offset = 0; $bytes += $len; while ($len) { &Log("$gc: $lab: syswrite...\n") if $Debug; $written = syswrite($out, $buf, $len, $offset); &Log("$gc: $lab: wrote $written/$len\n") if $Debug; die "$lab: System write error: $!\n" unless defined $written; $len -= $written; $offset += $written; } } close($in); close($out); } sub ready_to_read { local($nf); $nf = select($ROUT=$RIN, undef, undef, undef); # $nf = select($ROUT=$RIN, undef, undef, 1.0); return ($nf, $ROUT); } sub bitview { local($vec) = @_; local($bits) = unpack("b*", $vec); return $bits; } sub nap { local($t) = @_; select(undef,undef,undef, $t); } sub FirewallConnect { local($host, $port, $firewall_cmd) = @_; $firewall_cmd =~ s/%HOST/$host/g; $firewall_cmd =~ s/%PORT/$port/g; $local_ip = &Local_IP(); $remote_ip = &IP_From_Name($host); if ( &SocketError($remote_ip) ) { $remote_ip = "FirewallConnect:UNDETERMINED($host)"; } local($read_fh) = ++$Firewall_Handles; local($write_fh) = ++$Firewall_Handles; local($gc) = ''; if ( defined ($Global_Connection) ) { $gc = $Global_Connection; } else { $gc = '?'; } if ( $firewall_cmd =~ /^TCP:/ ) { local($rest) = $'; local($fw_host, $fw_port, $fw_send, $fw_skip, $fw_trim) = split(/:/, $rest, 5); $fw_send = "%HOST %PORT" if !defined($fw_send) || $fw_send eq ''; $fw_trim = "" if !defined($fw_trim); $fw_skip = 0 if !defined($fw_skip); $fw_send =~ s/%HOST/$host/g; $fw_send =~ s/%PORT/$port/g; local($fw_socket); ($fw_socket, $remote_ip, $local_ip) = &Connect($fw_host, $fw_port); $read_fh = $fw_socket; $write_fh = $fw_socket; &Log("$gc: FirewallConnect: fw_host=\"$fw_host\", fw_port=\"$fw_port\", fw_send=\"$fw_send\", fw_trim=\"$fw_trim\"\n"); if ( ! &SocketError($fw_socket) ) { local($i, $tmp); for($i = 0; $i < length($fw_trim); $i++) { $tmp = getc($fw_socket); &Log("$gc: FirewallConnect:trim: \"$tmp\"\n") if $Debug; } print $fw_socket "$fw_send\n"; for($i = 0; $i < $fw_skip; $i++) { $tmp = <$fw_socket>; &Log("$gc: FirewallConnect:skip: $tmp") if $Debug; } } } else { &Log("$gc: FirewallConnect:doing: open2($firewall_cmd)\n"); &Log("$gc: FirewallConnect:read_fh: $read_fh\n") if $Debug; &Log("$gc: FirewallConnect:write_fh: $write_fh\n") if $Debug; &Use_open2; &open2($read_fh, $write_fh, $firewall_cmd); } return ($local_ip, $remote_ip, $read_fh, $write_fh); } sub die { local($msg) = @_; if ( defined($Global_Connection) ) { $msg = "$Global_Connection: $msg"; } &Log("$msg\n") unless $Connect_Only; die $msg; exit 1; } sub Log { print LOG @_ unless $Quiet; } sub short_sleep { local($time) = $_[0]; select(undef, undef, undef, $time); } sub null { local($n, $fill) = @_; local(@array, $i); if ( !defined($fill) ) { $fill = ''; } if ( $n =~ /^\d+$/ ) { for ($i = 0; $i < $n ; $i++) { push(@array, $fill); } return @array; } else { return $fill; } } sub open_log_file { if ( $Log_File ne $Log_File0 ) { open(LOG, ">> $Log_File"); } elsif ( $Debug ) { open(LOG, "| cat"); } elsif ($Connect_Only) { $Quiet = 1; open(LOG, ">/dev/null"); } else { open(LOG, ">> $Log_File"); } } sub initialize_remote_host { if ( $Remote_Machine_Name eq '' ) { &die("No remote machine specified"); } else { if ( $Firewall ) { $Remote_Machine_IP = ''; $Remote_Machine = $Remote_Machine_Name; # might not be numerical } elsif ( $Remote_Machine_Name !~ /^\d+/ ) { $Remote_Machine_IP = &IP_From_Name($Remote_Machine_Name); &die($Remote_Machine_IP) if &SocketError($Remote_Machine_IP); $Remote_Machine = $Remote_Machine_IP; } else { $Remote_Machine_IP = $Remote_Machine_Name; $Remote_Machine_Name = ''; $Remote_Machine = $Remote_Machine_IP; } } &Log("Remote_Machine_IP: \"$Remote_Machine_IP\"\n"); local($passmach) = $Remote_Machine_IP; if ( $Firewall && $Remote_Machine_IP eq '' ) { $passmach = "$Remote_Machine_Name -via- $Firewall_Command"; } &Log("Starting up: service=\"$Service\", port=\"$Port\", pass-to-machine=\"$passmach\"\n"); &Log("$A_Line\n\n"); } sub initialize_variables { $Version_num = "0.4"; $Version = "$Version_num Copyright (c) 1995-1998 by Karl J. Runge. "; $Version_url = "$Version_num Copyright © 1995-1998 by Karl J. Runge. runge\@karlrunge.com"; $Debug = ''; $Quiet = ''; $Denied = 'Permission denied.'; $Pre_Cmd = ''; $A_Line = '--------------------------------------------------------------'; $Port = '4001'; # default port to listen on. $Service = '23'; # default service to connect client to. $Limit = ''; # default IP patterns allowed. $Proxy = ''; # whether or not to do proxying $Connect_Only = ''; # whether or not to just do one connection. $ProxyName = "${program}_proxy"; $Bytes = 0; $Bytes_Default = 4096; $Firewall = ''; $Firewall_Handles = 'FIREWALL0000'; $Firewall_Perl = "/dist/pkg/term/termperl"; $Firewall_Command = "%Firewall_Perl $0 -connect_only -q -r %HOST -s %PORT" ; # File to find default machine $Remote_Machine_File = "$ENV{'HOME'}/.machine"; # Default machine to pass connection to $Remote_Machine_Name = ''; $Remote_Machine_IP = ''; $Remote_Machine = ''; # Place to log connections. local($pid) = $$; $Log_File = "/tmp/${program}_${pid}_log"; $Log_File0 = $Log_File; local($machine_ip0); if ( -f $Remote_Machine_File ) { # might as well read file now... chop($machine_ip0 = `head -1 $Remote_Machine_File`); if ( $machine_ip0 ne '' ) { $Remote_Machine_IP = $machine_ip0; } if ( $machine_ip0 !~ /^\d+\./ ) { # hmmm probably FQDN not number... $Remote_Machine_Name = $machine_ip0; } } &AvoidThoseNastyWarnings if 0; } sub AvoidThoseNastyWarnings { local($x) = ''; &client_read_shutdown if 0; &do_shutdown if 0; } sub usage_statement { local($x) = <<"EOU"; # Set to contain help message $program: $Version Listen on a port ($Port) and redirect connections to tcp service ($Service) on another machine ($Remote_Machine_IP). Usage: $program -p Local port to listen to. -r Remote machine to connect to. -s Remote port to connect to. -l Place info in logfile -limit xxx.xxx.xxx Limiting connecting IP number to pattern(s). (use : as separator) -d Debug reporting -q Quiet about logging -h -help Print this help -bytes Try to read/write n bytes at a time, n=1 implies select or getc(). (default mode is one line at a time, e.g. http, smtp, nntp) -binary Use select + sysread + syswrite to transfer data (default blksize: $Bytes_Default) -proxy Run as a proxy server -connect_only Do not be a server, just connect once, using STDIN and STDOUT as "client". -firewall Make connections using default firewall command. -firewall_cmd Make connections using alternative firewall command "cmd". -sysv use = 0 or 1 depending if machine is sysv. Notes: If "service" or "port" is a standard service, the name may be used instead of the number (e.g. smtp = 25). Limit xxx.xxx.xxx limits access of incoming IP's number to those matching that pattern (e.g. xxx.xxx.xxx.yyy is OK). Separate multiple ones with `:' Default logging to ($Log_File). Default IP addr limiting to ($Limit). File to look for default machine ($Remote_Machine_File). Firewall command is: $Firewall_Command When supplying your own firewall command, the STDIO is hooked to it and %HOST %PORT are expanded to the desired remote machine. However if Firewall_Command matches: TCP:fwhost:fwport:send_string:skip_lines:trim_string you can use a TCP connection to a firewall machine (instead of running a separate command). In this case a TCP socket is opened to port "fwport" on machine "fwhost". The number of bytes in "trim_string" is then read from the firewall host and discarded. Then "send_string" plus a newline is sent to the firewall host, with %HOST and %PORT expanded accordingly. If "send_string" is empty, then "%HOST %PORT" is used. Then, the next "skip_lines" lines from the connection are discarded. The TCP connection is then handed over to the client. E.g.: -firewall_cmd "TCP:myproxy.foo.com:3666::2:(to) " This works for itelnet/telnet-passthru service. The "trim_string" is basically a prompt, to which "%HOST %PORT" is sent back. Then the number "skip_lines" are skipped, and finally the connection proceeds back and forth as normal. Environment used: TCP_B_CHAR_AT_A_TIME use getc() TCP_B_VERBOSE verbose TCP_B_BINARY run in binary mode (?) TCP_B_ALLSTR in binary xfer mode, print out all data to log HOME used to fine ~/.machine PAGER used for -help Bugs: Telnet login very slow [due to getc()]. (much better now with select/sysread/syswrite) Ftp will not work (multiple ports 20+21, 2nd cannot reach original caller). Tested on: echo(7), daytime(13), chargen(19), telnet(23), mail(25), finger(79), pop(110), news(119). ssh(22). Proxy: http. EOU return $x; } sub domain_limits { local($limit_line) = @_; local(@array); local($pattern, $tmp) = &null(2); if ($limit_line) { &Log("Limiting to: $limit_line\n"); foreach $pattern (split(/:/, $limit_line)) { $tmp = '^'."$pattern"; # $tmp =~ s,\.,\\s+,g; # when did this last work? $tmp =~ s,\.,\\.,g; &Log("$pattern => $tmp -- \n"); push(@array, $tmp); } &Log("\n"); } return @array; } sub check_access { local(*limits, $address) = @_; local($test_ok) = ''; local($count) = '0'; return 0 if $address eq ''; foreach $pattern (@limits) { $count++; if ($address =~ /$pattern/ ) { $test_ok = $pattern; last; } } $test_ok = 'NO_LIMITS_IMPOSED' if $count == 0; return $test_ok; } #================================================================ package Ksockets; #=========================================================================== # Ksockets v0.2: Karl's sockets. Copyright (c) 1996-2001 by Karl J. Runge # Socket macros to perl's low level socket interface. #=========================================================================== sub main'Use_Ksockets { ################################################################### # # Initialize Ksockets Package # # USAGE: # # &Use_Ksockets(); # ################################################################### if ( defined($ksockets_initialized) ) { return; } if (0) { # List of functions in this package: &main'Use_Ksockets; &main'Server; &main'Connect; &main'AcceptConnection; &main'Recv; &main'Send; &main'Ready; &main'SocketError; &main'IP_From_Name; &main'SocketInfo; &main'Local_IP; &main'spit_ksockets; &get_sock_type; # List of variables referenced: $main'Debug; } $pkg = 'Ksockets'; $Server_Handles = 'KSOCKETSSERVER00000'; $Connect_Handles = 'KSOCKETSCONNECT0000'; $Ksockets_Handle_String = 'KSOCKETS'; $AF_INET = 2; # choose INET family local($is_svr4) = ''; if ( defined($main'IS_SYSV) ) { if ( $main'IS_SYSV ) { $is_svr4 = 1; } else { $is_svr4 = 0; } } else { if ( -x "/bin/uname" || -x "/usr/bin/uname" ) { # OK, this is quite a hack... local($osname) = `uname -s`; local($release) = `uname -r`; if ( $osname =~ /sunos/i && $release !~ /^4\./ ) { $is_svr4 = 1; } # add others ... } } if ( ! $is_svr4 ) { $SOCK_STREAM = 1; # BSD flavor $SOCK_DGRAM = 2; # known to work on Linux # SunOS 4. } else { $SOCK_STREAM = 2; # SVR4 flavor $SOCK_DGRAM = 1; # known to work on solaris } # todo: SOCK_RAW SOCK_SEQPACKET SOCK_RDM $Default_Proto = 'tcp'; $Default_Backlog = '8'; # template for packing socket address into binary. $SockAddr = 'S n a4 x8'; if ( $main'Local ne '' ) { $Hostname = $main'Local; } else { $Hostname = &this_host(); } local($name,$aliases,$type,$len,$localaddr) = gethostbyname($Hostname); $Local_IP = join('.', unpack('C4',$localaddr)); $ksockets_initialized = 1; } # end Use_Ksockets sub main'Server { ################################################################### # # Open a listening socket on a port. # # USAGE: # # $file_handle = &Server($port, $backlog[OPT], $proto_in[OPT]) # # Then use $file_handle in &AcceptConnection($file_handle) calls # Test for errors via a call to: &SocketError($file_handle) # # e.g: # $Server = &Server('8888'); # die "$Server" if &SocketError($Server); # ($Client) = &AcceptConnection($Server); # die "$Client" if &SocketError($Client); # ... ################################################################### local($port, $backlog, $proto_in) = @_; local($name, $aliases, $proto); if ( !defined($proto_in) || $proto_in eq '' ) { $proto_in = $Default_Proto; } if ($port !~ /^\d+$/) { # port could be "smtp" though not likely ($name, $aliases, $port, $proto) = getservbyname($port, $proto_in); } ($name, $aliases, $proto) = getprotobyname($proto_in); $backlog = $Default_Backlog unless $backlog; print STDERR "Server:port:$port\n" if $main'Debug; # pack our address local($us) = pack($SockAddr, $AF_INET, $port, "\0\0\0\0"); local($listen) = ++$Server_Handles; # force filehandles into callers' package local($package) = caller; $listen =~ s/^[^']+$/$package'$&/; local($old) = select($listen); $| = 1; select($old); local($SOCK_TYPE) = &get_sock_type($proto_in); if ( ! socket($listen, $AF_INET, $SOCK_TYPE, $proto) ) { return "Error:$pkg:Server:socket($listen, $AF_INET, $SOCK_TYPE, $proto): \"$!\""; } if ( ! bind($listen, $us) ) { return "Error:$pkg:Server:bind($listen, SOCKADDR): \"$!\""; } if ( $proto_in eq 'tcp' ) { if ( ! listen($listen, $backlog) ) { return "Error:$pkg:Server:listen($listen, $backlog): \"$!\""; } } return $listen; } # end Server sub main'Connect { ################################################################### # # Make a connection to a remote host port. # # USAGE: # # ($file_handle, $host_ip, $Local_IP, $host_sockaddr, $local_sockaddr) = # &Connect($host, $port, $proto_in[OPT]) # # Then use $file_handle in to read and write from remote connection # # Test for errors via a call to: &SocketError($file_handle) # # Use $host_ip and $local_ip as IP address information. # The $host_sockaddr and $local_sockaddr are packed socket addresses # for us in connectionless sockets (e.g. 'udp') # # e.g: # ($Remote) = &Connect('www.netscape.com', '80'); # die "$Remote" if &SocketError($Remote); # ... ################################################################### local($host, $port, $proto_in) = @_; local($name, $aliases, $proto); local($type,$len); local($local_addr, $local_sockaddr); # , $local_ip); local($host_addr, $host_sockaddr, $host_ip); if ( !defined($proto_in) || $proto_in eq '' ) { $proto_in = $Default_Proto; } if ($port !~ /^\d+$/) { # port could be "smtp" ($name, $aliases, $port, $proto) = getservbyname($port, $proto_in); if ( $port eq '' ) { return ("Error:$pkg:Connect:getservbyname($port, $proto_in): Empty port returned", '', '', '', ''); } } ($name, $aliases, $proto) = getprotobyname($proto_in); if ( !defined($proto) || $proto eq '' ) { return ("Error:$pkg:Connect:getprotobyname($proto_in): Empty proto returned", '', '', '', ''); } if ( $host =~ /^\d+/ ) { $host_ip = $host; $host_addr = pack('C4', split(/\./, $host, 4)); } else { ($name,$aliases,$type,$len,$host_addr) = gethostbyname($host); if ( !defined($host_addr) || $host_addr eq '' ) { return ("Error:$pkg:Connect:gethostbyname($host): Empty host_addr returned", '', '', '', ''); } $host_ip = join('.', unpack('C4', $host_addr)); } $local_addr = pack('C4', split(/\./, $Local_IP, 4)); $local_sockaddr = pack($SockAddr, $AF_INET, 0, $local_addr); $host_sockaddr = pack($SockAddr, $AF_INET, $port, $host_addr); local($connection) = ++$Connect_Handles; # force filehandles into callers' package local($package) = caller; $connection =~ s/^[^']+$/$package'$&/; local($old) = select($connection); $| = 1; select($old); local($SOCK_TYPE) = &get_sock_type($proto_in); if ( ! socket($connection, $AF_INET, $SOCK_TYPE, $proto) ) { return ("Error:$pkg:Connect:socket($connection, $AF_INET, $SOCK_TYPE, $proto): \"$!\"", '', '', '', ''); } if ( ! bind($connection, $local_sockaddr) ) { return ("Error:$pkg:Connect:bind($connection, $Local_IP): \"$!\"", '', '', '', ''); } if ( $proto_in eq 'tcp' ) { if ( ! connect($connection, $host_sockaddr) ) { return ("Error:$pkg:Connect:connect($connection, $host_ip): \"$!\"", '', '', '', ''); } } return ($connection, $host_ip, $Local_IP, $host_sockaddr, $local_sockaddr); } # end Connect sub main'AcceptConnection { ################################################################### # # Wait for and accept a connection on a listening port. # # Listening port created with &Server(); # # USAGE: # # ($file_handle, $address, $client_port) = # &AcceptConnection($listen_file_handle)) # # Then use $file_handle in to read and write from remote connection # Test for errors via a call to: &SocketError($file_handle) # # e.g: # ($OurService) = &Server('8888'); # die "$OurService" if &SocketError($OurService); # ($Guest) = &AcceptConnection($OurService); # die "$Guest" if &SocketError($Guest); # ... ################################################################### local($listen_socket) = @_; local($real_socket) = ++$Server_Handles; # force filehandles into callers' package local($package) = caller; $real_socket =~ s/^[^']+$/$package'$&/; local($old) = select($real_socket); $| = 1; select($old); local($address) = ''; local($fail) = ''; ($address = accept($real_socket, $listen_socket)) || ($fail = "Error:$pkg:AcceptConnection:accept($real_socket, $listen_socket): \"$!\""); if ( $fail ne '' ) { close($real_socket); return ($fail, '', ''); } local($af,$c_port,$inetaddr) = unpack($SockAddr,$address); $address = join('.', unpack('C4',$inetaddr)); return ($real_socket, $address, $c_port); } # end AcceptConnection sub main'Ready { ################################################################### # # See if a socket handle is ready to be read or written to. # # USAGE: # # ($handle_ready, $time_left ) = # &Ready($socket_handle, $time_out, $mode[OPT]) # # Given handle will be checked for readiness up to $time_out seconds # Default for $mode is 'read', but it could be 'write' or 'error' # # $handle_ready will be the integer result of the select() call, # i.e. the number of 'ready' handles. Should be 0 (problem) or # 1 (handle ready). # # If time_out is undef, it should block waiting for readiness. # # E.g.: # ($ready) = &Ready($socket, '30'); # if ($ready) { # # } else { # # } # ################################################################### local($socket, $time_out, $mode) = @_; local($handle_ready, $time_left, $in, $out) = ('', '', '',''); vec($in, fileno($socket), 1) = 1; # note, the $out = $in stuff is not useful since they are # reset every time. if ( ! defined($mode) || $mode eq 'read' ) { ($handle_ready, $time_left) = select($out = $in, undef, undef, $time_out); } elsif ( $mode eq 'write' ) { ($handle_ready, $time_left) = select(undef, $out = $in, undef, $time_out); } elsif ( $mode eq 'error' ) { ($handle_ready, $time_left) = select(undef, undef, $out = $in, $time_out); } elsif ( $mode eq 'sleep' ) { # undoc'd floating pt sleep ($handle_ready, $time_left) = select(undef, undef, undef, $time_out); } else { # should handle error case better $handle_ready = undef; $time_left = 'Error:Ready:unknown mode'; } return ($handle_ready, $time_left); } # end Ready sub main'Recv { ################################################################### # # Receive data from a socket. # # USAGE: # # ($data, $recv_address_info ) = &Recv($socket_handle, $size) # # ################################################################### local($socket, $size) = @_; local($recv_info, $buf) = ('', ''); $recv_info = recv($socket, $buf, $size, 0); return ($buf, $recv_info); } # end Recv sub main'Send { ################################################################### # # Send data to a socket. # # USAGE: # # $nchars = &Send($socket_handle, $message, $TO[OPT]) # # $nchars is number of characters sent. # optional $TO is for 'udp' sockets. The Remote sockaddr # returned on Connect() say. # # ################################################################### local($socket, $message, $to) = @_; local($send_info) = ''; if ( !defined($to) || $to eq '' ) { $send_info = send($socket, $message, 0); } else { $send_info = send($socket, $message, 0, $to); } if ( !defined($send_info) ) { $send_info = "Error:$pkg:Send:send($socket, $message, 0, $to): \"$!\""; } return $send_info; } # end Send sub main'SocketError { ################################################################### # # Test for error on a Ksocket command. # # USAGE: # # ($socket_file_handle, .... ) = &Ksocket_Command(...) # # Ksocket_Command can be: # Server() # Connect() # AcceptConnection() # IP_From_Name() # # $error = &SocketError($socket_file_handle) # # if ( $error ) { # # } else { # # } # ################################################################### local($msg) = @_; if ( $msg =~ /^\s*$/ ) { return 1; } if ( $msg =~ /^\s*ERROR/i ) { return 1; } if ( $msg !~ /$Ksockets_Handle_String/ && $msg !~ /^\d+\.\d+\.\d+\.\d+$/ ) { return 1; } return 0; } # end SocketError sub main'IP_From_Name { ################################################################### # # Translate a machine host name into IP number nnn.nnn.nnn.nnn # # USAGE: # # $ip_number = &IP_From_Name($name) # # e.g. # $ip_number = &IP_From_Name('www.netscape.com'); # die "$ip_number" if &SocketError($ip_number); # ... # ################################################################### local($input_name) = @_; local($remote_address); local($name, $aliases, $type, $len, $ip); if ( $input_name =~ /^\d+\./ ) { return $input_name; } # Turn name -> ddd.ddd.ddd.ddd ($name,$aliases,$type,$len,$remote_address) = gethostbyname($input_name); if ( !defined($remote_address) || $remote_address eq '' ) { return "Error:$pkg:IP_From_Name:gethostbyname($input_name): \"$!\""; } $ip = join(".", unpack('C4',$remote_address)); return $ip; } # end IP_From_Name sub main'SocketInfo { ################################################################### # # Translate a internal address format into human readable information # # USAGE: # # ($r_port, $r_ip, $af) = &SocketInfo($address) # # $address comes from some socket operation, like recv # NOTE: not finished # ################################################################### local($input_address) = @_; local($af, $remote_port, $remote_address) = unpack($SockAddr, $input_address); $remote_address = join('.', unpack('C4', $remote_address)); return ($remote_port, $remote_address, $af) } # end SocketInfo sub get_sock_type { ################################################################### # # INTERNAL ROUTINE not available in main'. # # Translate string protocol name (e.g. tcp or udp) # into Socket type. # # USAGE: # # $SOCK_TYPE = &get_sock_type($name) # # e.g. # $SOCK_TYPE = &get_sock_type('udp'); # ... # ################################################################### local($name) = @_; if ( $name eq 'tcp' || $name eq '' ) { return $SOCK_STREAM; } elsif ( $name eq 'udp' ) { return $SOCK_DGRAM; } } # end get_sock_type sub this_host { local($this_host, $i); $this_host = ''; for ($i = 0; $i < 2; $i++) { chop($this_host = `hostname 2>/dev/null`); if ( $this_host ) { return $this_host; } select(undef, undef, undef, 0.2); } for ($i = 0; $i < 2; $i++) { chop($this_host = `uname -n 2>/dev/null`); if ( $this_host ) { return $this_host; } select(undef, undef, undef, 0.2); } return 'localhost'; # bummer. } # end this_host sub main'Local_IP { local($ip) = @_; if ( $ip ne '' ) { $Local_IP = $ip; } return $Local_IP; } # end Local_IP sub main'spit_ksockets { open(THIS_FILE, "$0"); local($ok_to_print) = ''; local($first) = 1; while () { if ( $first ) { $first = ''; print STDOUT <<'EOQ'; #!/bin/sh -- # A comment mentioning perl, to prevent perl from looping. Indented to work with bash. eval 'exec perl -S $0 ${1+"$@"}' if 0; EOQ print STDOUT "\n"; print STDOUT "&Use_Ksockets();\n"; print STDOUT "\n"; print STDOUT "#----------------------------------------------------------------------\n"; } if ( /^package Ksockets/ ) { $ok_to_print = 1; } print STDOUT if $ok_to_print; if ( /^[\s\#]*END OF PACKAGE Ksockets/ ) { $ok_to_print = ''; } } } # END OF PACKAGE Ksockets #============================================================================== # &open2: tom christiansen, # # usage: $pid = &open2('rdr', 'wtr', 'some cmd and args'); # or $pid = &open2('rdr', 'wtr', 'some', 'cmd', 'and', 'args'); # # spawn the given $cmd and connect $rdr for # reading and $wtr for writing. return pid # of child, or 0 on failure. # # WARNING: this is dangerous, as you may block forever # unless you are very careful. # # $wtr is left unbuffered. # # abort program if # rdr or wtr are null # pipe or fork or exec fails package open2; sub main'Use_open2 { if ( defined($open2_initialized) ) { return; } $fh = 'FHOPEN000'; # package static in case called more than once $open2_initialized = 1; } # end Use_open2 sub main'open2 { local($kidpid); local($dad_rdr, $dad_wtr, @cmd) = @_; $dad_rdr ne '' || die "open2: rdr should not be null"; $dad_wtr ne '' || die "open2: wtr should not be null"; # force filehandles into callers' package local($package) = caller; $dad_rdr =~ s/^[^']+$/$package'$&/; $dad_wtr =~ s/^[^']+$/$package'$&/; local($kid_rdr) = ++$fh; local($kid_wtr) = ++$fh; pipe($dad_rdr, $kid_wtr) || die "open2: pipe 1 failed: \"$!\""; pipe($kid_rdr, $dad_wtr) || die "open2: pipe 2 failed: \"$!\""; if (($kidpid = fork) < 0) { die "open2: fork failed: \"$!\""; } elsif ($kidpid == 0) { close $dad_rdr; close $dad_wtr; open(STDIN, "<&$kid_rdr"); open(STDOUT, ">&$kid_wtr"); $debug = 0 unless $debug; warn "execing @cmd\n" if $debug; exec @cmd; die "open2: exec of @cmd failed"; } close $kid_rdr; close $kid_wtr; select((select($dad_wtr), $| = 1)[0]); # unbuffer pipe $kidpid; } # end open2 ###1; # so require is happy