Environment
OES2SP2
Domain Services for Windows 1.1
DSFW 1.1
Situation
There are two issues found with DNS records that affect the installation of ADCs.
- Wrong domain GUID record in DNS.
- PDC record length is wrong
Issue 1:
Wrong domain GUID record in DNS.
The domain guid record is rarely used to identify the domain. In SP1 each domain controller wrongly creates its own SRV record. There should be only one record and it should have a list of all domain controllers.
Although its rarely used still we need to clear this because installing a sp2-ADC with sp1-FRD might fail in the dns task of the provisioning tool.
An example of a domain guid record is as follows:
$ORIGIN _msdcs.domain.com._ldap._tcp.12b0ca11-b91a-5eca-3b71-1cab05613a9b.domains SRV 0 100 389 frd.domain.com.
Issue 2:
PDC record length is wrong
If the host name is more than 9 characters the dns data record length will be wrong. We wont be able to see where its wrong, data and length are combined and converted into base64 format stored in the dniprr attribute.
While loading the data the DNS server does not take notice of the wrong length but when a ddns update is done it wont be able to identify which record to update because of the length.
Resolution
The "upgrade_dns_data.pl" fixes both issues.
The “upgrade_dns_data.pl” script will only update eDirectory and does not update the dns server until dns is restarted. Sometimes deleting the .db files from /etc/opt/novell/name and restarting is needed.
“upgrade_dns_data.pl -a” will run the script and fix both issues.
“upgrade_dns_data.pl -an” will do a dry run of the upgrade.
“upgrade_dns_data.pl -h” will print the help screen.
Here is the supgrade_dns_data.pl script. Copy and paste into a text editor and chmod 755 the file.
#!/usr/bin/perl
####################################################
#
# (C) Copyright 2009-2010 Novell, Inc.
# All Rights Reserved.
#
# This program is an unpublished copyrighted work which is proprietary
# to Novell, Inc. and contains confidential information that is not
# to be reproduced or disclosed to any other person or entity without
# prior written consent from Novell, Inc. in each and every instance.
#
# WARNING: Unauthorized reproduction of this program as well as
# unauthorized preparation of derivative works based upon the
# program or distribution of copies by sale, rental, lease or
# lending are violations of federal copyright laws and state trade
# secret laws, punishable by civil and criminal penalties.
#
#####################################################
use strict;
use warnings;
use Getopt::Std;
use MIME::Base64;
use Term::ReadKey;
use Net::LDAP;
use Net::LDAPS;
use Net::LDAP::Util qw(ldap_error_text
ldap_error_name
ldap_error_desc
); #SSL bind
use Net::LDAP::LDIF;
use Net::LDAP::Control;
use Net::LDAP::Constant qw( LDAP_CONTROL_MATCHEDVALS );
my $dry_run = 0;
my $username = "";
my $password = "";
my $ld;
my ($domainname, $domain_container, $zone_name);
my %RR_Types_maps = (
"A" => "1",
"NS" => "2",
"CNAME" => "5",
"PTR" => "12",
"SRV" => "33"
);
my %RR_value = (
"RR_Type" => "$RR_Types_maps{SRV}",
"RR_Adr_Class" => "1",
"RR_TTL" => "0",
"RR_Data_len" => ""
);
my %RR_Data_Value = (
"Priority" => "0",
"Weight" => "100",
"Port" => "389",
"Data" => ""
);
sub usage
{
print <<USE;
This script fixes two dns problems which will help to make the upgrade smooth.
dsfw-fix-sp1-dns-records.pl [-g] [-r] [-a] [-h]
-g Fix the unknown domain guid record
-r Fix the rr record length problem
-a Fix both of the above problem
-D Administrator name
-w Password for the Administrator
-n dry run
-h Print this help
USE
}
sub read_pwd_terminal
{
my $str = shift;
my $pwd;
print "\n$str: ";
ReadMode('noecho');
$pwd = ReadLine(0);
ReadMode('normal');
print "\n";
chomp($pwd);
return $pwd;
}
sub initiate_connection_variables
{
$ld = Net::LDAP->new("localhost", scheme=>"ldaps", port=>"636", timeout=>120) or die("Could not create connection");
my $msg = $ld->bind($username, password=>$password);
$msg->code && die $msg->error;
if (ldap_error_name($msg) ne "LDAP_SUCCESS") {
die(ldap_error_text($msg));
}
$domainname = `/bin/dnsdomainname`;
chomp($domainname);
$domain_container = "dc=$domainname";
$domain_container =~ s/\./,dc=/g;
$zone_name = "cn=$domainname,ou=novell,$domain_container";
$zone_name =~ s/\./_/g;
}
sub get_credential
{
if($username eq "")
{
if(defined $ENV{ADM_NAME})
{
$username = $ENV{ADM_NAME}
}
else
{
print "Username is neither passed with -D flag or with ADM_NAME environement variable.\n";
my $domain = `/bin/dnsdomainname`;
chomp($domain);
$domain =~ s/\./,dc=/;
print "Assuming the default administrator : cn=Administrator,cn=users,dc=$domain\n";
$username = "cn=Administrator,cn=users,dc=$domain";
}
}
if($password eq "")
{
if(defined $ENV{ADM_PASSWD})
{
$password = $ENV{ADM_PASSWD};
}
else
{
$password = read_pwd_terminal("Enter the password for the administrator")
}
}
initiate_connection_variables;
}
sub fix_domain_guid
{
print "\nGetting the domain container..\n";
print "Domain container = $domain_container\n\n";
print "Getting the correct domain guid...\n";
my $domain_guid = `/opt/novell/xad/share/dcinit/provisionTools.sh get-domain-guid -p localhost -c $domain_container`;
if($? == 0) {
chomp($domain_guid);
}
else {
print "Could not get the domain guid using, \n";
print "/opt/novell/xad/share/dcinit/provisionTools.sh get-domain-guid -p localhost -c $domain_container\n";
exit(1);
}
print "Domain GUID = $domain_guid\n\n";
print "Searching for the wrong domain guid srv records..\n";
my $wrong_list = $ld->search(base=>$zone_name,
scope=>"sub",
filter=>"(&(cn=#ldap_#tcp_*domains_#msdcs)(!(cn=#ldap_#tcp_".$domain_guid."_domains_#msdcs)))");
for (my $i=0; $i<$wrong_list->count; $i++)
{
my $ent = $wrong_list->entry( $i );
print $ent->dn(). "\n";
}
$| = 1; # perform flush after each write to STDOUT
print "The above records will be corrected to $domain_guid \n";
print "(P)roceed:";
my $c;
$c = ReadLine(0);
chomp($c);
# sysread(STDIN,$c,1);
if(lc($c) eq "p"){
my $entry;
print "\n Proceeding further\n";
my $out = $ld->search(base=>$zone_name,
scope=>"one",
filter=>"cn=#ldap_#tcp_"."$domain_guid"."_domains_#msdcs");
if($out->count == 1) {
print "cn=#ldap_#tcp_"."$domain_guid"."_domains_#msdcs,$zone_name already exist, modifying it\n";
$entry = $out->entry(0);
for (my $i=0; $i<$wrong_list->count; $i++)
{
my $ent = $wrong_list->entry( $i );
my $dn = $ent->dn;
next if $dn =~ /$domain_guid/; #skiping the same entries content
my $val = $ent->get_value('dNIPRR');
$entry->add('dNIPRR' => $val);
}
}
else {
print "Creating cn=#ldap_#tcp_"."$domain_guid"."_domains_#msdcs,$zone_name\n";
$entry = Net::LDAP::Entry->new();
$entry->dn( "cn=\\#ldap_#tcp_".$domain_guid."_domains_#msdcs,$zone_name");
$entry->add('objectClass' => [qw( top dNIPDNSRRset )]);
$entry->add('dNIPRRStatus' => 0);
$entry->add('dNIPDNSDomainName' =>"_ldap._tcp.$domain_guid.domains._msdcs.$domainname" );
for (my $i=0; $i<$wrong_list->count; $i++)
{
my $ent = $wrong_list->entry( $i );
my $val = $ent->get_value('dNIPRR');
$entry->add('dNIPRR' => $val);
}
}
if ($dry_run == 0) {
my $msg = $entry->update($ld);
$msg->code && die $msg->error;
if (ldap_error_name($msg) ne "LDAP_SUCCESS") {
print "Could not create the correct domain guid entry";
die(ldap_error_text($msg));
}
}
for (my $i=0; $i<$wrong_list->count; $i++)
{
my $ent = $wrong_list->entry( $i );
$ent->delete();
if($dry_run == 0) {
my $msg = $ent->update($ld);
$msg->code && die $msg->error;
if (ldap_error_name($msg) ne "LDAP_SUCCESS") {
print "Could not delete the old wrong domain guid entries";
die(ldap_error_text($msg));
}
}
}
}
else {
print "\n Not proceeding further\n";
return;
}
print "Correct domain guid record is created\n";
}
sub Form_Data_format {
my ($data_string) = @_;
my (@data_array, @data_member_length, @aggregate_array) = "";
my ($data_array_length, $i, $j) = 0;
my ($final_data_string, $datapackstring);
$data_string =~ s/\./_/g;
@data_array = split("_", $data_string);
$data_array_length = @data_array;
$datapackstring = "ca";
for($i=0; $i < $data_array_length; $i++)
{
$data_member_length[$i] = length($data_array[$i]);
$datapackstring .= length($data_array[$i])."ca";
}
# chop off the last two added 'ca' to datapackstring
chop $datapackstring;
chop $datapackstring;
for($i=0, $j=0; $i < $data_array_length; $i++, $j=$j+2)
{
$aggregate_array[$j] = $data_member_length[$i];
$aggregate_array[$j+1] = $data_array[$i];
}
# create the final data string
$final_data_string = join(',', @aggregate_array);
return ($final_data_string, $datapackstring);
}
sub fix_rr_length
{
print "\n\n Finding the pdc record with the wrong length\n\n";
my %rr_list = ();
my $out = $ld->search(base=>"ou=Domain Controllers,$domain_container",
scope=>"one",
filter=>"objectclass=computer");
#We need to get the domain controller fqdn. Search under ou=Domain controller
#and form the DC's fqdn and formulate the dnipRR and compare.
for(my $i=0; $i<$out->count; $i++) {
#The problem exist only with the host more than 9 characters
if(length($out->entry($i)->get_value('cn')) > 9) { ##> 9
my $dcname = $out->entry($i)->get_value("cn").".$domainname";
my ($final_data_string, $datapackstring) = Form_Data_format(lc($dcname));
my @data_array = split(/,/, $final_data_string);
my $data_len = 0;
for(my $j=0; $j<@data_array; $j+=2)
{
#lenth of each word between dots
$data_len += int($data_array[$j]);
}
#one byte to store each word's length
$data_len += (@data_array)/2;
my $temp_data_string = $final_data_string;
$temp_data_string =~ s/,//g;
# data length = 2 bytes (priority) + 2 byte (weight) + 2 byte (port) + length(data) in bytes + 1 byte (null char), as the 1 byte of 1st data segment is counted in $data_len now
$RR_value{RR_Data_correct_len} = 2 + 2 + 2 + "$data_len" + 1;
$RR_value{RR_Data_wrong_len} = 2 + 2 + 2 + length($temp_data_string) + 1;
my $finalpackstring = "nnNnnnn".$datapackstring."x";
my $finalcorrectbitstring = pack "$finalpackstring", $RR_value{RR_Type}, $RR_value{RR_Adr_Class}, $RR_value{RR_TTL}, $RR_value{RR_Data_correct_len}, $RR_Data_Value{Priority}, $RR_Data_Value{Weight}, $RR_Data_Value{Port}, split(",", "$final_data_string");
my $finalwrongbitstring = pack "$finalpackstring", $RR_value{RR_Type}, $RR_value{RR_Adr_Class}, $RR_value{RR_TTL}, $RR_value{RR_Data_wrong_len}, $RR_Data_Value{Priority}, $RR_Data_Value{Weight}, $RR_Data_Value{Port}, split(",", "$final_data_string");
my @arr = [$finalwrongbitstring, $finalcorrectbitstring];
$rr_list{$dcname}{wrong} = $finalwrongbitstring;
$rr_list{$dcname}{correct} = $finalcorrectbitstring;
}
}
$out = $ld->search(base=>$zone_name,
scope=>"one",
filter=>"cn=#ldap_#tcp_pdc_#msdcs");
my $entry;
if($out->count == 1) {
$entry = $out->entry(0);
}
else {
print "Could not find the pdc record, exiting";
exit(1);
}
my @rr_vals = $entry->get_value('dNIPRR');
foreach my $rr_val (@rr_vals) {
for my $key (keys %rr_list) {
if ($rr_list{$key}{wrong} eq $rr_val) {
print "$key contains wrong length in the pdc record (dNIPRR)\n";
$entry->delete('dNIPRR'=>[$rr_val]);
$entry->add('dNIPRR'=>[$rr_list{$key}{correct}]);
}
}
}
$| = 1; # perform flush after each write to STDOUT
print "The above domain controller name will be fixed in the pdc record \n";
print "(P)roceed:";
my $c;
$c = ReadLine(0);
chomp($c);
# sysread(STDIN,$c,1);
if(lc($c) eq "p"){
print "\n Proceeding further\n";
if ($dry_run == 0) {
my $msg = $entry->update($ld);
$msg->code && die $msg->error;
if (ldap_error_name($msg) ne "LDAP_SUCCESS") {
print "Could not fix the dniprr attribute of the pdc record";
die(ldap_error_text($msg));
}
}
}
else {
print "\n Not proceeding further\n";
exit(0);
}
print "The pdc record is fixed. ddns update will happen correctly\n";
}
sub main
{
my %options=();
getopts("granhD:w:", \%options) or die("Unexpected arguments");
my $count = scalar keys %options;
print "\n\n";
$dry_run = 1 if defined $options{n};
$username = $options{D} if defined $options{D};
$password = $options{w} if defined $options{w};
if (defined $options{h} || $count == 0) {
usage
}
elsif (defined $options{a})
{
get_credential;
fix_domain_guid;
fix_rr_length;
}
elsif (defined $options{g})
{
get_credential;
fix_domain_guid;
}
elsif (defined $options{r})
{
get_credential;
fix_rr_length;
}
else
{
usage;
}
}
main