File Coverage

lib/SMB/Auth.pm
Criterion Covered Total %
statement 246 280 87.8
branch 85 120 70.8
condition 45 93 48.3
subroutine 24 28 85.7
pod 15 15 100.0
total 415 536 77.4


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