mirror of
				https://github.com/xcat2/xcat-core.git
				synced 2025-10-31 11:22:27 +00:00 
			
		
		
		
	git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@104 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd
		
			
				
	
	
		
			708 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			708 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env perl
 | |
| # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
 | |
| 
 | |
| use Socket;
 | |
| use Socket6;
 | |
| use IO::Socket;
 | |
| use IO::Handle;
 | |
| use IO::Select;
 | |
| use IO::Socket::INET6;
 | |
| use IO::Socket::SSL qw(inet6);
 | |
| use XML::Simple;
 | |
| use xCAT::Table;
 | |
| use Data::Dumper;
 | |
| use Getopt::Long;
 | |
| use Sys::Syslog;
 | |
| use xCAT::NotifHandler;
 | |
| 
 | |
| 
 | |
| 
 | |
| Getopt::Long::Configure("bundling");
 | |
| Getopt::Long::Configure("pass_through");
 | |
| 
 | |
| 
 | |
| use Storable qw(dclone);
 | |
| use POSIX qw(WNOHANG setsid);
 | |
| use strict;
 | |
| my $pidfile;
 | |
| GetOptions(
 | |
|   'pidfile|p=s' => \$pidfile
 | |
| );
 | |
| 
 | |
| my $quit = 0;
 | |
| my $port;
 | |
| my $sport;
 | |
| my $domain;
 | |
| my $xcatdir;
 | |
| my $sitetab=xCAT::Table->new('site');
 | |
| unless ($sitetab) {
 | |
|     print ("ERROR: Unable to open basic site table for configuration\n");
 | |
| }
 | |
| $::XCATPREFIX = '/usr';
 | |
| my ($tmp) = $sitetab->getAttribs({'key'=>'xcatdport'},'value');
 | |
| unless ($tmp) {
 | |
|   die "ERROR:Need xcatdport defined in site table, try chtab key=xcatdport site.value=3001";
 | |
| }
 | |
| $port = $tmp->{value};
 | |
| $sport = $tmp->{value}+1;
 | |
| 
 | |
| ($tmp) = $sitetab->getAttribs({'key'=>'xcatprefix'},'value');
 | |
| if ($tmp and $tmp->{value}) {
 | |
|     $::XCATPREFIX = $tmp->{value};
 | |
| }
 | |
| require($::XCATPREFIX."/lib/xcat/monitoring/monitorctrl.pm");
 | |
| my $plugins_dir=$::XCATPREFIX.'/lib/xcat/plugins';
 | |
| ($tmp) = $sitetab->getAttribs({'key'=>'xcatconfdir'},'value');
 | |
| $xcatdir = (($tmp and $tmp->{value}) ? $tmp->{value} : "/etc/xcat");
 | |
| 
 | |
| my $progname;
 | |
| $SIG{PIPE} = sub { die "SIGPIPE $$progname encountered a broken pipe (probably Ctrl-C by client)" };
 | |
| sub daemonize {
 | |
|   chdir('/');
 | |
|   umask 0;
 | |
|   my $pid;
 | |
|   defined($pid = fork) or die "Can't fork: $!";
 | |
|   if ($pid) {
 | |
|     if ($pidfile) {
 | |
|       open(PFILE, '>', $pidfile);
 | |
|       print PFILE $pid;
 | |
|       close (PFILE);
 | |
|     } else {
 | |
|       printf ("xCATd starting as PID $pid \n");
 | |
|     }
 | |
|     exit;
 | |
|   }
 | |
|   open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
 | |
|   open STDOUT, '>/dev/null';
 | |
|   open STDERR, '>/dev/null';
 | |
|   $0='xcatd';
 | |
|   $progname = \$0;
 | |
|   setsid or die "Can't start new session";
 | |
| }
 | |
|   
 | |
| my %cmd_handlers;
 | |
| sub do_installm_service { 
 | |
|   #This function servers as a handler for messages from installing nodes
 | |
|   my $socket = IO::Socket::INET6->new(LocalPort=>$sport,
 | |
|                                      Proto    => 'tcp',
 | |
|                                      ReuseAddr => 1,
 | |
|                                      Listen => 64);
 | |
|   unless ($socket) {
 | |
|     syslog("local1|err","xcatd unable to open install monitor services on $sport");
 | |
|     die;
 | |
|   }
 | |
|   until ($quit) {
 | |
|     $SIG{ALRM} = sub { die "XCATTIMEOUT"; }; 
 | |
|     my $conn;
 | |
|     next unless $conn = $socket->accept;
 | |
|     my @clients = gethostbyaddr($conn->peeraddr,AF_INET6);
 | |
|     my $validclient=0;
 | |
|     my $node;
 | |
|     foreach my $client (@clients) {
 | |
|         $client =~ s/\..*//;
 | |
|         ($node) = noderange($client); #ensure this is coming from a node IP at least
 | |
|         if ($node) {  #Means the source isn't a valid deal...
 | |
|         $validclient=1;
 | |
|         last;
 | |
|         }
 | |
|     }
 | |
|     unless ($validclient) {
 | |
|         close($conn);
 | |
|         next;
 | |
|     }
 | |
|     eval {
 | |
|       alarm(2);
 | |
|       print $conn "ready\n";
 | |
|       while (my $text = <$conn>) {
 | |
|         alarm(0);
 | |
|         print $conn "done\n";
 | |
|         $text =~ s/\r//g;
 | |
|         if ($text =~ /next/) {
 | |
|           my %request = (
 | |
|             command => [ 'nodeset' ],
 | |
|             node => [ $node ],
 | |
|             arg => [ 'next' ],
 | |
|             );
 | |
|           close($conn);
 | |
|           my $pid=fork();
 | |
|           unless ($pid) { #fork off the nodeset and potential slowness
 | |
|             plugin_command(\%request,undef,\&convey_response);
 | |
|             exit(0);
 | |
|           }
 | |
|         }
 | |
|         alarm(2);
 | |
|       }
 | |
|       alarm(0);
 | |
|     };
 | |
|     if ($@) {
 | |
|       if ($@ =~ /XCATTIMEOUT/) {
 | |
|         syslog("local1|err","xcatd installmonitor timed out talking to $node");
 | |
|       } else {
 | |
|         syslog("local1|err","xcatd: possible BUG encountered by xCAT install monitor service: ".$@);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
|     
 | |
| 
 | |
| sub do_udp_service { #This function opens up a UDP port
 | |
|   #It will do similar to the standard service, except:
 | |
|   #-Obviously, unencrypted and messages are not guaranteed
 | |
|   #-For that reason, more often than not plugins designed with
 | |
|   #-this method will not expect to have a callback
 | |
|   #Also, this throttles to handle one message at a time, so no forking either
 | |
|   #Explicitly, to handle whatever operations nodes periodically send during discover state
 | |
|   #Could be used for heartbeating and such as desired
 | |
|   my $socket = IO::Socket::INET6->new(LocalPort => $port,
 | |
|                                      Proto     => 'udp',
 | |
|                                      Domain => AF_INET);
 | |
|   openlog("xCAT UDP",'','local1');
 | |
|   unless ($socket) {
 | |
|     syslog("err","xCAT UDP service unable to open port $port: $!");
 | |
|     closelog();
 | |
|     die "Unable to start UDP on $port";
 | |
|   }
 | |
|   my $data;
 | |
|   my $part;
 | |
|   my $sport;
 | |
|   my $client;
 | |
|   my $peerhost;
 | |
|   until ($quit) {
 | |
|     eval { while ($part = $socket->recv($data,1500)) {
 | |
|         ($sport,$client) = sockaddr_in($part);
 | |
|         $peerhost=gethostbyaddr($client,AF_INET)."\n";
 | |
|         my $req = eval { XMLin($data, SuppressEmpty=>undef,ForceArray=>1) };
 | |
|         if ($req and $req->{command} and ($req->{command}->[0] eq "findme")) {
 | |
|           $req->{'!xcat_clienthost'}=gethostbyaddr($client,AF_INET)."\n";
 | |
|           $req->{'!xcat_clientip'}=inet_ntoa($client);
 | |
|           $req->{'!xcat_clientport'}=$sport;
 | |
|           if (defined($cmd_handlers{"findme"})) {
 | |
|             plugin_command($req,undef,\&convey_response);
 | |
|           }
 | |
|         }
 | |
|         if ($quit) { last; }
 | |
|       }
 | |
|     };
 | |
|     if ($@) {
 | |
|       syslog("local1|err","xcatd: possible BUG encountered by xCAT UDP service: ".$@);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| sub scan_plugins {
 | |
|   my @plugins=glob($plugins_dir."/*.pm");
 | |
|   foreach (@plugins) {
 | |
|     /.*\/([^\/]*).pm$/;
 | |
|     my $modname = $1;
 | |
|     require "$_";
 | |
|     no strict 'refs';
 | |
|     my $cmd_adds=${"xCAT_plugin::".$modname."::"}{handled_commands}->();
 | |
|     foreach (keys %$cmd_adds) {
 | |
|       my $value = $_;
 | |
|       if (defined($cmd_handlers{$_})) {
 | |
|         my $add=1;
 | |
|         #This next bit of code iterates through the handlers.
 | |
|         #If the value doesn't contain an equal, and has an equivalent entry added by 
 | |
|         # another plugin already, don't add (otherwise would hit the DB multiple times)
 | |
|         # a better idea, restructure the cmd_handlers as a multi-level hash
 | |
|         # prove out this idea real quick before doing that
 | |
|         foreach (@{$cmd_handlers{$_}}) {
 | |
|           if (($_->[1] eq $cmd_adds->{$value}) and (($cmd_adds->{$value} !~ /=/) or ($_->[0] eq $modname))) {
 | |
|             $add = 0;
 | |
|           }
 | |
|         }
 | |
|         if ($add) { push @{$cmd_handlers{$_}},[$modname,$cmd_adds->{$_}]; }
 | |
|         #die "Conflicting handler information from $modname";
 | |
|       } else {
 | |
|         $cmd_handlers{$_} = [ [$modname,$cmd_adds->{$_}] ];
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| scan_plugins;
 | |
| daemonize;
 | |
| $SIG{CHLD} = sub { while (waitpid(-1,WNOHANG) > 0) {} };
 | |
| $SIG{TERM} = $SIG{INT} = sub { printf("Asked to quit...\n"); $quit++ };
 | |
|   
 | |
| my $pid = fork;
 | |
| defined $pid or die "Unable to fork for UDP/TCP";
 | |
| unless ($pid) {
 | |
|   $$progname="xcatd: UDP listener";
 | |
|   do_udp_service;
 | |
|   exit(0);
 | |
| }
 | |
| $pid = fork;
 | |
| defined $pid or die "Unable to fork installmonitor";
 | |
| unless ($pid) {
 | |
|   $$progname="xcatd: install monitor";
 | |
|   do_installm_service;
 | |
|   exit(0);
 | |
| }
 | |
| $$progname="xcatd: SSL listener";
 | |
| openlog("xCAT SSL","","local1");
 | |
| my $listener = IO::Socket::SSL->new(
 | |
|     LocalPort => $port,
 | |
|     Listen => 64,
 | |
|     Reuse => 1,
 | |
|     SSL_key_file=>$xcatdir."/cert/server-key.pem",
 | |
|     SSL_cert_file=>$xcatdir."/cert/server-cert.pem",
 | |
|     SSL_ca_file=>$xcatdir."/cert/ca.pem",
 | |
|     SSL_verify_mode=> 1
 | |
|     );
 | |
| 
 | |
| unless ($listener) {
 | |
|   kill $pid;
 | |
|   syslog("err","xCAT service unable to open SSL services on $port: $!");
 | |
|   closelog();
 | |
|   die "ERROR:Unable to start xCAT service on port $port.";
 | |
| }
 | |
| closelog();
 | |
| 
 | |
| #setup signal in NotifHandler so that the cache can be updated
 | |
| xCAT::NotifHandler::setup($$);
 | |
| 
 | |
| #start the monitoring process
 | |
| xCAT_monitoring::monitorctrl::start($$);
 | |
| 
 | |
| my $peername;
 | |
| until ($quit) {
 | |
|   next unless my $connection=$listener->accept;
 | |
|   my $child = fork(); #Yes we fork, IO::Socket::SSL is not threadsafe..
 | |
|   
 | |
|   unless (defined $child) {
 | |
|     die "xCATd cannot fork";
 | |
|   }
 | |
| 
 | |
|   if ($child == 0) {
 | |
|     $listener->close;
 | |
|     my $peerhost=undef;
 | |
|     my $peer=$connection->peer_certificate("owner");
 | |
|     if ($peer) {
 | |
|       $peer =~ m/CN=([^\/]*)/;
 | |
|       $peername = $1;
 | |
|     } else {
 | |
|       $peername=undef;
 | |
|     }
 | |
|     my ($tmp) = $sitetab->getAttribs({'key'=>'domain'},'value');
 | |
|     if (defined $tmp->{value}) {
 | |
|         $domain = $tmp->{value};
 | |
|     }
 | |
|     $peerhost = gethostbyaddr($connection->peeraddr,AF_INET6);
 | |
|     $peerhost =~ s/\.$domain\.*$//;
 | |
|     $peerhost =~ s/-eth\d*$//;
 | |
|     $peerhost =~ s/-myri\d*$//;
 | |
|     $peerhost =~ s/-ib\d*$//;
 | |
|     #printf('info'.": xcatd: connection from ".($peername ? $peername . "@" . $peerhost : $peerhost)."\n");
 | |
|     $$progname="xCATd SSL: Instance for ".($peername ? $peername ."@".$peerhost : $peerhost);
 | |
|     service_connection($connection,$peername,$peerhost);
 | |
|     exit(0);
 | |
|   }
 | |
|   $connection->close(SSL_no_shutdown => 1); #Without no shutdown, you can guess what the client ends up thinking..
 | |
| }
 | |
| $listener->close;
 | |
| 
 | |
| #stop the monitoring process
 | |
| xCAT_monitoring::monitorctrl::stop($$);
 | |
| 
 | |
| my $parent_fd;
 | |
| my %resps;
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| sub plugin_command {
 | |
|   my $req = shift;
 | |
|   my $sock = shift;
 | |
|   my $callback = shift;
 | |
|   my %handler_hash;
 | |
|   use xCAT::NodeRange;
 | |
|   $Main::resps={};  
 | |
|   my @nodes;
 | |
|   if ($req->{node}) {
 | |
|     @nodes = @{$req->{node}};
 | |
|   } elsif ($req->{noderange}) {
 | |
|     @nodes = noderange($req->{noderange}->[0]);
 | |
|     if (nodesmissed) {
 | |
|       my $rsp = {errorcode=>1,error=>"Invalid nodes in noderange:".join(',',nodesmissed)};
 | |
|       if ($sock) {
 | |
|         print $sock XMLout($rsp,RootName=>'xcatresponse' ,NoAttr=>1);
 | |
|       }
 | |
|       return ($rsp);
 | |
|     }
 | |
|   }
 | |
|   if (@nodes) { $req->{node} = \@nodes; }
 | |
|   if (defined($cmd_handlers{$req->{command}->[0]})) {
 | |
|     my $hdlspec;
 | |
|     foreach (@{$cmd_handlers{$req->{command}->[0]}}) {
 | |
|       $hdlspec =$_->[1];
 | |
|       my $ownmod = $_->[0];
 | |
|       if ($hdlspec =~ /:/) { #Specificed a table lookup path for plugin name
 | |
|         my $table;
 | |
|         my $cols;
 | |
|         ($table,$cols) = split(/:/,$hdlspec);
 | |
|         my @colmns=split(/,/,$cols);
 | |
|         my @columns;
 | |
|         my $hdlrtable=xCAT::Table->new($table);
 | |
|         unless ($hdlrtable) {
 | |
|           #TODO: proper error handling
 | |
|         }
 | |
|         my $node;
 | |
|         my $colvals = {};
 | |
|         foreach my $colu (@colmns) {
 | |
|           if ($colu =~ /=/) { #a value redirect to a pattern/specific name
 | |
|             my $coln; my $colv;
 | |
|             ($coln,$colv) = split(/=/,$colu,2);
 | |
|             $colvals->{$coln} = $colv;
 | |
|             push (@columns,$coln);
 | |
|           } else {
 | |
|             push (@columns,$colu);
 | |
|           }
 | |
|         }
 | |
|             
 | |
| 
 | |
|         unless (@nodes) { #register the plugin in the event of usage
 | |
|           $handler_hash{$ownmod} = 1;
 | |
|         }
 | |
|         foreach $node (@nodes) {
 | |
|           my $attribs = $hdlrtable->getNodeAttribs($node,\@columns);
 | |
|           unless (defined($attribs)) { next; } #TODO: This really ought to craft an unsupported response for this request
 | |
|           foreach (@columns) {
 | |
|             my $col=$_;
 | |
|             if (defined($attribs->{$col})) {
 | |
|               if ($colvals->{$col}) { #A pattern match style request.
 | |
|                 if ($attribs->{$col} =~ /$colvals->{$col}/) {
 | |
|                   $handler_hash{$ownmod}->{$node} = 1;
 | |
|                   last;
 | |
|                 }
 | |
|               } else {
 | |
|                 $handler_hash{$attribs->{$col}}->{$node} = 1;
 | |
|                 last;
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       } else {
 | |
|         unless (@nodes) {
 | |
|           $handler_hash{$hdlspec} = 1;
 | |
|         }
 | |
|         foreach (@nodes) { #Specified a specific plugin, not a table lookup
 | |
|           $handler_hash{$hdlspec}->{$_} = 1;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } else {
 | |
|     return 1;  #TODO: error back that request has no known plugin for it
 | |
|   }
 | |
|   my $children=0;
 | |
|   $SIG{CHLD} = sub {while (waitpid(-1, WNOHANG) > 0) { $children--; } };
 | |
|   my $check_fds;
 | |
|   if ($sock) { 
 | |
|     $check_fds = new IO::Select;
 | |
|   }  
 | |
|   foreach (keys %handler_hash) {
 | |
|     my $modname = $_;
 | |
|     if (-r $plugins_dir."/".$modname.".pm") {
 | |
|       require $plugins_dir."/".$modname.".pm";
 | |
|       $children++;
 | |
|       my $pfd; #will be referenced for inter-process messaging.
 | |
|       my $child;
 | |
|       if ($sock) { #If $sock not passed in, don't fork..
 | |
|         socketpair($pfd, $parent_fd,AF_UNIX,SOCK_STREAM,PF_UNSPEC) or die "socketpair: $!";
 | |
|         #pipe($pfd,$cfd);
 | |
|         $parent_fd->autoflush(1);
 | |
|         $pfd->autoflush(1);
 | |
|         $child = fork;
 | |
|       } else {
 | |
|         $child = 0;
 | |
|       }
 | |
|       unless (defined $child) { die "Fork failed"; }
 | |
|       if ($child == 0) {
 | |
|         my $oldprogname=$$progname;
 | |
|         $$progname=$oldprogname.": $modname instance";
 | |
|         if ($sock) { close $pfd; }
 | |
|         unless ($handler_hash{$_} == 1) {
 | |
|           my @nodes = sort {($a =~ /(\d+)/)[0] <=> ($b =~ /(\d+)/)[0] || $a cmp $b } (keys %{$handler_hash{$_}});
 | |
|           $req->{node}=\@nodes;
 | |
|         }
 | |
|         no strict  "refs";
 | |
|         ${"xCAT_plugin::".$modname."::"}{process_request}->($req,$callback,\&do_request);
 | |
|         $$progname=$oldprogname;
 | |
|         if ($sock) { 
 | |
|           close($parent_fd);
 | |
|           exit(0);
 | |
|         } 
 | |
|       } else {
 | |
|         close $parent_fd;
 | |
|         $check_fds->add($pfd);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   unless ($sock) { return $Main::resps };
 | |
|   while ($children > 0) {
 | |
|     relay_fds($check_fds,$sock);
 | |
|   }
 | |
|   #while (relay_fds($check_fds,$sock)) {}
 | |
|   my %done;
 | |
|   $done{serverdone} = {};
 | |
|   if ($req->{transid}) {
 | |
|     $done{transid}=$req->{transid}->[0];
 | |
|   }
 | |
|   if ($sock) { print $sock XMLout(\%done,RootName => 'xcatresponse',NoAttr=>1); }
 | |
| }
 | |
| 
 | |
| 
 | |
| sub do_request {
 | |
|   my $req = shift;
 | |
|   my $second = shift;
 | |
|   my $rsphandler = \&build_response;
 | |
|   my $sock = undef;
 | |
|   if ($second) {
 | |
|     if (ref($second) eq "CODE") {
 | |
|       $rsphandler = $second;
 | |
|     } elsif (ref($second) eq "GLOB") {
 | |
|       $sock = $second;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   #my $sock = shift; #If no sock, will return a response hash
 | |
|   if ($cmd_handlers{$req->{command}->[0]}) {
 | |
|      return plugin_command($req,$sock,$rsphandler);
 | |
|   } elsif ($req->{command}->[0] eq "noderange" and $req->{noderange}) {
 | |
|      my @nodes = noderange($req->{noderange}->[0]);
 | |
|      my %resp;
 | |
|      if (nodesmissed) {
 | |
|        $resp{warning}="Invalid nodes in noderange:".join ',',nodesmissed;
 | |
|      }
 | |
|      $resp{serverdone} = {};
 | |
|      @{$resp{node}}=@nodes;
 | |
|      if ($req->{transid}) {
 | |
|        $resp{transid}=$req->{transid}->[0];
 | |
|      }
 | |
|      if ($sock) { 
 | |
|        print $sock XMLout(\%resp,RootName => 'xcatresponse',NoAttr=>1); 
 | |
|      } else {
 | |
|        return (\%resp);
 | |
|      }
 | |
|   } else {
 | |
|      my %resp=(error=>"Unsupported request");
 | |
|      $resp{serverdone} = {};
 | |
|      if ($req->{transid}) {
 | |
|        $resp{transid}=$req->{transid}->[0];
 | |
|      }
 | |
|      if ($sock) { 
 | |
|        print $sock XMLout(\%resp,RootName => 'xcatresponse',NoAttr=>1);
 | |
|      } else {
 | |
|        return (\%resp);
 | |
|      }
 | |
|   }
 | |
| }
 | |
| 
 | |
| sub convey_response {
 | |
|   my $resp=shift;
 | |
|   #TODO: This is where the following will/may happen:
 | |
|   #-Track transaction id
 | |
|   #-Save output for deferred commands
 | |
|   unless ($parent_fd) {
 | |
|     build_response($resp);
 | |
|     return;
 | |
|   }
 | |
|   print $parent_fd XMLout($resp,KeyAttr=>[], NoAttr=>1,RootName=>'xcatresponse');
 | |
|   <$parent_fd>; #Block until parent acks data
 | |
|   #print "woo";
 | |
| #  KeyAttr => [], NoAttr => 1)
 | |
| }
 | |
| 
 | |
| sub build_response {
 | |
| # Handle responses from do_request calls made directly from a plugin
 | |
| #   Merge this response into the full response hash.  We'll collect all
 | |
| #   the responses and ship it back on the return to the plugin.
 | |
| #   Note:  Need to create a new "deep clone" copy of each response structure
 | |
| #      otherwise the next call will overwrite the reference we pushed on
 | |
| #      the response array
 | |
|   my $resp = shift;
 | |
|   foreach (keys %$resp) {
 | |
|     my $subresp = dclone($resp->{$_});
 | |
|     push (@{$Main::resps->{$_}}, @{$subresp});
 | |
|   }
 | |
| }
 | |
| 
 | |
| sub service_connection {
 | |
|   my $sock = shift;
 | |
|   my $peername = shift;
 | |
|   my $peerhost = shift;
 | |
|   my $peerport = $sock->peerport;
 | |
|   my %tables=();
 | |
|   #some paranoid measures could reduce a third party abusing stage3 image to attempting to get USER/PASS for BMCs:
 | |
|   # -Well, minimally, ignore requests if requesting node is not in spconfig mode (stage3)
 | |
|   # -Option to generate a random password per 'getipmi' request.  This reduces the exposure to a D.O.S. hopefully
 | |
|   #Give only 15 seconds of silence allowed or terminate connection.  Using alarm since we are in thread-unsafe world anyway
 | |
|   my $timedout = 0;
 | |
| 
 | |
|   $SIG{ALRM} = sub { $timedout = 1; die; }; 
 | |
|   eval {
 | |
|     my $request;
 | |
|     my $req=undef;
 | |
|     alarm(15);
 | |
|     while (<$sock>) {
 | |
|       alarm(0);
 | |
|       $request .= $_;
 | |
|       #$req = eval { XMLin($request, ForceArray => [ 'attribute' , 'attributepair' ]) };
 | |
|       if ($request =~ m/<\/xcatrequest>/) {
 | |
|         $req = eval { XMLin($request, SuppressEmpty=>undef,ForceArray=>1) };
 | |
|         #we have a full request..
 | |
|         #printf $request."\n";
 | |
|         $request="";
 | |
|         if (validate($peername,$peerhost,$req)) {
 | |
|           $req->{'!xcat_authname'} = [$peername];
 | |
|           $req->{'!xcat_clienthost'} = [$peerhost];
 | |
|           $req->{'!xcat_clientport'}= [$peerport];
 | |
|           $$progname="xCATd SSL: ".$req->{command}->[0]." for ".($peername ? $peername ."@".$peerhost : $peerhost);
 | |
|           if ($cmd_handlers{$req->{command}->[0]}) {
 | |
|             return plugin_command($req,$sock,\&convey_response);
 | |
|           } elsif ($req->{command}->[0] eq "noderange" and $req->{noderange}) {
 | |
|             my @nodes = noderange($req->{noderange}->[0]);
 | |
|             my %resp;
 | |
|             if (nodesmissed) {
 | |
|               $resp{warning}="Invalid nodes in noderange:".join ',',nodesmissed;
 | |
|             }
 | |
|             $resp{serverdone} = {};
 | |
|             @{$resp{node}}=@nodes;
 | |
|             if ($req->{transid}) {
 | |
|               $resp{transid}=$req->{transid}->[0];
 | |
|             }
 | |
|             print $sock XMLout(\%resp,RootName => 'xcatresponse',NoAttr=>1);
 | |
|             next;
 | |
|           } else {
 | |
|             my %resp=(error=>"Unsupported request");
 | |
|             $resp{serverdone} = {};
 | |
|             if ($req->{transid}) {
 | |
|               $resp{transid}=$req->{transid}->[0];
 | |
|             }
 | |
|             print $sock XMLout(\%resp,RootName => 'xcatresponse',NoAttr=>1);
 | |
|             next;
 | |
|           }
 | |
|         } else { 
 | |
|           my %resp=(error=>"Permission denied for request");
 | |
|           $resp{serverdone} = {};
 | |
|           if ($req->{transid}) {
 | |
|             $resp{transid}=$req->{transid}->[0];
 | |
|           }
 | |
|           my $response=XMLout(\%resp,RootName =>'xcatresponse',NoAttr => 1);
 | |
|           print $sock $response;
 | |
|           next;
 | |
|         }
 | |
|       }
 | |
|       alarm(15);
 | |
|     } 
 | |
|   };
 | |
|   if ($@) { # The eval statement caught a program bug..
 | |
|     unless ($@ =~ /^SIGPIPE/) {
 | |
|         syslog("local1|err","xcatd: possible BUG encountered by xCAT TCP service: ".$@);
 | |
|     } else {
 | |
|         syslog("local1|info","xcatd: Unexpected client disconnect");
 | |
|     }
 | |
|   }
 | |
|   alarm(0);
 | |
|   foreach (keys %tables) {
 | |
|     $tables{$_}->commit;
 | |
|   }
 | |
|   $sock->close;
 | |
|   if ($timedout == 1) {
 | |
|     printf ("Client timeout");
 | |
|   }
 | |
| }
 | |
| 
 | |
| sub relay_fds { #Relays file descriptors from pipes to children to the SSL socket
 | |
|   my $fds = shift;
 | |
|   my $sock = shift;
 | |
|   unless ($sock) { return 0; }
 | |
|   my $collate = ( scalar @_ > 0 ? shift  : 0);
 | |
|   my @readyset = $fds->can_read(1);
 | |
|   my $rfh;
 | |
|   my $rc = @readyset;
 | |
|   my $text;
 | |
|   foreach $rfh (@readyset) { #go through each child, extract a complete, atomic message
 | |
|     my $line;
 | |
|     while ($line = <$rfh>) { #Will break on complete </xcatresponse> messages, avoid interleave
 | |
|       print $sock $line; 
 | |
|       if ($line =~ /<\/xcatresponse>/) {
 | |
|         last;
 | |
|       }
 | |
|     }                       
 | |
|     if ($line) { 
 | |
|       print $rfh "fin\n"; #Notify convey_response message done
 | |
|     } else {
 | |
|       $fds->remove($rfh);      
 | |
|       close($rfh);             
 | |
|     }
 | |
|   }
 | |
|   return $rc;
 | |
| }
 | |
| 
 | |
| sub validate {
 | |
|   #BIG TODO, make this do something meaningful
 | |
|   #here is where we check if  $peername is allowed to do $request.  $peername if set signifies client has a 
 | |
|   #cert that the xCAT CA accepted.  This will be a policy table with $peername as key
 | |
|   #things like 'stage2/stage3' and install images will have no client certificate.
 | |
|   #A client key for something that a third party could easily tftp down themselves means nothing
 | |
|   #however, privacy between the nodes can be maintained, and $peerhost will be checked just like 1.2.0.
 | |
| # returns 1 if policy engine allows the action, 0 if denied
 | |
|   my $peername=shift;
 | |
|   my $peerhost=shift;
 | |
|   my $request=shift;
 | |
|   my $policytable = xCAT::Table->new('policy');
 | |
|   unless ($policytable) {
 | |
|     syslog("err","Unable to open policy data, denying");
 | |
|     return 0;
 | |
|   }
 | |
|   my @policies = $policytable->getTable;
 | |
|   my $rule;
 | |
|   foreach $rule (@policies) {
 | |
|     if ($rule->{name} and $rule->{name} ne '*') {
 | |
|       #TODO: more complex matching (lists, wildcards)
 | |
|       next unless ($peername eq $rule->{name});
 | |
|     }
 | |
|     if ($rule->{time} and $rule->{time} ne '*') {
 | |
|       #TODO: time ranges
 | |
|     }
 | |
|     if ($rule->{host} and $rule->{host} ne '*') {
 | |
|       #TODO: more complex matching (lists, noderanges?, wildcards)
 | |
|       next unless ($peerhost eq $rule->{host});
 | |
|     }
 | |
|     if ($rule->{commands} and $rule->{commands} ne '*') {
 | |
|       #TODO: syntax for multiple commands
 | |
|       next unless ($request->{command}->[0] eq $rule->{commands});
 | |
|     }
 | |
|     if ($rule->{parameters} and $rule->{parameters} ne '*') {
 | |
|       next; #TODO: not ignore this field
 | |
|     }
 | |
|     if ($rule->{noderange} and $rule->{noderange} ne '*') {
 | |
|       next; #TODO: not ignore this field
 | |
|     }
 | |
|     # If we are still in, that means this rule is the first match and dictates behavior.
 | |
|     if ($rule->{rule}) {
 | |
|       if ($rule->{rule} =~ /allow/i or $rule->{rule} =~ /accept/i) {
 | |
|         my $logst = "xCAT: Allowing ".$request->{command}->[0];
 | |
|         if ($peername) { $logst .= " for " . $peername };
 | |
|         syslog("authpriv|info",$logst);
 | |
|         return 1;
 | |
|       } else {
 | |
|         my $logst = "xCAT: Denying ".$request->{command}->[0];
 | |
|         if ($peername) { $logst .= " for " . $peername };
 | |
|         syslog("authpriv|info",$logst);
 | |
|         return 0;
 | |
|       }
 | |
|     } else { #Shouldn't be possible....
 | |
|       syslog("err","Impossible line in xcatd reached");
 | |
|       return 0;
 | |
|     }
 | |
|   }
 | |
|   #Reached end of policy table, reject by default.
 | |
|   syslog("err","Request matched no policy rule: ".$request->{command}->[0]);
 | |
|   return 0;
 | |
| }
 | |
| 
 |