3 # (C) 2000 Francesco Chemolli <kinkie@kame.usr.dsi.unimi.it>
4 # Distributed freely under the terms of the GNU General Public License,
5 # version 2. For the licensing terms, see the file COPYING that
8 # This is a dummy NTLM authentication module for Squid.
9 # It performs the NTLM challenge, but then it doesn't verify the
10 # user's credentials, it just takes the client's domain and username
12 # It's included mostly for demonstration purposes.
14 # TODO: use command-line arguments
19 #$authdomain="your_domain_goes_here";
20 $challenge="deadbeef";
22 $authdomain=$ARGV[0] if ($#ARGV >=0);
24 die ("Edit $0 to configure a domain!") unless (defined($authdomain));
28 if (substr($_, 0, 2) eq "YR") {
29 print "TT ".encode_base64
(&make_ntlm_static_challenge
);
33 %res=decode_ntlm_any
(decode_base64
($got));
34 # print STDERR "got: ".hash_to_string(%res);
35 if (!res
) { # broken NTLM, deny
36 print "BH Couldn't decode NTLM packet\n";
39 if ($res{type
} eq "negotiate") { # ok, send a challenge
40 print "BH Squid-helper protocol error: unexpected negotiate-request\n";
43 if ($res{type
} eq "challenge") { # Huh? WE are the challengers.
44 print "BH Squid-helper protocol error: unexpected challenge-request\n";
47 if ($res{type
} eq "authentication") {
48 print "AF $res{domain}\\$res{user}\n";
51 print "BH internal error\n"; # internal error
55 sub make_ntlm_static_challenge
{
56 $rv = pack ("a8 V", "NTLMSSP", 0x2);
59 $rv .= add_to_data
(uc($authdomain),\
$payload);
60 $rv .= pack ("V Z8 v8", 0x18206, $challenge,0,0,0,0,0,0,0x3a,0);
61 #flags, challenge, 8 bytes of unknown stuff
66 #gets as argument the decoded authenticate packet.
67 #returns either undef (failure to decode) or an hash with the decoded
69 sub decode_ntlm_authentication
{
71 my ($signature, $type, %rv, $hdr, $rest);
72 ($signature, $type, $rest) = unpack ("a8 V a*",$got);
73 return unless ($signature eq "NTLMSSP\0");
74 return unless ($type == 0x3);
75 $rv{type
}="authentication";
76 ($hdr, $rest) = unpack ("a8 a*", $rest);
77 $rv{lmresponse
}=get_from_data
($hdr,$got);
78 ($hdr, $rest) = unpack ("a8 a*", $rest);
79 $rv{ntresponse
}=get_from_data
($hdr,$got);
80 ($hdr, $rest) = unpack ("a8 a*", $rest);
81 $rv{domain
}=get_from_data
($hdr,$got);
82 ($hdr, $rest) = unpack ("a8 a*", $rest);
83 $rv{user
}=get_from_data
($hdr,$got);
84 ($hdr, $rest) = unpack ("a8 a*", $rest);
85 $rv{workstation
}=get_from_data
($hdr,$got);
86 ($hdr, $rest) = unpack ("a8 a*", $rest);
87 $rv{sessionkey
}=get_from_data
($hdr,$got);
88 $rv{flags
}=unpack("V",$rest);
92 #args: len, maxlen, offset
94 return pack ("v v V", @_);
97 #args: string to add, ref to payload
98 # returns ntlm header.
100 my ($toadd, $pl) = @_;
102 # $toadd.='\0' unless ($toadd[-1]=='\0'); #broken
103 $offset=48+length $pl; #48 is the length of the header
105 return make_ntlm_hdr
(length $toadd, length $toadd, $offset);
108 #args: encoded descriptor, entire decoded packet
109 # returns the decoded data
111 my($desc,$packet) = @_;
112 my($offset,$length, $rv);
113 ($length, undef, $offset) = unpack ("v v V", $desc);
114 return unless ($length+$offset <= length $packet);
115 $rv = unpack ("x$offset a$length",$packet);
122 foreach (sort keys %hash) {
123 $rv.=$_." => ".$hash{$_}."\n";
129 #more decoder functions, added more for debugging purposes
130 #than for any real use in the application.
131 #args: the base64-decoded packet
132 #returns: either undef or an hash describing the packet.
133 sub decode_ntlm_negotiate
{
135 my($signature, $type, %rv, $hdr, $rest);
136 ($signature, $type, $rest) = unpack ("a8 V a*",$got);
137 return unless ($signature eq "NTLMSSP\0");
138 return unless ($type == 0x1);
139 $rv{type
}="negotiate";
140 ($rv{flags
}, $rest)=unpack("V a*",$rest);
141 ($hdr, $rest) = unpack ("a8 a*", $rest);
142 $rv{domain
}=get_from_data
($hdr,$got);
143 ($hdr, $rest) = unpack ("a8 a*", $rest);
144 $rv{workstation
}=get_from_data
($hdr,$got);
148 sub decode_ntlm_challenge
{
150 my($signature, $type, %rv, $hdr, $rest, $j);
151 ($signature, $type, $rest) = unpack ("a8 V a*",$got);
152 return unless ($signature eq "NTLMSSP\0");
153 return unless ($type == 0x2);
154 $rv{type
}="challenge";
155 ($rv{flags
}, $rest)=unpack("V a*",$rest);
156 ($rv{challenge
}, $rest)=unpack("a8 a*",$rest);
157 for ($j=0;$j<8;$j++) { # don't shoot on the programmer, please.
158 ($rv{"context.$j"},$rest)=unpack("v a*",$rest);
163 #decodes any NTLMSSP packet.
164 #arg: the encoded packet, returns an hash with packet info
165 sub decode_ntlm_any
{
167 my ($signature, $type);
168 ($signature, $type, undef) = unpack ("a8 V a*",$got);
169 return unless ($signature eq "NTLMSSP\0");
170 return decode_ntlm_negotiate
($got) if ($type == 1);
171 return decode_ntlm_challenge
($got) if ($type == 2);
172 return decode_ntlm_authentication
($got) if ($type == 3);
173 return undef; # default
179 sub encode_base64
($;$)
183 $eol = "\n" unless defined $eol;
184 pos($_[0]) = 0; # ensure start at the beginning
185 while ($_[0] =~ /(.{1,45})/gs) {
186 $res .= substr(pack('u', $1), 1);
189 $res =~ tr
|` -_|AA-Za-z0-9+/|; # `# help emacs
190 # fix padding at the end
191 my $padding = (3 - length($_[0]) % 3) % 3;
192 $res =~ s/.{$padding}$/'=' x $padding/e if $padding;
193 # break encoded string into lines of no more than 76 characters each
195 $res =~ s/(.{1,76})/$1$eol/g;
201 sub decode_base64
($)
203 local($^W
) = 0; # unpack("u",...) gives bogus warning in 5.00[123]
208 $str =~ tr
|A
-Za
-z0
-9+=/||cd
; # remove non-base64 chars
209 if (length($str) % 4) {
211 Carp
::carp
("Length of base64 data not a multiple of 4")
213 $str =~ s/=+$//; # remove padding
214 $str =~ tr
|A
-Za
-z0
-9+/| -_
|; # convert to uuencoded format
215 while ($str =~ /(.{1,60})/gs) {
216 my $len = chr(32 + length($1)*3/4); # compute length byte
217 $res .= unpack("u", $len . $1 ); # uudecode