1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-25 23:21:54 +03:00
samba-mirror/examples/misc/cldap.pl
Günther Deschner dbf7a692ed r21105: Quick fix for CLDAP reply without NetLogon attribute.
Guenther
(This used to be commit aa135c960e)
2007-10-10 12:17:37 -05:00

492 lines
12 KiB
Perl
Executable File

#!/usr/bin/perl -w
# Copyright (C) Guenther Deschner <gd@samba.org> 2006
use strict;
use IO::Socket;
use Convert::ASN1 qw(:debug);
use Getopt::Long;
# TODO: timeout handling, user CLDAP query
##################################
my $server = "";
my $domain = "";
my $host = "";
##################################
my (
$opt_debug,
$opt_domain,
$opt_help,
$opt_host,
$opt_server,
);
my %cldap_flags = (
ADS_PDC => 0x00000001, # DC is PDC
ADS_GC => 0x00000004, # DC is a GC of forest
ADS_LDAP => 0x00000008, # DC is an LDAP server
ADS_DS => 0x00000010, # DC supports DS
ADS_KDC => 0x00000020, # DC is running KDC
ADS_TIMESERV => 0x00000040, # DC is running time services
ADS_CLOSEST => 0x00000080, # DC is closest to client
ADS_WRITABLE => 0x00000100, # DC has writable DS
ADS_GOOD_TIMESERV => 0x00000200, # DC has hardware clock (and running time)
ADS_NDNC => 0x00000400, # DomainName is non-domain NC serviced by LDAP server
);
my %cldap_samlogon_types = (
SAMLOGON_AD_UNK_R => 23,
SAMLOGON_AD_R => 25,
);
my $MAX_DNS_LABEL = 255 + 1;
my %cldap_netlogon_reply = (
type => 0,
flags => 0x0,
guid => 0,
forest => undef,
domain => undef,
hostname => undef,
netbios_domain => undef,
netbios_hostname => undef,
unk => undef,
user_name => undef,
server_site_name => undef,
client_site_name => undef,
version => 0,
lmnt_token => 0x0,
lm20_token => 0x0,
);
sub usage {
print "usage: $0 [--domain|-d domain] [--help] [--host|-h host] [--server|-s server]\n\n";
}
sub connect_cldap ($) {
my $server = shift || return undef;
return IO::Socket::INET->new(
PeerAddr => $server,
PeerPort => 389,
Proto => 'udp',
Type => SOCK_DGRAM,
Timeout => 10,
);
}
sub send_cldap_netlogon ($$$$) {
my ($sock, $domain, $host, $ntver) = @_;
my $asn_cldap_req = Convert::ASN1->new;
$asn_cldap_req->prepare(q<
SEQUENCE {
msgid INTEGER,
[APPLICATION 3] SEQUENCE {
basedn OCTET STRING,
scope ENUMERATED,
dereference ENUMERATED,
sizelimit INTEGER,
timelimit INTEGER,
attronly BOOLEAN,
[CONTEXT 0] SEQUENCE {
[CONTEXT 3] SEQUENCE {
dnsdom_attr OCTET STRING,
dnsdom_val OCTET STRING
}
[CONTEXT 3] SEQUENCE {
host_attr OCTET STRING,
host_val OCTET STRING
}
[CONTEXT 3] SEQUENCE {
ntver_attr OCTET STRING,
ntver_val OCTET STRING
}
}
SEQUENCE {
netlogon OCTET STRING
}
}
}
>);
my $pdu_req = $asn_cldap_req->encode(
msgid => 0,
basedn => "",
scope => 0,
dereference => 0,
sizelimit => 0,
timelimit => 0,
attronly => 0,
dnsdom_attr => $domain ? 'DnsDomain' : "",
dnsdom_val => $domain ? $domain : "",
host_attr => 'Host',
host_val => $host,
ntver_attr => 'NtVer',
ntver_val => $ntver,
netlogon => 'NetLogon',
) || die "failed to encode pdu: $@";
if ($opt_debug) {
print"------------\n";
asn_dump($pdu_req);
print"------------\n";
}
return $sock->send($pdu_req) || die "no send: $@";
}
# from source/libads/cldap.c :
#
#/*
# These seem to be strings as described in RFC1035 4.1.4 and can be:
#
# - a sequence of labels ending in a zero octet
# - a pointer
# - a sequence of labels ending with a pointer
#
# A label is a byte where the first two bits must be zero and the remaining
# bits represent the length of the label followed by the label itself.
# Therefore, the length of a label is at max 64 bytes. Under RFC1035, a
# sequence of labels cannot exceed 255 bytes.
#
# A pointer consists of a 14 bit offset from the beginning of the data.
#
# struct ptr {
# unsigned ident:2; // must be 11
# unsigned offset:14; // from the beginning of data
# };
#
# This is used as a method to compress the packet by eliminated duplicate
# domain components. Since a UDP packet should probably be < 512 bytes and a
# DNS name can be up to 255 bytes, this actually makes a lot of sense.
#*/
sub pull_netlogon_string (\$$$) {
my ($ret, $ptr, $str) = @_;
my $pos = $ptr;
my $followed_ptr = 0;
my $ret_len = 0;
my $retp = pack("x$MAX_DNS_LABEL");
do {
$ptr = unpack("c", substr($str, $pos, 1));
$pos++;
if (($ptr & 0xc0) == 0xc0) {
my $len;
if (!$followed_ptr) {
$ret_len += 2;
$followed_ptr = 1;
}
my $tmp0 = $ptr; #unpack("c", substr($str, $pos-1, 1));
my $tmp1 = unpack("c", substr($str, $pos, 1));
if ($opt_debug) {
printf("tmp0: 0x%x\n", $tmp0);
printf("tmp1: 0x%x\n", $tmp1);
}
$len = (($tmp0 & 0x3f) << 8) | $tmp1;
$ptr = unpack("c", substr($str, $len, 1));
$pos = $len;
} elsif ($ptr) {
my $len = scalar $ptr;
if ($len + 1 > $MAX_DNS_LABEL) {
warn("invalid string size: %d", $len + 1);
return 0;
}
$ptr = unpack("a*", substr($str, $pos, $len));
$retp = sprintf("%s%s\.", $retp, $ptr);
$pos += $len;
if (!$followed_ptr) {
$ret_len += $len + 1;
}
}
} while ($ptr);
$retp =~ s/\.$//; #ugly hack...
$$ret = $retp;
return $followed_ptr ? $ret_len : $ret_len + 1;
}
sub dump_cldap_flags ($) {
my $flags = shift || return;
printf("Flags:\n".
"\tIs a PDC: %s\n".
"\tIs a GC of the forest: %s\n".
"\tIs an LDAP server: %s\n".
"\tSupports DS: %s\n".
"\tIs running a KDC: %s\n".
"\tIs running time services: %s\n".
"\tIs the closest DC: %s\n".
"\tIs writable: %s\n".
"\tHas a hardware clock: %s\n".
"\tIs a non-domain NC serviced by LDAP server: %s\n",
($flags & $cldap_flags{ADS_PDC}) ? "yes" : "no",
($flags & $cldap_flags{ADS_GC}) ? "yes" : "no",
($flags & $cldap_flags{ADS_LDAP}) ? "yes" : "no",
($flags & $cldap_flags{ADS_DS}) ? "yes" : "no",
($flags & $cldap_flags{ADS_KDC}) ? "yes" : "no",
($flags & $cldap_flags{ADS_TIMESERV}) ? "yes" : "no",
($flags & $cldap_flags{ADS_CLOSEST}) ? "yes" : "no",
($flags & $cldap_flags{ADS_WRITABLE}) ? "yes" : "no",
($flags & $cldap_flags{ADS_GOOD_TIMESERV}) ? "yes" : "no",
($flags & $cldap_flags{ADS_NDNC}) ? "yes" : "no");
}
sub guid_to_string ($) {
my $guid = shift || return undef;
if ((my $len = length $guid) != 16) {
printf("invalid length: %d\n", $len);
return undef;
}
my $string = sprintf "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X",
unpack("I", $guid),
unpack("S", substr($guid, 4, 2)),
unpack("S", substr($guid, 6, 2)),
unpack("C", substr($guid, 8, 1)),
unpack("C", substr($guid, 9, 1)),
unpack("C", substr($guid, 10, 1)),
unpack("C", substr($guid, 11, 1)),
unpack("C", substr($guid, 12, 1)),
unpack("C", substr($guid, 13, 1)),
unpack("C", substr($guid, 14, 1)),
unpack("C", substr($guid, 15, 1));
return lc($string);
}
sub recv_cldap_netlogon ($\$) {
my ($sock, $return_string) = @_;
my ($ret, $pdu_out);
$ret = $sock->recv($pdu_out, 8192) || die "failed to read from socket: $@";
#$ret = sysread($sock, $pdu_out, 8192);
if ($opt_debug) {
print"------------\n";
asn_dump($pdu_out);
print"------------\n";
}
my $asn_cldap_rep = Convert::ASN1->new;
my $asn_cldap_rep_fail = Convert::ASN1->new;
$asn_cldap_rep->prepare(q<
SEQUENCE {
msgid INTEGER,
[APPLICATION 4] SEQUENCE {
dn OCTET STRING,
SEQUENCE {
SEQUENCE {
attr OCTET STRING,
SET {
val OCTET STRING
}
}
}
}
}
SEQUENCE {
msgid2 INTEGER,
[APPLICATION 5] SEQUENCE {
error_code ENUMERATED,
matched_dn OCTET STRING,
error_message OCTET STRING
}
}
>);
$asn_cldap_rep_fail->prepare(q<
SEQUENCE {
msgid2 INTEGER,
[APPLICATION 5] SEQUENCE {
error_code ENUMERATED,
matched_dn OCTET STRING,
error_message OCTET STRING
}
}
>);
my $asn1_rep = $asn_cldap_rep->decode($pdu_out) ||
$asn_cldap_rep_fail->decode($pdu_out) ||
die "failed to decode pdu: $@";
if ($asn1_rep->{'error_code'} == 0) {
$$return_string = $asn1_rep->{'val'};
}
return $ret;
}
sub parse_cldap_reply ($) {
my $str = shift || return undef;
my %hash;
my $p = 0;
$hash{type} = unpack("L", substr($str, $p, 4)); $p += 4;
$hash{flags} = unpack("L", substr($str, $p, 4)); $p += 4;
$hash{guid} = unpack("a16", substr($str, $p, 16)); $p += 16;
$p += pull_netlogon_string($hash{forest}, $p, $str);
$p += pull_netlogon_string($hash{domain}, $p, $str);
$p += pull_netlogon_string($hash{hostname}, $p, $str);
$p += pull_netlogon_string($hash{netbios_domain}, $p, $str);
$p += pull_netlogon_string($hash{netbios_hostname}, $p, $str);
$p += pull_netlogon_string($hash{unk}, $p, $str);
if ($hash{type} == $cldap_samlogon_types{SAMLOGON_AD_R}) {
$p += pull_netlogon_string($hash{user_name}, $p, $str);
} else {
$hash{user_name} = "";
}
$p += pull_netlogon_string($hash{server_site_name}, $p, $str);
$p += pull_netlogon_string($hash{client_site_name}, $p, $str);
$hash{version} = unpack("L", substr($str, $p, 4)); $p += 4;
$hash{lmnt_token} = unpack("S", substr($str, $p, 2)); $p += 2;
$hash{lm20_token} = unpack("S", substr($str, $p, 2)); $p += 2;
return %hash;
}
sub display_cldap_reply {
my $server = shift;
my (%hash) = @_;
my ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($server);
printf("Information for Domain Controller: %s\n\n", $name);
printf("Response Type: ");
if ($hash{type} == $cldap_samlogon_types{SAMLOGON_AD_R}) {
printf("SAMLOGON_USER\n");
} elsif ($hash{type} == $cldap_samlogon_types{SAMLOGON_AD_UNK_R}) {
printf("SAMLOGON\n");
} else {
printf("unknown type 0x%x, please report\n", $hash{type});
}
# guid
printf("GUID: %s\n", guid_to_string($hash{guid}));
# flags
dump_cldap_flags($hash{flags});
# strings
printf("Forest:\t\t\t%s\n", $hash{forest});
printf("Domain:\t\t\t%s\n", $hash{domain});
printf("Domain Controller:\t%s\n", $hash{hostname});
printf("Pre-Win2k Domain:\t%s\n", $hash{netbios_domain});
printf("Pre-Win2k Hostname:\t%s\n", $hash{netbios_hostname});
if ($hash{unk}) {
printf("Unk:\t\t\t%s\n", $hash{unk});
}
if ($hash{user_name}) {
printf("User name:\t%s\n", $hash{user_name});
}
printf("Server Site Name:\t%s\n", $hash{server_site_name});
printf("Client Site Name:\t%s\n", $hash{client_site_name});
# some more int
printf("NT Version:\t\t%d\n", $hash{version});
printf("LMNT Token:\t\t%.2x\n", $hash{lmnt_token});
printf("LM20 Token:\t\t%.2x\n", $hash{lm20_token});
}
sub main() {
my ($ret, $sock, $reply);
GetOptions(
'debug' => \$opt_debug,
'domain|d=s' => \$opt_domain,
'help' => \$opt_help,
'host|h=s' => \$opt_host,
'server|s=s' => \$opt_server,
);
$server = $server || $opt_server;
$domain = $domain || $opt_domain || undef;
$host = $host || $opt_host;
if (!$host) {
$host = `/bin/hostname`;
chomp($host);
}
if (!$server || !$host || $opt_help) {
usage();
exit 1;
}
my $ntver = sprintf("%c%c%c%c", 6,0,0,0);
$sock = connect_cldap($server);
if (!$sock) {
die("could not connect to $server");
}
$ret = send_cldap_netlogon($sock, $domain, $host, $ntver);
if (!$ret) {
close($sock);
die("failed to send CLDAP request to $server");
}
$ret = recv_cldap_netlogon($sock, $reply);
if (!$ret) {
close($sock);
die("failed to receive CLDAP reply from $server");
}
close($sock);
if (!$reply) {
printf("no 'NetLogon' attribute received\n");
exit 0;
}
%cldap_netlogon_reply = parse_cldap_reply($reply);
if (!%cldap_netlogon_reply) {
die("failed to parse CLDAP reply from $server");
}
display_cldap_reply($server, %cldap_netlogon_reply);
exit 0;
}
main();