register.pl 6.31 KB
Newer Older
Leigh B. Stoller's avatar
Leigh B. Stoller committed
1 2 3
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2003 University of Utah and the Flux Group.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
5 6 7 8 9 10 11 12 13 14 15 16 17
# All rights reserved.
#

#
# This file goes in /usr/site/sbin on the CDROM.
#
use English;
use Getopt::Std;
use Fcntl;
use IO::Handle;
use Socket;

#
18 19 20
# Get the instructions script from NetBed central and run it. This is
# basically wrapper that does no real work, but just downloads and
# runs a script, then reboots the nodes if that script ran okay.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
21 22 23
#
sub usage()
{
24
    print("Usage: register.pl <bootdisk> <ipaddr>\n");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
25 26 27 28
    exit(-1);
}
my  $optlist = "";

29 30 31 32 33 34 35 36 37 38
#
# Catch ^C and exit with error. This will cause the CDROM to boot to
# the login prompt, but thats okay.
# 
sub handler () {
    $SIG{INT} = 'IGNORE';
    exit(1);
}
$SIG{INT}  = \&handler;

Leigh B. Stoller's avatar
Leigh B. Stoller committed
39 40 41 42 43 44 45 46 47 48
#
# Turn off line buffering on output
#
STDOUT->autoflush(1);
STDERR->autoflush(1);

#
# Untaint the environment.
# 
$ENV{'PATH'} = "/tmp:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:".
49
    "/usr/local/bin:/usr/site/bin:/usr/site/sbin";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
50 51 52
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
53
# Definitions.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
54
#
55
my $WWW         = "https://www.emulab.net";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
56
#my $WWW        = "http://golden-gw.ballmoss.com:8080/~stoller/testbed";
57 58
#my $WWW        = "https://www.emulab.net/~stoller/www";
my $cdkeyfile	= "/etc/emulab.cdkey";
59
my $pubkey      = "/etc/emulab_pubkey.pem";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
60 61
my $wget	= "wget";
my $logfile     = "/tmp/register.log";
62
my $scriptfile  = "/tmp/netbed-setup.pl";
63
my $sigfile     = "/tmp/netbed-setup.pl.sig";
64
my $tmpfile	= "/tmp/foo.$$";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
65

66 67 68 69 70 71 72
#
# Function prototypes
#
sub fatal($);
sub mysystem($);
sub Prompt($$;$);

73 74 75 76
#
# Locals. 
# 
my $cdkey;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
77 78 79 80 81 82 83 84 85

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
86
if (@ARGV != 2) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
87 88 89
    usage();
}
my $rawbootdisk = $ARGV[0];
90
my $IP = $ARGV[1];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
91 92 93 94

#
# See if we want to continue. Useful for debugging.
# 
95
print "\n";
96
if (! (Prompt("Dance with Netbed?", "Yes", 15) =~ /yes/i)) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
97 98 99 100
    exit(0);
}

#
101 102 103 104
# The cdkey describes the cd. We send it along in the request.
# 
if (! -s $cdkeyfile) {
    fatal("No CD key on the CD!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
105
}
106 107
$cdkey = `cat $cdkeyfile`;
chomp($cdkey);
108

109
	
Leigh B. Stoller's avatar
Leigh B. Stoller committed
110
#
111 112 113 114
# Get the script from netbed central. We have to be able to get it,
# otherwise we are hosed, so just keep trying. If the user hits ^C
# on the console, this script will exit and the node will try to
# go to single user mode (asking for passwword of course). 
Leigh B. Stoller's avatar
Leigh B. Stoller committed
115
#
116 117
while (1) {
    my $emulab_status;
118
    my ($url, $md5, $sig);
119 120 121

    while (1) {
	print "Checking in at Netbed Central for instructions ...\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
122
    
123 124
	mysystem("$wget -q -O $tmpfile ".
		 "'${WWW}/cdromcheckin.php3?cdkey=$cdkey&needscript=1'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
125
    
126 127
	if (!$?) {
	    last;
128
	}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
129

130 131
	print "Error getting instructions. Will try again in one minute ...\n";
	sleep(60);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
132
    }
133 134
    if (! -s $tmpfile) {
	fatal("Could not get valid instructions from $WWW!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
135 136 137
    }

    #
138
    # Parse the response. We get back the URL and the signature, as well as
139
    # a status code.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
140
    #
141 142
    if (! open(INSTR, $tmpfile)) {
	fatal("$tmpfile could not be opened for reading: $!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
143 144 145 146 147
    }
    while (<INSTR>) {
	chomp();
        SWITCH1: {
	    /^emulab_status=(.*)$/ && do {
148
		$emulab_status = $1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
149 150
		last SWITCH1;
	    };
151 152
	    /^URL=(.*)$/ && do {
		$url = $1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
153 154
		last SWITCH1;
	    };
155
	    # Leave this in to avoid error below.
156 157
	    /^MD5=(.*)$/ && do {
		$md5 = $1;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
158 159
		last SWITCH1;
	    };
160 161 162 163 164
	    /^SIG=(.*)$/ && do {
		$sig = $1;
		last SWITCH1;
	    };
	    print STDERR "Ignoring unknown instruction: $_\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
165 166 167 168
	}
    }
    close(INSTR);

169 170 171
    if (defined($emulab_status)) {
	if ($emulab_status) {
	    fatal("Bad response code from Netbed: $emulab_status!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
172 173
	}
    }
174 175
    else {
	fatal("Improper instructions; did not include netbed status!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
176
    }
177 178
    fatal("Improper instructions; did not include URL!")
	if (!defined($url));
179 180
    fatal("Improper instructions; did not include digital signature!")
	if (!defined($sig));
Leigh B. Stoller's avatar
Leigh B. Stoller committed
181 182

    while (1) {
183 184 185
	print "Downloading script from Netbed Central ...\n";

	mysystem("$wget -nv -O $scriptfile $url");	
Leigh B. Stoller's avatar
Leigh B. Stoller committed
186 187 188 189
	if (!$?) {
	    last;
	}

190 191 192 193 194 195 196 197 198 199 200 201 202 203
	print "Error getting scriptfile. Will try again in 15 seconds!\n";
	sleep(15);
    }

    while (1) {
	print "Downloading script signature from Netbed Central ...\n";

	mysystem("$wget -nv -O $sigfile $sig");	
	if (!$?) {
	    last;
	}

	print "Error getting signature. Will try again in 15 seconds!\n";
	sleep(15);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
204 205 206
    }

    #
207 208 209
    # Check the digital signature. 
    #
    print "Checking digital signature of the scriptfile.\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
210

211 212 213
    my $cmd  = "openssl dgst -sha1 -verify $pubkey ".
	"-signature $sigfile $scriptfile";
    my $hval = `$cmd`;
214
    chomp($hval);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
215

216 217 218 219 220 221 222 223 224
    #
    # Yep, its amazing. Instead of exiting with status, this stupid
    # program always exits with 0, so have to check the output. 
    #
    last
	if ($hval eq "Verified OK");

    print("Bad signature! Will try again in 15 seconds!\n");
    sleep(15);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
225 226 227
}

#
228
# Run the script. It must succeed or we are hosed!
Leigh B. Stoller's avatar
Leigh B. Stoller committed
229
#
230 231 232
mysystem("chmod +x $scriptfile");
system("$scriptfile $rawbootdisk $IP") == 0
    or fatal("$scriptfile failed!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
233 234

#
235
# One last chance to hold things up.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
236
# 
237 238 239
if (Prompt("Reboot from ${rawbootdisk}?", "Yes", 10) =~ /yes/i) {
    mysystem("shutdown -r now");
    fatal("Failed to reboot!")
Leigh B. Stoller's avatar
Leigh B. Stoller committed
240
	if ($?);
241
    sleep(100000);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
242
}
243 244
exit(0);

Leigh B. Stoller's avatar
Leigh B. Stoller committed
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
#
# Print error and exit.
#
sub fatal($)
{
    my ($msg) = @_;

    die("*** $0:\n".
	"    $msg\n");
}

#
# Run a command string, redirecting output to a logfile.
#
sub mysystem($)
{
    my ($command) = @_;

    if (defined($logfile)) {
	system("echo \"$command\" >> $logfile");
	system("($command) >> $logfile 2>&1");
    }
    else {
	print "Command: '$command\'\n";
	system($command);
    }
    return $?;
}

#
# Spit out a prompt and a default answer. If optional timeout supplied,
# then wait that long before returning the default. Otherwise, wait forever.
#
sub Prompt($$;$)
{
    my ($prompt, $default, $timeout) = @_;

    if (!defined($timeout)) {
	$timeout = 10000000;
    }

    print "$prompt";
    if (defined($default)) {
	print " [$default]";
    }
    print ": ";

    eval {
	local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required
	
	alarm $timeout;
	$_ = <STDIN>;
	alarm 0;
    };
    if ($@) {
	if ($@ ne "alarm\n") {
	    die("Unexpected interrupt in prompt\n");
	}
	#
	# Timed out.
	#
	print "\n";
	return $default;
    }
    return undef
	if (!defined($_));
	
    chomp();
    if ($_ eq "") {
	return $default;
    }

    return $_;
}