#!/usr/local/bin/perl # # This is a CGI script to execute an arbitrary sh command on the # server computer # # In the naive implementation, we can simply submit a given command to # sh and send out its output. But a sh-command may take a long time to # complete, and we don't want to keep the server (and the user) waiting. # For another thing, executing one command at a time and spiiting out its # output does not provide any history. # # In the present implementation, we try to maintain an illusion of a session, # just as if we were in a telnet window. We run a given command in a background, # while redirecting its stdout and stderr into a special session file. We # then send out the tail of the file to the user. # # This script is supposed to be called in response to a POST or GET action. # Upon receiving this kind of request, an HHTP server parses it and calls # the present script, passing request headers as env variables, and the # post message as this script's standard input. # # Thus this script should receive the following environment # # PATH=/sbin:/usr/sbin:/usr/bin # REMOTE_HOST=10.10.1.1 # HTTP_HOST=10.1.1.1:80 # GATEWAY_INTERFACE=CGI/1.1 # SERVER_SOFTWARE=Netscape-Communications/1.12 # SERVER_URL=http://day-1 # REQUEST_METHOD=POST # SERVER_NAME=day-1 # HTTP_USER_AGENT=Do-sh/1.0 # SCRIPT_NAME=/cgi-bin/admin/do-sh.pl # SERVER_PORT=80 # SERVER_PROTOCOL=HTTP/1.0 # REMOTE_ADDR=10.10.1.1 # TZ=GMT0 # # Of interest (importance) to us are REQUEST_METHOD (which can be either # POST, or GET). HTTP_USER_AGENT tells the name and the version of the agent, # just for reference. The request itself is specified by an env variable # QUERY_STRING (for the request method GET), or sent as the POST message, # which is available to this script as its standard input. In the latter # case, CONTENT_TYPE and CONTENT_LENGTH env variables _must_ be present, # to tell the message size and data format. # # A QUERY_STRING or a POST message may specify one or several parameters, # as name=value pairs. A value sometimes may be omitted; furthermore, # for certain names the value is irrelevant: the mere occurrence of this # particular name is important (rather than the value that may be associated # with it); we will call such a name key-name or a key parameter. # # The following parameters are used by the current script: # stmt= - a shell-command to execute # logfile= - the name of the log file # tail-lines= the number of the tail lines to display # do-clear if this key parameter is present, we # clear out the log file before executing the command. # do-tail if this key parameter is present, tail the session # file, but do not execute statement even if defined # # None of these parameters are mandatory. If the stmt is absent, we # simply display the tail of the logfile (and clear the log file if # do-clear is present). If logfile is present, we create it as # $log_prefix . $ENV{"REMOTE_HOST"} # If tail-lines= is omitted, some reasonable default is used. # # The script is executed on behalf of the user that runs the httpd. Therefore, # the script can do _everything_ that user may. Be careful with this # strict, and never allow an unrestricted access to it. # # Obviously the script can be used to run truly interactive programs # (like vi). But in general, it should provide a pretty good emulation # of telnet. # # # $Id: do-sh.pl,v 1.3 1998/05/08 01:04:59 oleg Exp oleg $ $log_prefix="/temp/do-sh"; # Prefix of a default session file $FORM{"tail-lines"} = 40; # Default values for some parameters # (may be overridden by a user) # Send HTTP headers back to the server print "Content-type: text/html\n" . "Pragma: no-cache\n\n"; #$| = 1; # this sets autoflush... # Parse the submitted POST/GET message &parse_form; # Figure out the session file name if( not ( $session_file = $FORM{"logfile"} ) ) { ($session_file = $log_prefix . $ENV{"REMOTE_HOST"}) =~ tr/\./-/; } # Remove the session file if was requested if( exists $FORM{"do-clear"} ) { unlink($session_file); } open(SESSION_FILE,"+>>$session_file") or die "Failed to open the session file $session_file: $!"; # submit the command in the background if( ($sh_cmd = $FORM{"stmt"}) and not exists $FORM{"do-tail"} ) { print SESSION_FILE ">>>> $sh_cmd\n"; seek(SESSION_FILE,0,2); open (CMD_OUT,"$sh_cmd |") or die "Failed to launch $sh_cmd: $!"; while() { s/; while( @t_lines > $tail_lines ) { shift @t_lines; # remove extra lines from the top } print "
\n"; print @t_lines; print "
"; #while (($key,$value) = each %FORM) { # print "$key: $value\n"; #} # that's it... # Parse the POST message (or the QUERY_STRING) and put all the FORM's # parameters into a @FORM hash, as name=value associations sub parse_form { # Get the input if( $ENV{"REQUEST_METHOD"} eq "POST" ) { read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); } else { $buffer = $ENV{"QUERY_STRING"}; } # Split the name-value pairs @pairs = split(/&/, $buffer); foreach $pair (@pairs) { ($name, $value) = split(/=/, $pair); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $FORM{$name} = $value; } }