Install of OES2SP2 DSFW server to an OES2SP1 domain fails

  • 7005314
  • 08-Feb-2010
  • 27-Apr-2012

Environment

Novell Open Enterprise Server 2 (OES 2) Linux Support Pack 2
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.

  1. Wrong domain GUID record in DNS.
  2. 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

Additional Information

The upgrade_dns_data.pl script is included in March Maintenance patch for OES2SP2