File Coverage

lib/SMB/Auth.pm
Criterion Covered Total %
statement 257 296 86.8
branch 89 128 69.5
condition 52 105 49.5
subroutine 24 28 85.7
pod 15 15 100.0
total 437 572 76.4


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