diff --git a/perl-xCAT/xCAT/Schema.pm b/perl-xCAT/xCAT/Schema.pm index 26ee84c18..3e008ab59 100644 --- a/perl-xCAT/xCAT/Schema.pm +++ b/perl-xCAT/xCAT/Schema.pm @@ -56,17 +56,19 @@ statelite => { #tables come into play. Given no explicit request to span domains and no effort to #seriously evaluate wider support of multi-domain environments, will leave them #commented rather than tempt people to try with an expectation that it could work. -#domain => { -# cols => [qw(node domain comments disable)], -# keys => ['node'], -# table_desc => 'Mapping of nodes to domains', -# descriptions => { -# node => 'The node or group the entry applies to', +domain => { + cols => [qw(node ou comments disable)], + keys => ['node'], + table_desc => 'Mapping of nodes to domain attributes', + descriptions => { + node => 'The node or group the entry applies to', # domain => 'The name of the domain it is a member of, such as "example.com". Defaults to domain value from the site table', -# comments => 'Any user-written notes.', -# disable => "Set to 'yes' or '1' to comment out this row.", -# }, -#}, +# the above column is unimplemented by anything, so leave it out for this pass + ou => 'For an LDAP described machine account (i.e. Active Directory), the orginaztional unit to place the system. If not set, defaults to cn=Computers,dc=your,dc=domain', + comments => 'Any user-written notes.', + disable => "Set to 'yes' or '1' to comment out this row.", + }, +}, #domains => { # cols => [qw(domain nameserver authserver realm comments disable)], # keys => ['domain'], diff --git a/perl-xCAT/xCAT/Template.pm b/perl-xCAT/xCAT/Template.pm index e76993034..06fa0e976 100644 --- a/perl-xCAT/xCAT/Template.pm +++ b/perl-xCAT/xCAT/Template.pm @@ -8,6 +8,11 @@ use File::Basename; use File::Path; use Data::Dumper; use Sys::Syslog; +use xCAT::ADUtils; #to allow setting of one-time machine passwords +my $netdnssupport = eval { + require Net::DNS; + 1; +}; my $tmplerr; my $table; @@ -15,6 +20,7 @@ my $key; my $field; my $idir; my $node; +my %loggedrealms; sub subvars { my $self = shift; @@ -70,6 +76,7 @@ sub subvars { #ok, now do everything else.. $inc =~ s/#XCATVAR:([^#]+)#/envvar($1)/eg; $inc =~ s/#ENV:([^#]+)#/envvar($1)/eg; + $inc =~ s/#MACHINEPASSWORD#/machinepassword()/eg; $inc =~ s/#TABLE:([^:]+):([^:]+):([^#]+)#/tabdb($1,$2,$3)/eg; $inc =~ s/#TABLEBLANKOKAY:([^:]+):([^:]+):([^#]+)#/tabdb($1,$2,$3,'1')/eg; $inc =~ s/#CRYPT:([^:]+):([^:]+):([^#]+)#/crydb($1,$2,$3)/eg; @@ -86,6 +93,78 @@ sub subvars { close($outh); return 0; } +sub machinepassword { + my $domaintab = xCAT::Table->new('domain'); + $ENV{LDAPCONF}='/etc/xcat/ad.ldaprc'; + my $ou; + if ($domaintab) { + my $ouent = $domaintab->getNodeAttribs('node','ou'); + if ($ouent and $ouent->{ou}) { + $ou = $ouent->{ou}; + } + } + my $sitetab = xCAT::Table->new('site'); + unless ($sitetab) { + return "ERROR: unable to open site table"; + } + my $domain; + (my $et) = $sitetab->getAttribs({key=>"domain"},'value'); + if ($et and $et->{value}) { + $domain = $et->{value}; + } + unless ($domain) { + return "ERROR: no domain set in site table"; + } + 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) { sendmsg([1,"Error authenticating to Active Directory"],$node); return; } + (my $adpent) = $passtab->getAttribs({key=>'activedirectory'},['username','password']); + unless ($adpent and $adpent->{username} and $adpent->{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']); + if ($server and $server->{value}) { + $server = $server->{value}; + } 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) { + sendmsg([1,"Unable to determine a directory server to communicate with, try site.directoryserver"]); + return; + } + } + 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 { + return $data->{password}; + } +} sub includefile { my $file = shift; @@ -95,8 +174,7 @@ sub includefile $file = $idir."/".$file; } - open(INCLUDE,$file) || \ - return "#INCLUDEBAD:cannot open $file#"; + open(INCLUDE,$file) || return "#INCLUDEBAD:cannot open $file#"; while() { $text .= "$_"; diff --git a/xCAT-server/lib/perl/xCAT/ADUtils.pm b/xCAT-server/lib/perl/xCAT/ADUtils.pm index 5799f1873..bdd624165 100644 --- a/xCAT-server/lib/perl/xCAT/ADUtils.pm +++ b/xCAT-server/lib/perl/xCAT/ADUtils.pm @@ -41,6 +41,12 @@ dn: CN=##NODENAME##,##OU## changetype: modify replace: unicodePwd unicodePwd::##B64PASSWORD##'; + +my $machineldifpasschange = 'dn: CN=##NODENAME##,##OU## +changetype: modify +replace: unicodePwd +unicodePwd::##B64PASSWORD##'; + my $useraccounttemplate = 'dn: CN=##FULLNAME##,##OU## changetype: add objectClass: top @@ -466,6 +472,7 @@ sub add_host_account { my %args = @_; my $nodename = $args{node}; my $dnsdomain = $args{dnsdomain}; + my $changepassondupe = $args{changepassondupe}; if (not $dnsdomain and $nodename =~ /\./) { #if no domain provided, guess from nodename $dnsdomain = $nodename; $dnsdomain =~ s/^[^\.]*//; @@ -492,28 +499,37 @@ sub add_host_account { return {error=>"Unable to determine all required parameters"}; } my $newpassword = $args{password}; - unless ($newpassword) { - $newpassword = '"'.genpassword(8).'"'; + if ($newpassword) { + $newpassword = '"'.$newpassword.'"'; + } else { + $newpassword = '"'.genpassword(8).'a0P"'; #add a little to assure that a fluke doesn't produce a password that won't pass MS filters } + my $nativenewpassword = $newpassword; Encode::from_to($newpassword,"utf8","utf16le"); #ms uses utf16le, we use utf8 my $b64password = encode_base64($newpassword); - my $ldif = $machineldiftemplate; + my $ldif; + my $dn = "CN=$nodename,$ou"; + my $rc = system("ldapsearch -H ldaps://$directoryserver -b $dn"); #TODO: for mass add, search once, hit that + if ($rc == 0) { + if ($changepassondupe) { + $ldif = $machineldifpasschange; + } else { + return {error=>"System already exists"}; + } + } elsif (not $rc==8192) { + return {error=>"Unknown error $rc"}; + } else { + $ldif = $machineldiftemplate; + } $ldif =~ s/##B64PASSWORD##/$b64password/g; $ldif =~ s/##OU##/$ou/g; $ldif =~ s/##REALMDCS##/$domain_components/g; $ldif =~ s/##DNSDOMAIN##/$dnsdomain/g; $ldif =~ s/##NODENAME##/$nodename/g; - my $dn = "CN=$nodename,$ou"; - my $rc = system("ldapsearch -H ldaps://$directoryserver -b $dn"); - if ($rc == 0) { - return {error=>"System already exists"}; - } elsif (not $rc==8192) { - return {error=>"Unknown error $rc"}; - } - open(HUH,">","/tmp/huhh"); - print HUH $ldif; $rc = system("echo '$ldif'|ldapmodify -H ldaps://$directoryserver"); - return {password=>$newpassword}; + substr $nativenewpassword,0,1,''; + chop($nativenewpassword); + return {password=>$nativenewpassword}; } sub krb_login {