register.pl 6.56 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
#
# Locals. 
# 
my $cdkey;
Leigh B. Stoller's avatar
Leigh B. Stoller committed
70 71 72 73 74 75 76 77 78

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
79
if (@ARGV != 2) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
80 81 82
    usage();
}
my $rawbootdisk = $ARGV[0];
83
my $IP = $ARGV[1];
Leigh B. Stoller's avatar
Leigh B. Stoller committed
84 85 86 87

#
# See if we want to continue. Useful for debugging.
# 
88
print "\n";
89
if (! (Prompt("Dance with Netbed?", "Yes", 15) =~ /yes/i)) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
90 91 92 93
    exit(0);
}

#
94 95 96 97
# 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
98
}
99 100
$cdkey = `cat $cdkeyfile`;
chomp($cdkey);
101 102 103 104 105 106 107 108 109 110 111 112 113 114

#
# If our IP came via DHCP, we need to figure out what it is. 
#
if ($IP eq "DHCP") {
    my $hostname = `hostname`;

    if ($hostname =~ /^([-\w\.]+)$/) {
	$hostname = $1;

	my (undef,undef,undef,undef,@ipaddrs) = gethostbyname($hostname);
	$IP = inet_ntoa($ipaddrs[0]);
    }
    else {
115
	fatal("Invalid hostname: $hostname");
116 117
    }
}
118
	
Leigh B. Stoller's avatar
Leigh B. Stoller committed
119
#
120 121 122 123
# 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
124
#
125 126
while (1) {
    my $emulab_status;
127
    my ($url, $md5, $sig);
128 129 130

    while (1) {
	print "Checking in at Netbed Central for instructions ...\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
131
    
132 133
	mysystem("$wget -q -O $tmpfile ".
		 "'${WWW}/cdromcheckin.php3?cdkey=$cdkey&needscript=1'");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
134
    
135 136
	if (!$?) {
	    last;
137
	}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
138

139 140
	print "Error getting instructions. Will try again in one minute ...\n";
	sleep(60);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
141
    }
142 143
    if (! -s $tmpfile) {
	fatal("Could not get valid instructions from $WWW!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
144 145 146
    }

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

178 179 180
    if (defined($emulab_status)) {
	if ($emulab_status) {
	    fatal("Bad response code from Netbed: $emulab_status!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
181 182
	}
    }
183 184
    else {
	fatal("Improper instructions; did not include netbed status!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
185
    }
186 187
    fatal("Improper instructions; did not include URL!")
	if (!defined($url));
188 189
    fatal("Improper instructions; did not include digital signature!")
	if (!defined($sig));
Leigh B. Stoller's avatar
Leigh B. Stoller committed
190 191

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

	mysystem("$wget -nv -O $scriptfile $url");	
Leigh B. Stoller's avatar
Leigh B. Stoller committed
195 196 197 198
	if (!$?) {
	    last;
	}

199 200 201 202 203 204 205 206 207 208 209 210 211 212
	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
213 214 215
    }

    #
216 217 218
    # Check the digital signature. 
    #
    print "Checking digital signature of the scriptfile.\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
219

220 221 222
    my $cmd  = "openssl dgst -sha1 -verify $pubkey ".
	"-signature $sigfile $scriptfile";
    my $hval = `$cmd`;
223
    chomp($hval);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
224

225 226 227 228 229 230 231 232 233
    #
    # 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
234 235 236
}

#
237
# Run the script. It must succeed or we are hosed!
Leigh B. Stoller's avatar
Leigh B. Stoller committed
238
#
239 240 241
mysystem("chmod +x $scriptfile");
system("$scriptfile $rawbootdisk $IP") == 0
    or fatal("$scriptfile failed!");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
242 243

#
244
# One last chance to hold things up.
Leigh B. Stoller's avatar
Leigh B. Stoller committed
245
# 
246 247 248
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
249
	if ($?);
250
    sleep(100000);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
251
}
252 253
exit(0);

Leigh B. Stoller's avatar
Leigh B. Stoller committed
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 320 321 322 323 324 325 326 327 328
#
# 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 $_;
}