sshtb.in 6.22 KB
Newer Older
1
#!/usr/bin/perl -w
Leigh Stoller's avatar
Leigh Stoller committed
2 3

#
4
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
Leigh Stoller's avatar
Leigh Stoller committed
24 25
#

26 27 28 29 30 31 32 33
use English;

#
# An ssh frontend to determine if the node is local or remote, and to
# add in special options.
#
sub usage()
{
34 35 36 37
    print STDERR
	"Usage: sshtb [ssh args] [-mng] -host <hostname> [command and args]\n";
    print STDERR
	"       Use -mng option to talk to ilo/drac interface\n";
38 39 40 41 42 43 44 45 46
    exit(-1);
}

#
# Configure variables
#
my $TB		= "@prefix@";
my $SSH	        = "@SSH@";
my $SSH_ARGS	= '@SSH_ARGS@';
47 48 49
my $BOSSNODE    = "@BOSSNODE@";
my $USERNODE    = "@USERNODE@";
my $FSNODE      = "@FSNODE@";
50
my $MAINSITE    = @TBMAINSITE@;
51 52 53 54 55

# Locals
my $debug	= 0;
my @args;
my $hostname;
56
my $chpid       = 0;
57 58
# Run command on the local machine.
my $runlocal    = 0;
59
my $domng       = 0;
60 61 62 63 64 65 66

#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
Leigh Stoller's avatar
Leigh Stoller committed
67
use Node;
68
use Interface;
69
use EmulabConstants;
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

#
# Turn off line buffering on output
#
$| = 1;

# un-taint path
$ENV{'PATH'} = "/bin:/usr/bin:/usr/local/bin:$TB/libexec:$TB/sbin:$TB/bin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
# We run through the args looking for -host. We save everything else.
#
if (@ARGV < 2) {
    usage();
}

# Find everything before the -host.
while (@ARGV) {
    my $arg = shift(@ARGV);
90 91 92 93
    if ($arg eq "-mng") {
	$domng = 1;
	next;
    }
94 95 96 97 98 99 100 101 102 103 104 105 106 107
    if ($arg eq "-host") {
	$hostname = shift(@ARGV);
	last;
    }
    push(@args, $arg);
}

if (!defined($hostname)) {
    usage();
}

#
# Different stuff for remote nodes.
#
108 109 110
# Special case: When asking to do something on the the FSNODE, and the
# FSNODE is actually BOSSNODE, run it locally.
#
111
my @cmdargs;
112

113 114
if ($hostname eq $BOSSNODE ||
    ($hostname eq $FSNODE && $FSNODE eq $BOSSNODE)) {
115 116
    @cmdargs  = "@ARGV";
    $runlocal = 1;
117 118
}
else {
119
    my $user;
Leigh Stoller's avatar
Leigh Stoller committed
120
    my $node = Node->Lookup($hostname);
121
    my $key;
122

Leigh Stoller's avatar
Leigh Stoller committed
123 124 125
    if (defined($node)) {
	if ($node->isvirtnode()) {
	    if ($node->isplabdslice()) {
126
		TBPlabNodeUsername($hostname, \$user);
127 128 129 130 131
                # if the node didn't exist in the db, we don't want to just
                # try logging in as root!
                if (!defined($user) || "$user" eq "0") {
                    exit(1);
                }
132
	    }
133 134 135 136 137 138 139 140 141 142
	    else {
		#
		# jailip is deprecated, but nodes might still exist.
		# Now we create interface table entries. 
		#
		my $interface = Interface->LookupControl($node);
		if (! (defined($interface) || defined($node->jailip()))) {
		    $hostname = $node->phys_nodeid();
		}
	    }
143
	}
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
	elsif ($domng) {
	    #
	    # We want to log into the management port. 
	    #
	    my $interface = Interface->LookupManagement($node);
	    if (! defined($interface)) {
		print STDERR "No management interface for $node\n";
		exit(1);
	    }
	    $hostname = $interface->IP();
	    
	    #
	    # Also need info from the outlets authorization table.
	    #
	    ($user,$key) = $node->GetOutletAuthInfo("ssh-key");
	    if (!defined($user)) {
		print STDERR "No authinfo user defined for $node\n";
		exit(1);
	    }
	    if (! -e $key) {
		print STDERR "Key $key does not exist\n";
		exit(1);
	    }
	}
168
	elsif ($node->isremotenode() && !$node->isdedicatedremote()) {
169 170 171 172
	    $user = "emulabman";
	}
    }

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    #
    # Yuck, the point is to turn the above string into a proper list for
    # exec so that we do not get a shell to interpret the arguments cause
    # there are quotes embedded. I am sure there is a regex that will do this
    # for me, but hell if I know what it is.
    #
    my @sshargs = ();
    my $tmp;
    foreach my $f (split('\s+', $SSH_ARGS)) {
	if (defined($tmp)) {
	    if ($f =~ /(.*)\"$/) {
		$tmp = "$tmp $1";
		push(@sshargs, $tmp);
		undef($tmp);
	    }
	    else {
		$tmp = "$tmp $1";
	    }
	    next;
	}
	elsif ($f =~ /^\"(.*)$/) {
	    $tmp = $1;
	    next;
	}
	push(@sshargs, $f);
198
    }
199

200
    @cmdargs = (@sshargs, @args,
201
		(defined($user) ? ("-l", "$user") : ()),
202
		(defined($key)  ? ("-i", "$key") : ()),
203
		$hostname, @ARGV);
204
}
205

206
if ($debug) {
207
    print "@cmdargs\n";
208
}
209 210 211
# Close our connection the DB to avoid holding open connections.
TBDBDisconnect();

212 213 214 215 216 217 218 219 220 221 222 223
#
# Signal Helper - help reap child process
#
sub sighandler {
    kill("TERM", $chpid);
    my $kpid;
    do {
        $kpid = wait();
    } until $kpid == -1;
    exit $?;
}

224 225 226 227 228 229
#
# Run command locally.
#
if ($runlocal) {
    exec(@cmdargs);
}
230 231 232 233
#
# Check for existing protocol specification args.
# If they exist, don't set the protocol.
#
234
elsif (grep {/Protocol/ || /-[12]/} @args) {
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
    print "Protocol spec present on command line - not forcing it.\n" 
        if $debug;
    exec($SSH, @cmdargs);
} else {
    #
    # Try both ssh protocol 2 and 1 for backward compatibility with
    # old images utilizing only v1 keys.
    #
    print "Trying ssh protocol 2...\n" if $debug;
    if ($chpid = fork()) {
        # Yuck.  Must deal with termination signals (kill child proc)
        local $SIG{TERM} = \&sighandler;
        local $SIG{HUP}  = \&sighandler;
        local $SIG{INT}  = \&sighandler;
        my $kidpid = waitpid($chpid, 0);
        # Sanity check.
        if ($kidpid < 1) { 
            warn "*** $0: waitpid() returned $kidpid.  Exiting.";
            sighandler();
        }
255 256
        my $kidstatus = $? >> 8;
        if ($kidstatus == 255) {
257 258 259 260
            # XXX: May not be due to v2 vs. v1, but there is no
            #      way to differentiate from the exit value.
            print "Protocol 2 failed:  Trying ssh protocol 1\n" if $debug;
            exec($SSH, "-o Protocol=1", @cmdargs);
261 262
        } else {
            exit $kidstatus;
263 264 265 266 267 268
        }
    } else {
        exec($SSH, "-o Protocol=2", @cmdargs);
    }
}