File Coverage

lib/SMB/Auth.pm
Criterion Covered Total %
statement 247 281 87.9
branch 85 120 70.8
condition 49 99 49.4
subroutine 24 28 85.7
pod 15 15 100.0
total 420 543 77.3


line stmt bran cond sub pod time code
1             # SMB-Perl library, Copyright (C) 2014-2018 Mikhael Goikhman, migo@cpan.org
2             #
3             # This program is free software: you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation, either version 3 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program. If not, see .
15              
16             package SMB::Auth;
17              
18 2     2   620 use strict;
  2         3  
  2         43  
19 2     2   20 use warnings;
  2         3  
  2         76  
20              
21 2     2   390 use parent 'SMB';
  2         229  
  2         23  
22              
23 2     2   549 use bytes;
  2         13  
  2         24  
24 2     2   787 use Sys::Hostname qw(hostname);
  2         1516  
  2         90  
25 2     2   445 use Encode qw(encode);
  2         7573  
  2         85  
26              
27 2     2   705 use SMB::Crypt qw(des_crypt56 md4 hmac_md5);
  2         4  
  2         98  
28 2     2   335 use SMB::Parser;
  2         3  
  2         39  
29 2     2   326 use SMB::Packer;
  2         3  
  2         40  
30 2     2   611 use SMB::Time qw(to_nttime);
  2         3  
  2         95  
31              
32             # Abstract Syntax Notation One (small subset)
33              
34             use constant {
35 2         158 ASN1_BINARY => 0x04,
36             ASN1_OID => 0x06,
37             ASN1_ENUMERATED => 0x0a,
38             ASN1_SEQUENCE => 0x30,
39             ASN1_SET => 0x31,
40             ASN1_APPLICATION => 0x60,
41             ASN1_CONTEXT => 0xa0,
42 2     2   10 };
  2         2  
43              
44             # Generic Security Service API / Simple Protected Negotiation
45              
46             use constant {
47 2         156 OID_SPNEGO => '1.3.6.1.5.5.2',
48             OID_MECH_NTLMSSP => '1.3.6.1.4.1.311.2.2.10',
49              
50             SPNEGO_ACCEPT_COMPLETED => 0,
51             SPNEGO_ACCEPT_INCOMPLETE => 1,
52 2     2   9 };
  2         2  
53              
54             # NTLMSSP mechanism
55              
56             use constant {
57 2         6609 NTLMSSP_ID_STR => "NTLMSSP\0",
58              
59             NTLMSSP_NEGOTIATE => 1,
60             NTLMSSP_CHALLENGE => 2,
61             NTLMSSP_AUTH => 3,
62             NTLMSSP_SIGNATURE => 4,
63              
64             NTLMSSP_ITEM_TERMINATOR => 0,
65             NTLMSSP_ITEM_NETBIOSHOST => 1,
66             NTLMSSP_ITEM_NETBIOSDOMAIN => 2,
67             NTLMSSP_ITEM_DNSHOST => 3,
68             NTLMSSP_ITEM_DNSDOMAIN => 4,
69             NTLMSSP_ITEM_TIMESTAMP => 7,
70              
71             NTLMSSP_FLAGS_CLIENT => 0x60008215,
72             NTLMSSP_FLAGS_SERVER => 0x628a8215,
73 2     2   10 };
  2         2  
74              
75             sub new ($) {
76 2     2 1 436 my $class = shift;
77              
78 2         8 return $class->SUPER::new(
79             ntlmssp_supported => undef,
80             client_host => undef,
81             client_domain => undef,
82             server_challenge => undef,
83             server_host => undef,
84             server_netbios_host => undef,
85             server_netbios_domain => undef,
86             server_dns_host => undef,
87             server_dns_domain => undef,
88             server_timestamp => undef,
89             client_challenge => undef,
90             lm_response => undef,
91             ntlm_response => undef,
92             domain => undef,
93             host => undef,
94             username => undef,
95             session_key => undef,
96             auth_completed => undef,
97             user_passwords => {},
98             parser => SMB::Parser->new,
99             packer => SMB::Packer->new,
100             );
101             }
102              
103             sub set_user_passwords ($$) {
104 0     0 1 0 my $self = shift;
105 0   0     0 my $user_passwords = shift || die "No user passwords to set";
106              
107 0 0       0 die "User passwords should be HASH"
108             unless ref($user_passwords) eq 'HASH';
109              
110 0         0 $self->user_passwords($user_passwords);
111             }
112              
113             sub create_lm_hash ($) {
114 1   50 1 1 7 my $password = substr(encode('ISO-8859-1', uc(shift // "")), 0, 14);
115 1         37 $password .= "\0" x (14 - length($password));
116              
117             return join('', map {
118 1         6 des_crypt56('KGS!@#$%', $_)
  2         6  
119             } $password =~ /^(.{7})(.{7})$/);
120             }
121              
122             sub create_ntlm_hash ($) {
123 3   50 3 1 13 my $password = encode('UTF-16LE', shift // "");
124              
125 3         151 return md4($password);
126             }
127              
128             sub create_lm_response ($$) {
129 0   0 0 1 0 my $lm_hash = shift || die;
130 0         0 my $server_challenge = shift;
131              
132 0         0 $lm_hash .= "\0" x (21 - length($lm_hash));
133              
134             return join('', map {
135 0         0 des_crypt56([ map { ord($_) } split '', $server_challenge ], $_)
  0         0  
  0         0  
136             } $lm_hash =~ /^(.{7})(.{7})(.{7})$/);
137             }
138              
139             sub create_ntlmv2_hash ($$$) {
140 3   50 3 1 6 my $ntlm_hash = shift || die;
141 3   50     5 my $username = shift // '';
142 3   50     7 my $domain = shift // '';
143              
144 3         8 return hmac_md5(encode('UTF-16LE', uc($username . $domain)), $ntlm_hash);
145             }
146              
147             sub create_lmv2_response ($$$$) {
148 1     1 1 3 return create_ntlmv2_response($_[0], $_[1], $_[2], $_[3], 8);
149             }
150              
151             sub create_ntlmv2_response ($$$$;$) {
152 1     1 1 1 my $ntlm_hash = shift;
153 1         1 my $username = shift;
154 1         2 my $domain = shift;
155 1         1 my $server_challenge = shift;
156 1   50     2 my $client_challenge_len = shift || 24;
157              
158 1         3 my $client_challenge = join('', map { chr(rand(0x100)) } 1 .. $client_challenge_len);
  8         12  
159 1         2 my $ntlmv2_hash = create_ntlmv2_hash($ntlm_hash, $username, $domain);
160              
161 1         3 return hmac_md5($server_challenge . $client_challenge, $ntlmv2_hash) . $client_challenge;
162             }
163              
164             sub get_user_passwd_line ($$) {
165 0     0 1 0 my $username = shift;
166 0         0 my $password = shift;
167              
168             return "$username:" . join('',
169 0         0 map { map { sprintf "%02x", ord($_) } split '', $_ }
  0         0  
  0         0  
170             create_lm_hash($password), create_ntlm_hash($password)
171             );
172             }
173              
174             sub load_user_passwords ($$) {
175 0     0 1 0 my $self = shift;
176 0   0     0 my $filename = shift || return;
177              
178 0 0       0 open PASSWD, "<$filename" or return;
179 0         0 my @lines = ;
180 0 0       0 close PASSWD or return;
181              
182             my %user_passwords = map {
183 0         0 s/^\s+//;
  0         0  
184 0         0 s/\s+$//;
185 0         0 my ($username, $hash_str) = split ':', $_;
186             my @hash_bytes = ($hash_str || '') =~ /^[0-9a-f]{64}$/
187 0 0 0     0 ? map { chr(hex(substr($hash_str, $_ * 2, 2))) } 0 .. 31
  0         0  
188             : ();
189 0 0 0     0 $username && $username =~ /^\w[\w.+-]*$/ && @hash_bytes
190             ? ($username => [ join('', @hash_bytes[0 .. 15]), join('', @hash_bytes[16 .. 31]) ])
191             : ();
192             } grep !/^\s*#/, @lines;
193              
194 0         0 $self->user_passwords(\%user_passwords);
195              
196             # in scalar context - number of users loaded
197 0         0 return keys %user_passwords;
198             }
199              
200             sub is_user_authenticated ($) {
201 1     1 1 1 my $self = shift;
202              
203             # my $lm_response = $self->lm_response || return $self->err("No lm_response from client");
204 1   50     2 my $ntlm_response = $self->ntlm_response || return $self->err("No ntlm_response from client");
205              
206 1         5 my ($hmac, $client_data) = $ntlm_response =~ /^(.{16})(.+)$/s;
207 1 50       4 return $self->err("Invalid short ntlm_response from client")
208             unless $hmac;
209              
210 1   50     2 my $username = $self->username // return $self->err("No username from client");
211 1   50     3 my $password = $self->user_passwords->{$username} // return $self->err("No user '$username' on server");
212 1 50       4 my ($lm_hash, $ntlm_hash) = ref($password) eq 'ARRAY' ? @$password : ();
213              
214             # $lm_hash ||= create_lm_hash($password);
215 1   33     6 $ntlm_hash ||= create_ntlm_hash($password);
216 1         4 my $ntlmv2_hash = create_ntlmv2_hash($ntlm_hash, $username, $self->client_domain);
217              
218 1 50       5 return $self->err("Failed password check for user '$username', client not authenticated")
219             unless $hmac eq hmac_md5($self->server_challenge . $client_data, $ntlmv2_hash);
220              
221 1         5 return 1;
222             }
223              
224             my @parsed_context_values;
225              
226             sub parse_asn1 {
227 41     41 1 38 my $bytes = shift;
228              
229 41         38 my $tag = ord(shift @$bytes);
230 41         41 my $len = ord(shift @$bytes);
231 41 100       54 if ($len >= 0x80) {
232 6         8 my $llen = $len - 0x80;
233 6         4 my $factor = 1;
234 6         7 $len = 0;
235 6         8 for (1 .. $llen) {
236 12         13 $len = $len * $factor + ord(shift @$bytes);
237 12         11 $factor *= 256;
238             }
239             }
240              
241 41         39 my @contents;
242 41         98 my @bytes = splice(@$bytes, 0, $len);
243 41 100 66     155 if ($tag == ASN1_BINARY) {
    100 100        
    100 33        
    100          
    50          
244 4         6 @contents = (\@bytes);
245             } elsif ($tag == ASN1_OID) {
246 7         6 my $idx = 0;
247 7         6 my $carry = 0;
248             @contents = (join('.', map {
249 58         41 my @i;
250 58 100       62 if (0 == $idx++) { @i = (int($_ / 40), $_ % 40); }
  7 100       18  
251 4         5 elsif ($_ >= 0x80) { $carry = $carry * 0x80 + $_ - 0x80; }
252 47         44 else { @i = ($carry * 0x80 + $_); $carry = 0; }
  47         37  
253             @i
254 7         21 } map { ord($_) } @bytes));
  58         97  
  58         48  
255             } elsif ($tag == ASN1_ENUMERATED) {
256 2 50       4 die "Unsupported len=$len" unless $len == 1;
257 2         4 @contents = map { ord($_) } @bytes;
  2         5  
258             } elsif ($tag == ASN1_SEQUENCE || $tag == ASN1_SET || $tag == ASN1_APPLICATION) {
259 12         25 push @contents, parse_asn1(\@bytes)
260             while @bytes;
261             } elsif ($tag >= ASN1_CONTEXT && $tag <= ASN1_CONTEXT + 3) {
262 16         10 @contents = @{parse_asn1(\@bytes)};
  16         47  
263 16   100     49 $parsed_context_values[$tag - ASN1_CONTEXT] //= \@contents;
264             } else {
265 0         0 warn sprintf "Unsupported asn1 tag 0x%x on parse\n", $tag;
266             }
267              
268 41         99 return [ $tag, @contents ];
269             }
270              
271             sub generate_asn1 {
272 41   50 41 1 45 my $tag = shift // die "No asn1 tag";
273 41   50     48 my $content = shift // die "No asn1 tag content";
274              
275 41         33 my @bytes;
276 41 100 66     154 if ($tag == ASN1_BINARY) {
    100 100        
    100 33        
    100          
    50          
277 4         73 @bytes = split('', $content);
278             } elsif ($tag == ASN1_OID) {
279 7         7 my $idx = 0;
280 7         5 my $id0;
281 58         72 @bytes = map { chr($_) } map {
282 7 50 33     17 0 == $idx++ ? ($id0 = $_) && () : 2 == $idx ? ($id0 * 40 + $_) : (
  61 50       157  
    50          
    100          
    100          
    100          
283             $_ >= 1 << 28 ? (0x80 | (($_ >> 28) & 0x7f)) : (),
284             $_ >= 1 << 21 ? (0x80 | (($_ >> 21) & 0x7f)) : (),
285             $_ >= 1 << 14 ? (0x80 | (($_ >> 14) & 0x7f)) : (),
286             $_ >= 1 << 7 ? (0x80 | (($_ >> 7) & 0x7f)) : (),
287             $_ & 0x7f
288             )
289             } split(/\./, $content);
290             } elsif ($tag == ASN1_ENUMERATED) {
291 2         4 @bytes = (chr($content));
292             } elsif ($tag == ASN1_SEQUENCE || $tag == ASN1_SET || $tag == ASN1_APPLICATION) {
293 12         11 do {
294 19         13 push @bytes, @{generate_asn1(@$content)};
  19         28  
295 19         59 $content = shift;
296             } while $content;
297             } elsif ($tag >= ASN1_CONTEXT && $tag <= ASN1_CONTEXT + 3) {
298 16         15 @bytes = @{generate_asn1($content, @_)};
  16         24  
299             } else {
300 0         0 warn sprintf "Unsupported asn1 tag 0x%x on generate\n", $tag;
301             }
302              
303 41         82 my $len = @bytes;
304 41         30 my @sub_lens;
305 41         64 while ($len >= 0x80) {
306 6         9 push @sub_lens, $len % 256;
307 6         11 $len /= 256;
308             }
309 41 100       62 my @len_bytes = @sub_lens ? (0x80 + @sub_lens + 1, $len, @sub_lens) : ($len);
310              
311 41         43 return [ (map { chr($_) } $tag, @len_bytes), @bytes ];
  94         720  
312             }
313              
314             sub process_spnego ($$%) {
315 6     6 1 1588 my $self = shift;
316 6   50     13 my $buffer = shift // return;
317 6         9 my %options = @_;
318              
319 6         65 my @bytes = split '', $buffer;
320 6 50       13 return unless @bytes > 2;
321              
322 6         19 @parsed_context_values = ();
323 6         11 my $struct = parse_asn1(\@bytes);
324 6 50       8 return unless $struct;
325              
326 6 100 100     13 if (!defined $self->ntlmssp_supported || $options{is_initial}) {
327 2         3 my $value = $parsed_context_values[0];
328 2 50 33     7 return $self->err("No expected spnego context value")
329             unless ref($value) eq 'ARRAY' && shift @$value == ASN1_SEQUENCE;
330 2         3 for (@$value) {
331 2 50 33     9 return $self->ntlmssp_supported(1)
332             if $_->[0] == ASN1_OID && $_->[1] eq OID_MECH_NTLMSSP;
333             }
334 0         0 return $self->ntlmssp_supported(0);
335             }
336              
337 4         4 my $value = $parsed_context_values[2];
338 4 100 66     14 my $ntlmssp_bytes = ref($value) eq 'ARRAY' && shift @$value == ASN1_BINARY
339             ? shift @$value
340             : undef;
341 4         12 my $parser = $self->parser;
342 4 100       9 unless (defined $self->client_challenge) {
343 3 50       4 return $self->err("No expected spnego context+2 value (ntlmssp)")
344             unless $ntlmssp_bytes;
345 3         21 $parser->set(join('', @$ntlmssp_bytes));
346 3 50       18 return $self->err("No expected NTLMSSP id string")
347             unless $parser->bytes(length(NTLMSSP_ID_STR)) eq NTLMSSP_ID_STR;
348             }
349              
350 4 100       7 if (!defined $self->client_host) {
    100          
    100          
    50          
351 1 50       2 return $self->err("No expected NTLMSSP_NEGOTIATE")
352             unless $parser->uint32 == NTLMSSP_NEGOTIATE;
353 1         3 $parser->skip(4); # skip flags
354 1         7 my $len1 = $parser->uint16;
355 1         2 my $off1 = $parser->skip(2)->uint32;
356 1         2 my $len2 = $parser->uint16;
357 1         2 my $off2 = $parser->skip(2)->uint32;
358 1         2 $self->client_domain($parser->reset($off1)->bytes($len1));
359 1         2 $self->client_host ($parser->reset($off2)->bytes($len2));
360             } elsif (!defined $self->server_challenge) {
361 1 50       2 return $self->err("No expected NTLMSSP_CHALLENGE")
362             unless $parser->uint32 == NTLMSSP_CHALLENGE;
363 1         2 my $len1 = $parser->uint16;
364 1         3 my $off1 = $parser->skip(2)->uint32;
365 1         2 $self->server_challenge($parser->reset(24)->bytes(8));
366 1         2 $self->server_host($parser->reset($off1)->str($len1));
367 1         1 my $itemtype;
368 1         2 do {{
369 6         7 $itemtype = $parser->uint16;
  6         7  
370 6 100 33     10 $parser->uint16 == 8 && $self->server_timestamp($parser->uint64), next
371             if $itemtype == NTLMSSP_ITEM_TIMESTAMP;
372 5         7 my $str = $parser->str($parser->uint16);
373 5 100       145 $self->server_netbios_host($str)
374             if $itemtype == NTLMSSP_ITEM_NETBIOSHOST;
375 5 100       7 $self->server_netbios_domain($str)
376             if $itemtype == NTLMSSP_ITEM_NETBIOSDOMAIN;
377 5 100       9 $self->server_dns_host($str)
378             if $itemtype == NTLMSSP_ITEM_DNSHOST;
379 5 100       11 $self->server_dns_domain($str)
380             if $itemtype == NTLMSSP_ITEM_DNSDOMAIN;
381             }} while ($itemtype != NTLMSSP_ITEM_TERMINATOR)
382             } elsif (!defined $self->client_challenge) {
383 1 50       2 return $self->err("No expected NTLMSSP_AUTH")
384             unless $parser->uint32 == NTLMSSP_AUTH;
385 1         2 my $llen = $parser->uint16;
386 1         2 my $loff = $parser->skip(2)->uint32;
387 1         2 my $nlen = $parser->uint16;
388 1         2 my $noff = $parser->skip(2)->uint32;
389 1         2 my $len1 = $parser->uint16;
390 1         3 my $off1 = $parser->skip(2)->uint32;
391 1         2 my $len2 = $parser->uint16;
392 1         2 my $off2 = $parser->skip(2)->uint32;
393 1         2 my $len3 = $parser->uint16;
394 1         2 my $off3 = $parser->skip(2)->uint32;
395 1         3 $self->client_challenge($parser->reset($noff + 28)->bytes(8));
396 1         3 $self->lm_response ($parser->reset($loff)->bytes($llen));
397 1         2 $self->ntlm_response($parser->reset($noff)->bytes($nlen));
398 1         3 $self->client_domain($parser->reset($off1)->str($len1));
399 1         2 $self->username ($parser->reset($off2)->str($len2));
400 1         3 $self->client_host ($parser->reset($off3)->str($len3));
401             } elsif (!defined $self->auth_completed) {
402 1         2 my $value = $parsed_context_values[0];
403 1 50 33     6 return $self->err("No expected spnego context value (ACCEPT_COMPLETED)")
404             unless ref($value) eq 'ARRAY' && shift @$value == ASN1_ENUMERATED;
405 1 50       4 $self->auth_completed(shift @$value == SPNEGO_ACCEPT_COMPLETED ? 1 : 0);
406             } else {
407 0         0 $self->err("process_spnego called after auth_completed");
408             }
409              
410 4         26 return 1;
411             }
412              
413             sub generate_spnego ($%) {
414 6     6 1 14 my $self = shift;
415 6         10 my %options = @_;
416              
417 6         6 my $struct;
418              
419 6 100 100     12 if (!defined $self->ntlmssp_supported || $options{is_initial}) {
420 2         4 $self->ntlmssp_supported(1);
421 2         5 $struct = [ ASN1_APPLICATION,
422             [ ASN1_OID, OID_SPNEGO ],
423             [ ASN1_CONTEXT, ASN1_SEQUENCE,
424             [ ASN1_CONTEXT, ASN1_SEQUENCE,
425             [ ASN1_OID, OID_MECH_NTLMSSP ],
426             ],
427             ],
428             ];
429 2         7 goto RETURN;
430             }
431              
432 4         9 my @names = hostname =~ /^([^.]*+)\.?+(.*)$/;
433 4   66     49 my $host = $options{host} || $names[0];
434 4   66     11 my $domain = $options{domain} || $names[1];
435              
436 4 100       14 if (!defined $self->client_host) {
    100          
    100          
    50          
437 1         3 $self->client_host($host);
438 1         4 $self->client_domain($domain);
439              
440 1         5 $self->packer->reset
441             ->bytes(NTLMSSP_ID_STR)
442             ->uint32(NTLMSSP_NEGOTIATE)
443             ->uint32(NTLMSSP_FLAGS_CLIENT)
444             ->uint16(length($domain))
445             ->uint16(length($domain))
446             ->uint32(32)
447             ->uint16(length($host))
448             ->uint16(length($host))
449             ->uint32(32 + length($domain))
450             ->bytes($domain)
451             ->bytes($host)
452             ;
453 1         3 $struct = [ ASN1_APPLICATION,
454             [ ASN1_OID, OID_SPNEGO ],
455             [ ASN1_CONTEXT, ASN1_SEQUENCE,
456             [ ASN1_CONTEXT, ASN1_SEQUENCE,
457             [ ASN1_OID, OID_MECH_NTLMSSP ],
458             ],
459             [ ASN1_CONTEXT + 2, ASN1_BINARY, $self->packer->data ],
460             ],
461             ];
462             } elsif (!defined $self->server_challenge) {
463 1         2 $self->server_challenge(join('', map { chr(rand(0x100)) } 1 .. 8));
  8         65  
464 1         8 $self->server_host($host);
465 1         5 $self->server_netbios_host($host);
466 1         5 $self->server_netbios_domain($domain);
467 1         4 $self->server_dns_host($host);
468 1         7 $self->server_dns_domain($domain);
469 1         2 my $tlen = 32 + length(
470             $self->server_netbios_host .
471             $self->server_netbios_domain .
472             $self->server_dns_host .
473             $self->server_dns_domain
474             ) * 2;
475              
476 1         3 $self->packer->reset
477             ->bytes(NTLMSSP_ID_STR)
478             ->uint32(NTLMSSP_CHALLENGE)
479             ->uint16(length($self->server_host) * 2)
480             ->uint16(length($self->server_host) * 2)
481             ->uint32(56)
482             ->uint32(NTLMSSP_FLAGS_SERVER)
483             ->bytes($self->server_challenge)
484             ->uint64(0) # reserved
485             ->uint16($tlen)
486             ->uint16($tlen)
487             ->uint32(56 + length($self->server_host) * 2)
488             ->bytes("\x06\x01\xb1\x1d\x00\x00\x00\x0f") # version
489             ->str($self->server_host)
490             ->uint16(NTLMSSP_ITEM_NETBIOSDOMAIN)
491             ->uint16(length($self->server_netbios_domain) * 2)
492             ->str($self->server_netbios_domain)
493             ->uint16(NTLMSSP_ITEM_NETBIOSHOST)
494             ->uint16(length($self->server_netbios_host) * 2)
495             ->str($self->server_netbios_host)
496             ->uint16(NTLMSSP_ITEM_DNSDOMAIN)
497             ->uint16(length($self->server_dns_domain) * 2)
498             ->str($self->server_dns_domain)
499             ->uint16(NTLMSSP_ITEM_DNSHOST)
500             ->uint16(length($self->server_dns_host) * 2)
501             ->str($self->server_dns_host)
502             ->uint16(NTLMSSP_ITEM_TIMESTAMP)
503             ->uint16(8)
504             ->bytes("\0" x 8)
505             ->uint16(NTLMSSP_ITEM_TERMINATOR)
506             ->uint16(0)
507             ;
508              
509 1         5 $struct = [ ASN1_CONTEXT + 1, ASN1_SEQUENCE,
510             [ ASN1_CONTEXT, ASN1_ENUMERATED, SPNEGO_ACCEPT_INCOMPLETE ],
511             [ ASN1_CONTEXT + 1, ASN1_OID, OID_MECH_NTLMSSP ],
512             [ ASN1_CONTEXT + 2, ASN1_BINARY, $self->packer->data ],
513             ];
514             } elsif (!defined $self->client_challenge) {
515 1   50     2 my $username = $options{username} || '';
516 1   50     3 my $password = $options{password} || '';
517 1   50     2 $domain = $options{domain} || 'MYGROUP';
518 1         2 $self->client_challenge(join('', map { chr(rand(0x100)) } 1 .. 8));
  8         14  
519 1         8 $self->username($username);
520 1         5 $self->domain($domain);
521 1         2 $self->session_key([ map { chr(rand(0x100)) } 1 .. 16 ]);
  16         24  
522              
523             # my $lm_hash = $options{lm_password_hash} || create_lm_hash($password);
524 1   33     5 my $ntlm_hash = $options{ntlm_password_hash} || create_ntlm_hash($password);
525 1         4 my $ntlmv2_hash = create_ntlmv2_hash($ntlm_hash, $self->username, $self->domain);
526              
527 1   50     4 $self->packer->reset
528             ->uint32(0x0101) # header
529             ->uint32(0) # reserved
530             ->uint64(to_nttime(time))
531             ->bytes($self->client_challenge)
532             ->uint32(0) # unknown
533             ->uint16(NTLMSSP_ITEM_NETBIOSDOMAIN)
534             ->uint16(length($self->server_netbios_domain) * 2)
535             ->str($self->server_netbios_domain)
536             ->uint16(NTLMSSP_ITEM_NETBIOSHOST)
537             ->uint16(length($self->server_netbios_host) * 2)
538             ->str($self->server_netbios_host)
539             ->uint16(NTLMSSP_ITEM_DNSDOMAIN)
540             ->uint16(length($self->server_dns_domain) * 2)
541             ->str($self->server_dns_domain)
542             ->uint16(NTLMSSP_ITEM_DNSHOST)
543             ->uint16(length($self->server_dns_host) * 2)
544             ->str($self->server_dns_host)
545             ->uint16(NTLMSSP_ITEM_TIMESTAMP)
546             ->uint16(8)
547             ->uint64($self->server_timestamp || 0)
548             ->uint16(NTLMSSP_ITEM_TERMINATOR)
549             ->uint16(0)
550             ;
551              
552 1         4 my $client_data = $self->packer->data;
553 1         3 my $hmac = hmac_md5($self->server_challenge . $client_data, $ntlmv2_hash);
554 1         2 my $ntlm_response = "$hmac$client_data";
555 1         9 my $nlen = 16 + $self->packer->size; # hmac + client data
556              
557 1         2 my $lm_response = create_lmv2_response($ntlm_hash, $username, $domain, $self->server_challenge);
558              
559 1         8 $self->lm_response($lm_response);
560 1         5 $self->ntlm_response($ntlm_response);
561              
562 1         3 $self->packer->reset
563             ->bytes(NTLMSSP_ID_STR)
564             ->uint32(NTLMSSP_AUTH)
565             ->uint16(24)
566             ->uint16(24)
567             ->uint32(64)
568             ->uint16($nlen)
569             ->uint16($nlen)
570             ->uint32(88)
571             ->uint16(length($domain) * 2)
572             ->uint16(length($domain) * 2)
573             ->uint32(88 + $nlen)
574             ->uint16(length($username) * 2)
575             ->uint16(length($username) * 2)
576             ->uint32(88 + $nlen + length($domain) * 2)
577             ->uint16(length($host) * 2)
578             ->uint16(length($host) * 2)
579             ->uint32(88 + $nlen + length("$domain$username") * 2)
580             ->uint16(16)
581             ->uint16(16)
582             ->uint32(88 + $nlen + length("$domain$username$host") * 2)
583             ->uint32(NTLMSSP_FLAGS_CLIENT)
584             ->bytes($lm_response)
585             ->bytes($ntlm_response)
586             ->str($domain)
587             ->str($username)
588             ->str($host)
589             ->bytes($self->session_key)
590             ;
591              
592 1         4 $struct = [ ASN1_CONTEXT + 1, ASN1_SEQUENCE,
593             [ ASN1_CONTEXT + 2, ASN1_BINARY, $self->packer->data ],
594             ];
595             } elsif (!defined $self->auth_completed) {
596 1 50       4 $self->auth_completed($self->is_user_authenticated ? 1 : 0);
597 1         2 my $mechlist_mic = "\x01\x00\x00\x00\x89\x22\x91\x93\xff\xdb\xcb\x26\x00\x00\x00\x00"; #"\x00" x 16;
598 1 50       2 $struct = [ ASN1_CONTEXT + 1, ASN1_SEQUENCE,
599             [ ASN1_CONTEXT, ASN1_ENUMERATED, SPNEGO_ACCEPT_COMPLETED ],
600             [ ASN1_CONTEXT + 3, ASN1_BINARY, $mechlist_mic ],
601             ] if $self->auth_completed;
602             } else {
603 0         0 $self->err("generate_spnego called after auth_completed");
604             }
605              
606 6 50       12 RETURN:
607             return undef unless $struct;
608              
609 6         4 return join '', @{generate_asn1(@$struct)};
  6         13  
610             }
611              
612             1;
613              
614             __END__