#!/usr/bin/perl # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html use xCAT::TZUtils; use xCAT::WinUtils; package xCAT::Template; use strict; use xCAT::Table; use File::Basename; use File::Path; #use Data::Dumper; use Sys::Syslog; use xCAT::ADUtils; #to allow setting of one-time machine passwords use xCAT::Utils; use xCAT::TableUtils; use xCAT::NetworkUtils; use xCAT::PasswordUtils; use xCAT::MsgUtils; use XML::Simple; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat'; } my $netdnssupport = eval { require Net::DNS; 1; }; my $tmplerr; my $table; my $key; my $field; my $idir; my $node; my %loggedrealms; my $lastmachinepassdata; my $localadminenabled; #indicate whether Windows template has local logins enabled or not my %tab_replacement = ( "noderes:nfsserver" => "noderes:xcatmaster", "noderes:tftpserver" => "noderes:xcatmaster", ); sub subvars { my $self = shift; my $inf = shift; my $outf = shift; $tmplerr = undef; #clear tmplerr since we are starting fresh $node = shift; my $pkglistfile = shift; my $media_dir = shift; my $platform = shift; my $partitionfileval = shift; my $tmpl_hash = shift; my %namedargs = @_; #further expansion of this function will be named arguments, should have happened sooner. unless ($namedargs{reusemachinepass}) { $lastmachinepassdata->{password} = ""; } my $outh; my $inh; $idir = dirname($inf); open($inh, "<", $inf); unless ($inh) { return "Unable to open $inf, aborting"; } mkpath(dirname($outf)); open($outh, ">", $outf); unless ($outh) { return "Unable to open $outf for writing/creation, aborting"; } my $inc; #First load input into memory.. while (<$inh>) { $inc .= $_; } close($inh); #the logic to determine the $ENV{XCATMASTER} confirm to the following priority(from high to low): ## 1, the "xcatmaster" attribute of the node ## 2, the ip address of the mn/sn facing the compute node ## 3, the site.master my $master; #the "xcatmaster" attribute of the node if ($tmpl_hash->{'xcatmaster'}) { $master = $tmpl_hash->{'xcatmaster'}; } unless ($master) { #the ip address of the mn facing the compute node my $ipfn; my @ipfnd = xCAT::NetworkUtils->my_ip_facing($node); unless ($ipfnd[0]) { $ipfn = $ipfnd[1]; } if ($ipfn) { $master = $ipfn; } } unless ($master) { #the site.master my @masters = xCAT::TableUtils->get_site_attribute("master"); my $tmp = $masters[0]; if (defined($tmp)) { $master = $tmp; } } unless ($master) { $tmplerr = "Unable to identify master for $node"; return; } $ENV{XCATMASTER} = $master; my $ipaddr = xCAT::NetworkUtils->getipaddr($master); if ($ipaddr) { $ENV{MASTER_IP} = "$ipaddr"; } my @nodestatus = xCAT::TableUtils->get_site_attribute("nodestatus"); my $tmp = $nodestatus[0]; if (defined($tmp)) { $ENV{NODESTATUS} = $tmp; } $ENV{PERSKCMDLINE} = getPersistentKcmdline($node); my $installroot; $installroot = xCAT::TableUtils->get_site_attribute("installdir"); if (!defined($installroot)) { $installroot = "/install"; } $ENV{INSTALLDIR} = $installroot; #replace the env with the right value so that correct include files can be found $inc =~ s/#ENV:([^#]+)#/envvar($1)/eg; my $res; if ($pkglistfile) { #substitute the tag #INCLUDE_DEFAULT_PKGLIST# with package file name (for full install of rh, centos,SL, esx fedora) $inc =~ s/#INCLUDE_DEFAULT_PKGLIST#/#INCLUDE:$pkglistfile#/g; #substitute the tag #INCLUDE_DEFAULT_PKGLIST_S# with package file name (for full install of sles) #substitute the tag #INCLUDE_DEFAULT_PERNLIST_S# with package file name (for full install sles #substitute the tag #INCLUDE_DEFAULT_RMPKGLIST_S# with package file name (for full install sles) $inc =~ s/#INCLUDE_DEFAULT_PKGLIST_S#/#INCLUDE_PKGLIST:$pkglistfile#/g; $inc =~ s/#INCLUDE_DEFAULT_PTRNLIST_S#/#INCLUDE_PTRNLIST:$pkglistfile#/g; $inc =~ s/#INCLUDE_DEFAULT_RMPKGLIST_S#/#INCLUDE_RMPKGLIST:$pkglistfile#/g; } if (("ubuntu" eq $platform) || ("debian" eq $platform)) { # since debian/ubuntu uses a preseed file instead of a kickstart file, pkglist # must be included via simple string replacement instead of using includefile() # the first line of $pkglistfile is the space-delimited package list # the additional lines are considered preseed directives and included as is if ($pkglistfile) { my $allpkglist = xCAT::Postage->get_pkglist_tex($pkglistfile); if ($allpkglist =~ /#INCLUDEBAD:(.*)#/) { return "$1"; } $allpkglist =~ s/,/ /g; $inc =~ s/#INCLUDE_DEFAULT_PKGLIST_PRESEED#/$allpkglist/g; } else { # handle no $pkglistfile $inc =~ s/#INCLUDE_DEFAULT_PKGLIST_PRESEED#//g; } } #if user specify the partion file, replace the default partition strategy my $partcontent; my $diskcontent; my $configcontent; my $scriptflag = 0; my $partscriptflag = 0; my $diskscriptflag = 0; my $configscriptflag = 0; my $preseedflag = 0; my $configfile; my $partitionfile; my $diskfile; my @partitionfilelist = split(/,/, $partitionfileval); foreach my $filepath (@partitionfilelist) { $scriptflag = 0; if ($filepath =~ /^s:(.*)/) { $scriptflag = 1; $filepath = $1; } if ($filepath =~ /^d:(.*)/) { $diskfile = $1; if ($scriptflag) { $diskscriptflag = 1; } } elsif ($filepath =~ /^c:(.*)/) { $configfile = $1; if ($scriptflag) { $configscriptflag = 1; } } elsif ($filepath) { $partitionfile = $filepath; if ($scriptflag) { $partscriptflag = 1; } } } if ($configfile) { if (-r $configfile) { open($inh, "<", $configfile); while (<$inh>) { $configcontent .= $_; } close($inh); } # append the partition file into the $inc to do the replacement $inc .= "\nFFFFFFFFFFFFPARTITIONCONFIGFILESTART\n" . $configcontent . "\nFFFFFFFFFFFFPARTITIONCONFIGFILEEND\n"; } if ($diskfile) { if (-r $diskfile) { open($inh, "<", $diskfile); while (<$inh>) { $diskcontent .= $_; } close($inh); } # append the partition file into the $inc to do the replacement $inc .= "\nFFFFFFFFFFFFPARTITIONDISKFILESTART\n" . $diskcontent . "\nFFFFFFFFFFFFPARTITIONDISKFILEEND\n"; } if ($partitionfile) { if ($inc =~ /#XCA_PARTMAN_RECIPE_SCRIPT#/) { $preseedflag = 1; } if (-r $partitionfile) { open($inh, "<", $partitionfile); while (<$inh>) { $partcontent .= $_; } close($inh); } # append the partition file into the $inc to do the replacement $inc .= "\nFFFFFFFFFFFFPARTITIONFILESTART\n" . $partcontent . "\nFFFFFFFFFFFFPARTITIONFILEEND\n"; } #do *all* includes, recursive for all my $doneincludes = 0; while (not $doneincludes) { $doneincludes = 1; $inc =~ s/#ENV:([^#]+)#/envvar($1)/eg; # handle the variable in #INCLUDE if ($inc =~ /#INCLUDE_PKGLIST:[^\n]+#/) { # get in this section but might not do the replace since it needs wait # for the replace of sting inside the #INCLUDE_PKGLIST:xx# $doneincludes = 0; $inc =~ s/#INCLUDE_PKGLIST:([^#^\n]+)#/includefile($1, 0, 1)/eg; } if ($inc =~ /#INCLUDE_PTRNLIST:[^\n]+#/) { $doneincludes = 0; $inc =~ s/#INCLUDE_PTRNLIST:([^#^\n]+)#/includefile($1, 0, 2)/eg; } if ($inc =~ /#INCLUDE_RMPKGLIST:[^\n]+#/) { $doneincludes = 0; $inc =~ s/#INCLUDE_RMPKGLIST:([^#^\n]+)#/includefile($1, 0, 3)/eg; } if ($inc =~ /#INCLUDE:[^\n]+#/) { $doneincludes = 0; $inc =~ s/#INCLUDE:([^#^\n]+)#/includefile($1, 0, 0)/eg; } #support multiple paths of osimage in rh/sles diskfull installation my @pkgdirs; if (defined($media_dir)) { @pkgdirs = split(",", $media_dir); my $source; my $source_in_pre; my $writerepo; my $c = 0; foreach my $pkgdir (@pkgdirs) { if ($platform =~ /^(rh|SL|centos|fedora)$/) { if ($c == 0) { # After some tests, if we put the repo in pre scripts in the kickstart like for rhels6.x # the rhels5.9 will not be installed successfully. So put in kickstart directly. $source_in_pre .= "echo 'url --url http://'\$nextserver'/$pkgdir' >> /tmp/repos"; $source .= "url --url http://#TABLE:noderes:\$NODE:nfsserver#/$pkgdir\n"; #For rhels5.9 } else { $source_in_pre .= "\necho 'repo --name=pkg$c --baseurl=http://'\$nextserver'/$pkgdir' >> /tmp/repos"; $source .= "repo --name=pkg$c --baseurl=http://#TABLE:noderes:\$NODE:nfsserver#/$pkgdir\n"; #for rhels5.9 } my $distrepofile="/install/postscripts/repos/$pkgdir/local-repository.tmpl"; if( -f "$distrepofile"){ my $repofd; my $repo_in_post; local $/=undef; open($repofd,"<","$distrepofile"); $repo_in_post = <$repofd>; close($repofd); $repo_in_post =~ s#baseurl=#baseurl=http://$master/#g; $writerepo .= "\ncat >/etc/yum.repos.d/local-repository-$c.repo << 'EOF'\n"; $writerepo .="$repo_in_post\n"; $writerepo .="EOF\n"; } } elsif ($platform =~ /^(sles|suse)/) { my $http = "http://#TABLE:noderes:\$NODE:nfsserver#$pkgdir"; $source .= " $http SuSE-Linux-pkg$c / false SuSE-Linux-pkg$c "; $source_in_pre .= "http://'\$nextserver'$pkgdirSuSE-Linux-pkg$c/falseSuSE-Linux-pkg$c"; } $c++; } $inc =~ s/#INSTALL_SOURCES#/$source/g; $inc =~ s/#INSTALL_SOURCES_IN_PRE#/$source_in_pre/g; if (("ubuntu" eq $platform) || ("debian" eq $platform)) { $inc =~ s/#INCLUDE_OSIMAGE_PKGDIR#/$pkgdirs[-1]/; } $inc =~ s/#WRITEREPO#/$writerepo/g; } #ok, now do everything else.. my $shortname = $node; $shortname =~ s/\..*//; $inc =~ s/#TABLE:([^:]+):([^:]+):([^#]+)#/tabdb($1,$2,$3)/eg; $inc =~ s/#TABLEBLANKOKAY:([^:]+):([^:]+):([^#]+)#/tabdb($1,$2,$3,'1')/eg; $inc =~ s/#INCLUDE_NOP:([^#^\n]+)#/includefile($1,1,0)/eg; $inc =~ s/#XCATVAR:([^#]+)#/envvar($1)/eg; $inc =~ s/#ENV:([^#]+)#/envvar($1)/eg; $inc =~ s/#MACHINEPASSWORD#/machinepassword()/eg; $inc =~ s/#CRYPT:([^:]+):([^:]+):([^#]+)#/crydb($1,$2,$3)/eg; $inc =~ s/#COMMAND:([^#]+)#/command($1)/eg; $inc =~ s/#KICKSTARTNET#/kickstartnetwork()/eg; $inc =~ s/#MIRRORSPEC#/mirrorspec()/eg; $inc =~ s/#YAST2NET#/yast2network()/eg; $inc =~ s/#KICKSTARTBOOTLOADER#/kickstartbootloader()/eg; $inc =~ s/#ESXIPV6SETUP#/esxipv6setup()/eg; $inc =~ s/#WINTIMEZONE#/xCAT::TZUtils::get_wintimezone()/eg; $inc =~ s/#WINPRODKEY:([^#]+)#/get_win_prodkey($1)/eg; $inc =~ s/#WINNETCFG#/windows_net_cfg()/eg; $inc =~ s/#WINADJOIN#/windows_join_data()/eg; $inc =~ s/#WINPOSTSCRIPTS#/windows_postscripts()/eg; $inc =~ s/#WINDNSCFG#/windows_dns_cfg()/eg; $inc =~ s/#WINACCOUNTDATA#/windows_account_data()/eg; $inc =~ s/#WINDISABLENULLADMIN#/windows_disable_null_admin()/eg; $inc =~ s/#MANAGEDADDRESSMODE#/managed_address_mode()/eg; $inc =~ s/#HOSTNAME#/$node/g; $inc =~ s/#SHORTNAME#/$shortname/g; $inc =~ s/#GETNODEDOMAIN:([^#]+)#/get_node_domain($1)/eg; $inc =~ s/#GETPRINICMAC:([^#]+)#/xCAT::Utils::parseMacTabEntry(tabdb("mac",$1,"mac"),$1)/eg; if (($::XCATSITEVALS{xcatdebugmode} eq "1") or ($::XCATSITEVALS{xcatdebugmode} eq "2")) { $inc =~ s/#UNCOMMENTOENABLEDEBUGPORT#/ /g; } if ($::XCATSITEVALS{xcatdebugmode} eq "2") { $inc =~ s/#UNCOMMENTOENABLESSH#/ /g; } my $sles_sdk_media = "http://" . $tmpl_hash->{tftpserver} . $media_dir . "/sdk1"; $inc =~ s/#SLES_SDK_MEDIA#/$sles_sdk_media/eg; if ($partitionfile && $doneincludes) { #the content of the specified file is a script which can write partition definition into /tmp/partitionfile # split the partition file out from the $inc ($inc, $partcontent) = split(/FFFFFFFFFFFFPARTITIONFILESTART\n/, $inc); ($partcontent, $res) = split(/\nFFFFFFFFFFFFPARTITIONFILEEND/, $partcontent); $inc .= $res; if ($partscriptflag or $preseedflag) { # since the whole partition file needs be packaged in %pre first and generate an executable file at running time, # all the special chars like ',",%,\ need be kept, we have to use the base64 coding to code it and put it in # %pre and decode it out during the running time. use MIME::Base64; $partcontent = encode_base64($partcontent); $partcontent =~ s/\n//g; #for redhat/sl/centos/kvm/fedora if ($inc =~ /#XCAT_PARTITION_START#/) { # Put the base64 coded partitionfile into %pre part $partcontent = "cat > /tmp/partscript.enc << EOFEOF\n" . $partcontent . "\nEOFEOF\n"; $partcontent .= "rm -rf /tmp/partitionfile\n"; # Put the code to decode the partitionfile $partcontent .= "python -c 'import base64; print base64.b64decode(open(\"/tmp/partscript.enc\",\"rb\").read())' >/tmp/partscript\n"; $partcontent .= "chmod 755 /tmp/partscript\n"; $partcontent .= "/tmp/partscript\n"; #replace the #XCA_PARTITION_SCRIPT# $inc =~ s/#XCA_PARTITION_SCRIPT#/$partcontent/; } #for sles/suse elsif ($inc =~ //) { my $tempstr = "XCATPARTITIONTEMP"; $inc =~ s/[\s\S]*/$tempstr/; # Put the base64 coded partitionfile into %pre part $partcontent = "cat > /tmp/partscript.enc << EOFEOF\n" . $partcontent . "\nEOFEOF\n"; # Put the code to decode the partitionfile $partcontent .= "perl -ne 'use MIME::Base64; print decode_base64(\$_)' /tmp/partscript\n"; $partcontent .= "chmod 755 /tmp/partscript\n"; $partcontent .= "/tmp/partscript\n"; $inc =~ s/#XCA_PARTITION_SCRIPT#/$partcontent/; } #for ubuntu elsif ($inc =~ /#XCA_PARTMAN_RECIPE_SCRIPT#/) { # Put the base64 coded partitionfile into %pre part $partcontent = "cat > /tmp/partscript.enc << EOFEOF\n" . $partcontent . "\nEOFEOF\n"; if ($scriptflag) { # Put the code to decode preseed script and run it to generate pressed recipe file #$partcontent .= "python -c 'import base64; print base64.b64decode(open(\"/tmp/partscript.enc\",\"rb\").read())' >/tmp/partscript\n"; $partcontent .= "base64decode/tmp/partscript\n"; $partcontent .= "chmod 755 /tmp/partscript\n"; $partcontent .= "/tmp/partscript\n"; } else { # Put the code to decode the preseed recipe file #$partcontent .= "python -c 'import base64; print base64.b64decode(open(\"/tmp/partscript.enc\",\"rb\").read())' >/tmp/partitionfile\n"; $partcontent .= "base64decode/tmp/partitionfile\n"; } #replace the #XCA_PARTMAN_RECIPE_SCRIPT# $inc =~ s/#XCA_PARTMAN_RECIPE_SCRIPT#/$partcontent/; } } else { $partcontent =~ s/\s$//; if ($inc =~ /#XCAT_PARTITION_START#/) { # %pre and decode it out during the running time. use MIME::Base64; $partcontent = encode_base64($partcontent); $partcontent =~ s/\n//g; # Put the base64 coded partitionfile into %pre part $partcontent = "cat > /tmp/partitionfile.enc << EOFEOF\n" . $partcontent . "\nEOFEOF\n"; $partcontent .= "rm -rf /tmp/partitionfile\n"; # Put the code to decode the partitionfile $partcontent .= "python -c 'import base64; print base64.b64decode(open(\"/tmp/partitionfile.enc\",\"rb\").read())' >/tmp/partitionfile\n"; #replace the #XCA_PARTITION_SCRIPT# $inc =~ s/#XCA_PARTITION_SCRIPT#/$partcontent/; } elsif ($inc =~ //) { $inc =~ s/[\s\S]*/$partcontent/; } } } if ($diskfile && $doneincludes) { # The content of the specified file is the disknames to partition or a script which can write disk names into /tmp/xcat.install_disk # split the disk file out from the $inc ($inc, $diskcontent) = split(/FFFFFFFFFFFFPARTITIONDISKFILESTART\n/, $inc); ($diskcontent, $res) = split(/\nFFFFFFFFFFFFPARTITIONDISKFILEEND/, $diskcontent); $inc .= $res; # since the whole partition file needs be packaged in %pre first and generate an executable file at running time, # all the special chars like ',",%,\ need be kept, we have to use the base64 coding to code it and put it in # %pre and decode it out during the running time. use MIME::Base64; $diskcontent = encode_base64($diskcontent); $diskcontent =~ s/\n//g; if ($inc =~ /#XCA_PARTMAN_DISK_SCRIPT#/) { # Put the base64 coded disk file/script into %pre part $diskcontent = "cat > /tmp/diskscript.enc << EOFEOF\n" . $diskcontent . "\nEOFEOF\n"; if ($diskscriptflag) { # Put the code to decode disk script and run it to generate pressed disk file #$diskcontent .= "python -c 'import base64; print base64.b64decode(open(\"/tmp/diskscript.enc\",\"rb\").read())' >/tmp/diskscript\n"; $diskcontent .= "base64decode/tmp/diskscript\n"; $diskcontent .= "chmod 755 /tmp/diskscript\n"; $diskcontent .= "/tmp/diskscript\n"; } else { # Put the code to decode the preseed disk file $diskcontent .= "base64decode /tmp/xcat.install_disk\n"; } #replace the #XCA_PARTMAN_DISK_SCRIPT# $inc =~ s/#XCA_PARTMAN_DISK_SCRIPT#/$diskcontent/; } } elsif ("ubuntu" eq $platform) { my $default_script = " wget http://`cat /tmp/xcatserver`" . $ENV{INSTALLDIR} . "/autoinst/getinstdisk; chmod u+x getinstdisk; ./getinstdisk;"; $inc =~ s/#INCLUDE_GET_INSTALL_DISK_SCRIPT#/$default_script/; } else { $inc =~ s/#INCLUDE_GET_INSTALL_DISK_SCRIPT#/ /; } if ($configfile && $doneincludes) { #the content of the specified file is the additional pressed config with 'd-i' or # a script set the additional pressed config with "debconf-set" # split the config file out from the $inc ($inc, $configcontent) = split(/FFFFFFFFFFFFPARTITIONCONFIGFILESTART\n/, $inc); ($configcontent, $res) = split(/\nFFFFFFFFFFFFPARTITIONCONFIGFILEEND/, $configcontent); $inc .= $res; if ($configscriptflag) { # since the whole partition file needs be packaged in %pre first and generate an executable file at running time, # all the special chars like ',",%,\ need be kept, we have to use the base64 coding to code it and put it in # %pre and decode it out during the running time. use MIME::Base64; $configcontent = encode_base64($configcontent); $configcontent =~ s/\n//g; # Put the base64 coded config script into %pre part $configcontent = "cat > /tmp/configscript.enc << EOFEOF\n" . $configcontent . "\nEOFEOF\n"; # Put the code to decode config script and run it to generate pressed config $configcontent .= "base64decode/tmp/configscript\n"; $configcontent .= "chmod 755 /tmp/configscript\n"; $configcontent .= "/tmp/configscript\n"; $inc =~ s/#XCA_PARTMAN_ADDITIONAL_CONFIG_SCRIPT#/$configcontent/; } else { $inc =~ s/#XCA_PARTMAN_ADDITIONAL_CFG#/$configcontent/; } } } if ($tmplerr) { close($outh); return $tmplerr; } print $outh $inc; close($outh); return 0; } sub windows_disable_null_admin { #in the event where windows_account_data has not set an administrator user, we explicitly disable the administrator user unless ($localadminenabled) { return "\r 100\r cmd /c %systemroot%\\system32\\net.exe user Administrator /active:no\r "; } return ""; } sub windows_account_data { #this will add domain accounts if configured to be in active directory #it will also put in an administrator password for local account, *if* specified my $passtab = xCAT::Table->new('passwd', -create => 0); my $useraccountxml = ""; $localadminenabled = 0; if ($passtab) { my $passent = $passtab->getAttribs({ key => "system", username => "Administrator" }, ['password']); if ($passent and $passent->{password}) { $useraccountxml = "\r\n" . $passent->{password} . "\r\ntrue</PlainText>\r\n</AdministratorPassword>\r\n"; $useraccountxml .= "<!-- Plaintext=false would only protect against the most cursory over the shoulder glance, this implementation opts not to even give the illusion of privacy by only doing plaintext. -->\r\n"; $localadminenabled = 1; } } my $domain; my $doment; my $domaintab = xCAT::Table->new('domain', -create => 0); if ($domaintab) { $doment = $domaintab->getNodeAttribs($node, [ 'authdomain', 'type' ], prefetchcache => 1); } unless ($::XCATSITEVALS{directoryprovider} eq "activedirectory" or ($doment and $doment->{type} eq "activedirectory")) { return $useraccountxml; } if ($doment and $doment->{authdomain}) { $domain = $doment->{authdomain}; } else { $domain = $::XCATSITEVALS{domain}; } $useraccountxml .= "<DomainAccounts><DomainAccountList>\r\n<DomainAccount wcm:action=\"add\">\r\n<Group>Administrators</Group>\r\n<Name>Domain Admins</Name>\r\n</DomainAccount>\r\n<Domain>" . $domain . "</Domain>\r\n</DomainAccountList>\r\n</DomainAccounts>\r\n"; return $useraccountxml; } sub windows_net_cfg { if ($::XCATSITEVALS{managedaddressmode} =~ /static/) { return "<!-- WINCFG Static not supported -->"; } unless ($::XCATSITEVALS{managedaddressmode} =~ /autoula/) { # handle the general windows deployment that create interfaces sections from nic table my $component_head = '<component name="Microsoft-Windows-TCPIP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'; my $component_end = '</component>'; my $interfaces_cfg = '<Interfaces>'; # get the installnic my $nrtab = xCAT::Table->new('noderes', -create => 0); my $installnic; if ($nrtab) { my $nrent = $nrtab->getNodeAttribs($node, [ 'installnic', 'primarynic' ]); if ($nrent) { if (defined($nrent->{'installnic'})) { $installnic = $nrent->{'installnic'}; } elsif (defined($nrent->{'primarynic'})) { $installnic = $nrent->{'primarynic'}; } } } # get the site.setinstallnic my @ents = xCAT::TableUtils->get_site_attribute("setinstallnic"); my $setinstallnic; if ($ents[0] =~ /1|yes|y/i) { $setinstallnic = 1; } my $nicstab = xCAT::Table->new('nics', -create => 0); my $hasif; if ($nicstab) { my $nicsent = $nicstab->getNodeAttribs($node, ['nicips']); if ($nicsent->{nicips}) { my @nics = split(/,/, $nicsent->{nicips}); foreach (@nics) { my $gateway; my $interface_cfg = '<Interface wcm:action="add">'; my ($nicname, $ips) = split(/!/, $_); unless ($nicname) { next; } if ($nicname =~ /^bmc/) { next; } # do nothing for bmc interface my $dosetgw = 0; if ($nicname eq $installnic) { if ($setinstallnic) { # set to static with gateway $dosetgw = 1; } else { # else: do nothing means using dhcp next; } } # else: do not set gateway, since gateway only set for installnic if ($ips) { $interface_cfg .= '<Ipv4Settings><DhcpEnabled>false</DhcpEnabled></Ipv4Settings><Ipv6Settings><DhcpEnabled>false</DhcpEnabled></Ipv6Settings>'; $interface_cfg .= "<Identifier>$nicname</Identifier>"; $interface_cfg .= '<UnicastIpAddresses>'; my @setip = split(/\|/, $ips); my $num = 1; foreach my $ip (@setip) { my ($netmask, $gw) = getNM_GW($ip); unless ($netmask) { next; } if ($gw) { $gateway = $gw; } if ($gateway eq '<xcatmaster>') { my @gatewayd = xCAT::NetworkUtils->my_ip_facing($ip); unless ($gatewayd[0]) { $gateway = $gatewayd[1]; } } $interface_cfg .= '<IpAddress wcm:action="add" wcm:keyValue="' . $num++ . '">' . $ip . "/$netmask" . '</IpAddress>'; } if ($num == 1) { # no correct IP with correct network is found next; } $interface_cfg .= "</UnicastIpAddresses>" } else { # set with dhcp $interface_cfg .= '<Ipv4Settings><DhcpEnabled>true</DhcpEnabled></Ipv4Settings><Ipv6Settings><DhcpEnabled>true</DhcpEnabled></Ipv6Settings>'; $interface_cfg .= "<Identifier>$nicname</Identifier>"; } # add the default gateway if ($gateway && $dosetgw) { $interface_cfg .= '<Routes><Route wcm:action="add"><Identifier>1</Identifier><NextHopAddress>' . $gateway . '</NextHopAddress><Prefix>0/0</Prefix></Route></Routes>'; } $interface_cfg .= '</Interface>'; $interfaces_cfg .= $interface_cfg; $hasif = 1; } } } $interfaces_cfg .= "</Interfaces>"; if ($hasif) { return "$component_head$interfaces_cfg$component_end"; #windows default behavior } else { return ""; } } #autoula, my $hoststab; my $mactab = xCAT::Table->new('mac', -create => 0); unless ($mactab) { die "mac table should always exist prior to template processing when doing autoula"; } my $ent = $mactab->getNodeAttribs($node, ['mac'], prefetchcache => 1); unless ($ent and $ent->{mac}) { die "missing mac data for $node"; } my $suffix = xCAT::Utils->parseMacTabEntry($ent->{mac}, $node); my $mac = $suffix; $suffix = lc($suffix); $mac =~ s/:/-/g; unless ($hoststab) { $hoststab = xCAT::Table->new('hosts', -create => 1); } my $ulaaddr = autoulaaddress($suffix); $hoststab->setNodeAttribs($node, { ip => $ulaaddr }); return '<component name="Microsoft-Windows-TCPIP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . "\r\n<Interfaces><Interface wcm:action=\"add\">\r\n<Ipv4Settings><DhcpEnabled>false</DhcpEnabled></Ipv4Settings><Ipv6Settings><DhcpEnabled>false</DhcpEnabled></Ipv6Settings>\r\n<Identifier>$mac</Identifier>\r\n<UnicastIpAddresses>\r\n<IpAddress wcm:action=\"add\" wcm:keyValue=\"1\">$ulaaddr/64</IpAddress>\r\n</UnicastIpAddresses>\r\n</Interface>\r\n</Interfaces>\r\n</component>\r\n"; } sub windows_dns_cfg { my $domain; my $doment; my $noderesent; my $noderestab = xCAT::Table->new("noderes", -create => 0); unless ($noderestab) { return ""; } $noderesent = $noderestab->getNodeAttribs($node, ['nameservers'], prefetchcache => 1); unless ($noderesent and $noderesent->{nameservers}) { return ""; } my $mac = "==PRINIC=="; my $mactab = xCAT::Table->new('mac', -create => 0); if ($mactab) { my $macent = $mactab->getNodeAttribs($node, ['mac'], prefetchcache => 1); if ($macent and $macent->{mac}) { $mac = $macent->{mac}; $mac =~ s/!.*//; $mac =~ s/\|.*//; $mac =~ s/:/-/g; } } my $nameservers = $noderesent->{nameservers}; my $domaintab = xCAT::Table->new('domain', -create => 0); if ($domaintab) { $doment = $domaintab->getNodeAttribs($node, ['authdomain'], prefetchcache => 1); } if ($doment and $doment->{authdomain}) { $domain = $doment->{authdomain}; } else { $domain = $::XCATSITEVALS{domain}; } my $componentxml = '<component name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . "\r\n<DNSDomain>$domain</DNSDomain>\r\n" . "<Interfaces><Interface wcm:action=\"add\">\r\n<Identifier>$mac</Identifier>\r\n<DNSServerSearchOrder>\r\n"; my $idx = 1; foreach (split /,/, $nameservers) { $componentxml .= "<IpAddress wcm:action=\"add\" wcm:keyValue=\"$idx\">$_</IpAddress>\r\n"; $idx += 1; } $componentxml .= "</DNSServerSearchOrder>\r\n</Interface>\r\n</Interfaces>\r\n</component>\r\n"; return $componentxml; } #this will lay out the data from postscripts table in a manner that is appropriate for windows consumption in Microsoft-Windows-Deployment #component under specialize pass sub windows_postscripts { my $posttab = xCAT::Table->new('postscripts', -create => 0); unless ($posttab) { return ""; } my $psent = $posttab->getNodeAttribs($node, ['postscripts'], prefetchcache => 1); unless ($psent and $psent->{postscripts}) { return ""; } my @cmds = split /,/, $psent->{postscripts}; my $order = 1; my $xml; my $pscript; foreach $pscript (@cmds) { unless ($pscript =~ /\\/) { $pscript = "C:\\xcatpost\\" . $pscript; } $xml .= "<RunSynchronousCommand wcm:action=\"add\">\r\n<Order>$order</Order>\r\n<Path>$pscript</Path>\r\n</RunSynchronousCommand>\r\n"; } } #this will examine table data, decide *if* a Microsoft-Windows-UnattendedJoin is warranted #there are two variants in how to proceed: #-Hide domain administrator from node: xCAT will use MACHINEPASSWORD to do joining to AD. Currently requires SSL be enabled on DC. Samba 4 TODO #-Provide domain administrator credentials, avoiding the SSL scenario. This is by default forbidden as it is high risk for exposing sensitive credentials. # Also populate MachineObjectOU sub windows_join_data { my $doment; my $domaintab = xCAT::Table->new('domain', -create => 0); if ($domaintab) { $doment = $domaintab->getNodeAttribs($node, [ 'ou', 'type', 'authdomain', 'adminuser', 'adminpassword' ], prefetchcache => 1); } unless ($::XCATSITEVALS{directoryprovider} eq "activedirectory" or ($doment and $doment->{type} eq "activedirectory")) { return ""; } #we are still here, meaning configuration has a domain and activedirectory set, probably want to join.. #TODO: provide a per-node 'disable' so that non-AD could be mixed into a nominally AD environment my $prejoin = 1; if (defined $::XCATSITEVALS{prejoinactivedirectory} and not $::XCATSITEVALS{prejoinactivedirectory}) { $prejoin = 0; } my $domain; my $ou; if ($doment and $doment->{ou}) { $ou = $doment->{ou}; } if ($doment and $doment->{authdomain}) { $domain = $doment->{authdomain}; } else { $domain = $::XCATSITEVALS{domain}; } my $componentxml = '<component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' . "\r\n<Identification>\r\n<JoinDomain>" . $domain . "</JoinDomain>\r\n"; if ($ou) { $componentxml .= "<MachineObjectOU>" . $ou . "</MachineObjectOU>\r\n"; } if ($prejoin) { my $adinfo = machinepassword(wantref => 1); #TODO: needs rearranging in non prejoin case #a note, MS is incorrect when they document unsecure join as " UnsecureJoin is performed, by using a null session with a pre-existing account. This means there is no authentication to the domain controller when configuring the machine account; it is done anonymously". #the more informative bit is http://technet.microsoft.com/en-us/library/cc730845%28v=ws.10%29.aspx which says of 'securejoin': this method is actually less secure because the credentials reside in the ImageUnattend.xml file in plain text. #xCAT is generating a one-time password that is kept as limited as is feasible for the deployment strategy #in theory, a domain join will either fail of the one-time password is compromised and changed, or domain #join will invalidate any 'snooped' one time password $componentxml .= "<MachinePassword>" . $adinfo->{password} . "</MachinePassword>\n<UnsecureJoin>true</UnsecureJoin>\n"; } else { #this is the pass-through credentials case, currrently inaccessible until TODO, this must be used #with care as used incorrectly, an LDAP manager account is at high risk of compromise my $adminuser; my $adminpass; if ($doment and $doment->{adminuser}) { $adminuser = $doment->{adminuser}; } if ($doment and $doment->{adminpassword}) { $adminpass = $doment->{adminpassword}; } unless ($adminuser and $adminpass) { my $passtab = xCAT::Table->new('passwd', -create => 0); unless ($passtab) { xCAT::MsgUtils->message("SE", "[ERROR]$node: Error authenticating to Active Directory"); return; } my @adpents = $passtab->getAttribs({ key => 'activedirectory' }, [ 'username', 'password', 'authdomain' ]); my $adpent; foreach $adpent (@adpents) { if ($adpent and $adpent->{authdomain} and $adpent->{authdomain} ne $domain) { next; } if ($adpent and $adpent->{username} and $adpent->{password}) { $adminuser = $adpent->{username}; $adminpass = $adpent->{password}; last; } } } unless ($adminuser and $adminpass) { die "Missing active directory admin auth data from passwd table" } $componentxml .= "<Credentials><Domain>" . $domain . "</Domain>\r\n<Username>" . $adminuser . "</Username>\r\n<Password>" . $adminpass . "</Password>\r\n</Credentials>\r\n"; } $componentxml .= "</Identification>\r\n</component>\r\n"; } sub get_win_prodkey { my $osvariant = shift; my $keytab = xCAT::Table->new("prodkey", -create => 0); my $keyent; if ($keytab) { my @keyents = $keytab->getNodeAttribs($node, [qw/product key/]); foreach my $tkey (@keyents) { if ($tkey->{product} eq $osvariant) { $keyent = $tkey; last; } elsif (not $tkey->{product}) { $keyent = $tkey; } } unless ($keyent) { $keyent = $keytab->getAttribs({ product => $osvariant }, "key"); } } if ($keyent and $keyent->{key}) { return "<ProductKey><WillShowUI>OnError</WillShowUI><Key>" . $keyent->{key} . "</Key></ProductKey>"; } if ($xCAT::WinUtils::kmskeymap{$osvariant}) { return "<ProductKey><WillShowUI>OnError</WillShowUI><Key>" . $xCAT::WinUtils::kmskeymap{$osvariant} . "</Key></ProductKey>"; } return ""; #in the event that we have no specified key and no KMS key, then try with no key, user may have used some other mechanism } sub managed_address_mode { return $::XCATSITEVALS{managedaddressmode}; } sub get_node_domain { my $lcnode = shift; if ($lcnode eq 'THISNODE') { $lcnode = $node; } my $nd = xCAT::NetworkUtils->getNodeDomains([$lcnode]); my %nodedomains = %$nd; my $domain = $nodedomains{$lcnode}; return $domain; } #get persistent command options from node and osimage definition sub getPersistentKcmdline { my $lcnode = shift; if ($lcnode eq 'THISNODE') { $lcnode = $node; } my @nodes = ($lcnode); my $bptab = xCAT::Table->new('bootparams', -create => 1); my $bphash = $bptab->getNodesAttribs(\@nodes, ['addkcmdline']); my $typetab = xCAT::Table->new('nodetype', -create => 1); my $typehash = $typetab->getNodesAttribs(\@nodes, ['provmethod']); my $linuximgtab = xCAT::Table->new('linuximage', -create => 1); my $ent = $typehash->{$lcnode}->[0]; my $osimgname = $ent->{'provmethod'}; my $linuximghash = $linuximgtab->getAttribs({ imagename => $osimgname }, 'boottarget', 'addkcmdline'); my $kern = $bphash->{$lcnode}->[0]; my $addkcmdline; if ($kern->{addkcmdline}) { $addkcmdline .= $kern->{addkcmdline} . " "; } if ($linuximghash and $linuximghash->{'addkcmdline'}) { unless ($linuximghash->{'boottarget'}) { $addkcmdline .= $linuximghash->{'addkcmdline'} . " "; } } my $cmdhashref; if ($addkcmdline) { $cmdhashref = xCAT::Utils->splitkcmdline($addkcmdline); } my $ret = ""; if ($cmdhashref and $cmdhashref->{persistent}) { $ret = $cmdhashref->{persistent}; } return $ret; } sub kickstartbootloader { my $line = "bootloader"; if ($ENV{PERSKCMDLINE}) { $line .= " --append=\"" . $ENV{PERSKCMDLINE} . "\""; } return $line; } sub esxipv6setup { if (not $::XCATSITEVALS{managedaddressmode} or $::XCATSITEVALS{managedaddressmode} =~ /v4/) { return ""; } # blank line for ipv4 schemes my $v6addr; if ($::XCATSITEVALS{managedaddressmode} eq "autoula") { my $hoststab; my $mactab = xCAT::Table->new('mac', -create => 0); my $ent = $mactab->getNodeAttribs($node, ['mac'], prefetchcache => 1); my $suffix = xCAT::Utils->parseMacTabEntry($ent->{mac}, $node); $suffix = lc($suffix); unless ($mactab) { die "mac table should always exist prior to template processing when doing autoula"; } #in autoula, because ESXi weasel doesn't seemingly grok IPv6 at all, we'll have to do it in %pre unless ($hoststab) { $hoststab = xCAT::Table->new('hosts', -create => 1); } $v6addr = autoulaaddress($suffix); $hoststab->setNodeAttribs($node, { ip => $v6addr }); } else { my $hoststab = xCAT::Table->new('hosts', -create => 0); unless ($hoststab) { die "unable to proceed, no hosts table to read from" } my $ent = $hoststab->getNodeAttribs($node, ["ip"], prefetchcache => 1); unless ($ent and $ent->{ip}) { die "no hosts table entry with viable IP in hosts table for $node" } $v6addr = $ent->{ip}; unless ($v6addr =~ /:/) { die "incorrect format for static ipv6 in hosts table for $node" } } return 'esxcfg-vmknic -i ' . $v6addr . '/64 "Management Network"' . " #ESXISTATICV6\n"; } sub kickstartnetwork { my $line = "network --onboot=yes --bootproto="; my $hoststab; my $mactab = xCAT::Table->new('mac', -create => 0); unless ($mactab) { $tmplerr = "mac table should always exist prior to template processing when doing autoula"; return; } my $ent = $mactab->getNodeAttribs($node, ['mac'], prefetchcache => 1); unless ($ent and $ent->{mac}) { $tmplerr = "missing mac data for $node"; return; } my $suffix = xCAT::Utils->parseMacTabEntry($ent->{mac}, $node); $suffix = lc($suffix); if ($::XCATSITEVALS{managedaddressmode} eq "autoula") { unless ($hoststab) { $hoststab = xCAT::Table->new('hosts', -create => 1); } $line .= "static --device=$suffix --noipv4 --ipv6="; my $ulaaddr = autoulaaddress($suffix); $hoststab->setNodeAttribs($node, { ip => $ulaaddr }); $line .= $ulaaddr; } elsif ($::XCATSITEVALS{managedaddressmode} =~ /static/) { my ($ipaddr, $hostname, $gateway, $netmask) = xCAT::NetworkUtils->getNodeNetworkCfg($node); unless ($ipaddr) { $tmplerr = "cannot resolve the network configuration of $node"; return; } if ($gateway eq '<xcatmaster>') { my @gatewayd = xCAT::NetworkUtils->my_ip_facing($ipaddr); unless ($gatewayd[0]) { $gateway = $gatewayd[1]; } } $line .= "static --device=$suffix --ip=$ipaddr --netmask=$netmask --gateway=$gateway --hostname=$hostname "; my %nameservers = %{ xCAT::NetworkUtils->getNodeNameservers([$node]) }; my @nameserverARR = split(",", $nameservers{$node}); my @nameserversIP; foreach (@nameserverARR) { my $ip; if ($_ eq '<xcatmaster>') { my @ipd = xCAT::NetworkUtils->my_ip_facing($gateway); unless ($ipd[0]) { $ip = $ipd[1]; } } else { (undef, $ip) = xCAT::NetworkUtils->gethostnameandip($_); } push @nameserversIP, $ip; } #there is no network option to set dns search domain in kickstart, it will be set in %post if (scalar @nameserversIP) { $line .= " --nameserver=" . join(",", @nameserversIP); } #return "#KSNET static unsupported"; } else { $line .= "dhcp --device=$suffix"; } return $line; } sub mirrorspec { my $line; my $ostab = xCAT::Table->new('nodetype'); my %oents = %{ $ostab->getNodesAttribs([$node], [qw(os arch profile provmethod)]) }; my $ent = $oents{$node}->[0]; my $imagename; if ($ent and $ent->{provmethod} and ($ent->{provmethod} ne 'install') and ($ent->{provmethod} ne 'netboot') and ($ent->{provmethod} ne 'statelite')) { $imagename = $ent->{provmethod}; } unless ($imagename) { $tmplerr = "cannot determine the osimage for $node"; return; } my $pkgdirval; my $linuximagetab = xCAT::Table->new('linuximage', -create => 1); my $ref = $linuximagetab->getAttribs({ imagename => $imagename }, 'pkgdir'); if (($ref) && ($ref->{'pkgdir'})) { $pkgdirval = $ref->{'pkgdir'}; } my $pkgdir; my @mirrors; my @pkgdirlist = split(/,/, $pkgdirval); my $masternode = xCAT::TableUtils->get_site_Master(); foreach (@pkgdirlist) { if ($_ =~ /^http|ssh/) { push @mirrors, $_; } else { # If multiple pkgdirs are provided, The first path in the value of osimage.pkgdir if (!$pkgdir) { $pkgdir = $_; } else { my $osuurl = "http://" . $masternode . $_ . " ./"; push @mirrors, $osuurl; } } } if ($pkgdir) { $line .= " d-i mirror/country string manual\n d-i mirror/protocol string http\n d-i mirror/http/directory string $pkgdir\n d-i mirror/http/proxy string\n"; } if (scalar @mirrors) { my $index = 0; foreach (@mirrors) { $line .= " d-i apt-setup/local$index/repository string deb $_\n d-i apt-setup/local$index/comment string online mirror $index\n"; $index = $index + 1; } } return $line; } sub yast2network { my $line; my $hoststab; my $mactab = xCAT::Table->new('mac', -create => 0); unless ($mactab) { $tmplerr="mac table should always exist prior to template processing when doing autoula"; return;} my $ent = $mactab->getNodeAttribs($node, ['mac'], prefetchcache => 1); unless ($ent and $ent->{mac}) { $tmplerr="missing mac data for $node"; return; } my $suffix = xCAT::Utils->parseMacTabEntry($ent->{mac}, $node); $suffix = lc($suffix); if ($::XCATSITEVALS{managedaddressmode} eq "autoula") { #TODO return "#YAST2NET autoula unsupported" } elsif ($::XCATSITEVALS{managedaddressmode} =~ /static/) { my ($ipaddr, $hostname, $gateway, $netmask) = xCAT::NetworkUtils->getNodeNetworkCfg($node); unless ($ipaddr) { $tmplerr = "cannot resolve the network configuration of $node"; return; } if ($gateway eq '<xcatmaster>') { my @gatewayd = xCAT::NetworkUtils->my_ip_facing($ipaddr); unless ($gatewayd[0]) { $gateway = $gatewayd[1]; } } my %nameservers = %{ xCAT::NetworkUtils->getNodeNameservers([$node]) }; my @nameserverARR = split(",", $nameservers{$node}); my @nameserversIP; foreach (@nameserverARR) { my $ip; if ($_ eq '<xcatmaster>') { my @ipd = xCAT::NetworkUtils->my_ip_facing($gateway); unless ($ipd[0]) { $ip = $ipd[1]; } } else { (undef, $ip) = xCAT::NetworkUtils->gethostnameandip($_); } push @nameserversIP, $ip; } # get the domains for each node - one call for all nodes in hosts file my $nd = xCAT::NetworkUtils->getNodeDomains([$node]); my %nodedomains = %$nd; my $domain = $nodedomains{$node}; my $networkhash = { 'networking' => [ { 'dns' => [ { 'domain' => [ "$domain" ], 'dhcp_hostname' => [ { 'content' => 'false', 'config:type' => 'boolean' } ], 'dhcp_resolv' => [ { 'content' => 'false', 'config:type' => 'boolean' } ], 'nameservers' => [ { 'config:type' => 'list', 'nameserver' => [@nameserversIP] } ], 'hostname' => [ $hostname ], 'searchlist' => [ { 'search' => [ $domain ], 'config:type' => 'list' } ] } ], 'interfaces' => [ { 'interface' => [ { 'bootproto' => [ 'static' ], 'startmode' => [ 'onboot' ], 'netmask' => [ $netmask ], 'device' => [ 'eth0' ], 'ipaddr' => [ $ipaddr ] } ], 'config:type' => 'list' } ], 'routing' => [ { 'ip_forward' => [ { 'content' => 'false', 'config:type' => 'boolean' } ], 'routes' => [ { 'route' => [ { 'destination' => [ 'default' ], 'gateway' => [ $gateway ], 'netmask' => [ '-' ], 'device' => [ '-' ] } ], 'config:type' => 'list' } ] } ] } ] }; my $xml = new XML::Simple(KeepRoot => 1); $line = $xml->XMLout($networkhash); #return "#KSNET static unsupported"; } else { my $networkhash = { 'networking' => [ { 'dns' => [ { 'domain' => [ 'local' ], 'dhcp_hostname' => [ { 'content' => 'true', 'config:type' => 'boolean' } ], 'hostname' => [ 'linux' ], 'dhcp_resolv' => [ { 'content' => 'true', 'config:type' => 'boolean' } ] } ], 'interfaces' => [ { 'interface' => [ { 'startmode' => [ 'onboot' ], 'bootproto' => [ 'dhcp' ], 'device' => [ 'eth0' ] } ], 'config:type' => 'list' } ], 'routing' => [ { 'ip_forward' => [ { 'content' => 'false', 'config:type' => 'boolean' } ], 'routes' => [ { 'config:type' => 'list' } ] } ] } ] }; my $xml = new XML::Simple(KeepRoot => 1); $line = $xml->XMLout($networkhash); } return $line; } sub autoulaaddress { my $suffix = shift; my $prefix = $::XCATSITEVALS{autoulaprefix}; $suffix =~ /(..):(..:..):(..:..):(..)/; my $leadbyte = $1; my $mask = ((hex($leadbyte) & 2) ^ 2); if ($mask) { $leadbyte = hex($leadbyte) | $mask; } else { $leadbyte = hex($leadbyte) & 0xfd; #mask out the one bit } $suffix = sprintf("%02x$2ff:fe$3$4", $leadbyte); return $prefix . $suffix; } sub machinepassword { my %funargs = @_; if ($lastmachinepassdata->{password}) { #note, this should only happen after another call #to subvars that does *not* request reuse #the issue being avoiding reuse in the installmonitor case #subvars function clears this if appropriate if ($funargs{wantref}) { return $lastmachinepassdata; } return $lastmachinepassdata->{password}; } my $passdata; my $domaintab = xCAT::Table->new('domain'); $ENV{HOME} = '/etc/xcat'; $ENV{LDAPRC} = 'ad.ldaprc'; my $ou; my $domain; if ($domaintab) { my $ouent = $domaintab->getNodeAttribs($node, [ 'ou', 'authdomain' ], prefetchcache => 1); if ($ouent and $ouent->{ou}) { $ou = $ouent->{ou}; } if ($ouent and $ouent->{authdomain}) { $domain = $ouent->{authdomain}; } } $passdata->{ou} = $ou; #my $sitetab = xCAT::Table->new('site'); #unless ($sitetab) { # return "ERROR: unable to open site table"; #} #(my $et) = $sitetab->getAttribs({key=>"domain"},'value'); unless ($domain) { my @domains = xCAT::TableUtils->get_site_attribute("domain"); my $tmp = $domains[0]; if (defined($tmp)) { $domain = $tmp; } else { return "ERROR: no domain set in site table or in domain.authdomain for $node"; } } $passdata->{domain} = $domain; my $realm = uc($domain); $realm =~ s/\.$//; $realm =~ s/^\.//; $ENV{KRB5CCNAME} = "/tmp/xcat/krbcache.$realm.$$"; unless ($loggedrealms{$realm}) { my $passtab = xCAT::Table->new('passwd', -create => 0); unless ($passtab) { xCAT::MsgUtils->message("SE", "[ERROR]$node: Error authenticating to Active Directory"); return; } my @adpents = $passtab->getAttribs({ key => 'activedirectory' }, [ 'username', 'password', 'authdomain' ]); my $adpent; my $username; my $password; foreach $adpent (@adpents) { if ($adpent and $adpent->{authdomain} and $adpent->{authdomain} ne $domain) { next; } if ($adpent and $adpent->{username} and $adpent->{password}) { $username = $adpent->{username}; $password = $adpent->{password}; last; } } unless ($username and $password) { return "ERROR: activedirectory entry missing from passwd table"; } my $err = xCAT::ADUtils::krb_login(username => $adpent->{username}, password => $adpent->{password}, realm => $realm); if ($err) { return "ERROR: authenticating to Active Directory"; } $loggedrealms{$realm} = 1; } #my $server = $sitetab->getAttribs({key=>'directoryserver'},['value']); my $server; my @servers = xCAT::TableUtils->get_site_attribute("directoryserver"); my $tmp = $servers[0]; if (defined($tmp)) { $server = $tmp; } else { $server = ''; if ($netdnssupport) { my $res = Net::DNS::Resolver->new; my $query = $res->query("_ldap._tcp.$domain", "SRV"); if ($query) { foreach my $srec ($query->answer) { $server = $srec->{target}; } } } unless ($server) { xCAT::MsgUtils->message("SE", "[ERROR]Unable to determine a directory server to communicate with, try site.directoryserver"); return; } } $passdata->{dc} = $server; my %args = ( node => $node, dnsdomain => $domain, directoryserver => $server, changepassondupe => 1, ); if ($ou) { $args{ou} = $ou } my $data = xCAT::ADUtils::add_host_account(%args); if ($data->{error}) { return "ERROR: " . $data->{error}; } else { $passdata->{password} = $data->{password}; $lastmachinepassdata = $passdata; if ($funargs{wantref}) { return $passdata; } return $data->{password}; } } sub includefile { my $file = shift; my $special = shift; my $pkglist = shift; #1 means package list, #2 means pattern list, pattern list starts with @, #3 means remove package list, packages to be removed start with -. my $text = ""; unless ($file =~ /^\//) { $file = $idir . "/" . $file; } open(INCLUDE, $file) || return "#INCLUDEBAD:cannot open $file#"; my $pkgb = ""; my $pkge = ""; if ($pkglist) { if ($pkglist == 2) { $pkgb = "<pattern>"; $pkge = "</pattern>"; } else { $pkgb = "<package>"; $pkge = "</package>"; } } while (<INCLUDE>) { if ($pkglist == 1) { s/#INCLUDE:/#INCLUDE_PKGLIST:/; } elsif ($pkglist == 2) { s/#INCLUDE:/#INCLUDE_PTRNLIST:/; } elsif ($pkglist == 3) { s/#INCLUDE:/#INCLUDE_RMPKGLIST:/; } if (($_ =~ /^\s*#/) || ($_ =~ /^\s*$/)) { $text .= "$_"; } else { my $tmp = $_; chomp($tmp); #remove return char $tmp =~ s/\s*$//; #removes trailing spaces next if (($pkglist == 1) && (($tmp =~ /^\s*@/) || ($tmp =~ /^\s*-/))); #for packge list, do not include the lines start with @ if ($pkglist == 2) { #for pattern list, only include the lines start with @ if ($tmp =~ /^\s*@(.*)/) { $tmp = $1; $tmp =~ s/^\s*//; #removes leading spaces } else { next; } } elsif ($pkglist == 3) { #for rmpkg list, only include the lines start with - if ($tmp =~ /^\s*-(.*)/) { $tmp = $1; $tmp =~ s/^\s*//; #removes leading spaces } else { next; } } $text .= "$pkgb$tmp$pkge\n"; } } close(INCLUDE); if ($special) { $text =~ s/\$/\\\$/g; $text =~ s/`/\\`/g; } chomp($text); return ($text); } sub command { my $command = shift; my $r; # if(($r = `$command`) == 0) { # chomp($r); # return($r); # } # else { # return("#$command: failed $r#"); # } $r = `$command`; chomp($r); return ($r); } sub envvar { my $envvar = shift; if ($envvar =~ /^\$/) { $envvar =~ s/^\$//; } return ($ENV{$envvar}); } sub crydb { my ($table, $key, $field) = @_; my @fields = [$field, 'cryptmethod']; my $kp; my $get_query_map = sub { my $key = shift; my %kp; foreach (split /,/, $key) { my ($k, $v); ($k, $v) = split /=/, $_; $kp{$k} = $v if defined($k); } return \%kp if %kp; xCAT::MsgUtils->message("SE", "[ERROR]Unable to parse password parameters $key"); return undef; }; $kp = $get_query_map->($key); return undef if (!defined($kp)); return xCAT::PasswordUtils::crypt_system_password($table, $kp, \@fields); } sub tabdb { my $table = shift; my $key = shift; my $field = shift; my $blankok = shift; if (%::GLOBAL_TAB_HASH && defined($::GLOBAL_TAB_HASH{$table})) { if (!defined($::GLOBAL_TAB_HASH{$table}{$key})) { return "''"; } if (defined($::GLOBAL_TAB_HASH{$table}{$key}{$field})) { return "'" . $::GLOBAL_TAB_HASH{$table}{$key}{$field} . "'"; } else { return "''"; } } my $tabh = xCAT::Table->new($table); unless ($tabh) { $tmplerr = "Unable to open table named $table"; if ($table =~ /\.tab/) { $tmplerr .= " (.tab should not be specified as part of the table name in xCAT 2, as seems to be the case here)"; } return ""; } my $ent; my $bynode = 0; if ($key eq "THISNODE" or $key eq '$NODE') { $ent = $tabh->getNodeAttribs($node, [$field]); $key = "node=$node"; } else { my %kp; foreach (split /,/, $key) { my $key; my $val; if ($_ eq 'THISNODE' or $_ eq '$NODE') { $bynode = 1; } else { ($key, $val) = split /=/, $_; $kp{$key} = $val; } } if ($bynode) { my @ents = $tabh->getNodeAttribs($node, [ keys %kp, $field ]); my $tent; #Temporary ent TENT: foreach $tent (@ents) { foreach (keys %kp) { unless ($kp{$_} eq $tent->{$_}) { next TENT; } } #If still here, we found it $ent = $tent; } } else { ($ent) = $tabh->getAttribs(\%kp, $field); } } $tabh->close; unless ($ent and defined($ent->{$field})) { unless ($blankok) { if ($field eq "xcatmaster") { my $ipfn; my @ipfnd = xCAT::NetworkUtils->my_ip_facing($node); unless ($ipfnd[0]) { $ipfn = $ipfnd[1]; } if ($ipfn) { return $ipfn; } } #$tmplerr="Unable to find requested $field from $table, with $key"; my $savekey = $key; $key = '$NODE'; # make sure we use getNodeAttribs when get_replacement # calls this routine (tabdb) my $rep = get_replacement($table, $key, $field); $key = $savekey; # restore just in case we rely on the node=$node setting if ($rep) { return tabdb($rep->[0], $rep->[1], $rep->[2]); } else { $tmplerr = "Unable to find requested field <$field> from table <$table>, with key <$key>" } } return ""; #return "#TABLEBAD:$table:field $field not found#"; } else { # check for site.xcatdebugmode if (($table =~ /site/) and ($key =~ /xcatdebugmode/)) { if ((($ent->{$field}) ne "0") and (($ent->{$field}) ne "1") and (($ent->{$field}) ne "2")) { $tmplerr = "Unable to recognise field <$field> from table <$table>, with key <$key>. Please enter '0' '1' or '2'" } } } return $ent->{$field}; #if($key =~ /^\$/) { # $key =~ s/^\$//; # $key = $ENV{$key}; #} #if($field =~ /^\$/) { # $field =~ s/^\$//; # $field = $ENV{$field}; #} #if($field == '*') { # $field = 1; # $all = 1; #} #--$field; #if($field < 0) { # return "#TABLE:field not found#" #} #open(TAB,$table) || \ # return "#TABLE:cannot open $table#"; #while(<TAB>) { # if(/^$key(\t|,| )/) { # m/^$key(\t|,| )+(.*)/; # if($all == 1) { # return "$2"; # } # @fields = split(',',$2); # if(defined $fields[$field]) { # return "$fields[$field]"; # } # else { # return "#TABLE:field not found#" # } # } #} #close(TAB); #return "#TABLE:key not found#" } sub get_replacement { my $table = shift; my $key = shift; my $field = shift; my $rep; if (exists($tab_replacement{"$table:$field"})) { my $repstr = $tab_replacement{"$table:$field"}; if ($repstr) { my @a = split(':', $repstr); if (@a > 2) { $rep = \@a; } else { $rep->[0] = $a[0]; $rep->[1] = $key; $rep->[2] = $a[1]; } } } return $rep; } # This routine is used in the creation of the mypostscript file and is defined # in /opt/xcat/share/xcat/templates/mypostcript/mypostscript.tmpl # It cannot be moved to another perl library, due to migration problems. # sub enablesshbetweennodes { my $node = shift; my $result; my $enablessh = xCAT::TableUtils->enablessh($node); if ($enablessh == 1) { $result = "'YES'"; } else { $result = "'NO'"; } return $result; } sub getNM_GW() { my $ip = shift; my $nettab = xCAT::Table->new("networks"); if ($nettab) { my @nets = $nettab->getAllAttribs('net', 'mask', 'gateway'); foreach my $net (@nets) { if (xCAT::NetworkUtils::isInSameSubnet($net->{'net'}, $ip, $net->{'mask'}, 0)) { return (xCAT::NetworkUtils::formatNetmask($net->{'mask'}, 0, 1), $net->{'gateway'}); } } } return (undef, undef); } 1;