drsuapi: rerun make idl and add drsblobs generated files.
[kai/samba-autobuild/.git] / examples / misc / cldap.pl
1 #!/usr/bin/perl -w
2
3 # Copyright (C) Guenther Deschner <gd@samba.org> 2006
4
5 use strict;
6 use IO::Socket;
7 use Convert::ASN1 qw(:debug);
8 use Getopt::Long;
9
10 # TODO: timeout handling, user CLDAP query
11
12 ##################################
13
14 my $server = "";
15 my $domain = "";
16 my $host   = "";
17
18 ##################################
19
20 my (
21         $opt_debug,
22         $opt_domain,
23         $opt_help,
24         $opt_host,
25         $opt_server,
26 );
27
28 my %cldap_flags = (
29         ADS_PDC                 => 0x00000001, # DC is PDC
30         ADS_GC                  => 0x00000004, # DC is a GC of forest
31         ADS_LDAP                => 0x00000008, # DC is an LDAP server
32         ADS_DS                  => 0x00000010, # DC supports DS
33         ADS_KDC                 => 0x00000020, # DC is running KDC
34         ADS_TIMESERV            => 0x00000040, # DC is running time services
35         ADS_CLOSEST             => 0x00000080, # DC is closest to client
36         ADS_WRITABLE            => 0x00000100, # DC has writable DS
37         ADS_GOOD_TIMESERV       => 0x00000200, # DC has hardware clock (and running time) 
38         ADS_NDNC                => 0x00000400, # DomainName is non-domain NC serviced by LDAP server
39 );
40
41 my %cldap_samlogon_types = (
42         SAMLOGON_AD_UNK_R       => 23,
43         SAMLOGON_AD_R           => 25,
44 );
45
46 my $MAX_DNS_LABEL = 255 + 1;
47
48 my %cldap_netlogon_reply = (
49         type                    => 0,
50         flags                   => 0x0,
51         guid                    => 0,
52         forest                  => undef,
53         domain                  => undef,
54         hostname                => undef,
55         netbios_domain          => undef,
56         netbios_hostname        => undef,
57         unk                     => undef,
58         user_name               => undef,
59         server_site_name        => undef,
60         client_site_name        => undef,
61         version                 => 0,
62         lmnt_token              => 0x0,
63         lm20_token              => 0x0,
64 );
65
66 sub usage { 
67         print "usage: $0 [--domain|-d domain] [--help] [--host|-h host] [--server|-s server]\n\n";
68 }
69
70 sub connect_cldap ($) {
71
72         my $server = shift || return undef;
73         
74         return IO::Socket::INET->new(
75                 PeerAddr        => $server,
76                 PeerPort        => 389,
77                 Proto           => 'udp',
78                 Type            => SOCK_DGRAM,
79                 Timeout         => 10,
80         );
81 }
82
83 sub send_cldap_netlogon ($$$$) {
84
85         my ($sock, $domain, $host, $ntver) = @_;
86
87         my $asn_cldap_req = Convert::ASN1->new;
88
89         $asn_cldap_req->prepare(q<
90
91                 SEQUENCE {
92                         msgid INTEGER,
93                         [APPLICATION 3] SEQUENCE {
94                                 basedn OCTET STRING,
95                                 scope ENUMERATED,
96                                 dereference ENUMERATED,
97                                 sizelimit INTEGER,
98                                 timelimit INTEGER,
99                                 attronly BOOLEAN,
100                                 [CONTEXT 0] SEQUENCE {
101                                         [CONTEXT 3] SEQUENCE {
102                                                 dnsdom_attr OCTET STRING,
103                                                 dnsdom_val  OCTET STRING
104                                         }
105                                         [CONTEXT 3] SEQUENCE {
106                                                 host_attr OCTET STRING,
107                                                 host_val  OCTET STRING
108                                         }
109                                         [CONTEXT 3] SEQUENCE {
110                                                 ntver_attr OCTET STRING,
111                                                 ntver_val  OCTET STRING
112                                         }
113                                 }
114                                 SEQUENCE {
115                                         netlogon OCTET STRING
116                                 }
117                         }
118                 }
119         >);
120
121         my $pdu_req = $asn_cldap_req->encode( 
122                                 msgid => 0,
123                                 basedn => "", 
124                                 scope => 0,
125                                 dereference => 0,
126                                 sizelimit => 0,
127                                 timelimit => 0,
128                                 attronly => 0,
129                                 dnsdom_attr => $domain ? 'DnsDomain' : "",
130                                 dnsdom_val => $domain ? $domain : "",
131                                 host_attr => 'Host',
132                                 host_val => $host,
133                                 ntver_attr => 'NtVer',
134                                 ntver_val => $ntver,
135                                 netlogon => 'NetLogon',
136                                 ) || die "failed to encode pdu: $@";
137
138         if ($opt_debug) {
139                 print"------------\n";
140                 asn_dump($pdu_req);
141                 print"------------\n";
142         }
143
144         return $sock->send($pdu_req) || die "no send: $@";
145 }
146
147 # from source/libads/cldap.c :
148 #
149 #/*
150 #  These seem to be strings as described in RFC1035 4.1.4 and can be:
151 #
152 #   - a sequence of labels ending in a zero octet
153 #   - a pointer
154 #   - a sequence of labels ending with a pointer
155 #
156 #  A label is a byte where the first two bits must be zero and the remaining
157 #  bits represent the length of the label followed by the label itself.
158 #  Therefore, the length of a label is at max 64 bytes.  Under RFC1035, a
159 #  sequence of labels cannot exceed 255 bytes.
160 #
161 #  A pointer consists of a 14 bit offset from the beginning of the data.
162 #
163 #  struct ptr {
164 #    unsigned ident:2; // must be 11
165 #    unsigned offset:14; // from the beginning of data
166 #  };
167 #
168 #  This is used as a method to compress the packet by eliminated duplicate
169 #  domain components.  Since a UDP packet should probably be < 512 bytes and a
170 #  DNS name can be up to 255 bytes, this actually makes a lot of sense.
171 #*/
172
173 sub pull_netlogon_string (\$$$) {
174         
175         my ($ret, $ptr, $str) = @_;
176
177         my $pos = $ptr;
178
179         my $followed_ptr = 0;
180         my $ret_len = 0;
181
182         my $retp = pack("x$MAX_DNS_LABEL");
183
184         do {
185         
186                 $ptr = unpack("c", substr($str, $pos, 1));
187                 $pos++;
188
189                 if (($ptr & 0xc0) == 0xc0) {
190
191                         my $len;
192
193                         if (!$followed_ptr) {
194                                 $ret_len += 2;
195                                 $followed_ptr = 1;
196                         }
197
198                         my $tmp0 = $ptr; #unpack("c", substr($str, $pos-1, 1));
199                         my $tmp1 = unpack("c", substr($str, $pos, 1));
200
201                         if ($opt_debug) {
202                                 printf("tmp0: 0x%x\n", $tmp0);
203                                 printf("tmp1: 0x%x\n", $tmp1);
204                         }
205
206                         $len = (($tmp0 & 0x3f) << 8) | $tmp1;
207                         $ptr = unpack("c", substr($str, $len, 1));
208                         $pos = $len;
209
210                 } elsif ($ptr) {
211
212                         my $len = scalar $ptr;
213
214                         if ($len + 1 > $MAX_DNS_LABEL) {
215                                 warn("invalid string size: %d", $len + 1);
216                                 return 0;
217                         }
218
219                         $ptr = unpack("a*", substr($str, $pos, $len));
220
221                         $retp = sprintf("%s%s\.", $retp, $ptr);
222
223                         $pos += $len;
224                         if (!$followed_ptr) {
225                                 $ret_len += $len + 1;
226                         }
227                 }
228
229         } while ($ptr);
230
231         $retp =~ s/\.$//; #ugly hack...
232
233         $$ret = $retp;
234
235         return $followed_ptr ? $ret_len : $ret_len + 1;
236 }
237
238 sub dump_cldap_flags ($) {
239
240         my $flags = shift || return;
241         printf("Flags:\n".
242                  "\tIs a PDC:                                   %s\n".
243                  "\tIs a GC of the forest:                      %s\n".
244                  "\tIs an LDAP server:                          %s\n".
245                  "\tSupports DS:                                %s\n".
246                  "\tIs running a KDC:                           %s\n".
247                  "\tIs running time services:                   %s\n".
248                  "\tIs the closest DC:                          %s\n".
249                  "\tIs writable:                                %s\n".
250                  "\tHas a hardware clock:                       %s\n".
251                  "\tIs a non-domain NC serviced by LDAP server: %s\n",
252                  ($flags & $cldap_flags{ADS_PDC}) ? "yes" : "no",
253                  ($flags & $cldap_flags{ADS_GC}) ? "yes" : "no",
254                  ($flags & $cldap_flags{ADS_LDAP}) ? "yes" : "no",
255                  ($flags & $cldap_flags{ADS_DS}) ? "yes" : "no",
256                  ($flags & $cldap_flags{ADS_KDC}) ? "yes" : "no",
257                  ($flags & $cldap_flags{ADS_TIMESERV}) ? "yes" : "no",
258                  ($flags & $cldap_flags{ADS_CLOSEST}) ? "yes" : "no",
259                  ($flags & $cldap_flags{ADS_WRITABLE}) ? "yes" : "no",
260                  ($flags & $cldap_flags{ADS_GOOD_TIMESERV}) ? "yes" : "no",
261                  ($flags & $cldap_flags{ADS_NDNC}) ? "yes" : "no");
262 }
263
264 sub guid_to_string ($) {
265
266         my $guid = shift || return undef;
267         if ((my $len = length $guid) != 16) {
268                 printf("invalid length: %d\n", $len);
269                 return undef;
270         }
271         my $string = sprintf "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", 
272                 unpack("I", $guid), 
273                 unpack("S", substr($guid, 4, 2)), 
274                 unpack("S", substr($guid, 6, 2)),
275                 unpack("C", substr($guid, 8, 1)),
276                 unpack("C", substr($guid, 9, 1)),
277                 unpack("C", substr($guid, 10, 1)),
278                 unpack("C", substr($guid, 11, 1)),
279                 unpack("C", substr($guid, 12, 1)),
280                 unpack("C", substr($guid, 13, 1)),
281                 unpack("C", substr($guid, 14, 1)),
282                 unpack("C", substr($guid, 15, 1)); 
283         return lc($string);
284 }
285
286 sub recv_cldap_netlogon ($\$) {
287
288         my ($sock, $return_string) = @_;
289         my ($ret, $pdu_out);
290
291         $ret = $sock->recv($pdu_out, 8192) || die "failed to read from socket: $@";
292         #$ret = sysread($sock, $pdu_out, 8192);
293
294         if ($opt_debug) {
295                 print"------------\n";
296                 asn_dump($pdu_out);
297                 print"------------\n";
298         }
299
300         my $asn_cldap_rep = Convert::ASN1->new;
301         my $asn_cldap_rep_fail = Convert::ASN1->new;
302
303         $asn_cldap_rep->prepare(q<
304                 SEQUENCE {
305                         msgid INTEGER,
306                         [APPLICATION 4] SEQUENCE {
307                                 dn OCTET STRING,
308                                 SEQUENCE {
309                                         SEQUENCE {
310                                                 attr OCTET STRING,
311                                                 SET {
312                                                         val OCTET STRING
313                                                 }
314                                         }
315                                 }
316                         }
317                 }
318                 SEQUENCE {
319                         msgid2 INTEGER,
320                         [APPLICATION 5] SEQUENCE {
321                                 error_code ENUMERATED,
322                                 matched_dn OCTET STRING,
323                                 error_message OCTET STRING
324                         }
325                 }
326         >);
327
328         $asn_cldap_rep_fail->prepare(q<
329                 SEQUENCE {
330                         msgid2 INTEGER,
331                         [APPLICATION 5] SEQUENCE {
332                                 error_code ENUMERATED,
333                                 matched_dn OCTET STRING,
334                                 error_message OCTET STRING
335                         }
336                 }
337         >);
338
339         my $asn1_rep =  $asn_cldap_rep->decode($pdu_out) || 
340                         $asn_cldap_rep_fail->decode($pdu_out) || 
341                         die "failed to decode pdu: $@";
342
343         if ($asn1_rep->{'error_code'} == 0) {
344                 $$return_string = $asn1_rep->{'val'};
345         } 
346
347         return $ret;
348 }
349
350 sub parse_cldap_reply ($) {
351
352         my $str = shift || return undef;
353         my %hash;
354         my $p = 0;
355
356         $hash{type}     = unpack("L", substr($str, $p, 4)); $p += 4;
357         $hash{flags}    = unpack("L", substr($str, $p, 4)); $p += 4;
358         $hash{guid}     = unpack("a16", substr($str, $p, 16)); $p += 16;
359
360         $p += pull_netlogon_string($hash{forest}, $p, $str);
361         $p += pull_netlogon_string($hash{domain}, $p, $str);
362         $p += pull_netlogon_string($hash{hostname}, $p, $str);
363         $p += pull_netlogon_string($hash{netbios_domain}, $p, $str);
364         $p += pull_netlogon_string($hash{netbios_hostname}, $p, $str);
365         $p += pull_netlogon_string($hash{unk}, $p, $str);
366
367         if ($hash{type} == $cldap_samlogon_types{SAMLOGON_AD_R}) {
368                 $p += pull_netlogon_string($hash{user_name}, $p, $str);
369         } else {
370                 $hash{user_name} = "";
371         }
372
373         $p += pull_netlogon_string($hash{server_site_name}, $p, $str);
374         $p += pull_netlogon_string($hash{client_site_name}, $p, $str);
375
376         $hash{version}          = unpack("L", substr($str, $p, 4)); $p += 4;
377         $hash{lmnt_token}       = unpack("S", substr($str, $p, 2)); $p += 2;
378         $hash{lm20_token}       = unpack("S", substr($str, $p, 2)); $p += 2;
379
380         return %hash;
381 }
382
383 sub display_cldap_reply {
384
385         my $server = shift;
386         my (%hash) = @_;
387
388         my ($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($server);
389
390         printf("Information for Domain Controller: %s\n\n", $name);
391
392         printf("Response Type: ");
393         if ($hash{type} == $cldap_samlogon_types{SAMLOGON_AD_R}) {
394                 printf("SAMLOGON_USER\n");
395         } elsif ($hash{type} == $cldap_samlogon_types{SAMLOGON_AD_UNK_R}) {
396                 printf("SAMLOGON\n");
397         } else {
398                 printf("unknown type 0x%x, please report\n", $hash{type});
399         }
400
401         # guid
402         printf("GUID: %s\n", guid_to_string($hash{guid}));
403
404         # flags
405         dump_cldap_flags($hash{flags});
406
407         # strings
408         printf("Forest:\t\t\t%s\n", $hash{forest});
409         printf("Domain:\t\t\t%s\n", $hash{domain});
410         printf("Domain Controller:\t%s\n", $hash{hostname});
411
412         printf("Pre-Win2k Domain:\t%s\n", $hash{netbios_domain});
413         printf("Pre-Win2k Hostname:\t%s\n", $hash{netbios_hostname});
414
415         if ($hash{unk}) { 
416                 printf("Unk:\t\t\t%s\n", $hash{unk}); 
417         }
418         if ($hash{user_name}) { 
419                 printf("User name:\t%s\n", $hash{user_name}); 
420         }
421
422         printf("Server Site Name:\t%s\n", $hash{server_site_name});
423         printf("Client Site Name:\t%s\n", $hash{client_site_name});
424
425         # some more int
426         printf("NT Version:\t\t%d\n", $hash{version});
427         printf("LMNT Token:\t\t%.2x\n", $hash{lmnt_token});
428         printf("LM20 Token:\t\t%.2x\n", $hash{lm20_token});
429 }
430
431 sub main() {
432
433         my ($ret, $sock, $reply);
434
435         GetOptions(
436                 'debug'         => \$opt_debug,
437                 'domain|d=s'    => \$opt_domain,
438                 'help'          => \$opt_help,
439                 'host|h=s'      => \$opt_host,
440                 'server|s=s'    => \$opt_server,
441         );
442
443         $server = $server || $opt_server;
444         $domain = $domain || $opt_domain || undef;
445         $host = $host || $opt_host;
446         if (!$host) {
447                 $host = `/bin/hostname`;
448                 chomp($host);
449         }
450
451         if (!$server || !$host || $opt_help) {
452                 usage();
453                 exit 1;
454         }
455
456         my $ntver = sprintf("%c%c%c%c", 6,0,0,0);
457
458         $sock = connect_cldap($server);
459         if (!$sock) {
460                 die("could not connect to $server");
461         }
462
463         $ret = send_cldap_netlogon($sock, $domain, $host, $ntver);
464         if (!$ret) {
465                 close($sock);
466                 die("failed to send CLDAP request to $server");
467         }
468
469         $ret = recv_cldap_netlogon($sock, $reply);
470         if (!$ret) {
471                 close($sock);
472                 die("failed to receive CLDAP reply from $server");
473         }
474         close($sock);
475
476         if (!$reply) {
477                 printf("no 'NetLogon' attribute received\n");
478                 exit 0;
479         }
480
481         %cldap_netlogon_reply = parse_cldap_reply($reply);
482         if (!%cldap_netlogon_reply) {
483                 die("failed to parse CLDAP reply from $server");
484         }
485
486         display_cldap_reply($server, %cldap_netlogon_reply);
487
488         exit 0;
489 }
490
491 main();