diff --git a/rssh b/rssh index 561deec..2a45b45 100755 --- a/rssh +++ b/rssh @@ -19,16 +19,93 @@ # 2016-01-16 Modified license and uploaded to git for someone else to use. (v0.2) # 2016-04-21 Incorporated sshrc copies your bashrc env to remote server (v0.3) # 2016-05-20 updated sshrc and enabled direct use of ssh config if single hop (v0.4) +# 2016-06-29 Updated to allow user/ port on command line plus run commands (v0.5) # --------------------------------------------------------------------------- PROGNAME=${0##*/} -VERSION="0.4" +VERSION="0.5" + +# define variables +hops=0 +default_hops=0 +declare -a host +declare -a hostdata +declare -a hostname +declare -a port +declare -a user +declare -a options +conffile=$(mktemp) +ignore_default_route=0 +host_list="" +_USE_DEBUG=0 +opt="" clean_up() { # Perform pre-exit housekeeping + debug "" + debug "removing $conffile" + debug "" rm -f "$conffile" return } +debug() { # write out debug info if the debug flag has been set + if [ ${_USE_DEBUG} -eq 1 ]; then + echo "$@" + fi +} + +add_hop() { + ((hops++)); + l_host=$1 + l_user="" + l_port="" + if [[ $l_host == *"@"* ]]; then + l_user=$(echo $l_host | awk -F@ '{print $1}') + l_host=$(echo $l_host | awk -F@ '{print $2}') + fi + if [[ $l_host == *":"* ]]; then + l_port=$(echo $l_host | awk -F: '{print $2}') + l_host=$(echo $l_host | awk -F: '{print $1}') + fi + + host[${hops}]=$l_host; + hostdata[${hops}]=$(sed -n "/Host.* ${l_host}\( \|$\)/,/^[ ]*$/p" ~/.ssh/config); + # ignore dulicate hop of DEFAULT_SSH_ROUTE when connecting with "rssh $DEFAULT_SSH_ROUTE" + if [[ "$l_host" == "$DEFAULT_SSH_ROUTE" ]] && [[ $hops -eq $((default_hops+1)) ]]; then + ((hops--)) + debug "ignoring hop $l_host as it is the default route anyway" + else + # ignore default routing if there is first hop has a comment "Ignore_DEFAULT_SSH_ROUTE" in the .ssh/config + if [[ "$(echo "${hostdata[${hops}]}" | grep -o "Ignore_DEFAULT_SSH_ROUTE")" == "Ignore_DEFAULT_SSH_ROUTE" ]]; then + if [ ${hops} -eq $((default_hops+1)) ]; then + debug "ignore default route through $DEFAULT_SSH_ROUTE as config files states Ignore_DEFAULT_SSH_ROUTE" + for (( i=1; i<=$((hops-1)); i++ )); do + host[${i}]="" + hostname[${i}]="" + hostdata[${i}]="" + port[${i}]="" + user[${i}]="" + done + hops=1 + host[${hops}]=$l_host + hostdata[${hops}]=$(sed -n "/Host.* ${l_host}\( \|$\)/,/^[ ]*$/p" ~/.ssh/config); + fi + fi + hostname[${hops}]=$(echo "${hostdata[${hops}]}" | grep -i "Hostname" | awk '{print $2}' ) + hostname[${hops}]=${hostname[${hops}]:=${l_host}} + f_port=$(echo "${hostdata[${hops}]}" | grep -i "^[ ]*port" | awk '{print $2}' ) + port[${hops}]=${l_port:=$f_port} + port[${hops}]=${port[${hops}]:=22} + user[${hops}]=$l_user + options[${hops}]=$(echo "${hostdata[${hops}]}" | \ + grep -iv "^[ ]*host" | \ + grep -iv "^[ ]*port" | \ + grep -iv "^[ ]*#"| \ + grep -iv "^[ ]*ProxyCommand"| \ + grep -iv "^[ ]*DynamicForward"); + fi +} + error_exit() { echo -e "${PROGNAME}: ${1:-"Unknown Error"}" >&2 clean_up @@ -40,6 +117,30 @@ graceful_exit() { exit } +help_message() { + cat <<- _EOF_ + $PROGNAME ver. $VERSION + route ssh through a series of hosts + + $(usage) + + Options: + -h, --help Display this help message and exit. + -v verbose output from ssh + -id ignore default routing + -d debug + -s nnnn socks port + -c command command to run on remote server + + note: This script assumes that any hosts in your ~/.ssh/config file have a non-indented Host + line and the rest of the items related to that host are indented. + + The default routing is as defined by the variable DEFAULT_SSH_ROUTE + +_EOF_ + return +} + signal_exit() { # Handle trapped signals case $1 in INT) @@ -52,102 +153,85 @@ signal_exit() { # Handle trapped signals esac } -sshrc() { +function sshrc() { local SSHHOME=${SSHHOME:=~} - if [ -f "$SSHHOME/.sshrc" ]; then - local files=.sshrc - if [ -d "$SSHHOME/.sshrc.d" ]; then - files="$files .sshrc.d" - fi - SIZE=$(tar cz -h -C "$SSHHOME" $files | wc -c) - if [ "$SIZE" -gt 65536 ]; then - echo >&2 $'.sshrc.d and .sshrc files must be less than 64kb\ncurrent size: '"$SIZE"' bytes' - exit 1 - fi - ssh -t "$@" " - command -v ${decodefn} >/dev/null 2>&1 || { echo >&2 \"sshrc requires ${decodefn} to be installed on the server, but it's not. Aborting.\"; exit 1; } - mydecode() { ${decodefn}; } + if [ ! -f $SSHHOME/.sshrc ]; then + touch $SSHHOME/.sshrc + fi + if [ ! -z "$commandline" ]; then + export SSHHOMETMP=$(mktemp -d -t .$(whoami).sshhome.XXXX) + trap "rm -rf $SSHHOMETMP; exit" 0 + cat $SSHHOME/.sshrc > $SSHHOMETMP/.sshrc + echo "$commandline && exit || exit" >> $SSHHOMETMP/.sshrc + ln -s $SSHHOME/.sshrc.d $SSHHOMETMP/.sshrc.d + export SSHHOME=$SSHHOMETMP + WELCOME_MESSAGE="" + SSHRC_QUIET="-o LogLevel=QUIET" + SSHRC_ACTIVATE_BIN="" + else + WELCOME_MESSAGE=" if [ -e /etc/motd ]; then cat /etc/motd; fi if [ -e /etc/update-motd.d ]; then run-parts /etc/update-motd.d/ 2>/dev/null; fi - export SSHHOME=\$(mktemp -d -t .$(whoami).sshrc.XXXX) - export SSHRCCLEANUP=\$SSHHOME - trap \"rm -rf \$SSHRCCLEANUP; exit\" 0 - echo \"$(${encodefn} < "$0")\" | mydecode > \$SSHHOME/sshrc - chmod +x \$SSHHOME/sshrc - echo \"$( cat <<- 'EOF' | ${encodefn} - if [ -r /etc/profile ]; then source /etc/profile; fi - if [ -r ~/.bash_profile ]; then source ~/.bash_profile - elif [ -r ~/.bash_login ]; then source ~/.bash_login - elif [ -r ~/.profile ]; then source ~/.profile - fi - export PATH=$PATH:${SSHHOME}:${SSHHOME}/.sshrc.d - source $SSHHOME/.sshrc; - EOF - )\" | mydecode > \$SSHHOME/sshrc.bashrc - - echo \"$( cat <<- 'EOF' | ${encodefn} - #!/usr/bin/env bash - exec bash --rcfile <(echo ' - [ -r /etc/profile ] && source /etc/profile - if [ -r ~/.bash_profile ]; then source ~/.bash_profile - elif [ -r ~/.bash_login ]; then source ~/.bash_login - elif [ -r ~/.profile ]; then source ~/.profile - fi - source '$SSHHOME'/.sshrc; - export PATH=$PATH:'$SSHHOME' - ') "$@" - EOF - )\" | mydecode > \$SSHHOME/bashsshrc - chmod +x \$SSHHOME/bashsshrc - echo \"$(tar cz -h -C $SSHHOME $files | ${encodefn})\" | mydecode | tar mxz -C \$SSHHOME - export SSHHOME=\$SSHHOME - bash --rcfile \$SSHHOME/sshrc.bashrc - " - else - echo "No such file: $SSHHOME/.sshrc" >&2 + " + SSHRC_QUIET="" + SSHRC_ACTIVATE_BIN="chmod +x \$SSHHOME/sshrc" + fi + local files=.sshrc + if [ -d $SSHHOME/.sshrc.d ]; then + files="$files .sshrc.d" + fi + SIZE=$(tar cz -h -C $SSHHOME $files | wc -c) + if [ $SIZE -gt 65536 ]; then + echo >&2 $'.sshrc.d and .sshrc files must be less than 64kb\ncurrent size: '$SIZE' bytes' exit 1 fi + ssh $SSHRC_QUIET -t "$@" " + command -v openssl >/dev/null 2>&1 || { echo >&2 \"sshrc requires openssl to be installed on the server, but it's not. Aborting.\"; exit 1; } + $WELCOME_MESSAGE + export SSHHOME=\$(mktemp -d -t .$(whoami).sshrc.XXXX) + export SSHRCCLEANUP=\$SSHHOME + trap \"rm -rf \$SSHRCCLEANUP; exit\" 0 + echo $'"$(cat "$0" | openssl enc -base64)"' | tr -s ' ' $'\n' | openssl enc -base64 -d > \$SSHHOME/sshrc + $SSHRC_ACTIVATE_BIN + echo $'"$( cat << 'EOF' | openssl enc -base64 + if [ -r /etc/profile ]; then source /etc/profile; fi + if [ -r ~/.bash_profile ]; then source ~/.bash_profile + elif [ -r ~/.bash_login ]; then source ~/.bash_login + elif [ -r ~/.profile ]; then source ~/.profile + fi + export PATH=$PATH:$SSHHOME + source $SSHHOME/.sshrc; +EOF + )"' | tr -s ' ' $'\n' | openssl enc -base64 -d > \$SSHHOME/sshrc.bashrc + echo $'"$( cat << 'EOF' | openssl enc -base64 + #!/usr/bin/env bash + exec bash --rcfile <(echo ' + [ -r /etc/profile ] && source /etc/profile + if [ -r ~/.bash_profile ]; then source ~/.bash_profile + elif [ -r ~/.bash_login ]; then source ~/.bash_login + elif [ -r ~/.profile ]; then source ~/.profile + fi + source '$SSHHOME'/.sshrc; + export PATH=$PATH:'$SSHHOME' + ') "$@" +EOF + )"' | tr -s ' ' $'\n' | openssl enc -base64 -d > \$SSHHOME/bashsshrc + chmod +x \$SSHHOME/bashsshrc + echo $'"$(tar cz -h -C $SSHHOME $files | openssl enc -base64)"' | tr -s ' ' $'\n' | openssl enc -base64 -d | tar mxz -C \$SSHHOME + export SSHHOME=\$SSHHOME + bash --rcfile \$SSHHOME/sshrc.bashrc + " } usage() { - echo -e "Usage: $PROGNAME [-h|--help] [-v] [-s socks_port] sever1 server2 [server3] [server4 ....etc] [-c command]" + echo -e "Usage: $PROGNAME [-h|--help] [-v] [-id] [-s socks_port] sever1 server2 [server3] [server4 ....etc] [-c command]" } -help_message() { - cat <<- _EOF_ - $PROGNAME ver. $VERSION - route ssh through a series of hosts - - $(usage) - - Options: - -h, --help Display this help message and exit. - -v verbose output from ssh - -d debug on - -D debug off - -s nnnn socks port - -c command command to run on remote server - - note: This script assumes that any hosts in your ~/.ssh/config file have a non-indented Host - line and the rest of the items related to that host are indented. - -_EOF_ - return -} # Trap signals trap "signal_exit TERM" TERM HUP trap "signal_exit INT" INT -# define variables -hops=0 -declare -a host -declare -a hostdata -declare -a hostname -declare -a port -declare -a options -conffile=$(mktemp) -opt="" # Parse command-line while [[ -n $1 ]]; do @@ -159,48 +243,55 @@ while [[ -n $1 ]]; do -c | --command) shift;commandline=$(echo $1) ;; -d | --debug) - set -x ;; + _USE_DEBUG=1 ;; + -id | --ignore-default) + ignore_default_route=1 ;; -s | --socks) shift;socks=$(echo $1) ;; - -D | --debug_off) - set +x ;; -* | --*) usage error_exit "Unknown option $1" ;; *) - ((hops++)); - host[${hops}]=$1; - hostdata[${hops}]=$(sed -n "/Host.* ${1}\( \|$\)/,/^[ ]*$/p" ~/.ssh/config); - hostname[${hops}]=$(echo "${hostdata[${hops}]}" | grep -i "Hostname" | awk '{print $2}' ); - x=${hostname[${hops}]:=${1}}; - port[${hops}]=$(echo "${hostdata[${hops}]}" | grep -i "^[ ]*port" | awk '{print $2}' ); - if [[ ${1} == *":"* ]]; then - hostname[${hops}]=$(echo $1 | awk -F: '{print $1}') - port[${hops}]=$(echo $1 | awk -F: '{print $2}') - fi - x=${port[${hops}]:=22}; - options[${hops}]=$(echo "${hostdata[${hops}]}" | \ - grep -iv "^[ ]*host" | \ - grep -iv "^[ ]*port" | \ - grep -iv "^[ ]*#"| \ - grep -iv "^[ ]*ProxyCommand"| \ - grep -iv "^[ ]*DynamicForward"); + host_list="$host_list $1" ;; esac shift done -if [ $hops -lt 1 ]; then +if [ -z "$host_list" ]; then help_message graceful_exit fi # Main logic +if [ ! -z "$DEFAULT_SSH_ROUTE" ] && [ "$ignore_default_route" -eq "0" ]; then + debug "default route is set to $DEFAULT_SSH_ROUTE" + # loop in case there is more than one hop in the default route + for h in ${DEFAULT_SSH_ROUTE}; do + ((default_hops++)) + debug "adding default hop $h" + add_hop $h + done +fi + +for h in ${host_list}; do + debug "adding hop $h" + add_hop $h +done + + + i=${hops} while [ $i -gt 1 ]; do echo "Host ${host[${i}]}" >> "$conffile" + echo " Hostname ${hostname[${i}]}" >> "$conffile" echo " Port ${port[${i}]}" >> "$conffile" - echo "${options[${i}]}" >> "$conffile" + if [ ! -z "${user[$i]}" ] ; then + echo " User ${user[$i]}" >> "$conffile" + fi + if [ ! -z "${options[$i]}" ] ; then + echo "${options[${i}]}" >> "$conffile" + fi if [ ! -z "$socks" ] && [ $i -eq ${hops} ] ; then echo " DynamicForward localhost:${socks}" >> "$conffile" fi @@ -211,29 +302,26 @@ done echo "Host ${host[${i}]}" >> "$conffile" echo " Hostname ${hostname[${i}]}" >> "$conffile" echo " Port ${port[${i}]}" >> "$conffile" +if [ ! -z "${user[$i]}" ] ; then + echo " User ${user[$i]}" >> "$conffile" +fi echo "${options[${i}]}" >> "$conffile" echo " " >> "$conffile" sed -n "/^Host \*\( \|$\)/,/^$/p" ~/.ssh/config >> "$conffile" -if [ "$opt" == "-v" ]; then +if [ ${_USE_DEBUG} -eq 1 ]; then + debug "" + debug "config file which will be used is at ${conffile} with contents:" + debug "" cat "$conffile" + debug "" + for (( i=1; i<=$hops; i++ )); do + debug "hop $i is ${host[${i}]}" + done fi -encodefn="openssl enc -base64" -decodefn="openssl enc -d -base64" - -if [ ${hops} -eq 1 ]; then - useconf=~/.ssh/config -else - useconf="$conffile" -fi - -SSHHOME=${SSHHOME:=~} -if [[ -f "$SSHHOME/.sshrc" && -z "$commandline" ]]; then - sshrc ${opt} -F $useconf ${host[${hops}]} -else - ssh ${opt} -F $useconf ${host[${hops}]} "$commandline" -fi +debug "command: sshrc ${opt} -F $conffile ${host[${hops}]}" +sshrc ${opt} -F $conffile ${host[${hops}]} graceful_exit