register.pl 6.31 KB
Newer Older
1 2 3
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
4
# Copyright (c) 2000-2003 University of Utah and the Flux Group.
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.
21 22 23
#
sub usage()
{
24
    print("Usage: register.pl <bootdisk> <ipaddr>\n");
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;

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";
50 51 52
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
53
# Definitions.
54
#
55
my $WWW         = "https://www.emulab.net";
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";
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.$$";
65

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

73 74 75 76
#
# Locals. 
# 
my $cdkey;
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) {
87 88 89
    usage();
}
my $rawbootdisk = $ARGV[0];
90
my $IP = $ARGV[1];
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)) {
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!");
105
}
106 107
$cdkey = `cat $cdkeyfile`;
chomp($cdkey);
108

109
	
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). 
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";
122
    
123 124
	mysystem("$wget -q -O $tmpfile ".
		 "'${WWW}/cdromcheckin.php3?cdkey=$cdkey&needscript=1'");
125
    
126 127
	if (!$?) {
	    last;
128
	}
129

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

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

169 170 171
    if (defined($emulab_status)) {
	if ($emulab_status) {
	    fatal("Bad response code from Netbed: $emulab_status!");
172 173
	}
    }
174 175
    else {
	fatal("Improper instructions; did not include netbed status!");
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));
181 182

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

	mysystem("$wget -nv -O $scriptfile $url");	
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);
204 205 206
    }

    #
207 208 209
    # Check the digital signature. 
    #
    print "Checking digital signature of the scriptfile.\n";
210

211 212 213
    my $cmd  = "openssl dgst -sha1 -verify $pubkey ".
	"-signature $sigfile $scriptfile";
    my $hval = `$cmd`;
214
    chomp($hval);
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);
225 226 227
}

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

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

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 $_;
}