#!/usr/bin/perl # IBM_PROLOG_BEGIN_TAG # This is an automatically generated prolog. # # # # Licensed Materials - Property of IBM # # (C) COPYRIGHT International Business Machines Corp. 2001,2019 # All Rights Reserved # # US Government Users Restricted Rights - Use, duplication or # disclosure restricted by GSA ADP Schedule Contract with IBM Corp. # # IBM_PROLOG_END_TAG # sccsid = "@(#)53 1.26 src/rsct/rm/ConfigRM/cli/bin/preprpnode.perl, configrmcli, rsct_rady, rady2035a 11/12/15 16:40:58" ###################################################################### # # # Module: preprpnode # # # # Purpose: # # preprpnode - prepares a node to join an RSCT peer domain. # # # # Syntax: # # preprpnode [-h] [-k] [-TV] Node_name [Node_name ...] # # # # preprpnode [-h] [-k] -f File | -F File [-TV] # # # # Flags: # # -h Help. Writes the command's usage statement to standard # # output. # # -f File The file containing the nodes names to be added to the # # peer domain. The node names are defined as Node_name # # operands or in a file, not both. When in a file, each # # line of the file is scanned for one node name. Comments # # may be placed on each line, but after the comment # # character "#". Lines that start (col 1) with "#" or are # # entirely blank are ignored. Use -f "-" to specify STDIN # # as the input file. # # -F File Same as -f. # # -k Do not exchange public keys. # # -T Trace. Writes the command's trace messages to standard # # error. For your software-service organization's use only.# # -V Verbose. Writes the command's verbose messages to # # standard output. # # # # Operands: # # Node_name The node to prepare to join the peer domain. # # # # Description: # # The preprpnode command performs the proper actions to set up a # # peer domain. # # # # # # # # Exit Values: # # 0 CRM_CLI_SUCCESS Command completed successfully. # # 1 CRM_CLI_RMC_ERROR Command terminated due to an underlying # # RMC error. # # 2 CRM_CLI_ERROR Command terminated due to an underlying # # error in the command script. # # 3 CRM_CLI_BAD_FLAG Command terminated due to user # # specifying an invalid flag. # # 4 CRM_CLI_BAD_OPERAND Command terminated due to user # # specifying a bad operand. # # 5 CRM_CLI_USER_ERROR Command terminated due to a user error, # # for example specifying a name that # # already exists. # # # # Examples: # # 1. To prepare a node to join a peer domain, run: # # preprpnode abc.def.com # # # # Man Page: # # For the most current detailed description of this command see # # the preprpnode man page in /opt/rsct/man. # # # #--------------------------------------------------------------------# # Inputs: # # /opt/rsct/msgmaps/configrmcli.preprpnode.map - # # message mapping # # # # Outputs: # # stdout - none. # # stderr - any error message. # # # # External Ref: # # Commands: ctdspmsg # # Modules: CRM_cli_utils.pm, CRM_cli_rc.pm, # # CRM_cli_include.pm, CT_cli_utils.pm # # Perl library routines: Getopt::Std # # # # Tab Settings: # # 4 and tabs should be expanded to spaces before saving this file. # # in vi: (:set ts=4 and :%!expand -4) # # # # Change Activity: # # 011225 JAC 77313: Initial design & write. # # 020320 JAC 81275: Use lsrsrc-api and rework some acls updates. # # 020422 JAC 82248: Rename prepclnode to preprpnode. # # 020422 JAC 82249: Change cluster resource class name. # # 020428 JAC 82316: Call process_exit_code to check $rc. # # 020506 JAC 82449: Add -f flag and change acls processing. # # 020621 JAC 83646: Change rmcctrl rc check for only rc. # # 021205 JAC 89447: Put both name and IP addr in THL and ACLs. # # 030307 JAC 92297: Continue to next node instead of exit on error.# # 030728 JAC 94796: Redo some ACL file processing due to # # corruption problems. # # 030908 JAC 96488: Get Hostname from HostPublic class and add # # to rmc acls. # # 030929 JAC 99773: Add IPaddr of Hostname to rmcacls. # # 040319 JAC 105987: Add -F flag to do the same as -f. # # 040406 JAC 106712: Update usage for file options. # # 040502 JAC 108124: Add Hostname and it's IP addr to THL. # # 040524 JAC 101261: Use error msg with node name when lsrsrc fails# # 050406 JAC 119510: Add rc when calling process_api_error. # # 050805 JAC 127228: Fix find_stanzas for tabs and when to mv file.# # 050818 JAC 127309: Check print and close return codes. # # 050901 JAC 128532: Remove ACL file code and call chrmcacl. # # 060712 JAC 136899: Check for null PublicKey value. # ###################################################################### #--------------------------------------------------------------------# # General Program Flow/Logic: # # # # 1. Determine if public keys need to be exchanged (no -k) # # 2. Enable remote connections for rmc (rmcctrl -p) # # 3. For each node, # # a. retrieve its public key (lsrsrc on IBM.HostPublic) # # b. add it to trusted host list (ctsthl cmd) # # c. add root@node to rmc acls giving it rw permission to # # IBM.SharedResourceCluster # # 4. Return back any errors. # # # #--------------------------------------------------------------------# #--------------------------------------------------------------------# # Included Libraries and Extensions # #--------------------------------------------------------------------# use lib "/opt/rsct/pm"; use locale; use Getopt::Std; use CT_cli_utils qw(printIMsg printEMsg); use CRM_cli_rc qw(CRM_CLI_SUCCESS CRM_CLI_RMC_ERROR CRM_CLI_ERROR CRM_CLI_BAD_FLAG CRM_CLI_BAD_OPERAND CRM_CLI_USER_ERROR); use CRM_cli_utils qw(error_exit printCIMsg printCEMsg resolve_node_names_ipaddrs process_api_error process_exit_code remove_api_error get_nodes_nums_from_file get_opstate_by_name); use CRM_cli_include qw($TRUE $FALSE $RSCLUSTER $RMC_CLI_USER_ERROR $DELIMITER $CTBINDIR $CTDIR); #--------------------------------------------------------------------# # Global Variables # #--------------------------------------------------------------------# $Trace = $FALSE; # default - trace off $Verbose = $FALSE; # default - verbose turned off $Opt_NoKeys = $FALSE; # default - skip key exchange $Opt_File_Input = $FALSE; # default - no file $PROGNAME = "preprpnode"; # Program Name for messages $LSMSG = "$CTBINDIR/ctdspmsg"; # list / display message rtn $MSGCAT = "configrmcli.cat"; # msg catalogue for this cmd $ENV{'MSGMAPPATH'} = "$CTDIR/msgmaps"; # msg maps used by $LSMSG #--------------------------------------------------------------------# # Variables # #--------------------------------------------------------------------# my @node_names = (); # nodes in cluster my $one_node = ""; # one from nodelist my $resolved_node_names = ""; # resolved node names my $DNS_or_IP_names = ""; # DNS/IP hash reference my $unresolved_node_names = ""; # unresolved node names my $resolved_hostnames = ""; # resolved Hostnames my $DNS_or_IP_of_hostnames = ""; # DNS/IP hash for Hostnames my $unresolved_hostnames = ""; # unresolved Hostnames my $resolved_node_list = ""; # resolved node list my @lsr_out = (); # output from lsrsrc to get key my @cmd_out = (); # output from a command my $method = ""; # trusted key method my $keyvalue = ""; # trusted key value my $file_name = ""; # input file name my $node_names_file = ""; # array reference my $node_nums_file = ""; # array reference my @dns_or_ip_names = (); # dns or IP names my $one_dns_or_ip_name = ""; # one from dns_or_ip_names my $pubkey = ""; # public key from HostPublic my $hostname = ""; # hostname from HostPublic my @Lhostnames = (); # list of hostnames my @Lhostnames_unique = (); # list of new hostnames my @THL_names = (); # list to put in THL my $i = 0; my $j = 0; my $found = $FALSE; my $passopts = ""; # TV options to pass to RMC CLI my $other_opts = ""; # parameters to pass to RMC CLI #--------------------------------------------------------------------# # Main Code # #--------------------------------------------------------------------# my $rc = 0; my $config_rc = 0; # parse the command line, exit if there are errors ($rc, $file_name, @node_names) = &parse_cmd_line; ($rc == 0) || error_exit($rc); if ($Verbose) { printIMsg("IMsgpreprpnodeStart"); } if ($Trace) { $passopts = $passopts." -T"; } if ($Verbose) { $passopts = $passopts." -V"; } # get the node names and numbers from a file, if specified if ($Opt_File_Input) { # extract the node names and numbers from the file ($node_names_file, $node_nums_file) = get_nodes_nums_from_file($file_name); # copy to other array (don't care about node numbers) @node_names = @$node_names_file; } # enable remote rmc connections if ($Trace) { print STDERR "$PROGNAME: calling rmcctrl\n";} @cmd_out = `$CTBINDIR/rmcctrl -p 2>&1`; # capture the return code from rmcctrl $rc = $?; $rc = process_exit_code($rc); if ($Trace) { print STDERR "$PROGNAME: rmcctrl returned $rc\n";} # process any errors from rmcctrl if ($rc != 0) { print STDERR @cmd_out; printEMsg("EMsgpreprpnodermcctrlErr",$rc); exit(CRM_CLI_RMC_ERROR); } # rmcctrl ok but check for output else { if ($#cmd_out >= 0) { print @cmd_out; } } # resolve the node names # (rmc acls format requires it so might as well get it over with) ($resolved_node_names, $DNS_or_IP_names, $unresolved_node_names) = resolve_node_names_ipaddrs(@node_names); # write an error message for each unresolved node name found # continue processes but set rc foreach $one_node (@$unresolved_node_names) { printEMsg("EMsgpreprpnodeUnresolvedNode",$one_node); $config_rc = CRM_CLI_USER_ERROR; } # exit if there are no node names left to process if ($#$resolved_node_names < 0) { exit($config_rc); } # exchange public key, if required (which is the default) if (!$Opt_NoKeys) { # set the security to unauthenticated $ENV{CT_SEC_MECH} = "none"; # set the rmc management scope to local $ENV{CT_MANAGEMENT_SCOPE} = 1; # for each node passed to preprpnode, get its public key # and add it to the local node's trusted host list foreach $one_node (@$resolved_node_names) { # get its public key # set CT_CONTACT to access remote node $ENV{CT_CONTACT} = $one_node; # run lsrsrc to get the public key from the remote node if ($Trace) { print STDERR "$PROGNAME: calling lsrsrc-api for $one_node\n";} # Use a DELIMITER that isn't in IPv6-looking addresses, otherwise process_api_error() # (called if remove_hostname_not_found() fails) won't output messages correctly # if the hostname just so happens to be an IPv6 address. $DELIMITER = "#:#"; @lsr_out = `$CTBINDIR/lsrsrc-api -D $DELIMITER -o IBM.HostPublic::::::PublicKey::Hostname 2>&1`; # capture the return code from lsrsrc $rc = $?; $rc = process_exit_code($rc); if ($Trace) { print STDERR "lsrsrc-api results:\n"; print STDERR "@lsr_out";} if ($Trace) { print STDERR "$PROGNAME: lsrsrc-api returned $rc\n";} # remove any Hostname attribute not found error ($rc, @lsr_out) = &remove_hostname_not_found($rc, $DELIMITER, @lsr_out); # show any errors if there was a bad rc if ($rc != 0) { process_api_error($DELIMITER,$rc,@lsr_out); printEMsg("EMsgpreprpnodeKeyUnavail",$one_node); } # return ConfigRM CLI user error if it's an RMC CLI user error if ($rc == $RMC_CLI_USER_ERROR) { #exit(CRM_CLI_USER_ERROR); $config_rc = CRM_CLI_USER_ERROR; next; } # if lsrsrc failed for something else, print RMC error message and exit if ($rc != 0) { #exit(CRM_CLI_RMC_ERROR); $config_rc = CRM_CLI_RMC_ERROR; next; } # split the output into the public key and the hostname ($pubkey, $hostname) = split /$DELIMITER/, $lsr_out[0]; chomp($pubkey); chomp($hostname); # make sure the public key isn't null (not been set) if ($pubkey =~ /^\[,\]$/) { printEMsg("EMsgpreprpnodeKeyIsBadErr",$one_node); $config_rc = CRM_CLI_RMC_ERROR; next; } # clear out list of node to put in THL to rebuild it @THL_names = (); # if there's a hostname, save it to add to acls if ($hostname !~ /^$/ ) { $hostname =~ s/\"//g; push (@Lhostnames,$hostname); push (@THL_names, $hostname); } # get the IP address of the Hostname from IBM.HostPublic ($resolved_hostnames, $DNS_or_IP_of_hostnames, $unresolved_hostnames) = resolve_node_names_ipaddrs($hostname); # add the IP addrs to the hostname list and THL list @dns_or_ip_names = keys %$DNS_or_IP_of_hostnames; foreach $one_dns_or_ip_name (@dns_or_ip_names) { push (@Lhostnames,$$DNS_or_IP_of_hostnames{$one_dns_or_ip_name}); push (@THL_names,$$DNS_or_IP_of_hostnames{$one_dns_or_ip_name}); } # add the remote node to THL push (@THL_names, $one_node); # if it's an IP addr, add the DNS name too, and vice versa if (defined $$DNS_or_IP_names{$one_node}) { push (@THL_names, $$DNS_or_IP_names{$one_node}); } # update THL with the list of names/IP addrs in @THL_names $rc = &add_host_to_thl($pubkey, @THL_names); # set config_rc if it's not already set if ($config_rc == 0) { $config_rc = $rc; } } #end foreach to exchange key } # end if exchange keys required # Update ACLs # update the rmc acls file # use all resolved nodes plus any additional DNS names @node_names = @$resolved_node_names; @dns_or_ip_names = keys %$DNS_or_IP_names; foreach $one_dns_or_ip_name (@dns_or_ip_names) { push (@node_names,$$DNS_or_IP_names{$one_dns_or_ip_name}); } # add any hostnames found from HostPublic class to @node_names # but first make sure they're not already on the list for ($i=0; $i<=$#Lhostnames; $i++) { $found = $FALSE; for ($j=0; (($j<=$#node_names) && !$found); $j++) { if ($Lhostnames[$i] eq $node_names[$j]) { $found = $TRUE; } } if (!$found) { push(@Lhostnames_unique, $Lhostnames[$i]); } } # add the unique hostnames found from HostPublic class foreach $hostname (@Lhostnames_unique) { push (@node_names,$hostname); } &update_acls_file (@node_names); # refresh RMC to read in the new acls if ($Trace) { print STDERR "$PROGNAME: calling refresh\n";} @cmd_out = `refresh -s ctrmc 2>&1`; $rc = $?; $rc = process_exit_code($rc); if ($Trace) { print STDERR "$PROGNAME: refresh returned $rc\n";} # if it failed, print error message and exit if ($rc != 0) { print STDERR @cmd_out; printEMsg("EMsgpreprpnodeRefrErr"); exit(CRM_CLI_RMC_ERROR); } if ($Verbose) { printIMsg("IMsgpreprpnodeEnd"); } if ($config_rc == 0) { exit($rc); } else { exit($config_rc); } #--------------------------------------------------------------------# # End Main Code # #--------------------------------------------------------------------# #--------------------------------------------------------------------# # parse_cmd_line - Parse the command line for options and operands. # # Set appropriate global variables as outlined below, make sure we # # have a valid combination of arguments / options. # # # # Return: # # $rc 0 Command line parsed fine, no problem. # # CRM_CLI_BAD_FLAG Command line contained a bad flag. # # @node_names Nodes to belong to the cluster # # # # Global Variables Modified: # # $Verbose output True (-V) turn Verbose mode on. # # $Trace output True (-T) turn Trace mode on. # # $Opt_File_Input output True (-f) file name specified # # $Opt_NoKeys output True (-k) no keys exchanged. # #--------------------------------------------------------------------# sub parse_cmd_line { my(@original_argv) = @ARGV; my @node_names = (); # node names my $file_name = ""; my %opts = (); # Process the command line... if (!&getopts('hf:F:kVT', \%opts)) { # Gather options; # if errors &print_usage; # display proper usage return CRM_CLI_BAD_FLAG; # return bad rc - bad flag } # process h flag if (defined $opts{h}) { # -h, help request &print_usage; # print usage statement exit(0); # all done with good return! } if (defined $opts{T}) { # -T turn trace on $Trace = $TRUE; } if (defined $opts{V}) { # -V turn verbose mode on $Verbose = $TRUE; } # Get the arguments... # Operands: one or more node names if ($#ARGV >= 0) { # at least one node name @node_names = @ARGV; # get node names } # make sure -f for file used if there are no node names if (($#node_names < 0) && !(defined $opts{f}||defined $opts{F})) { printCEMsg("EMsgConfigRMcliInvalidNumberOfOperands"); &print_usage; return CRM_CLI_BAD_OPERAND; } if (defined $opts{f}) { # -f for file # make sure both -f and -F not used together if (defined $opts{F}) { printCEMsg("EMsgConfigRMcliImproperUsageCombination","-f","-F"); &print_usage; return CRM_CLI_BAD_FLAG; } $Opt_File_Input = $TRUE; $file_name = $opts{f}; # make sure file and cmd line not both used for node names if ($#node_names >= 0) { printCEMsg("EMsgConfigRMcliInvalidNumberOfOperands"); &print_usage; return CRM_CLI_BAD_OPERAND; } } if (defined $opts{F}) { # -F for file $Opt_File_Input = $TRUE; $file_name = $opts{F}; # make sure file and cmd line not both used for node names if ($#node_names >= 0) { printCEMsg("EMsgConfigRMcliInvalidNumberOfOperands"); &print_usage; return CRM_CLI_BAD_OPERAND; } } if (defined $opts{k}) { # -k for no key exchange $Opt_NoKeys = $TRUE; # -k flag specified } return(0, $file_name, @node_names); # success } # end parse_cmd_line #--------------------------------------------------------------------# # print_usage : print the usage statement (syntax) to stdout. # #--------------------------------------------------------------------# sub print_usage { &printIMsg("IMsgpreprpnodeUsageF"); } # end print_usage #--------------------------------------------------------------------# # add_host_to_thl: # # Adds the hosts included in the call to the THL file. The # # inputs are the public key attribute (from IBM.HostPublic) and # # the list of host names or IP addrs to use as the -n host for # # the ctsthl command. ctsthl is run one or more times, once for # # each host name or IP address. # # # # Paramaters: # # $pubkey The public key attribute from IBM.HostPublic. # # @thl_hosts The list of host names/IP adds to add to THL. # # # # Returns: # # $configrc =0 if all adds are successful. # # !0 if any errors. # # # #--------------------------------------------------------------------# sub add_host_to_thl { my $pubkey = shift(@_); my @thl_hosts = @_; my $method = ""; # trusted key method my $keyvalue = ""; # trusted key value my $one_host = ""; # a single host/IP addr my @cmd_out = (); # output from ctsthl my $rc = 0; # initialize rc my $configrc = 0; # initialize rc to return # parse the public key which is an SD of method and key ($method, $keyvalue) = split /,/, $pubkey; # do some cleaning up of the punctuation around the values $method =~ s/\"//g; $method =~ s/\[//g; $method =~ s/\]//g; $method =~ s/://g; $keyvalue =~ s/\"//g; $keyvalue =~ s/\[//g; $keyvalue =~ s/\]//g; $keyvalue =~ s/://g; # loop through each host/IP addr and add it to THL foreach $one_host (@thl_hosts) { # add this host/IP addr to local node's trusted host list if ($Trace) { print STDERR "$PROGNAME: calling ctsthl for $one_host\n";} @cmd_out = `$CTBINDIR/ctsthl -a -n $one_host -m $method -p $keyvalue 2>&1`; # capture the return code from ctsthl $rc = $?; $rc = process_exit_code($rc); if ($Trace) { print STDERR "$PROGNAME: ctsthl returned $rc\n";} # process any errors from ctsthl if ($rc != 0) { print STDERR @cmd_out; printEMsg("EMsgpreprpnodectsthlErr",$one_host,$rc); #exit(CRM_CLI_RMC_ERROR); $configrc = CRM_CLI_RMC_ERROR; next; } } return ($configrc); } # end add_host_to_thl #--------------------------------------------------------------------# # update_acls_file - Make updates to the acls file for configrm. # # Use the @node_names array to give access to root at these # # nodes. # # # # Input: # # @node_names The nodes to be used for preprpnode. # # # # Returns: # # None. # #--------------------------------------------------------------------# sub update_acls_file { my $i = -1; # acl file counter my $node_name = ""; my $linenum; my @cmd_out = (); my @acl_stanza_lines = (); my $acl_stanza_line = ""; my $UPDACL_error = $FALSE; # get the node list passed in my @node_names = @_; # create the ACL stanza lines that preprpnode needs to add # first for the Peer Domain class $acl_stanza_lines[++$i] = "$RSCLUSTER // Peer Domain class - preprpnode\n"; $acl_stanza_lines[++$i] = " none:root * rw // root on any node of active cluster - preprpnode\n"; $acl_stanza_lines[++$i] = " none:any_root * rw // root on any node of any cluster that this node is defined to - preprpnode\n"; # add the node names foreach $node_name (@node_names) { $acl_stanza_lines[++$i] = " root\@$node_name * rw // cluster node - preprpnode\n"; } # second, add the access for the DEFAULT class $acl_stanza_lines[++$i] = "DEFAULT // DEFAULT stanza - preprpnode\n"; $acl_stanza_lines[++$i] = " root\@LOCALHOST * rw // default authority for root - preprpnode\n"; $acl_stanza_lines[++$i] = " LOCALHOST * r // default authority for other user - preprpnode\n"; $acl_stanza_lines[++$i] = " none:root * rw // give root on any node access to all - preprpnode\n"; $acl_stanza_lines[++$i] = " none:clusteruser * r // give non-root on any node read access to all - preprpnode\n"; # call chrmcacl using a pipe to pass the ACL info if ($Trace) { print STDERR "$PROGNAME: calling chrmcacl\n";} if ( open(UPDACL,"|/opt/rsct/install/bin/chrmcacl -a ") ) { if ( print UPDACL @acl_stanza_lines ) { if (!( close(UPDACL) )) { # close failed $UPDACL_error = $TRUE; } } # print failed else { $UPDACL_error = $TRUE; } } # open failed else { $UPDACL_error = $TRUE; } # check the return code in case of error from chrmacl $rc = $?; $rc = process_exit_code($rc); if ( ($rc != 0) && $Trace ) { print STDERR "$PROGNAME: chrmcacl returned $rc\n"; } # if anything failed, print error message and exit if ( ($rc != 0) || $UPDACL_error ) { printEMsg("EMsgpreprpnodeChangeAclErr"); exit(CRM_CLI_RMC_ERROR); } } # end update_acls_file #--------------------------------------------------------------------# # remove_hostname_not_found - Scans the input varaible for error # # messages thought to be from the hostname attribute not found # # error. This error os deleted from the input variable, and if # # there are no other error, the rc variable is changed to 0. # # # # Parameters: # # $rc Return code from lsrsrc-api. # # $delim Delimiter for lsrsrc-api output. # # @command_output The output from the command. # # # # Returns: # # @errorless_output The original @command_output array contents # # with the hostname not found errors removed. # # # # Global Variables: # #--------------------------------------------------------------------# sub remove_hostname_not_found { my $rc = shift @_; # get the return code my $delim = shift @_; # get the delimiter my @command_output = @_; # command output to scan my @new_output = (); # Hostname errors removed my $line = ""; # for each output line my @err_msg = (); # split error message my $num_err = 0; # number of errors found my $num_host_err = 0; # number of hostname errors found # scan each line for ERROR foreach $line (@command_output) { # does it start with ERROR? if (!($line =~ /^ERROR.*/)) { # put it in the errorless array push @new_output, $line; } # so it's an error message else { $num_err++; # parse the message # if there's a rc=5, class is HostPublic, and there's "Hostname" in the # message text, it's probably the Hostname not found error. # message format: ERROR::IBM.HostPublic::::::5::::0::lsrsrc-api: 2612-018 Attribute name # Hostname is not a valid attribute name. @err_msg = split /$delim/, $line; if ( ($err_msg[1] =~ /^IBM.HostPublic/ ) && ($err_msg[2] == 5 ) && ($err_msg[5] =~ /Hostname/) ) { $num_host_err++; } else { # put it in the new output array push @new_output, $line; } } } # reset rc if needed if ( ($num_err > 0) && ($num_err == $num_host_err) ) { $rc = 0; } return ($rc, @new_output); } # end of remove_hostname_not_found