Script to create and manage SSH tunnels and open Remote Desktop sessions

Script simplifies connecting to a Windows host via RDP (Terminal Services) through a Linux host using a SSH tunnel.

Let's say I've got a Windows box inside a corporate firewall and I'd like to connect to it via RDP (Terminal Services), but port 3389 is not open to the Internet so I can't access it. Let's say I don't want to mess with VPN and have a box inside the corporate firewall which I can SSH into. In this scenario, I can RDP to the Windows box from the Internet via a SSH tunnel.

This, in itself, isn't particularly special, but here's a script that simplifies the whole process:

  • Allows to supply a password on the command line to save the trouble of typing
  • Can be called repeatedly to establish simultaneous connections to multiple Windows hosts
  • Keeps track of local tunnel port usage
  • Creates new ports for new connections
  • Frees up unused ports by killing tunnels for RDP connections as they're disconnected

It calls rdesktop for the RDP part, which provides a slew of useful features:

  • Mounts local Linux directories on the remote Windows host (can also mount local CD/DVD and printers)
  • Allows copy/paste
  • Can startup a particular application in Windows. For instance to launch just Internet Explorer upon login, add to rdesktop opts:
    -s C:\Progra~1\Intern~1\iexplore.exe
  • Can emulate various keyboard layouts
  • And probably more..

Anyway, here's the script:


# some defaults
rdesktop_opts="-z -g 1280x960 -a 16 -r disk:home=$HOME -k en-us"
#rdesktop_opts="-z -g 1280x960 -a 16 -r disk:home=$HOME -k en-us -s C:\Progra~1\Intern~1\iexplore.exe"

 echo "Usage:"
 echo "$(basename $0) -r rdp_host -u rdp_user -p rdp_pass -s sshuser@sshhost"
 echo " "
 echo "Connect to a Windows host via RDP through a Linux host using a SSH tunnel."
 echo ""
 echo "Required parameters:"
 echo "    -r, --rdp-host     FQDN hostname of the Windows machine (e.g."
 echo "    -u, --rdp-user     Username on the Windows host (e.g. juser)."
 echo "    -s, --ssh-info     SSH user and host info (e.g."
 echo ""
 echo "Optional parameters:"
 echo "    -p, --rdp-pass     Password of the Windows user."
 echo "    -h, --help         This help message."
 echo ""

err_exit() { echo -e 1>&2; exit 1; }

if [ -z "$1" ]; then usage; exit 1; fi

while [ "$1" ]; do
  case "$1" in
        exit 0
        echo "$(basename $0): invalid option $1" >&2
        echo "see --help for usage"
        exit 1

if [ "$rdp_host" == "" ]; then
    echo "Error: --rdp-host is a required parameter"
    exit 1

if [ "$rdp_user" == "" ]; then
    echo "Error: --rdp-user is a required parameter"
    exit 1

if [ "$ssh_info" == "" ]; then
    echo "Error: --ssh-info is a required parameter"
    exit 1

lastport=$(netstat -tapn | grep -Eo "526[0-9]{2}" | sort -u | tail -1)

if [ "$lastport" == "" ]; then

ssh -fNL $nextport:$rdp_host:$rdp_port $ssh_info || err_exit
rdesktop $rdesktop_opts -u $rdp_user -p $rdp_pass -T $rdp_host localhost:$nextport && pkill -f $nextport || err_exit &

Lends to other uses. For example, programatically provisioned a bunch of Windows virtual machines and had to connect to each one to change computer name. Ideally, something like this would be scripted, but even in the absence of that, used the following little script to establish a connection, logon without password prompts, do what I gotta do, then move on to the next box. Not completely automated, but better than nothing and definitely saved me some time.

for n in $(seq 1 20); do
 rdpssh -r winhost$ -u winuser -p winpass -s
 echo "Connected to winhost$"
 read -p "Press any key to go on to the next machine.."

Once in a while, the script won't clean up after itself and not kill the tunnel it created. Typically because rdesktop didn't exit cleanly. I could add some additional code to correct for that, but it happens rarely enough (usually just when I'm testing various rdesktop parameters), where a manual cleanup method should suffice.

To find all tunnels in the scripts port range (526xx):

ps -eo pid,command |  grep -E "526[0-9]{2}:"
15939 ssh -fNL
15957 ssh -fNL
16093 ssh -fNL
16096 ssh -fNL
16099 ssh -fNL

To kill all tunnels in the scripts port range (526xx) with "pkill":

pkill -f "526[0-9]{2}:"

It's always a good idea to test your search criteria first to ensure you're killing the right programs! Use "pgrep" as it's syntax is identical to "pkill", but instead of killing PIDs it displays them:

pgrep -f "526[0-9]{2}:"

Let's say you don't want to kill all tunnels, but just the ones for

ps -eo pid,command | grep ""
16093 ssh -fNL
16099 ssh -fNL
pgrep -f ""
pkill -f ""

Now they're gone:

ps -eo pid,command |  grep -E "526[0-9]{2}"
15939 ssh -fNL
15957 ssh -fNL
16096 ssh -fNL

Leave a comment

NOTE: Enclose quotes in <blockquote></blockquote>. Enclose code in <pre lang="LANG"></pre> (where LANG is one of these).