#!/bin/sh -u # Script to fetch information from a student VM. # Put this on a web site and use wget or curl to pull it and send it into /bin/sh: # # wget -nv -O - http://thisfile | /bin/sh -s "$@" # # curl -s -S -A 'Mozilla/4.0' http://thisfile | /bin/sh -s "$@" # Uses ssh to connect to the CLS, pulls a one-line command line to run, # runs it and sends all stdout back to the CLS. Any stderr causes abort. # -Ian! D. Allen - idallen@idallen.ca - www.idallen.com # In case someone runs this script with the "-i" option! if [ "${PS1-}" != "" ] ; then echo 1>&2 "$0: Cannot run this script interactively with PS1 defined." exit 1 fi PATH=/bin:/usr/bin:/sbin:/usr/sbin ; export PATH LC_ALL=C ; export LC_ALL umask 077 VERSION='3' TEACHUSER='idallen' COURSETERM='cst8177/15w' ASSIGNMENT='assignment07' CLSREMOTE=cst8177.idallen.ca CLSLOCAL=cst8177-alg.idallen.ca HOSTNAME=$( hostname ) cmd="$HOSTNAME ${0##*/}" # This fetch below sends us some script stuff to execute locally, and we # send the output back. # FETCH="~$TEACHUSER/$COURSETERM/$ASSIGNMENT/${ASSIGNMENT}check --fetch" # Do a bit of user-friendly USER setting in case run with USER=root # case "${USER-}" in root | "" ) echo 1>&2 "$cmd: Cannot use USER='${USER-}' to ssh into CLS" echo 1>&2 "$cmd: Run this as root with USER set to your CLS account userid" USER=${SUDO_USER-$HOSTNAME} echo 1>&2 "$cmd: Trying USER='${USER-}' instead ..." ;; esac echo " " echo "---------------------------------------------------------------------------" echo "$HOSTNAME: FETCH version $VERSION. Connecting to CLS as USER='${USER-}' using ssh" echo "---------------------------------------------------------------------------" WHOAMI=$( whoami ) if [ "$WHOAMI" != 'root' ] ; then echo 1>&2 "$cmd: ERROR: Run this as root with USER set to your CLS account userid" echo 1>&2 "$cmd: Running as user '$WHOAMI' with USER='${USER-}' -- nothing done" exit 1 fi # USER should be set to your own CLS userid, not "root". # Have to be fussy about USER since too many bad login attempts will get # you locked out of the CLS. # If USER isn't set correctly, try SUDO_USER, then, HOSTNAME, since it # should be the same as USER. # while : ; do case "${USER-}" in [a-z][a-z][a-z0-9][a-z0-9][0-9][0-9][0-9][0-9] ) break ;; # students cst8207[abc] | cst8177[abc] ) break ;; # test accounts $TEACHUSER | idallen | kelleyt | brandor | jiangw | pollmae ) break ;; # instructors $HOSTNAME | ${SUDO_USER-xxzxzxzxzxzxzx} ) echo 1>&2 "$cmd: ERROR: Cannot use USER='${USER-}' to ssh into CLS" echo 1>&2 "$cmd: Run this as root with USER set to your CLS account userid" exit 1 ;; * ) # is this "use the SUDO_USER or HOSTNAME on bad USER" trick useful? echo 1>&2 "$cmd: ERROR: Cannot use USER='${USER-}' to ssh into CLS" echo 1>&2 "$cmd: Run this as root with USER set to your CLS account userid" NEWUSER=${SUDO_USER-$HOSTNAME} echo 1>&2 "$cmd: Incorrect USER '${USER-}'; trying USER=$NEWUSER" USER=$NEWUSER echo " " ;; esac done # Unit 0 stdin has the script being read when run as "cat script | sh", # so we need to find the real tty if issuing a prompt. # Unit 2 could still be a real tty; could use it to read the answer. # # Don't prompt if unit 1 stdout is not also a tty because the prompt # appears out-of-sequence before buffered stdout when doing "script | more". # CLS=$CLSREMOTE CLSPORT=22 # may be changed by user, below if [ -t 1 -a -t 2 ] ; then read 0<&2 -p "$HOSTNAME: Use local Algonquin IP $CLSLOCAL [y/N/?]? " ans || exit $? if [ "$ans" = 'y' ] ; then CLS=$CLSLOCAL elif [ "$ans" = '?' ] ; then read 0<&2 -p "$HOSTNAME: Enter a CLS host[:port] name: " ans || exit $? if [ "$ans" = "" ] ; then echo 1>&2 "$cmd: using default $CLS" else CLS=$( expr "$ans" : '\([^:]*\)' ) case "$CLS" in *[0-9a-z].[0-9a-z]* ) ;; * ) echo 1>&2 "$cmd: ERROR: Cannot use host syntax '$CLS'" exit 1 ;; esac port=$( expr "$ans" : '.*:\([^:]*\)$' ) case "$port" in "" | " " ) ;; *[!0-9]* ) echo 1>&2 "$cmd: ERROR: Cannot use non-numeric port '$port'" exit 1 ;; [0-9]*[0-9] ) CLSPORT=$port ;; * ) echo 1>&2 "$cmd: ERROR: Cannot use non-numeric port '$port'" exit 1 ;; esac fi fi fi REMOTE="$USER@$CLS" echo "$HOSTNAME: Please wait; using ssh to connect to user '$USER' on $CLS ..." tmp=/tmp/iancst8xxxFETCH$$ tmp2=$tmp.errs # Send error message to both stderr (for user) and stdout (back to CLS). # Error () { echo 1>&2 "$cmd: ERROR: $*" echo 1>&2 "$cmd: ERROR: try again or contact instructor" echo "$cmd: ERROR: $(date): $*" echo "REMOTE: $REMOTE" echo "FETCH: $FETCH" hostname ; id ; uptime ; ifconfig ; printenv ; ps -efww } # The tricky part. # Start SSH reading from a FIFO. # Start a subshell reading from the SSH and writing to the FIFO. # Read one (long) command line from the remote machine. # Run the command line into sh locally and save the output. # Send the output and errors back to the remote machine via the FIFO. # Read one checksum back from the remote machine. # Compare checksum read against checksum of what we sent. # This means we use the same ssh connection to both read a command to # run and then send its stdout back to the CLS. # Is there a better way to do this? -IAN! # FIFO=/tmp/idallenFIFO$$ trap 'rm -f "$FIFO"' 0 1 2 rm -f "$FIFO" mkfifo --mode=600 "$FIFO" || exit $? #FETCH='echo "date; who; ifconfig; echo DONE" ; cat >/tmp/istuff; sum &2 "DEBUG $line" { printf "%s" "$line" | sh >"$tmp" ; } 2>"$tmp2" if [ -s "$tmp2" ] ; then Error "Errors executing commands from '$REMOTE'" cat "$tmp2" exit 1 fi if [ ! -s $tmp ] ; then Error "Empty output file generated from '$REMOTE'" rm $tmp $tmp2 exit 1 fi size=$( ls -s $tmp | awk '$1 ~ /^[0-9]+$/ {print $1}' ) if [ "$size" -gt 2000 ] ; then Error "Files on $HOSTNAME are too large to check ($size blocks)" echo 1>&2 "ERROR: You have files on $HOSTNAME that shouldn't be here" echo 1>&2 "*** You cannot check your assignment until you remove the extra files" echo 1>&2 "*** Check for vmware tools extracted in the wrong directory" echo 1>&2 "*** Check for large files (especially under the ROOT home directory)" rm $tmp $tmp2 exit 1 fi # Send the command output (probably a tar file) back to the remote # system. It should save it all and issue a checksum back to us. cat "$tmp" } >"$FIFO" || { status=$? echo 1>&2 "$0: ERROR: subshell FIFO exit $status" exit $status } # Read the checksum from the remote system and compare with local. sum=$( sum <$tmp ) IFS= read -r remsum if [ "$sum" != "$remsum" ] ; then echo 1>&2 "$0: INTERNAL ERROR: Checksum local '$sum' != checksum remote '$remsum'" >/dev/null echo 1>&2 "$0: INTERNAL ERROR: see $tmp and $tmp2" else rm $tmp $tmp2 fi # echo 1>&2 "DEBUG LOCAL DONE" exec cat }