My work involves logging into remote machines via ssh, possibly over multiple hops to do random tasks.

Tired of typing

ssh -p 3334 shmem@gateway -L1025:host.example.com:22

and for the next hop, in another shell,

ssh -p 1025 admin@localhost -L1026:192.168.123.2:975 -R993:localhost:1 +993

then in yet another shell

ssh -p 1026 wrxd15@localhost -L1027:192.168.254.2:22

and so on, I cranked out the following, which lets me say

ssc shmem@gateway:3334 admin@host.example.com wrxd15@192.168.123.2:975 +,-R993:localhost:1993 root@192.168.254.2

which does the chaining. I only have to type in the hops in order, and presto - I have a shell open at the endpoint. Aliasing a chain is much easier this way. Secure Shells are invoked with -f -N except the last.

#!/usr/bin/perl -w # $Id: ssc,v 0.4 2006/09/28 20:31:37 shmem Exp $ use strict; use Getopt::Std; my (%o,@pids); getopts('p:',\%o); $o{'p'} ||= 1025; # default port range start die "ssh chain - usage: ssc [-p] user\@host1[:sshport][,arg,arg,...] \ +\\n" ." user\@host1[:sshport][,arg,arg,...] \ +\\n" ." ...\n" ."where -p is of the forwarded ports range's start (default: $o{p}) +\n" unless @ARGV; $o{'p'}++ while port_busy($o{'p'}); while (@ARGV) { my ($host,@args) = split/,/,shift; my ($port,$nexthop,$silent,$nextport); $port = $nextport = $nexthop = $silent = ''; $host =~ s/:(\d+)// && ($port="-p $1"); if(@ARGV) { my ($c,@args) = split/,/,$ARGV[0]; my ($u,$host) = split /\@/,$c; $host =~ s/:(\d+)// && ($nextport=$1); $nexthop = "-L$o{p}:$host:". ($nextport ? $nextport : 22); $ARGV[0] = "$u\@localhost:$o{p}"; $ARGV[0] .= ','. join(',',@args); $o{'p'}++; $o{'p'}++ while port_busy($o{'p'}); $silent = '-f -N'; } if(@ARGV) { # clean up commandline (my $cmd = "ssh $silent $port $host $nexthop @args") =~ s/\s+/ /g; $cmd =~ s/\s+$//; # maybe I should just 'last' here # - not die - and kill all shells so far system $cmd and die "Can't '$cmd': $!\n"; # since ssh - f does a fork, we must get it's pid via ps # XXX is there a portable way to do this? open(I,"ps ux |") or die "Can't run ps: $!\n"; while(<I>) { /$cmd/ && /\b(\d+)\b/ && push @pids, $1; } } else { print "ssh $silent $port $host $nexthop @args\n"; system "ssh $silent $port $host $nexthop @args"; } } # reap all my shells kill 15,$_ for reverse @pids; # XXX is there a portable way to check for busy ports? sub port_busy { system("lsof -i :$_[0] >/dev/null") ? 0 : 1 }

Comments, critique and improvements welcome.

--shmem

update Added port_busy

update: Added code to reap the chain after endpoint shell exits

_($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                              /\_¯/(q    /
----------------------------  \__(m.====·.(_("always off the crowd"))."·
");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}

In reply to ssh chain by shmem

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.