5f8f8385cc
git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@2204 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd
452 lines
15 KiB
Plaintext
Executable File
452 lines
15 KiB
Plaintext
Executable File
if (defined $reqs and (scalar(@{$reqs}) == 1)) {
|
|
$onlyone=1;
|
|
}
|
|
|
|
foreach (@{$reqs}) {
|
|
my $pfd;
|
|
my $parfd; #use a private variable so it won't trounce itself recursively
|
|
my $child;
|
|
delete $_->{noderange};
|
|
if ($onlyone and not ($_->{'_xcatdest'} and thishostisnot($_->{'_xcatdest'}))) {
|
|
$SIG{CHLD}='DEFAULT';
|
|
${"xCAT_plugin::".$modname."::"}{process_request}->($_,$dispatch_cb,\&do_request);
|
|
return;
|
|
}
|
|
|
|
socketpair($pfd, $parfd,AF_UNIX,SOCK_STREAM,PF_UNSPEC) or die "socketpair: $!";
|
|
$parfd->autoflush(1);
|
|
$pfd->autoflush(1);
|
|
$child = xCAT::Utils->xfork;
|
|
if ($child) {
|
|
$dispatch_children++;
|
|
$dispatched_children{$child}=1;
|
|
$child_fdset->add($pfd);
|
|
next;
|
|
}
|
|
unless (defined $child) {
|
|
$dispatch_cb->({error=>['Fork failure dispatching request'],errorcode=>[1]});
|
|
}
|
|
$SIG{CHLD}='DEFAULT';
|
|
$dispatch_parentfd = $parfd;
|
|
if (ref($_->{'_xcatdest'}) eq 'ARRAY') {
|
|
$_->{'_xcatdest'} = $_->{'_xcatdest'}->[0];
|
|
}
|
|
if ($_->{'_xcatdest'} and thishostisnot($_->{'_xcatdest'})) {
|
|
$ENV{XCATHOST} = ( $_->{'_xcatdest'} =~ /:/ ? $_->{'_xcatdest'} : $_->{'_xcatdest'}.":3001" );
|
|
$$progname.=": connection to ".$ENV{XCATHOST};
|
|
eval {
|
|
undef $_->{'_xcatdest'};
|
|
xCAT::Client::submit_request($_,\&dispatch_callback,$xcatdir."/cert/server-cred.pem",$xcatdir."/cert/server-cred.pem",$xcatdir."/cert/ca.pem");
|
|
};
|
|
if ($@) {
|
|
my $errstr=$@;
|
|
dispatch_callback({error=>["Unable to dispatch command to ".$ENV{XCATHOST}.", command will not make changes to that server ($errstr)"],errorcode=>[1]});
|
|
xCAT::MsgUtils->message("S","Error dispatching request: ".$errstr);
|
|
}
|
|
} else {
|
|
$$progname.=": locally executing";
|
|
$SIG{CHLD}='DEFAULT';
|
|
${"xCAT_plugin::".$modname."::"}{process_request}->($_,\&dispatch_callback,\&do_request);
|
|
}
|
|
xexit;
|
|
}
|
|
while (($dispatch_children > 0) and ($child_fdset->count > 0)) { relay_dispatch($child_fdset) }
|
|
while (relay_dispatch($child_fdset)) { } #Potentially useless drain.
|
|
}
|
|
|
|
sub thishostisnot {
|
|
my $comparison = shift;
|
|
|
|
# use "ip addr" for linux, since ifconfig
|
|
# doesn't list "ip addr add" aliases for linux
|
|
#
|
|
my $cmd = ($^O !~ /^aix/i) ? "/sbin/ip addr" : "ifconfig -a";
|
|
my @ips = split /\n/,`$cmd`;
|
|
my $comp=inet_aton($comparison);
|
|
unless ($comp) {
|
|
return 1;
|
|
}
|
|
foreach (@ips) {
|
|
if (/^\s*inet/) {
|
|
my @ents = split(/\s+/);
|
|
my $ip=$ents[2];
|
|
$ip =~ s/\/.*//;
|
|
if (inet_aton($ip) and inet_aton($ip) eq $comp) {
|
|
return 0;
|
|
}
|
|
#print Dumper(inet_aton($ip));
|
|
}
|
|
}
|
|
return 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');
|
|
yield; #parent must get timeslice anyway before an ack could possibly return
|
|
my $parsel = new IO::Select;
|
|
$parsel->add($parent_fd);
|
|
my $selbits = $parsel->bits;
|
|
my $rsp;
|
|
while ($selbits && ($rsp = select($selbits, undef, undef, 5))) { #block up to five seconds
|
|
if ($quit) { # Obey quit flag
|
|
xexit 0;
|
|
}
|
|
if ($rsp == 0) { #This means the filedescriptor was removed
|
|
last;
|
|
}
|
|
if ($rsp < 0) { # A signal caused select to skip out, do-over
|
|
next;
|
|
}
|
|
#At this point, the only possibility is a positive return, meaning parent_fd requires attention of some sort
|
|
$rsp = <$parent_fd>;
|
|
if ($rsp) { #If data actually came in, last, otherwise, remove it from the IO::Select, but both should amount to the same thing
|
|
last;
|
|
} else {
|
|
$parsel->remove($parent_fd);
|
|
last;
|
|
}
|
|
}
|
|
yield; #If still around, it means a peer process still hasn't gotten to us, so might as well yield
|
|
$selbits = $parsel->bits;
|
|
}
|
|
|
|
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..
|
|
if ($@ =~ /^SIGPIPE/) {
|
|
xCAT::MsgUtils->message("S","xcatd: Unexpected client disconnect");
|
|
if ($sock) {
|
|
eval {
|
|
print $sock XMLout({error=>"Generic PIPE error occurred. $@"},RootName=>'xcatresponse',NoAttr=>1);
|
|
};
|
|
}
|
|
} elsif ($@ =~ /Client abort requested/) {
|
|
} else {
|
|
my $errstr="A fatal error was encountered, the following information may help identify a bug: $@";
|
|
chomp($errstr);
|
|
xCAT::MsgUtils->message("S","xcatd: possible BUG encountered by xCAT TCP service: ".$@);
|
|
if ($sock) {
|
|
eval {
|
|
print $sock XMLout({error=>$errstr},RootName=>'xcatresponse',NoAttr=>1);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
$SIG{ALRM}= sub { die "$$ failed shutting down" };
|
|
alarm(10);
|
|
foreach (keys %tables) {
|
|
$tables{$_}->commit;
|
|
}
|
|
$sock->close(SSL_fast_shutdown=>1);
|
|
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;
|
|
my $goneclient=0;
|
|
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
|
|
eval {
|
|
print $sock $line;
|
|
};
|
|
if ($@ and $@ =~ /PIPE/) {
|
|
$goneclient=1;
|
|
print "Piped while writing to client\n";
|
|
last;
|
|
}
|
|
if ($line =~ /<\/xcatresponse>/) {
|
|
last;
|
|
}
|
|
}
|
|
if ($line) {
|
|
print $rfh "nfin\n"; #Notify convey_response message done
|
|
} else {
|
|
$fds->remove($rfh);
|
|
close($rfh);
|
|
}
|
|
}
|
|
foreach my $rin ($clientselect->can_read(0)) {
|
|
my $subselect = new IO::Select;
|
|
$subselect->add($rin);
|
|
my $clientintr="";
|
|
my $subdata;
|
|
while ($subselect->can_read(1)) {
|
|
if ($subdata=<$rin>) {
|
|
$clientintr.=$subdata;
|
|
} else {
|
|
$subselect->remove($rin);
|
|
close($rin);
|
|
}
|
|
}
|
|
unless ($clientintr) {
|
|
next;
|
|
}
|
|
$clientintr=XMLin($clientintr, SuppressEmpty=>undef,ForceArray=>1 );
|
|
if ($clientintr->{abortcommand}->[0]) {
|
|
print "Aborting...";
|
|
foreach (keys %plugin_children) {
|
|
print "Sending INT to $_\n";
|
|
kill 2, $_;
|
|
}
|
|
foreach my $cin ($fds->handles) {
|
|
print $cin "die\n";
|
|
$fds->remove($cin);
|
|
close($cin);
|
|
}
|
|
die "Client abort requested";
|
|
}
|
|
}
|
|
yield; #Give other processes, including children, explicit control, to avoid uselessly aggressive looping
|
|
if ($goneclient) {
|
|
die "SIGPIPE $$progname encountered a broken pipe (Sudden client disconnect)"
|
|
}
|
|
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) {
|
|
xCAT::MsgUtils->message("S","Unable to open policy data, denying");
|
|
return 0;
|
|
}
|
|
my @policies = $policytable->getTable;
|
|
$policytable->close;
|
|
my $rule;
|
|
RULE: foreach $rule (@policies) {
|
|
if ($rule->{name} and $rule->{name} ne '*') {
|
|
#TODO: more complex matching (lists, wildcards)
|
|
next unless ($peername and $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 '*') {
|
|
my $parms;
|
|
if ($request->{arg}) {
|
|
$parms = join(' ',@{$request->{arg}});
|
|
} else {
|
|
$parms = "";
|
|
}
|
|
my $patt = $rule->{parameters};
|
|
unless ($parms =~ /$patt/) {
|
|
next;
|
|
}
|
|
}
|
|
if ($rule->{noderange} and $rule->{noderange} ne '*') {
|
|
my $matchall=0;
|
|
if ($rule->{rule} =~ /allow/i or $rule->{rule} =~ /accept/i) {
|
|
$matchall=1;
|
|
}
|
|
if (defined $request->{noderange}->[0]) {
|
|
my @tmpn=noderange($request->{noderange}->[0]);
|
|
$request->{node}=\@tmpn;
|
|
}
|
|
unless (defined $request->{node}) {
|
|
next RULE;
|
|
}
|
|
my @reqnodes = @{$request->{node}};
|
|
my %matchnodes;
|
|
foreach (noderange($rule->{noderange})) {
|
|
$matchnodes{$_}=1;
|
|
}
|
|
REQN: foreach (@reqnodes) {
|
|
if (defined ($matchnodes{$_})) {
|
|
if ($matchall) {
|
|
next REQN;
|
|
} else {
|
|
last REQN;
|
|
}
|
|
} elsif ($matchall) {
|
|
next RULE;
|
|
}
|
|
}
|
|
}
|
|
# 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 };
|
|
if ($peerhost) { $logst .= " from " . $peerhost };
|
|
xCAT::MsgUtils->message("S",$logst);
|
|
return 1;
|
|
} else {
|
|
my $logst = "xCAT: Denying ".$request->{command}->[0];
|
|
if ($peername) { $logst .= " for " . $peername };
|
|
if ($peerhost) { $logst .= " from " . $peerhost };
|
|
xCAT::MsgUtils->message("S",$logst);
|
|
return 0;
|
|
}
|
|
} else { #Shouldn't be possible....
|
|
xCAT::MsgUtils->message("S","Impossible line in xcatd reached");
|
|
return 0;
|
|
}
|
|
}
|
|
#Reached end of policy table, reject by default.
|
|
xCAT::MsgUtils->message("S","Request matched no policy rule: ".$request->{command}->[0]);
|
|
return 0;
|
|
}
|
|
|