s3-examples: make sure to match correct key name in adssearch.
[bbaumbach/samba-autobuild/.git] / examples / misc / adssearch.pl
index 6a677fa20556dddace4df79fb7d71630443a2aa6..026853d1524d95bac9656ed0c12d9fca1c98115b 100755 (executable)
@@ -3,17 +3,17 @@
 # adssearch.pl         - query an Active Directory server and
 #                display objects in a human readable format
 #
-# Copyright (C) Guenther Deschner <gd@samba.org> 2003-2005
+# Copyright (C) Guenther Deschner <gd@samba.org> 2003-2008
 #
 # TODO: add range retrieval
 #      write sddl-converter, decode userParameters
-#      chase referrals
 #      apparently only win2k3 allows simple-binds with machine-accounts.
 #      make sasl support independent from Authen::SASL::Cyrus v >0.11
 use strict;
 
 use Net::LDAP;
 use Net::LDAP::Control;
+use Net::LDAP::Constant qw(LDAP_REFERRAL);
 use Convert::ASN1;
 use Time::Local;
 use POSIX qw(strftime);
@@ -38,6 +38,8 @@ my $base      = "";
 my $binddn     = "";
 my $password   = "";
 my $server     = "";
+my $rebind_url;
+
 
 my $tdbdump    = "/usr/bin/tdbdump";
 my $testparm   = "/usr/bin/testparm";
@@ -47,8 +49,6 @@ my $nmblookup = "/usr/bin/nmblookup";
 my $secrets_tdb = "/etc/samba/secrets.tdb";
 my $klist      = "/usr/bin/klist";
 my $kinit      = "/usr/bin/kinit";
-my $ads_h      = "/home/gd/ads.h";
-my $page_size  = "1000";
 my $workgroup  = "";
 my $machine    = "";
 my $realm      = "";
@@ -62,18 +62,22 @@ my (
        $opt_display_extendeddn,
        $opt_display_metadata,
        $opt_display_raw,
+       $opt_domain_scope,
        $opt_dump_rootdse,
        $opt_dump_schema,
        $opt_dump_wknguid,
+       $opt_fastbind,
        $opt_help, 
        $opt_host, 
        $opt_machine,
        $opt_notify, 
-       $opt_notify_nodiffs, 
+       $opt_notify_nodiffs,
+       $opt_paging,
        $opt_password,
        $opt_port,
        $opt_realm,
        $opt_saslmech,
+       $opt_search_opt,
        $opt_scope, 
        $opt_simpleauth,
        $opt_starttls,
@@ -87,13 +91,16 @@ GetOptions(
        'base|b=s'      => \$opt_base,
        'D|DN=s'        => \$opt_binddn,
        'debug=i'       => \$opt_debug,
-       'extendeddn|e=i'        => \$opt_display_extendeddn,
+       'domain_scope'  => \$opt_domain_scope,
+       'extendeddn|e:i'        => \$opt_display_extendeddn,
+       'fastbind'      => \$opt_fastbind,
        'help'          => \$opt_help,
        'host|h=s'      => \$opt_host,
        'machine|P'     => \$opt_machine,
        'metadata|m'    => \$opt_display_metadata,
        'nodiffs'       => \$opt_notify_nodiffs,
        'notify|n'      => \$opt_notify,
+       'paging:i'      => \$opt_paging,
        'password|w=s'  => \$opt_password,
        'port=i'        => \$opt_port,
        'rawdisplay'    => \$opt_display_raw,
@@ -102,6 +109,7 @@ GetOptions(
        'saslmech|Y=s'  => \$opt_saslmech,
        'schema|c'      => \$opt_dump_schema,
        'scope|s=s'     => \$opt_scope,
+       'searchopt:i'   => \$opt_search_opt,
        'simpleauth|x'  => \$opt_simpleauth,
        'tls|Z'         => \$opt_starttls,
        'user|U=s'      => \$opt_user,
@@ -111,20 +119,27 @@ GetOptions(
        );
 
 
-# activate controls
-my $paging     = 1 if !$opt_notify;
-
 if (!@ARGV && !$opt_dump_schema && !$opt_dump_rootdse && !$opt_notify || $opt_help) {
        usage();
        exit 1;
 }
 
+if ($opt_fastbind && !$opt_simpleauth) {
+       printf("LDAP fast bind can only be performed with simple binds\n");
+       exit 1;
+}
+
+if ($opt_notify) {
+       $opt_paging = undef;
+}
+
 # get the query
 my $query      = shift;
 my @attrs      = @ARGV;
 
 # some global vars
-my ($filter, $dse, $uri);
+my $filter = "";
+my ($dse, $uri);
 my ($attr, $value);
 my (@ctrls, @ctrls_s);
 my ($ctl_paged, $cookie);
@@ -133,7 +148,6 @@ my ($sasl_hd, $async_ldap_hd, $sync_ldap_hd);
 my ($mesg, $usn);
 my (%entry_store);
 my $async_search;
-my (%ads_atype, %ads_gtype, %ads_grouptype, %ads_uf);
 
 # fixed values and vars
 my $set        = "X";
@@ -166,6 +180,7 @@ my %ads_controls = (
 "LDAP_SERVER_ASQ_OID"                  => "1.2.840.113556.1.4.1504",
 "NONE (Get stats control)"             => "1.2.840.113556.1.4.970",
 "LDAP_SERVER_QUOTA_CONTROL_OID"                => "1.2.840.113556.1.4.1852",
+"LDAP_SERVER_SHUTDOWN_NOTIFY_OID"      => "1.2.840.113556.1.4.1907",
 );
 
 my %ads_capabilities = (
@@ -215,6 +230,7 @@ my %ads_mixed_domain = (
 my %ads_ds_func = (
 "DS_BEHAVIOR_WIN2000"                  => 0,   # untested
 "DS_BEHAVIOR_WIN2003"                  => 2,
+"DS_BEHAVIOR_WIN2008"                  => 3,
 );
 
 my %ads_instance_type = (
@@ -229,6 +245,14 @@ my %ads_uacc = (
        "ACCOUNT_LOCKED_OUT"            => 0x800010, # 8388624
 );
 
+my %ads_enctypes = (
+       "DES-CBC-CRC"                           => 0x01,
+       "DES-CBC-MD5"                           => 0x02,
+       "RC4_HMAC_MD5"                          => 0x04,
+       "AES128_CTS_HMAC_SHA1_96"               => 0x08,
+       "AES128_CTS_HMAC_SHA1_128"              => 0x10,
+);
+
 my %ads_gpoptions = (
        "GPOPTIONS_INHERIT"             => 0,
        "GPOPTIONS_BLOCK_INHERITANCE"   => 1,
@@ -332,6 +356,75 @@ my %ads_gpo_default_guids = (
 "mist"                                 => "61718096-3D3F-4398-8318-203A48976F9E",
 );
 
+my %ads_uf = (
+       "UF_SCRIPT"                             => 0x00000001,
+       "UF_ACCOUNTDISABLE"                     => 0x00000002,
+#      "UF_UNUSED_1"                           => 0x00000004,
+       "UF_HOMEDIR_REQUIRED"                   => 0x00000008,
+       "UF_LOCKOUT"                            => 0x00000010,
+       "UF_PASSWD_NOTREQD"                     => 0x00000020,
+       "UF_PASSWD_CANT_CHANGE"                 => 0x00000040,
+       "UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED"    => 0x00000080,
+       "UF_TEMP_DUPLICATE_ACCOUNT"             => 0x00000100,
+       "UF_NORMAL_ACCOUNT"                     => 0x00000200,
+#      "UF_UNUSED_2"                           => 0x00000400,
+       "UF_INTERDOMAIN_TRUST_ACCOUNT"          => 0x00000800,
+       "UF_WORKSTATION_TRUST_ACCOUNT"          => 0x00001000,
+       "UF_SERVER_TRUST_ACCOUNT"               => 0x00002000,
+#      "UF_UNUSED_3"                           => 0x00004000,
+#      "UF_UNUSED_4"                           => 0x00008000,
+       "UF_DONT_EXPIRE_PASSWD"                 => 0x00010000,
+       "UF_MNS_LOGON_ACCOUNT"                  => 0x00020000,
+       "UF_SMARTCARD_REQUIRED"                 => 0x00040000,
+       "UF_TRUSTED_FOR_DELEGATION"             => 0x00080000,
+       "UF_NOT_DELEGATED"                      => 0x00100000,
+       "UF_USE_DES_KEY_ONLY"                   => 0x00200000,
+       "UF_DONT_REQUIRE_PREAUTH"               => 0x00400000,
+       "UF_PASSWORD_EXPIRED"                   => 0x00800000,
+       "UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION" => 0x01000000,
+       "UF_NO_AUTH_DATA_REQUIRED"              => 0x02000000,
+#      "UF_UNUSED_8"                           => 0x04000000,
+#      "UF_UNUSED_9"                           => 0x08000000,
+#      "UF_UNUSED_10"                          => 0x10000000,
+#      "UF_UNUSED_11"                          => 0x20000000,
+#      "UF_UNUSED_12"                          => 0x40000000,
+#      "UF_UNUSED_13"                          => 0x80000000,
+);
+
+my %ads_grouptype = (
+       "GROUP_TYPE_BUILTIN_LOCAL_GROUP"        => 0x00000001,
+       "GROUP_TYPE_ACCOUNT_GROUP"              => 0x00000002,
+       "GROUP_TYPE_RESOURCE_GROUP"             => 0x00000004,
+       "GROUP_TYPE_UNIVERSAL_GROUP"            => 0x00000008,
+       "GROUP_TYPE_APP_BASIC_GROUP"            => 0x00000010,
+       "GROUP_TYPE_APP_QUERY_GROUP"            => 0x00000020,
+       "GROUP_TYPE_SECURITY_ENABLED"           => 0x80000000,
+);
+
+my %ads_atype = (
+       "ATYPE_NORMAL_ACCOUNT"                  => 0x30000000,
+       "ATYPE_WORKSTATION_TRUST"               => 0x30000001,
+       "ATYPE_INTERDOMAIN_TRUST"               => 0x30000002,
+       "ATYPE_SECURITY_GLOBAL_GROUP"           => 0x10000000,
+       "ATYPE_DISTRIBUTION_GLOBAL_GROUP"       => 0x10000001,
+       "ATYPE_DISTRIBUTION_UNIVERSAL_GROUP"    => 0x10000001, # ATYPE_DISTRIBUTION_GLOBAL_GROUP
+       "ATYPE_SECURITY_LOCAL_GROUP"            => 0x20000000,
+       "ATYPE_DISTRIBUTION_LOCAL_GROUP"        => 0x20000001,
+       "ATYPE_ACCOUNT"                         => 0x30000000, # ATYPE_NORMAL_ACCOUNT
+       "ATYPE_GLOBAL_GROUP"                    => 0x10000000, # ATYPE_SECURITY_GLOBAL_GROUP
+       "ATYPE_LOCAL_GROUP"                     => 0x20000000, # ATYPE_SECURITY_LOCAL_GROUP
+);
+
+my %ads_gtype = (
+       "GTYPE_SECURITY_BUILTIN_LOCAL_GROUP"    => 0x80000005,
+       "GTYPE_SECURITY_DOMAIN_LOCAL_GROUP"     => 0x80000004,
+       "GTYPE_SECURITY_GLOBAL_GROUP"           => 0x80000002,
+       "GTYPE_SECURITY_UNIVERSAL_GROUP"        => 0x80000008,
+       "GTYPE_DISTRIBUTION_GLOBAL_GROUP"       => 0x00000002,
+       "GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP" => 0x00000004,
+       "GTYPE_DISTRIBUTION_UNIVERSAL_GROUP"    => 0x00000008,
+);
+
 my %munged_dial = (
        "CtxCfgPresent"         => \&dump_int,
        "CtxCfgFlags1"          => \&dump_int,
@@ -356,9 +449,6 @@ $SIG{__WARN__} = sub {
        Carp::cluck (shift);
 };
 
-# parse ads.h
-parse_ads_h();
-
 # if there is data missing, we try to autodetect with samba-tools (if installed)
 # this might fill up workgroup, machine, realm
 get_samba_info();
@@ -373,7 +463,7 @@ $server     = process_servername($opt_host) ||
 
 
 # get the base
-$base          = $opt_base || 
+$base          = defined($opt_base)? $opt_base : "" || 
                  get_base_from_rootdse($server,$dse);
 
 # get the realm
@@ -408,6 +498,7 @@ if (!$password) {
 my %attr_handler = (
        "Token-Groups-No-GC-Acceptable" => \&dump_sid,  #wrong name
        "accountExpires"                => \&dump_nttime,
+       "attributeSecurityGUID"         => \&dump_guid,
        "badPasswordTime"               => \&dump_nttime,                       
        "creationTime"                  => \&dump_nttime,
        "currentTime"                   => \&dump_timestr,
@@ -436,6 +527,7 @@ my %attr_handler = (
        "modifyTimeStamp"               => \&dump_timestr,
        "msDS-Behavior-Version"         => \&dump_ds_func,      #unsure
        "msDS-User-Account-Control-Computed" => \&dump_uacc,
+       "msDS-SupportedEncryptionTypes" => \&dump_enctypes,
        "mS-DS-CreatorSID"              => \&dump_sid,
 #      "msRADIUSFramedIPAddress"       => \&dump_ipaddr,
 #      "msRASSavedFramedIPAddress"     => \&dump_ipaddr,
@@ -449,6 +541,7 @@ my %attr_handler = (
        "pwdLastSet"                    => \&dump_nttime,
        "pwdProperties"                 => \&dump_pwdproperties,
        "sAMAccountType"                => \&dump_atype,
+       "schemaIDGUID"                  => \&dump_guid,
        "sDRightsEffective"             => \&dump_sdeffective,
        "securityIdentifier"            => \&dump_sid,
        "serverState"                   => \&dump_serverstate,
@@ -483,14 +576,17 @@ sub usage {
        print "\t--asq [attribute]\n\t\tAttribute to use for a attribute scoped query (LDAP_SERVER_ASQ_OID)\n";
        print "\t--base|-b [base]\n\t\tUse base [base]\n";
        print "\t--debug [level]\n\t\tUse debuglevel (for Net::LDAP)\n";
+       print "\t--domain_scope\n\t\tLimit LDAP search to local domain (LDAP_SERVER_DOMAIN_SCOPE_OID)\n";
        print "\t--DN|-D [binddn]\n\t\tUse binddn or principal\n";
-       print "\t--extendeddn|-e\n\t\tDisplay extended dn (LDAP_SERVER_EXTENDED_DN_OID)\n";
+       print "\t--extendeddn|-e [value]\n\t\tDisplay extended dn (LDAP_SERVER_EXTENDED_DN_OID)\n";
+       print "\t--fastbind\n\t\tDo LDAP fast bind using LDAP_SERVER_FAST_BIND_OID extension\n";
        print "\t--help\n\t\tDisplay help page\n";
        print "\t--host|-h [host]\n\t\tQuery Host [host] (either a hostname or an LDAP uri)\n";
        print "\t--machine|-P\n\t\tUse samba3 machine account stored in $secrets_tdb (needs root access)\n";
        print "\t--metdata|-m\n\t\tDisplay replication metadata\n";
        print "\t--nodiffs\n\t\tDisplay no diffs but full entry dump when running in notify mode\n";
        print "\t--notify|-n\n\t\tActivate asynchronous change notification (LDAP_SERVER_NOTIFICATION_OID)\n";
+       print "\t--paging [pagesize]\n\t\tUse paged results when searching\n";
        print "\t--password|-w [password]\n\t\tUse [password] for binddn\n";
        print "\t--port [port]\n\t\tUse [port] when connecting ADS\n";
        print "\t--rawdisplay\n\t\tDo not interpret values\n";
@@ -639,7 +735,7 @@ sub get_machine_password {
                        ($line,$password) = split(/"/, $line);
                        last;
                }
-               if ($line =~ /$key/) {
+               if ($line =~ /\"$key\"/) {
                        $found = 1;
                }
        }
@@ -710,33 +806,49 @@ sub check_user {
        return $acct;
 }
 
-sub check_ctrls ($$@) {
+sub check_root_dse($$$@) {
 
        # bogus function??
        my $server = shift || "";
        $dse = shift || get_dse($server) || return -1;
-       my @ctrls = @_;
+       my $attr = shift || die "unknown query";
+       my @array = @_;
 
-       my $dse_controls = $dse->get_value('supportedControl', asref => '1');
-       my @dse_controls = @$dse_controls;
+       my $dse_list = $dse->get_value($attr, asref => '1');
+       my @dse_array = @$dse_list;
 
-       foreach my $i (@ctrls) {
+       foreach my $i (@array) {
                # we could use -> supported_control but this 
                # is only available in newer versions of perl-ldap
 #              if ( ! $dse->supported_control( $i ) ) {
-               if ( grep(/$i->type()/, @dse_controls) ) { 
-                       printf("required control: %s is not supported by ADS-server.\n", $i->type());
+               if ( grep(/$i->type()/, @dse_array) ) { 
+                       printf("required \"$attr\": %s is not supported by ADS-server.\n", $i->type());
                        return -1;
                }
        }
        return 0;
 }
 
+sub check_ctrls ($$@) {
+       my $server = shift;
+       my $dse = shift;
+       my @array = @_;
+       return check_root_dse($server, $dse, "supportedControl", @array);
+}
+
+sub check_exts ($$@) {
+       my $server = shift;
+       my $dse = shift;
+       my @array = @_;
+       return check_root_dse($server, $dse, "supportedExtension", @array);
+}
+
 sub get_base_from_rootdse {
 
        my $server = shift || "";
        $dse = shift || get_dse($server,$async_ldap_hd) || return -1;
-       return $dse->get_value('defaultNamingContext');
+       return $dse->get_value($opt_dump_schema ? 'schemaNamingContext':
+                                                 'defaultNamingContext');
 }
 
 sub get_realm_from_rootdse {
@@ -866,45 +978,6 @@ sub check_sasl_mech {
        return 0;
 }
 
-
-sub parse_ads_h {
-
-       -e "$ads_h" || die "cannot open samba3 ads.h ($ads_h): $!";
-       open(ADSH,"$ads_h");
-       while (my $line = <ADSH>) {
-               chomp($line);
-               if ($line =~ /#define.UF.*0x/) {
-                       my ($tmp, $name, $val) = split(/\s+/,$line);
-                       next if ($name =~ /UNUSED/);
-#                      $ads_uf{$name} = sprintf("%d", hex $val);
-                       $ads_uf{$name} = hex $val;
-               }
-               if ($line =~ /#define.GROUP_TYPE.*0x/) {
-                       my ($tmp, $name, $val) = split(/\s+/,$line);
-                       $ads_grouptype{$name} = hex $val;
-               }
-               if ($line =~ /#define.ATYPE.*0x/) {
-                       my ($tmp, $name, $val) = split(/\s+/,$line);
-                       $ads_atype{$name} = 
-                               (exists $ads_atype{$val}) ? $ads_atype{$val} : hex $val;
-               }
-               if ($line =~ /#define.GTYPE.*0x/) {
-                       my ($val, $i);
-                       my ($tmp, $name, @val) = split(/\s+/,$line);
-                       foreach my $tempval (@val) {
-                               if ($tempval =~ /^0x/) {
-                                       $val = $tempval;
-                                       last;
-                               }
-                       }
-                       next if (!$val);
-                       $ads_gtype{$name} = sprintf("%d", hex $val);
-               }
-
-       }
-       close(ADSH);
-}
-
 sub store_result ($) {
 
        my $entry = shift;
@@ -1103,12 +1176,13 @@ sub dump_bitmask {
        my $mod = shift || die "no mod";
         my (%header) = @_;
        my %tmp;
-       $tmp{""} = $val;
+       $tmp{""} = sprintf("%s (0x%08x)", $val, $val);
        foreach my $key (sort keys %header) {   # sort by val !
+               my $val_hex = sprintf("0x%08x", $header{$key});
                if ($op eq "&") {
-                       $tmp{$key} = ( $val & $header{$key} ) ? $set:$unset; 
+                       $tmp{"$key ($val_hex)"} = ( $val & $header{$key} ) ? $set:$unset; 
                } elsif ($op eq "==") {
-                       $tmp{$key} = ( $val == $header{$key} ) ? $set:$unset; 
+                       $tmp{"$key ($val_hex)"} = ( $val == $header{$key} ) ? $set:$unset; 
                } else {
                        print "unknown operator: $op\n";
                        return;
@@ -1145,6 +1219,10 @@ sub dump_uacc {
        return dump_bitmask_equal(@_,%ads_uacc); 
 }
 
+sub dump_enctypes {
+       return dump_bitmask_and(@_,%ads_enctypes);
+}
+
 sub dump_uf {
        return dump_bitmask_and(@_,%ads_uf);
 }
@@ -1365,7 +1443,7 @@ sub print_header {
        printf "%10s: %s\n", "scope", $scope;
        printf "%10s: %s\n", "attrs", join(", ", @attrs);
        printf "%10s: %s\n", "controls", join(", ", @ctrls_s);
-       printf "%10s: %s\n", "page_size", $page_size if ($paging);
+       printf "%10s: %s\n", "page_size", $opt_paging if ($opt_paging);
        printf "%10s: %s\n", "start_tls", $opt_starttls ? "yes" : "no";
        printf "\n";
 }
@@ -1403,6 +1481,21 @@ sub gen_controls {
                        critical => 'true',
                        value => $opt_display_extendeddn ? $ctl_extended_dn_val : "");
 
+       # setup search options
+       my $search_opt = Convert::ASN1->new;
+       $search_opt->prepare(
+               q<      searchopt ::= SEQUENCE {
+                               flags     INTEGER
+                       }
+               >
+       );
+
+       my $tmp = $search_opt->encode( flags => $opt_search_opt);
+       my $ctl_search_opt = Net::LDAP::Control->new( 
+               type => $ads_controls{'LDAP_SERVER_SEARCH_OPTIONS_OID'},
+               critical => 'true',
+               value => $tmp);
+
        # setup notify control
        my $ctl_notification = Net::LDAP::Control->new( 
                type => $ads_controls{'LDAP_SERVER_NOTIFICATION_OID'},
@@ -1413,10 +1506,15 @@ sub gen_controls {
        $ctl_paged = Net::LDAP::Control->new( 
                type => $ads_controls{'LDAP_PAGED_RESULT_OID_STRING'},
                critical => 'true',
-               size => $page_size);
+               size => $opt_paging ? $opt_paging : 1000);
 
+       # setup domscope control
+       my $ctl_domscope = Net::LDAP::Control->new( 
+               type => $ads_controls{'LDAP_SERVER_DOMAIN_SCOPE_OID'},
+               critical => 'true',
+               value => "");
 
-       if ($paging) {
+       if (defined($opt_paging) || $opt_dump_schema) {
                push(@ctrls, $ctl_paged);
                push(@ctrls_s, "LDAP_PAGED_RESULT_OID_STRING" );
        }
@@ -1434,7 +1532,17 @@ sub gen_controls {
                push(@ctrls, $ctl_asq);
                push(@ctrls_s, "LDAP_SERVER_ASQ_OID");
        }
-       
+
+       if ($opt_domain_scope) {
+               push(@ctrls, $ctl_domscope);
+               push(@ctrls_s, "LDAP_SERVER_DOMAIN_SCOPE_OID");
+       }
+
+       if ($opt_search_opt) {
+               push(@ctrls, $ctl_search_opt);
+               push(@ctrls_s, "LDAP_SERVER_SEARCH_OPTIONS_OID");
+       }
+
        return @ctrls;
 }
 
@@ -1519,6 +1627,10 @@ sub default_callback {
 
        my ($res,$obj) = @_;
 
+       if (!$opt_notify && $res->code == LDAP_REFERRAL) {
+               return;
+       }
+
        if (!$opt_notify) {
                return process_result($res,$obj);
        } 
@@ -1566,6 +1678,9 @@ sub notify_callback {
                printf("\ngot changenotify for dn: [%s]\n%s\n", $async_entry->dn, ("-" x 80));
 
                my $new_usn = $async_entry->get_value('uSNChanged');
+               if (!$new_usn) {
+                       die "odd, no usnChanged attribute???";
+               }
                if (!$usn || $new_usn > $usn) {
                        $usn = $new_usn;
                        if ($opt_notify_nodiffs) {
@@ -1602,16 +1717,39 @@ sub do_bind($$) {
                $mesg = $async_ldap_hd->bind( 
                        $binddn, 
                        password => $password, 
-                       callback => \&error_callback
+                       callback => $opt_fastbind ? undef : \&error_callback
                ) || die "doesnt work";
        };
        if ($mesg->code) { 
-               display_ldap_err($mesg);
+               display_ldap_err($mesg) if (!$opt_fastbind);
                return -1;
        }
        return 0;
 }
 
+sub do_fast_bind() {
+
+       do_unbind();
+
+       my $hd = get_ldap_hd($server,1);
+
+       my $mesg = $hd->extension(
+               name => $ads_extensions{'LDAP_SERVER_FAST_BIND_OID'});
+       
+       if ($mesg->code) { 
+               display_ldap_err($mesg);
+               return -1;
+       }
+
+       my $ret = do_bind($hd, undef);
+       $hd->unbind;
+
+       printf("bind using 'LDAP_SERVER_FAST_BIND_OID' %s\n", 
+               $ret == 0 ? "succeeded" : "failed");
+
+       return $ret;
+}
+
 sub do_unbind() {
        if ($async_ldap_hd) {
                $async_ldap_hd->unbind;
@@ -1625,9 +1763,21 @@ sub do_unbind() {
 
 sub main () {
 
+       if ($opt_fastbind) {
+
+               if (check_exts($server,$dse,"LDAP_SERVER_FAST_BIND_OID") == -1) {
+                       print "LDAP_SERVER_FAST_BIND_OID not supported by this server\n";
+                       exit 1;
+               }
+
+               # fast bind can only bind, not search
+               exit do_fast_bind();
+       }
+
        $filter = construct_filter($query);
        @attrs  = construct_attrs(@attrs);
        @ctrls  = gen_controls();
+
        if (check_ctrls($server,$dse,@ctrls) == -1) {
                print "not all required LDAP Controls are supported by this server\n";
                exit 1;
@@ -1652,9 +1802,9 @@ sub main () {
 
        if ($opt_dump_schema) {
                print "Dumping Schema:\n";
-               my $ads_schema = $async_ldap_hd->schema;
-               $ads_schema->dump;
-               exit 0;
+#              my $ads_schema = $async_ldap_hd->schema;
+#              $ads_schema->dump;
+#              exit 0;
        }
 
        while (1) {
@@ -1668,6 +1818,22 @@ sub main () {
                        scope => $scope,
                                ) || die "cannot search";
 
+               if (!$opt_notify && ($async_search->code == LDAP_REFERRAL)) {
+                       foreach my $ref ($async_search->referrals) {
+                               print "\ngot Referral: [$ref]\n";
+                               my ($prot, $host, $base) = split(/\/+/, $ref);
+                               $async_ldap_hd->unbind();
+                               $async_ldap_hd = get_ldap_hd($host, 1);
+                               if (do_bind($async_ldap_hd, $sasl_bind) == -1) {
+                                       $async_ldap_hd->unbind();
+                                       next;
+                               }
+                               print "\nsuccessful rebind to: [$ref]\n";
+                               last;
+                       }
+                       next; # repeat the query
+               }
+
                if ($opt_notify) {
 
                        print "Base [$base] is registered now for change-notify\n";
@@ -1701,7 +1867,7 @@ sub main () {
        if ($async_search->entries == 0) {
                print "No entries found with filter $filter\n";
        } else {
-               print "Got $total_entry_count Entries in $page_count Pages\n" if ($paging);
+               print "Got $total_entry_count Entries in $page_count Pages\n" if ($opt_paging);
        }
 
        do_unbind()