File Coverage

blib/lib/Authen/NTLM/HTTP/Base.pm
Criterion Covered Total %
statement 312 336 92.8
branch 53 90 58.8
condition 2 6 33.3
subroutine 54 55 98.1
pod 0 22 0.0
total 421 509 82.7


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             # NTLM.pm - An implementation of NTLM. In this version, I only
3             # implemented the client side functions that calculates the NTLM response.
4             # I will add the corresponding server side functions in the next version.
5             #
6            
7             package Authen::NTLM::HTTP::Base;
8            
9 2     2   1687 use strict;
  2         3  
  2         86  
10 2     2   2055 use POSIX;
  2         29049  
  2         28  
11 2     2   10921 use Carp;
  2         12  
  2         550  
12             $Authen::NTLM::HTTP::Base::PurePerl = undef; # a flag to see if we load pure perl
13             # DES and MD4 modules
14             eval "require Crypt::DES && require Digest::MD4";
15             if ($@) {
16             eval "require Crypt::DES_PP && require Digest::Perl::MD4";
17             if ($@) {
18             die "Required DES and/or MD4 module doesn't exist!\n";
19             }
20             else {
21             $Authen::NTLM::HTTP::Base::PurePerl = 1;
22             }
23             }
24             else {
25             $Authen::NTLM::HTTP::Base::PurePerl = 0;
26             }
27            
28             if ($Authen::NTLM::HTTP::Base::PurePerl == 1) {
29             require Crypt::DES_PP;
30             Crypt::DES_PP->import;
31             require Digest::Perl::MD4;
32             import Digest::Perl::MD4 qw(md4);
33             }
34             else {
35             require Crypt::DES;
36             Crypt::DES->import;
37             require Digest::MD4;
38             import Digest::MD4;
39             }
40 2     2   13 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK);
  2         3  
  2         541  
41            
42             require Exporter;
43             require DynaLoader;
44            
45             *import = \&Exporter::import;
46            
47             @ISA = qw (Exporter DynaLoader);
48             @EXPORT = qw ();
49             @EXPORT_OK = qw (nt_hash lm_hash calc_resp);
50             $VERSION = '0.32';
51            
52             # Stolen from Crypt::DES.
53             sub usage {
54 0     0 0 0 my ($package, $filename, $line, $subr) = caller (1);
55 0         0 $Carp::CarpLevel = 2;
56 0         0 croak "Usage: $subr (@_)";
57             }
58            
59             # These constants are stolen from samba-2.2.4 and other sources
60 2     2   12 use constant NTLMSSP_SIGNATURE => 'NTLMSSP';
  2         4  
  2         179  
61            
62             # NTLMSSP Message Types
63 2     2   11 use constant NTLMSSP_NEGOTIATE => 1;
  2         3  
  2         89  
64 2     2   18 use constant NTLMSSP_CHALLENGE => 2;
  2         4  
  2         77  
65 2     2   9 use constant NTLMSSP_AUTH => 3;
  2         3  
  2         84  
66 2     2   9 use constant NTLMSSP_UNKNOWN => 4;
  2         11  
  2         96  
67            
68             # NTLMSSP Flags
69            
70             # Text strings are in unicode
71 2     2   16 use constant NTLMSSP_NEGOTIATE_UNICODE => 0x00000001;
  2         3  
  2         105  
72             # Text strings are in OEM
73 2     2   31 use constant NTLMSSP_NEGOTIATE_OEM => 0x00000002;
  2         3  
  2         122  
74             # Server should return its authentication realm
75 2     2   10 use constant NTLMSSP_REQUEST_TARGET => 0x00000004;
  2         3  
  2         82  
76             # Request signature capability
77 2     2   9 use constant NTLMSSP_NEGOTIATE_SIGN => 0x00000010;
  2         3  
  2         83  
78             # Request confidentiality
79 2     2   20 use constant NTLMSSP_NEGOTIATE_SEAL => 0x00000020;
  2         3  
  2         183  
80             # Use datagram style authentication
81 2     2   10 use constant NTLMSSP_NEGOTIATE_DATAGRAM => 0x00000040;
  2         3  
  2         85  
82             # Use LM session key for sign/seal
83 2     2   8 use constant NTLMSSP_NEGOTIATE_LM_KEY => 0x00000080;
  2         3  
  2         91  
84             # NetWare authentication
85 2     2   9 use constant NTLMSSP_NEGOTIATE_NETWARE => 0x00000100;
  2         4  
  2         88  
86             # NTLM authentication
87 2     2   9 use constant NTLMSSP_NEGOTIATE_NTLM => 0x00000200;
  2         4  
  2         77  
88             # Domain Name supplied on negotiate
89 2     2   9 use constant NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED => 0x00001000;
  2         3  
  2         82  
90             # Workstation Name supplied on negotiate
91 2     2   8 use constant NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED => 0x00002000;
  2         3  
  2         74  
92             # Indicates client/server are same machine
93 2     2   8 use constant NTLMSSP_NEGOTIATE_LOCAL_CALL => 0x00004000;
  2         3  
  2         74  
94             # Sign for all security levels
95 2     2   9 use constant NTLMSSP_NEGOTIATE_ALWAYS_SIGN => 0x00008000;
  2         4  
  2         80  
96             # TargetName is a domain name
97 2     2   9 use constant NTLMSSP_TARGET_TYPE_DOMAIN => 0x00010000;
  2         4  
  2         81  
98             # TargetName is a server name
99 2     2   9 use constant NTLMSSP_TARGET_TYPE_SERVER => 0x00020000;
  2         4  
  2         73  
100             # TargetName is a share name
101 2     2   9 use constant NTLMSSP_TARGET_TYPE_SHARE => 0x00040000;
  2         3  
  2         81  
102             # TargetName is a share name
103 2     2   10 use constant NTLMSSP_NEGOTIATE_NTLM2 => 0x00080000;
  2         4  
  2         86  
104             # get back session keys
105 2     2   9 use constant NTLMSSP_REQUEST_INIT_RESPONSE => 0x00100000;
  2         2  
  2         69  
106             # get back session key, LUID
107 2     2   56 use constant NTLMSSP_REQUEST_ACCEPT_RESPONSE => 0x00200000;
  2         4  
  2         123  
108             # request non-ntsession key
109 2     2   9 use constant NTLMSSP_REQUEST_NON_NT_SESSION_KEY => 0x00400000;
  2         3  
  2         290  
110 2     2   10 use constant NTLMSSP_NEGOTIATE_TARGET_INFO => 0x00800000;
  2         7  
  2         83  
111 2     2   9 use constant NTLMSSP_NEGOTIATE_128 => 0x20000000;
  2         22  
  2         69  
112 2     2   8 use constant NTLMSSP_NEGOTIATE_KEY_EXCH => 0x40000000;
  2         4  
  2         78  
113 2     2   9 use constant NTLMSSP_NEGOTIATE_80000000 => 0x80000000;
  2         4  
  2         17709  
114            
115             sub lm_hash($);
116             sub nt_hash($);
117             sub calc_resp($$);
118            
119             #########################################################################
120             # Constructor to initialize authentication related information. In this #
121             # version, we assume NTLM as the authentication scheme of choice. #
122             # The constructor takes the class name, LM hash of the client password #
123             # and the LM hash of the client password as arguments. #
124             #########################################################################
125             sub new_client {
126 1 50 33 1 0 9 usage("new_client Authen::NTLM(\$lm_hpw, \$nt_hpw\) or\nnew_client Authen::NTLM\(\$lm_hpw, \$nt_hpw, \$user, \$user_domain, \$domain, \$machine\)") unless @_ == 3 or @_ == 7;
127 1         4 my ($package, $lm_hpw, $nt_hpw, $user, $user_domain, $domain, $machine) = @_;
128 1         6 srand time;
129 1 50       3 if (not defined($user)) {$user = $ENV{'USERNAME'};}
  0         0  
130 1 50       3 if (not defined($user_domain)) {$user_domain = $ENV{'USERDOMAIN'};}
  0         0  
131 1 50       2 if (not defined($domain)) {$domain = Win32::DomainName();}
  0         0  
132 1 50       6 if (not defined($machine)) {$machine = $ENV{'COMPUTERNAME'};}
  0         0  
133 1 50       3 usage("LM hash must be 21-bytes long") unless length($lm_hpw) == 21;
134 1 50       3 usage("NT hash must be 21-bytes long") unless length($nt_hpw) == 21;
135 1 50       2 defined($user) or usage "Undefined User Name!\n";
136 1 50       6 defined($user_domain) or usage "Undefined User Domain!\n";
137 1 50       2 defined($domain) or usage "Undefined Network Domain!\n";
138 1 50       39 defined($machine) or usage "Undefined Computer Name!\n";
139 1         7 my $ctx_id = pack("V", rand 2**32);
140 1         9 bless {
141             'user' => $user,
142             'user_domain' => $user_domain,
143             'domain' => $domain,
144             'machine' => $machine,
145             'lm_hpw' => $lm_hpw,
146             'nt_hpw' => $nt_hpw
147             }, $package;
148             }
149            
150             ###########################################################################
151             # new_server instantiate a NTLM server that composes an NTLM challenge #
152             # It can take one argument for the server network domain. If the argument #
153             # is not supplied, it will call Win32::DomainName to obtain it. #
154             ###########################################################################
155             sub new_server {
156 1 50 33 1 0 445 usage("new_server Authen::NTLM or\nnew_server Authen::NTLM(\$domain\)") unless @_ == 1 or @_ == 2;
157 1         3 my ($package, $domain) = @_;
158 1 50       4 if (not defined($domain)) {$domain = Win32::DomainName();}
  0         0  
159 1 50       3 defined($domain) or usage "Undefined Network Domain!\n";
160 1         6 bless {
161             'domain' => $domain,
162             'cChallenge' => 0 # a counter to stir the seed to generate random
163             }, $package; # number for the nonce
164             }
165            
166             ##########################################################################
167             # lm_hash calculates the LM hash to be used to calculate the LM response #
168             # It takes a password and return the 21 bytes LM password hash. #
169             ##########################################################################
170             sub lm_hash($)
171             {
172 2     2 0 2170 my ($passwd) = @_;
173 2         6 my $cipher1;
174             my $cipher2;
175 2         6 my $magic = pack("H16", "4B47532140232425"); # magical string to be encrypted for the LM password hash
176 2         12 while (length($passwd) < 14) {
177 8         20 $passwd .= chr(0);
178             }
179 2         6 my $lm_pw = substr($passwd, 0, 14);
180 2         7 $lm_pw = uc($lm_pw); # change the password to upper case
181 2         12 my $key = convert_key(substr($lm_pw, 0, 7)) . convert_key(substr($lm_pw, 7, 7));
182 2 50       11 if ($Authen::NTLM::HTTP::Base::PurePerl) {
183 0         0 $cipher1 = Crypt::DES_PP->new(substr($key, 0, 8));
184 0         0 $cipher2 = Crypt::DES_PP->new(substr($key, 8, 8));
185             }
186             else {
187 2         23 $cipher1 = Crypt::DES->new(substr($key, 0, 8));
188 2         71 $cipher2 = Crypt::DES->new(substr($key, 8, 8));
189             }
190 2         32 return $cipher1->encrypt($magic) . $cipher2->encrypt($magic) . pack("H10", "0000000000");
191             }
192            
193             ##########################################################################
194             # nt_hash calculates the NT hash to be used to calculate the NT response #
195             # It takes a password and return the 21 bytes NT password hash. #
196             ##########################################################################
197             sub nt_hash($)
198             {
199 2     2 0 58 my ($passwd) = @_;
200 2         8 my $nt_pw = unicodify($passwd);
201 2         9 my $nt_hpw;
202 2 50       8 if ($Authen::NTLM::HTTP::Base::PurePerl == 1) {
203 0         0 $nt_hpw = md4($nt_pw) . pack("H10", "0000000000");
204             }
205             else {
206 2         28 my $md4 = new Digest::MD4;
207 2         14 $md4->add($nt_pw);
208 2         21 $nt_hpw = $md4->digest() . pack("H10", "0000000000");
209             }
210 2         22 return $nt_hpw;
211             }
212            
213             ####################################################################
214             # negotiate_msg creates the NTLM negotiate packet given the domain #
215             # (from Win32::DomainName()) and the workstation name (from #
216             # $ENV{'COMPUTERNAME'} or Win32::NodeName()) and the negotiation #
217             # flags. #
218             ####################################################################
219             sub negotiate_msg($$)
220             {
221 2     2 0 24 my $self = $_[0];
222 2         5 my $flags = pack("V", $_[1]);
223 2         17 my $domain = $self->{'domain'};
224 2         4 my $machine = $self->{'machine'};
225 2         3 my $msg = NTLMSSP_SIGNATURE . chr(0);
226 2         5 $msg .= pack("V", NTLMSSP_NEGOTIATE);
227 2         3 $msg .= $flags;
228 2         4 my $offset = length($msg) + 8*2;
229 2         9 $msg .= pack("v", length($domain)) . pack("v", length($domain)) . pack("V", $offset + length($machine));
230 2         7 $msg .= pack("v", length($machine)) . pack("v", length($machine)) . pack("V", $offset);
231 2         9 $msg .= $machine . $domain;
232 2         15 return $msg;
233             }
234            
235             ###########################################################################
236             # parse_negotiate parses the NTLM negotiate and return a list of NTLM #
237             # Negotiation Flags, Server Network Domain and Machine name of the client.#
238             ###########################################################################
239             sub parse_negotiate($$)
240             {
241 2     2 0 9 my ($self, $pkt) = @_;
242 2 50       13 substr($pkt, 0, 8) eq (NTLMSSP_SIGNATURE . chr(0)) or usage "NTLM Negotiate doesn't contain NTLMSSP_SIGNATURE!\n";
243 2         15 my $type = GetInt32(substr($pkt, 8));
244 2 50       8 $type == NTLMSSP_NEGOTIATE or usage "Not an NTLM Negotiate Message!\n";
245 2         5 my $flags = GetInt32(substr($pkt, 12));
246 2         7 my $domain = GetString($pkt, 16);
247 2         5 my $machine = GetString($pkt, 24);
248 2         10 return ($flags, $domain, $machine);
249             }
250            
251             ####################################################################
252             # challenge_msg composes the NTLM challenge message. It takes NTLM #
253             # Negotiation Flags as an argument. #
254             ####################################################################
255             sub challenge_msg($$)
256             {
257 2     2 0 1115 my $self = $_[0];
258 2         7 my $flags = pack("V", $_[1]);
259 2         4 my $nonce = undef;
260 2 100       8 $nonce = $_[2] if @_ == 3;
261 2         5 my $domain = $self->{'domain'};
262 2         5 my $msg = NTLMSSP_SIGNATURE . chr(0);
263 2         4 $self->{'cChallenge'} += 0x100;
264 2         3 $msg .= pack("V", NTLMSSP_CHALLENGE);
265 2 100       10 if ($_[1] & NTLMSSP_TARGET_TYPE_DOMAIN) {
266 1 50       12 if ($_[1] & NTLMSSP_NEGOTIATE_UNICODE) {
267 1         4 $msg .= pack("v", 2*length($domain)) . pack("v", 2*length($domain)) . pack("V", 48);
268             }
269             else {
270 0         0 $msg .= pack("v", length($domain)) . pack("v", length($domain)) . pack("V", 48);
271             }
272             }
273             else {
274 1         3 $msg .= pack("v", 0) . pack("v", 0) . pack("V", 40);
275             }
276 2         5 $msg .= $flags;
277 2 100       15 if (defined $nonce) {$msg .= $nonce;}
  1         3  
  1         3  
278             else {$msg .= compute_nonce($self->{'cChallenge'});}
279 2         5 $msg .= pack("VV", 0, 0); # 8 bytes of reserved 0s
280 2 100       8 if ($_[1] & NTLMSSP_TARGET_TYPE_DOMAIN) {
281 1         2 $msg .= pack("V", 0); # ServerContextHandleLower
282 1         1 $msg .= pack("V", 0x3c); # ServerContextHandleUpper
283 1 50       2 if ($_[1] & NTLMSSP_NEGOTIATE_UNICODE) {
284 1         3 $msg .= unicodify($domain);
285             }
286             else {
287 0         0 $msg .= $domain;
288             }
289             }
290 2         23 return $msg;
291             }
292            
293             ###########################################################################
294             # parse_challenge parses the NTLM challenge and return a list of server #
295             # network domain, NTLM Negotiation Flags, Nonce, ServerContextHandleUpper #
296             # and ServerContextHandleLower. #
297             ###########################################################################
298             sub parse_challenge
299             {
300 2     2 0 334 my ($self, $pkt) = @_;
301 2 50       14 substr($pkt, 0, 8) eq (NTLMSSP_SIGNATURE . chr(0)) or usage "NTLM Challenge doesn't contain NTLMSSP_SIGNATURE!\n";
302 2         11 my $type = GetInt32(substr($pkt, 8));
303 2 50       10 $type == NTLMSSP_CHALLENGE or usage "Not an NTLM Challenge Message!\n";
304 2         6 my $flags = GetInt32(substr($pkt, 20));
305 2         30 my $target = undef;
306 2         5 my $ctx_lower = undef;
307 2         4 my $ctx_upper = undef;
308 2 100       8 if ($flags & NTLMSSP_TARGET_TYPE_DOMAIN) {
309 1         3 $target = GetString($pkt, 12);
310 1 50       8 $target = un_unicodify($target) if $flags & NTLMSSP_NEGOTIATE_UNICODE;
311 1         5 $ctx_lower = GetInt32(substr($pkt, 40));
312 1         5 $ctx_upper = GetInt32(substr($pkt, 44));
313             }
314 2         83 my $nonce = substr($pkt, 24, 8);
315 2         24 return ($target, $flags, $nonce, $ctx_lower, $ctx_upper);
316             }
317            
318             ############################################################################
319             # GetString is called internally to get a UNICODE string in a NTLM message #
320             ############################################################################
321             sub GetString
322             {
323 17     17 0 23 my ($str, $loc) = @_;
324 17         44 my $len = GetInt16(substr($str, $loc));
325 17         46 my $max_len = GetInt16(substr($str, $loc+2));
326 17         42 my $offset = GetInt32(substr($str, $loc+4));
327 17         49 return substr($str, $offset, $max_len);
328             }
329            
330             ############################################################################
331             # GetInt32 is called internally to get a 32-bit integer in an NTLM message #
332             ############################################################################
333             sub GetInt32
334             {
335 31     31 0 56 my ($str) = @_;
336 31         78 return unpack("V", substr($str, 0, 4));
337             }
338            
339             ############################################################################
340             # GetInt16 is called internally to get a 16-bit integer in an NTLM message #
341             ############################################################################
342             sub GetInt16
343             {
344 34     34 0 52 my ($str) = @_;
345 34         70 return unpack("v", substr($str, 0, 2));
346             }
347            
348             ###########################################################################
349             # auth_msg creates the NTLM response to an NTLM challenge from the #
350             # server. It takes 2 arguments: $nonce obtained from parse_challenge and #
351             # NTLM Negotiation Flags. #
352             # This function ASSUMEs the input of user domain, user name and #
353             # workstation name are in ASCII format and not in UNICODE format. #
354             ###########################################################################
355             sub auth_msg($$$)
356             {
357 2     2 0 436 my ($self, $nonce) = @_;
358 2         8 my $session_key = session_key();
359 2         6 my $user_domain = $self->{'user_domain'};
360 2         4 my $username = $self->{'user'};
361 2         5 my $machine = $self->{'machine'};
362 2         14 my $lm_resp = calc_resp($self->{'lm_hpw'}, $nonce);
363 2         37 my $nt_resp = calc_resp($self->{'nt_hpw'}, $nonce);
364 2         42 my $flags = pack("V", $_[2]);
365 2         14 my $msg = NTLMSSP_SIGNATURE . chr(0);
366 2         6 $msg .= pack("V", NTLMSSP_AUTH);
367 2         4 my $offset = length($msg) + 8*6 + 4;
368 2 50       11 if ($_[2] & NTLMSSP_NEGOTIATE_UNICODE) {
369 2         13 $msg .= pack("v", length($lm_resp)) . pack("v", length($lm_resp)) . pack("V", $offset + 2*length($user_domain) + 2*length($username) + 2*length($machine) + length($session_key));
370 2         12 $msg .= pack("v", length($nt_resp)) . pack("v", length($nt_resp)) . pack("V", $offset + 2*length($user_domain) + 2*length($username) + 2*length($machine) + length($session_key) + length($lm_resp));
371 2         17 $msg .= pack("v", 2*length($user_domain)) . pack("v", 2*length($user_domain)) . pack("V", $offset);
372 2         10 $msg .= pack("v", 2*length($username)) . pack("v", 2*length($username)) . pack("V", $offset + 2*length($user_domain));
373 2         14 $msg .= pack("v", 2*length($machine)) . pack("v", 2*length($machine)) . pack("V", $offset + 2*length($user_domain) + 2*length($username));
374 2         10 $msg .= pack("v", length($session_key)) . pack("v", length($session_key)) . pack("V", $offset + 2*length($user_domain) + 2*length($username) + 2*length($machine)+ 48);
375 2         7 $msg .= $flags . unicodify($user_domain) . unicodify($username) . unicodify($machine) . $lm_resp . $nt_resp . $session_key;
376             }
377             else {
378 0         0 $msg .= pack("v", length($lm_resp)) . pack("v", length($lm_resp)) . pack("V", $offset + length($user_domain) + length($username) + length($machine) + length($session_key));
379 0         0 $msg .= pack("v", length($nt_resp)) . pack("v", length($nt_resp)) . pack("V", $offset + length($user_domain) + length($username) + length($machine) + length($session_key) + length($lm_resp));
380 0         0 $msg .= pack("v", length($user_domain)) . pack("v", length($user_domain)) . pack("V", $offset);
381 0         0 $msg .= pack("v", length($username)) . pack("v", length($username)) . pack("V", $offset + length($user_domain));
382 0         0 $msg .= pack("v", length($machine)) . pack("v", length($machine)) . pack("V", $offset + length($user_domain) + length($username));
383 0         0 $msg .= pack("v", length($session_key)) . pack("v", length($session_key)) . pack("V", $offset + length($user_domain) + length($username) + length($machine)+ 48);
384 0         0 $msg .= $flags . $user_domain . $username . $machine . $lm_resp . $nt_resp . $session_key;
385             }
386 2         13 return $msg;
387             }
388            
389             ###########################################################################
390             # parse_auth parses the NTLM authentication and return a list of NTLM #
391             # Negotiation Flags, LM response, NT response, User Domain, User Name, #
392             # User Machine Name and Session Key. #
393             ###########################################################################
394             sub parse_auth($$)
395             {
396 2     2 0 211 my ($self, $pkt) = @_;
397 2 50       17 substr($pkt, 0, 8) eq (NTLMSSP_SIGNATURE . chr(0)) or usage "NTLM Authentication doesn't contain NTLMSSP_SIGNATURE!\n";
398 2         13 my $type = GetInt32(substr($pkt, 8));
399 2 50       10 $type == NTLMSSP_AUTH or usage "Not an NTLM Authetication Message!\n";
400 2         6 my $lm_resp = GetString($pkt, 12);
401 2         5 my $nt_resp = GetString($pkt, 20);
402 2         7 my $flags = GetInt32(substr($pkt, 60));
403 2         9 my $user_domain = GetString($pkt, 28);
404 2 50       27 $user_domain = un_unicodify($user_domain) if $flags & NTLMSSP_NEGOTIATE_UNICODE;
405 2         7 my $username = GetString($pkt, 36);
406 2 50       16 $username = un_unicodify($username) if $flags & NTLMSSP_NEGOTIATE_UNICODE;
407 2         7 my $machine = GetString($pkt, 44);
408 2 50       11 $machine = un_unicodify($machine) if $flags & NTLMSSP_NEGOTIATE_UNICODE;
409 2         4 my $session_key = GetString($pkt, 52);
410 2         15 return ($flags, $lm_resp, $nt_resp, $user_domain, $username, $machine, $session_key);
411             }
412            
413             #####################################################################
414             # session_key computes a session key for an NTLM session. Currently #
415             # it is not implemented. #
416             #####################################################################
417             sub session_key
418             {
419 2     2 0 5 return "";
420             }
421            
422             #######################################################################
423             # compute_nonce computes the 8-bytes nonce to be included in server's
424             # NTLM challenge packet.
425             #######################################################################
426             sub compute_nonce($)
427             {
428 1     1 0 2 my ($cChallenge) = @_;
429 1         5 my @SysTime = UNIXTimeToFILETIME($cChallenge, time);
430 1         4 my $Seed = (($SysTime[1] + 1) << 0) |
431             (($SysTime[2] + 0) << 8) |
432             (($SysTime[3] - 1) << 16) |
433             (($SysTime[4] + 0) << 24);
434 1         3 srand $Seed;
435 1         2 my $ulChallenge0 = rand(2**16)+rand(2**32);
436 1         2 my $ulChallenge1 = rand(2**16)+rand(2**32);
437 1         2 my $ulNegate = rand(2**16)+rand(2**32);
438 1 50       3 if ($ulNegate & 0x1) {$ulChallenge0 |= 0x80000000;}
  1         2  
439 1 50       3 if ($ulNegate & 0x2) {$ulChallenge1 |= 0x80000000;}
  1         1  
440 1         4 return pack("V", $ulChallenge0) . pack("V", $ulChallenge1);
441             }
442            
443             #########################################################################
444             # convert_key converts a 7-bytes key to an 8-bytes key based on an
445             # algorithm.
446             #########################################################################
447             sub convert_key($) {
448 16     16 0 42 my ($in_key) = @_;
449 16         12 my @byte;
450 16         19 my $result = "";
451 16 50       37 usage("exactly 7-bytes key") unless length($in_key) == 7;
452 16         26 $byte[0] = substr($in_key, 0, 1);
453 16         39 $byte[1] = chr(((ord(substr($in_key, 0, 1)) << 7) & 0xFF) | (ord(substr($in_key, 1, 1)) >> 1));
454 16         27 $byte[2] = chr(((ord(substr($in_key, 1, 1)) << 6) & 0xFF) | (ord(substr($in_key, 2, 1)) >> 2));
455 16         31 $byte[3] = chr(((ord(substr($in_key, 2, 1)) << 5) & 0xFF) | (ord(substr($in_key, 3, 1)) >> 3));
456 16         31 $byte[4] = chr(((ord(substr($in_key, 3, 1)) << 4) & 0xFF) | (ord(substr($in_key, 4, 1)) >> 4));
457 16         27 $byte[5] = chr(((ord(substr($in_key, 4, 1)) << 3) & 0xFF) | (ord(substr($in_key, 5, 1)) >> 5));
458 16         26 $byte[6] = chr(((ord(substr($in_key, 5, 1)) << 2) & 0xFF) | (ord(substr($in_key, 6, 1)) >> 6));
459 16         33 $byte[7] = chr((ord(substr($in_key, 6, 1)) << 1) & 0xFF);
460 16         118 for (my $i = 0; $i < 8; ++$i) {
461 128         213 $byte[$i] = set_odd_parity($byte[$i]);
462 128         264 $result .= $byte[$i];
463             }
464 16         90 return $result;
465             }
466            
467             ##########################################################################
468             # set_odd_parity turns one-byte into odd parity. Odd parity means that
469             # a number in binary has odd number of 1's.
470             ##########################################################################
471             sub set_odd_parity($)
472             {
473 128     128 0 130 my ($byte) = @_;
474 128         119 my $parity = 0;
475 128         105 my $ordbyte;
476 128 50       201 usage("single byte input only") unless length($byte) == 1;
477 128         113 $ordbyte = ord($byte);
478 128         237 for (my $i = 0; $i < 8; ++$i) {
479 1024 100       1414 if ($ordbyte & 0x01) {++$parity;}
  342         295  
480 1024         1613 $ordbyte >>= 1;
481             }
482 128         110 $ordbyte = ord($byte);
483 128 100       203 if ($parity % 2 == 0) {
484 70 100       127 if ($ordbyte & 0x01) {
485 14         17 $ordbyte &= 0xFE;
486             }
487             else {
488 56         57 $ordbyte |= 0x01;
489             }
490             }
491 128         237 return chr($ordbyte);
492             }
493            
494             ###########################################################################
495             # calc_resp computes the 24-bytes NTLM response based on the password hash
496             # and the nonce.
497             ###########################################################################
498             sub calc_resp($$)
499             {
500 4     4 0 7 my ($key, $nonce) = @_;
501 4         11 my $cipher1;
502             my $cipher2;
503 0         0 my $cipher3;
504 4 50       10 usage("key must be 21-bytes long") unless length($key) == 21;
505 4 50       8 usage("nonce must be 8-bytes long") unless length($nonce) == 8;
506 4 50       10 if ($Authen::NTLM::HTTP::Base::PurePerl) {
507 0         0 $cipher1 = Crypt::DES_PP->new(convert_key(substr($key, 0, 7)));
508 0         0 $cipher2 = Crypt::DES_PP->new(convert_key(substr($key, 7, 7)));
509 0         0 $cipher3 = Crypt::DES_PP->new(convert_key(substr($key, 14, 7)));
510             }
511             else {
512 4         12 $cipher1 = Crypt::DES->new(convert_key(substr($key, 0, 7)));
513 4         45 $cipher2 = Crypt::DES->new(convert_key(substr($key, 7, 7)));
514 4         43 $cipher3 = Crypt::DES->new(convert_key(substr($key, 14, 7)));
515             }
516 4         49 return $cipher1->encrypt($nonce) . $cipher2->encrypt($nonce) . $cipher3->encrypt($nonce);
517             }
518            
519             #########################################################################
520             # un_unicodify takes a unicode string and turns it into an ASCII string.
521             # CAUTION: This function is intended to be used with unicodified ASCII
522             # strings.
523             #########################################################################
524             sub un_unicodify
525             {
526 7     7 0 11 my ($str) = @_;
527 7         10 my $newstr = "";
528 7         7 my $i;
529            
530 7 50       20 usage("$str must be a string of even length to be un_unicodify!: $!\n") if length($str) % 2;
531            
532 7         35 for ($i = 0; $i < length($str) / 2; ++$i) {
533 41         105 $newstr .= substr($str, 2*$i, 1);
534             }
535 7         16 return $newstr;
536             }
537            
538             #########################################################################
539             # unicodify takes an ASCII string and turns it into a unicode string.
540             #########################################################################
541             sub unicodify($)
542             {
543 9     9 0 15 my ($str) = @_;
544 9         16 my $newstr = "";
545 9         9 my $i;
546            
547 9         40 for ($i = 0; $i < length($str); ++$i) {
548 61         144 $newstr .= substr($str, $i, 1) . chr(0);
549             }
550 9         40 return $newstr;
551             }
552            
553             ##########################################################################
554             # UNIXTimeToFILETIME converts UNIX time_t to 64-bit FILETIME format used
555             # in win32 platforms. It returns two 32-bit integer. The first one is
556             # the upper 32-bit and the second one is the lower 32-bit. The result is
557             # adjusted by cChallenge as in NTLM spec. For those of you who want to
558             # use this function for actual use, please remove the cChallenge variable.
559             ##########################################################################
560             sub UNIXTimeToFILETIME($$)
561             {
562 1     1 0 1 my ($cChallenge, $time) = @_;
563 1         2 $time = $time * 10000000 + 11644473600000000 + $cChallenge;
564 1         2 my $uppertime = $time / (2**32);
565 1         13 my $lowertime = $time - floor($uppertime) * 2**32;
566 1         10 return ($lowertime & 0x000000ff,
567             $lowertime & 0x0000ff00,
568             $lowertime & 0x00ff0000,
569             $lowertime & 0xff000000,
570             $uppertime & 0x000000ff,
571             $uppertime & 0x0000ff00,
572             $uppertime & 0x00ff0000,
573             $uppertime & 0xff000000);
574             }
575            
576             1;
577            
578             __END__