# 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);
my $binddn = "";
my $password = "";
my $server = "";
+my $rebind_url;
+
my $tdbdump = "/usr/bin/tdbdump";
my $testparm = "/usr/bin/testparm";
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 = "";
$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,
'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,
'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,
);
-# 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);
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";
"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 = (
my %ads_ds_func = (
"DS_BEHAVIOR_WIN2000" => 0, # untested
"DS_BEHAVIOR_WIN2003" => 2,
+"DS_BEHAVIOR_WIN2008" => 3,
);
my %ads_instance_type = (
"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,
"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,
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();
# get the base
-$base = $opt_base ||
+$base = defined($opt_base)? $opt_base : "" ||
get_base_from_rootdse($server,$dse);
# get the realm
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,
"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,
"pwdLastSet" => \&dump_nttime,
"pwdProperties" => \&dump_pwdproperties,
"sAMAccountType" => \&dump_atype,
+ "schemaIDGUID" => \&dump_guid,
"sDRightsEffective" => \&dump_sdeffective,
"securityIdentifier" => \&dump_sid,
"serverState" => \&dump_serverstate,
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";
($line,$password) = split(/"/, $line);
last;
}
- if ($line =~ /$key/) {
+ if ($line =~ /\"$key\"/) {
$found = 1;
}
}
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 {
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;
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;
return dump_bitmask_equal(@_,%ads_uacc);
}
+sub dump_enctypes {
+ return dump_bitmask_and(@_,%ads_enctypes);
+}
+
sub dump_uf {
return dump_bitmask_and(@_,%ads_uf);
}
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";
}
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'},
$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" );
}
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;
}
my ($res,$obj) = @_;
+ if (!$opt_notify && $res->code == LDAP_REFERRAL) {
+ return;
+ }
+
if (!$opt_notify) {
return process_result($res,$obj);
}
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) {
$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;
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;
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) {
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";
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()