#!/usr/bin/perl -T use strict; require 'bigint.pl'; use Sys::Syslog; # emergency_stop # # Utility to shut down compute nodes of a cluster in case of emergency. # See http://t8web.lanl.gov/people/dsteck/nightshade/emergency.html # for more information. # # written 9/10/02 by D. Steck and T. Bhattacharya ### local settings my $logfile='/var/log/emergencylog'; my $nodebegin = 1; # my $nodeend = 36; # my $nodestem = 'cn'; # node names are cn1 - cn36 my $clustername = 'nightshade.lanl.gov'; my $shutdownopt = '-h'; # '-k' for test, '-h' is real thing my $powercontrol = 'vacm'; # set to 'vacm' or 'apm' for appropriate poweroff my $vash = '/usr/local/bin/vash'; my $vashopts = '-c localhost -u power -p 0ffug\\)'; my $wall = '/usr/bin/wall'; my $rsh = '/usr/bin/rsh'; my $ruptime = '/usr/bin/ruptime'; my $shutdowncmd = '/sbin/shutdown'; %ENV = (); my $nl = "\n"; ### log request to system log if ($shutdownopt eq '-h') { openlog($0, 'cons,pid', 'authpriv'); syslog('alert', "received shutdown request, see $logfile for details"); closelog(); } ### log request to dedicated log file die ('Error: cannot open file ' . $logfile) unless open (logfile, ">>$logfile"); print logfile localtime(time) . " $0 [ $$ ]: shutdown request received, copy follows\n"; print logfile ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"; ### time go through input email, perform authentication checks my $check1 = 0; my $check2 = 0; my $datestr = ''; my $authcode = ''; while (<>) { ### authenticate by looking for authentication strings if (/^(X-Date:.*)$/) {$datestr = $1; $check1 = 1;} if (/^X-AuthCode:\s*([\da-f]*)$/i) {$authcode = $1; $check1 = 1;} ### authenticate by looking if this is from fahrenheit.lanl.gov ### make an attempt to defeat spoofing, and assume that the message ### is delivered directly from fahrenheit to the cluster ### i.e., we check only the first receive line for the right stuff if ($check2 == 0) { if (/received/i) { if ( /from 127.0.0.1 \(fahrenheit.lanl.gov \[128.165.59.190\]\)/ ) { $check2 = -1; } else {$check2 = -2;} } } elsif ($check2 == -1) { if (/^\t\s*by $clustername/) { $check2 = 1; } else { $check2 = -2; } } print logfile $_; } print logfile "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"; ### Decrypt authorization code and verify result my $msg = pack('H*',$authcode); my $emsg; my $modulus = '1c630ce7db21dee339eb573d8dc92ffbe228465010cdef2df8f4ba90ae0bc71d'; my $pblckey = '129dad94f1d26ff9790d1f9a713672e39fdc34fd3633681b46cd0c26b6e53675'; if ($check1) { ### 5-line pure perl RSA routine, modified to act inline ### http://www.cypherspace.org/~adam/rsa/pureperl.html my ($n, $k, $x, $z); $n = $modulus; $_ = $pblckey; s/^.(..)*$/0$&/; # char = hex pair; so add leading 0 if needed ($k=unpack('B*',pack('H*',$_))) =~ s/^0*//; # hex->binary, strip leading 0's $x=0; $z = $n =~ s/./$x=&badd(&bmul($x,16),hex $&)/ge; while ( length $msg ) { # while $msg has unencoded stuff my ($d,$r,$c,$t); $d = 1; my $w=((2*$d-1+$z)&~1)/2; # Size of next parcel that we want to handle $_ = substr($msg,0,$w); # get parcel $msg = substr($msg,$w); # remove from unprocessed $msg $r=1; $_=substr($_."\0"x$w,$c=0,$w); # Pad $_ with "\0" to have $w chars if needed s/.|\n/$c=&badd(&bmul($c,256),ord $&)/ge; $_=$k; s/./$r=&bmod(&bmul($r,$r),$x),$&?$r=&bmod(&bmul($r,$c),$x):0,""/ge; ($r,$t)=&bdiv($r,256),$_=pack('C',$t).$_ while $w-- + 1 - 2 * $d; $emsg .= $_; } $emsg =~ s/\0*$//; ### decrypted message should match the X-Date header line if ( $emsg ne $datestr ) { print logfile localtime(time) . " $0 [ $$ ]: shutdown request failed, authentication key invalid\n"; print logfile " authcode='$authcode' message = '$emsg'\n"; exit 1; } ### additionally, the X-Date header should not be too old, in local ### cluster's time; this requires that the local clock be updated ### REGULARLY via ntp (e.g. via rdate -s time.nist.gov as an hourly ### cron job); also note that X-Date is in the format of the time ### function, to avoid daylight savings and whatnot problems. my $nowtime = time; my $xdatetime = $1 if $datestr =~ /^X-Date: ([0-9]+)$/; # my $xdatetime = $datestr =~ s/[^0-9]*//g; if ( abs($nowtime - $xdatetime) > 300 ) { print logfile localtime(time) . " $0 [ $$ ]: shutdown request failed, excessive time mismatch, " . ' current time = ' . localtime($nowtime) . ', X-Date time = ' . localtime($xdatetime) . $nl; exit 1; } } else { ### stop if we didn't match enough info to check authentication code print logfile localtime(time) . " $0 [ $$ ]: shutdown request failed, authentication key not found\n"; exit 1; } ### stop if email was from the wrong source if ($check2 < 1) { print logfile localtime(time) . " $0 [ $$ ]: shutdown request failed," . " message apparently not from fahrenheit.lanl.gov\n"; exit 1; } ### authenticate by checking to see who invoked wrapper my ($ruser,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell) = getpwuid $<; $ruser =~ /([-\@\w.]+)/g; $ruser = $1; if ($ruser ne 'mail') { print logfile localtime(time) . " $0 [ $$ ]: shutdown request failed, called by user $ruser\n"; exit 1; } ### all tests passed, so go ahead $< = $>; # set real to effective uid print logfile localtime(time) . " $0 [ $$ ]: shutdown request authenticated\n"; my $bcastmsg; $bcastmsg = <) { if (/JOB_COMPLETED/) { print logfile localtime(time) . " $0 [ $$ ]: cn$i powered off successfully\n"; } elsif (/JOB_ERROR/) { print logfile localtime(time) . " $0 [ $$ ]: error powering off cn$i\n"; } } close(vacm); sleep 1; } } elsif ( $shutdownopt eq '-h' ) { ### wait 5 minutes and then dump a copy of ruptime to log sleep 300; print logfile localtime(time) . " $0 [ $$ ]: ruptime follows:\n"; open(ruptime, "$ruptime|"); while() { print logfile; } close(ruptime); } print logfile localtime(time) . " $0 [ $$ ]: poweroff sequence completed\n"; $bcastmsg = <