pyu-ntlm-http 0.1.1.1 → 0.1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +5 -3
- data/lib/net/ntlm.rb +2 -1
- data/lib/net/ntlm_http.rb +67 -833
- data/ntlm-http.gemspec +1 -1
- metadata +32 -29
data/Rakefile
CHANGED
@@ -7,8 +7,10 @@ require 'rake/packagetask'
|
|
7
7
|
require 'rake/gempackagetask'
|
8
8
|
require File.join(File.dirname(__FILE__), 'lib', 'net', 'ntlm')
|
9
9
|
|
10
|
-
PKG_NAME = 'rubyntlm'
|
11
|
-
|
10
|
+
#PKG_NAME = 'rubyntlm'
|
11
|
+
PKG_NAME = 'ntlm-http'
|
12
|
+
# add a .1 to the end of the version, to distinguish my branch
|
13
|
+
PKG_VERSION = "#{Net::NTLM::VERSION::STRING}.2"
|
12
14
|
|
13
15
|
task :default => [:test]
|
14
16
|
|
@@ -63,4 +65,4 @@ Rake::GemPackageTask.new(spec) do |p|
|
|
63
65
|
end
|
64
66
|
|
65
67
|
|
66
|
-
|
68
|
+
|
data/lib/net/ntlm.rb
CHANGED
@@ -100,7 +100,8 @@ module Net #:nodoc:
|
|
100
100
|
end
|
101
101
|
|
102
102
|
def encode_utf16le(str)
|
103
|
-
|
103
|
+
# Kconv on JRUBY outputs a BOM... so strip that
|
104
|
+
swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII).gsub(/^\376\377/,''))
|
104
105
|
end
|
105
106
|
|
106
107
|
def pack_int64le(val)
|
data/lib/net/ntlm_http.rb
CHANGED
@@ -1,853 +1,87 @@
|
|
1
1
|
#
|
2
|
-
# = net/
|
2
|
+
# = net/ntlm_http.rb
|
3
3
|
#
|
4
|
-
#
|
4
|
+
# extra stuff to make nltm auth usage as easy as basic for Net::HTTP
|
5
|
+
# classes
|
5
6
|
#
|
6
|
-
|
7
|
-
|
8
|
-
# http://jp.rubyist.net/magazine/?0013-CodeReview
|
9
|
-
# -------------------------------------------------------------
|
10
|
-
# Copyright (c) 2005,2006 yrock
|
11
|
-
#
|
12
|
-
# This program is free software.
|
13
|
-
# You can distribute/modify this program under the terms of the
|
14
|
-
# Ruby License.
|
15
|
-
#
|
16
|
-
# 2006-02-11 refactored by Minero Aoki
|
17
|
-
# -------------------------------------------------------------
|
18
|
-
#
|
19
|
-
# All protocol information used to write this code stems from
|
20
|
-
# "The NTLM Authentication Protocol" by Eric Glass. The author
|
21
|
-
# would thank to him for this tremendous work and making it
|
22
|
-
# available on the net.
|
23
|
-
# http://davenport.sourceforge.net/ntlm.html
|
24
|
-
# -------------------------------------------------------------
|
25
|
-
# Copyright (c) 2003 Eric Glass
|
26
|
-
#
|
27
|
-
# Permission to use, copy, modify, and distribute this document
|
28
|
-
# for any purpose and without any fee is hereby granted,
|
29
|
-
# provided that the above copyright notice and this list of
|
30
|
-
# conditions appear in all copies.
|
31
|
-
# -------------------------------------------------------------
|
32
|
-
#
|
33
|
-
# The author also looked Mozilla-Firefox-1.0.7 source code,
|
34
|
-
# namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
|
35
|
-
# Jonathan Bastien-Filiatrault's libntlm-ruby.
|
36
|
-
# "http://x2a.org/websvn/filedetails.php?
|
37
|
-
# repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
|
38
|
-
# The latter has a minor bug in its separate_keys function.
|
39
|
-
# The third key has to begin from the 14th character of the
|
40
|
-
# input string instead of 13th:)
|
41
|
-
#--
|
42
|
-
# $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
|
43
|
-
#++
|
44
|
-
|
45
|
-
require 'base64'
|
46
|
-
require 'openssl'
|
47
|
-
require 'openssl/digest'
|
7
|
+
require 'net/ntlm'
|
8
|
+
require 'net/http'
|
48
9
|
|
49
10
|
module Net #:nodoc:
|
50
|
-
module NTLM
|
51
|
-
|
52
|
-
module VERSION #:nodoc:
|
53
|
-
MAJOR = 0
|
54
|
-
MINOR = 1
|
55
|
-
TINY = 1
|
56
|
-
STRING = [MAJOR, MINOR, TINY].join('.')
|
57
|
-
end
|
58
|
-
|
59
|
-
SSP_SIGN = "NTLMSSP\0"
|
60
|
-
BLOB_SIGN = 0x00000101
|
61
|
-
LM_MAGIC = "KGS!@\#$%"
|
62
|
-
TIME_OFFSET = 11644473600
|
63
|
-
MAX64 = 0xffffffffffffffff
|
64
|
-
|
65
|
-
FLAGS = {
|
66
|
-
:UNICODE => 0x00000001,
|
67
|
-
:OEM => 0x00000002,
|
68
|
-
:REQUEST_TARGET => 0x00000004,
|
69
|
-
# :UNKNOWN => 0x00000008,
|
70
|
-
:SIGN => 0x00000010,
|
71
|
-
:SEAL => 0x00000020,
|
72
|
-
# :UNKNOWN => 0x00000040,
|
73
|
-
:NETWARE => 0x00000100,
|
74
|
-
:NTLM => 0x00000200,
|
75
|
-
# :UNKNOWN => 0x00000400,
|
76
|
-
# :UNKNOWN => 0x00000800,
|
77
|
-
:DOMAIN_SUPPLIED => 0x00001000,
|
78
|
-
:WORKSTATION_SUPPLIED => 0x00002000,
|
79
|
-
:LOCAL_CALL => 0x00004000,
|
80
|
-
:ALWAYS_SIGN => 0x00008000,
|
81
|
-
:TARGET_TYPE_DOMAIN => 0x00010000,
|
82
|
-
:TARGET_INFO => 0x00800000,
|
83
|
-
:NTLM2_KEY => 0x00080000,
|
84
|
-
:KEY128 => 0x20000000,
|
85
|
-
:KEY56 => 0x80000000
|
86
|
-
}
|
87
|
-
|
88
|
-
FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
|
89
|
-
|
90
|
-
DEFAULT_FLAGS = {
|
91
|
-
:TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
|
92
|
-
:TYPE2 => FLAGS[:UNICODE],
|
93
|
-
:TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
|
94
|
-
}
|
95
|
-
|
96
|
-
# module functions
|
97
|
-
class << self
|
98
|
-
def decode_utf16le(str)
|
99
|
-
Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
|
100
|
-
end
|
101
|
-
|
102
|
-
def encode_utf16le(str)
|
103
|
-
swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
|
104
|
-
end
|
105
|
-
|
106
|
-
def pack_int64le(val)
|
107
|
-
[val & 0x00000000ffffffff, val >> 32].pack("V2")
|
108
|
-
end
|
109
|
-
|
110
|
-
def swap16(str)
|
111
|
-
str.unpack("v*").pack("n*")
|
112
|
-
end
|
113
|
-
|
114
|
-
def split7(str)
|
115
|
-
s = str.dup
|
116
|
-
until s.empty?
|
117
|
-
(ret ||= []).push s.slice!(0, 7)
|
118
|
-
end
|
119
|
-
ret
|
120
|
-
end
|
121
|
-
|
122
|
-
def gen_keys(str)
|
123
|
-
split7(str).map{ |str7|
|
124
|
-
bits = split7(str7.unpack("B*")[0]).inject('')\
|
125
|
-
{|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
|
126
|
-
[bits].pack("B*")
|
127
|
-
}
|
128
|
-
end
|
129
|
-
|
130
|
-
def apply_des(plain, keys)
|
131
|
-
dec = OpenSSL::Cipher::DES.new
|
132
|
-
keys.map {|k|
|
133
|
-
dec.key = k
|
134
|
-
dec.encrypt.update(plain)
|
135
|
-
}
|
136
|
-
end
|
137
|
-
|
138
|
-
def lm_hash(password)
|
139
|
-
keys = gen_keys password.upcase.ljust(14, "\0")
|
140
|
-
apply_des(LM_MAGIC, keys).join
|
141
|
-
end
|
142
|
-
|
143
|
-
def ntlm_hash(password, opt = {})
|
144
|
-
pwd = password.dup
|
145
|
-
unless opt[:unicode]
|
146
|
-
pwd = encode_utf16le(pwd)
|
147
|
-
end
|
148
|
-
OpenSSL::Digest::MD4.digest pwd
|
149
|
-
end
|
150
|
-
|
151
|
-
def ntlmv2_hash(user, password, target, opt={})
|
152
|
-
ntlmhash = ntlm_hash(password, opt)
|
153
|
-
userdomain = (user + target).upcase
|
154
|
-
unless opt[:unicode]
|
155
|
-
userdomain = encode_utf16le(userdomain)
|
156
|
-
end
|
157
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
|
158
|
-
end
|
159
|
-
|
160
|
-
# responses
|
161
|
-
def lm_response(arg)
|
162
|
-
begin
|
163
|
-
hash = arg[:lm_hash]
|
164
|
-
chal = arg[:challenge]
|
165
|
-
rescue
|
166
|
-
raise ArgumentError
|
167
|
-
end
|
168
|
-
chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
|
169
|
-
keys = gen_keys hash.ljust(21, "\0")
|
170
|
-
apply_des(chal, keys).join
|
171
|
-
end
|
172
|
-
|
173
|
-
def ntlm_response(arg)
|
174
|
-
hash = arg[:ntlm_hash]
|
175
|
-
chal = arg[:challenge]
|
176
|
-
chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
|
177
|
-
keys = gen_keys hash.ljust(21, "\0")
|
178
|
-
apply_des(chal, keys).join
|
179
|
-
end
|
180
|
-
|
181
|
-
def ntlmv2_response(arg, opt = {})
|
182
|
-
begin
|
183
|
-
key = arg[:ntlmv2_hash]
|
184
|
-
chal = arg[:challenge]
|
185
|
-
ti = arg[:target_info]
|
186
|
-
rescue
|
187
|
-
raise ArgumentError
|
188
|
-
end
|
189
|
-
chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
|
190
|
-
|
191
|
-
if opt[:client_challenge]
|
192
|
-
cc = opt[:client_challenge]
|
193
|
-
else
|
194
|
-
cc = rand(MAX64)
|
195
|
-
end
|
196
|
-
cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
|
197
|
-
|
198
|
-
if opt[:timestamp]
|
199
|
-
ts = opt[:timestamp]
|
200
|
-
else
|
201
|
-
ts = Time.now.to_i
|
202
|
-
end
|
203
|
-
# epoch -> milsec from Jan 1, 1601
|
204
|
-
ts = 10000000 * (ts + TIME_OFFSET)
|
205
|
-
|
206
|
-
blob = Blob.new
|
207
|
-
blob.timestamp = ts
|
208
|
-
blob.challenge = cc
|
209
|
-
blob.target_info = ti
|
210
|
-
|
211
|
-
bb = blob.serialize
|
212
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
|
213
|
-
end
|
214
|
-
|
215
|
-
def lmv2_response(arg, opt = {})
|
216
|
-
key = arg[:ntlmv2_hash]
|
217
|
-
chal = arg[:challenge]
|
218
|
-
|
219
|
-
chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
|
220
|
-
|
221
|
-
if opt[:client_challenge]
|
222
|
-
cc = opt[:client_challenge]
|
223
|
-
else
|
224
|
-
cc = rand(MAX64)
|
225
|
-
end
|
226
|
-
cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
|
227
|
-
|
228
|
-
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
|
229
|
-
end
|
230
|
-
|
231
|
-
def ntlm2_session(arg, opt = {})
|
232
|
-
begin
|
233
|
-
passwd_hash = arg[:ntlm_hash]
|
234
|
-
chal = arg[:challenge]
|
235
|
-
rescue
|
236
|
-
raise ArgumentError
|
237
|
-
end
|
238
|
-
|
239
|
-
if opt[:client_challenge]
|
240
|
-
cc = opt[:client_challenge]
|
241
|
-
else
|
242
|
-
cc = rand(MAX64)
|
243
|
-
end
|
244
|
-
cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
|
245
|
-
|
246
|
-
keys = gen_keys passwd_hash.ljust(21, "\0")
|
247
|
-
session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
|
248
|
-
response = apply_des(session_hash, keys).join
|
249
|
-
[cc.ljust(24, "\0"), response]
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
253
|
-
|
254
|
-
# base classes for primitives
|
255
|
-
class Field
|
256
|
-
attr_accessor :active, :value
|
257
|
-
|
258
|
-
def initialize(opts)
|
259
|
-
@value = opts[:value]
|
260
|
-
@active = opts[:active].nil? ? true : opts[:active]
|
261
|
-
end
|
262
|
-
|
263
|
-
def size
|
264
|
-
@active ? @size : 0
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
class String < Field
|
269
|
-
def initialize(opts)
|
270
|
-
super(opts)
|
271
|
-
@size = opts[:size]
|
272
|
-
end
|
273
|
-
|
274
|
-
def parse(str, offset=0)
|
275
|
-
if @active and str.size >= offset + @size
|
276
|
-
@value = str[offset, @size]
|
277
|
-
@size
|
278
|
-
else
|
279
|
-
0
|
280
|
-
end
|
281
|
-
end
|
282
|
-
|
283
|
-
def serialize
|
284
|
-
if @active
|
285
|
-
@value
|
286
|
-
else
|
287
|
-
""
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def value=(val)
|
292
|
-
@value = val
|
293
|
-
@size = @value.nil? ? 0 : @value.size
|
294
|
-
@active = (@size > 0)
|
295
|
-
end
|
296
|
-
end
|
297
11
|
|
12
|
+
module HTTPHeader
|
13
|
+
# could also try an automatic authentication. sends as basic first, then
|
14
|
+
# resends if required, or whatever.
|
15
|
+
# seems kind of messy exposing this stuff here.
|
298
16
|
|
299
|
-
|
300
|
-
|
301
|
-
super(opt)
|
302
|
-
@size = 2
|
303
|
-
end
|
304
|
-
def parse(str, offset=0)
|
305
|
-
if @active and str.size >= offset + @size
|
306
|
-
@value = str[offset, @size].unpack("v")[0]
|
307
|
-
@size
|
308
|
-
else
|
309
|
-
0
|
310
|
-
end
|
311
|
-
end
|
312
|
-
|
313
|
-
def serialize
|
314
|
-
[@value].pack("v")
|
315
|
-
end
|
17
|
+
def auth_data
|
18
|
+
@auth_data
|
316
19
|
end
|
317
20
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
if @active and str.size >= offset + @size
|
326
|
-
@value = str.slice(offset, @size).unpack("V")[0]
|
327
|
-
@size
|
328
|
-
else
|
329
|
-
0
|
330
|
-
end
|
331
|
-
end
|
332
|
-
|
333
|
-
def serialize
|
334
|
-
[@value].pack("V") if @active
|
335
|
-
end
|
21
|
+
# can set wait - don't authenticate unless challenged. useful when reusing
|
22
|
+
# the connection (otherwise you handshake for each request). wait should
|
23
|
+
# probably become the default, allowing the type of authentication to be
|
24
|
+
# driven by a server challenge.
|
25
|
+
def ntlm_auth user, password, wait=false
|
26
|
+
@auth_data = [:ntlm, user, password]
|
27
|
+
self['Authorization'] = 'NTLM ' + Net::NTLM::Message::Type1.new.encode64 unless wait
|
336
28
|
end
|
29
|
+
end
|
337
30
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
@size = 8
|
342
|
-
end
|
343
|
-
|
344
|
-
def parse(str, offset=0)
|
345
|
-
if @active and str.size >= offset + @size
|
346
|
-
d, u = str.slice(offset, @size).unpack("V2")
|
347
|
-
@value = (u * 0x100000000 + d)
|
348
|
-
@size
|
349
|
-
else
|
350
|
-
0
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
|
-
def serialize
|
355
|
-
[@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
|
356
|
-
end
|
357
|
-
end
|
358
|
-
|
359
|
-
# base class of data structure
|
360
|
-
class FieldSet
|
361
|
-
class << FieldSet
|
362
|
-
def define(&block)
|
363
|
-
c = Class.new(self)
|
364
|
-
def c.inherited(subclass)
|
365
|
-
proto = @proto
|
366
|
-
subclass.instance_eval {
|
367
|
-
@proto = proto
|
368
|
-
}
|
369
|
-
end
|
370
|
-
c.module_eval(&block)
|
371
|
-
c
|
372
|
-
end
|
373
|
-
|
374
|
-
def string(name, opts)
|
375
|
-
add_field(name, String, opts)
|
376
|
-
end
|
377
|
-
|
378
|
-
def int16LE(name, opts)
|
379
|
-
add_field(name, Int16LE, opts)
|
380
|
-
end
|
381
|
-
|
382
|
-
def int32LE(name, opts)
|
383
|
-
add_field(name, Int32LE, opts)
|
384
|
-
end
|
385
|
-
|
386
|
-
def int64LE(name, opts)
|
387
|
-
add_field(name, Int64LE, opts)
|
388
|
-
end
|
389
|
-
|
390
|
-
def security_buffer(name, opts)
|
391
|
-
add_field(name, SecurityBuffer, opts)
|
392
|
-
end
|
393
|
-
|
394
|
-
def prototypes
|
395
|
-
@proto
|
396
|
-
end
|
397
|
-
|
398
|
-
def names
|
399
|
-
@proto.map{|n, t, o| n}
|
400
|
-
end
|
401
|
-
|
402
|
-
def types
|
403
|
-
@proto.map{|n, t, o| t}
|
404
|
-
end
|
405
|
-
|
406
|
-
def opts
|
407
|
-
@proto.map{|n, t, o| o}
|
408
|
-
end
|
409
|
-
|
410
|
-
private
|
411
|
-
|
412
|
-
def add_field(name, type, opts)
|
413
|
-
(@proto ||= []).push [name, type, opts]
|
414
|
-
define_accessor name
|
415
|
-
end
|
416
|
-
|
417
|
-
def define_accessor(name)
|
418
|
-
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
419
|
-
def #{name}
|
420
|
-
self['#{name}'].value
|
421
|
-
end
|
422
|
-
|
423
|
-
def #{name}=(val)
|
424
|
-
self['#{name}'].value = val
|
425
|
-
end
|
426
|
-
End
|
427
|
-
end
|
428
|
-
end
|
429
|
-
|
430
|
-
def initialize
|
431
|
-
@alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
|
432
|
-
end
|
433
|
-
|
434
|
-
def serialize
|
435
|
-
@alist.map{|n, f| f.serialize }.join
|
436
|
-
end
|
437
|
-
|
438
|
-
def parse(str, offset=0)
|
439
|
-
@alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
|
440
|
-
end
|
441
|
-
|
442
|
-
def size
|
443
|
-
@alist.inject(0){|sum, a| sum += a[1].size}
|
444
|
-
end
|
445
|
-
|
446
|
-
def [](name)
|
447
|
-
a = @alist.assoc(name.to_s.intern)
|
448
|
-
raise ArgumentError, "no such field: #{name}" unless a
|
449
|
-
a[1]
|
450
|
-
end
|
451
|
-
|
452
|
-
def []=(name, val)
|
453
|
-
a = @alist.assoc(name.to_s.intern)
|
454
|
-
raise ArgumentError, "no such field: #{name}" unless a
|
455
|
-
a[1] = val
|
456
|
-
end
|
457
|
-
|
458
|
-
def enable(name)
|
459
|
-
self[name].active = true
|
460
|
-
end
|
461
|
-
|
462
|
-
def disable(name)
|
463
|
-
self[name].active = false
|
464
|
-
end
|
465
|
-
end
|
466
|
-
|
467
|
-
|
468
|
-
Blob = FieldSet.define {
|
469
|
-
int32LE :blob_signature, {:value => BLOB_SIGN}
|
470
|
-
int32LE :reserved, {:value => 0}
|
471
|
-
int64LE :timestamp, {:value => 0}
|
472
|
-
string :challenge, {:value => "", :size => 8}
|
473
|
-
int32LE :unknown1, {:value => 0}
|
474
|
-
string :target_info, {:value => "", :size => 0}
|
475
|
-
int32LE :unknown2, {:value => 0}
|
476
|
-
}
|
477
|
-
|
478
|
-
SecurityBuffer = FieldSet.define {
|
479
|
-
int16LE :length, {:value => 0}
|
480
|
-
int16LE :allocated, {:value => 0}
|
481
|
-
int32LE :offset, {:value => 0}
|
482
|
-
}
|
31
|
+
# here we override the default Net::HTTP#request method, in order to hide the
|
32
|
+
# necessary handshaking. maybe a more generic scheme for hooking into this
|
33
|
+
# could be useful, for other auth types
|
483
34
|
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
end
|
492
|
-
|
493
|
-
def parse(str, offset=0)
|
494
|
-
if @active and str.size >= offset + @size
|
495
|
-
super(str, offset)
|
496
|
-
@value = str[self.offset, self.length]
|
497
|
-
@size
|
498
|
-
else
|
499
|
-
0
|
35
|
+
# because of the handshaking i have to rewind body stream. maybe body stream
|
36
|
+
# shouldn't be sent when authenticating??
|
37
|
+
class HTTPRequest
|
38
|
+
def reuse
|
39
|
+
if body_stream
|
40
|
+
begin body_stream.rewind
|
41
|
+
rescue; raise "error rewinding body stream for authentication"
|
500
42
|
end
|
501
43
|
end
|
502
|
-
|
503
|
-
def serialize
|
504
|
-
super if @active
|
505
|
-
end
|
506
|
-
|
507
|
-
def value
|
508
|
-
@value
|
509
|
-
end
|
510
|
-
|
511
|
-
def value=(val)
|
512
|
-
@value = val
|
513
|
-
self.length = self.allocated = val.size
|
514
|
-
end
|
515
|
-
|
516
|
-
def data_size
|
517
|
-
@active ? @value.size : 0
|
518
|
-
end
|
519
44
|
end
|
520
|
-
|
521
|
-
class Message < FieldSet
|
522
|
-
class << Message
|
523
|
-
def parse(str)
|
524
|
-
m = Type0.new
|
525
|
-
m.parse(str)
|
526
|
-
case m.type
|
527
|
-
when 1
|
528
|
-
t = Type1.parse(str)
|
529
|
-
when 2
|
530
|
-
t = Type2.parse(str)
|
531
|
-
when 3
|
532
|
-
t = Type3.parse(str)
|
533
|
-
else
|
534
|
-
raise ArgumentError, "unknown type: #{m.type}"
|
535
|
-
end
|
536
|
-
t
|
537
|
-
end
|
538
|
-
|
539
|
-
def decode64(str)
|
540
|
-
parse(Base64.decode64(str))
|
541
|
-
end
|
542
|
-
end
|
543
|
-
|
544
|
-
def has_flag?(flag)
|
545
|
-
(self[:flag].value & FLAGS[flag]) == FLAGS[flag]
|
546
|
-
end
|
547
|
-
|
548
|
-
def set_flag(flag)
|
549
|
-
self[:flag].value |= FLAGS[flag]
|
550
|
-
end
|
551
|
-
|
552
|
-
def dump_flags
|
553
|
-
FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
|
554
|
-
end
|
555
|
-
|
556
|
-
def serialize
|
557
|
-
deflag
|
558
|
-
super + security_buffers.map{|n, f| f.value}.join
|
559
|
-
end
|
560
|
-
|
561
|
-
def encode64
|
562
|
-
Base64.encode64(serialize).gsub(/\n/, '')
|
563
|
-
end
|
564
|
-
|
565
|
-
def decode64(str)
|
566
|
-
parse(Base64.decode64(str))
|
567
|
-
end
|
568
|
-
|
569
|
-
alias head_size size
|
570
|
-
|
571
|
-
def data_size
|
572
|
-
security_buffers.inject(0){|sum, a| sum += a[1].data_size}
|
573
|
-
end
|
574
|
-
|
575
|
-
def size
|
576
|
-
head_size + data_size
|
577
|
-
end
|
578
|
-
|
579
|
-
|
580
|
-
private
|
581
|
-
|
582
|
-
def security_buffers
|
583
|
-
@alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
|
584
|
-
end
|
585
|
-
|
586
|
-
def deflag
|
587
|
-
security_buffers.inject(head_size){|cur, a|
|
588
|
-
a[1].offset = cur
|
589
|
-
cur += a[1].data_size
|
590
|
-
}
|
591
|
-
end
|
592
|
-
|
593
|
-
def data_edge
|
594
|
-
security_buffers.map{ |n, f| f.active ? f.offset : size}.min
|
595
|
-
end
|
596
|
-
|
597
|
-
# sub class definitions
|
598
|
-
|
599
|
-
Type0 = Message.define {
|
600
|
-
string :sign, {:size => 8, :value => SSP_SIGN}
|
601
|
-
int32LE :type, {:value => 0}
|
602
|
-
}
|
603
|
-
|
604
|
-
Type1 = Message.define {
|
605
|
-
string :sign, {:size => 8, :value => SSP_SIGN}
|
606
|
-
int32LE :type, {:value => 1}
|
607
|
-
int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
|
608
|
-
security_buffer :domain, {:value => "", :active => false}
|
609
|
-
security_buffer :workstation, {:value => "", :active => false}
|
610
|
-
string :padding, {:size => 0, :value => "", :active => false }
|
611
|
-
}
|
612
|
-
|
613
|
-
class Type1
|
614
|
-
class << Type1
|
615
|
-
def parse(str)
|
616
|
-
t = new
|
617
|
-
t.parse(str)
|
618
|
-
t
|
619
|
-
end
|
620
|
-
end
|
621
|
-
|
622
|
-
def parse(str)
|
623
|
-
super(str)
|
624
|
-
enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
|
625
|
-
enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
|
626
|
-
super(str)
|
627
|
-
if ( (len = data_edge - head_size) > 0)
|
628
|
-
self.padding = "\0" * len
|
629
|
-
super(str)
|
630
|
-
end
|
631
|
-
end
|
632
|
-
end
|
633
|
-
|
634
|
-
Type2 = Message.define{
|
635
|
-
string :sign, {:size => 8, :value => SSP_SIGN}
|
636
|
-
int32LE :type, {:value => 2}
|
637
|
-
security_buffer :target_name, {:size => 0, :value => ""}
|
638
|
-
int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
|
639
|
-
int64LE :challenge, {:value => 0}
|
640
|
-
int64LE :context, {:value => 0, :active => false}
|
641
|
-
security_buffer :target_info, {:value => "", :active => false}
|
642
|
-
string :padding, {:size => 0, :value => "", :active => false }
|
643
|
-
}
|
644
|
-
|
645
|
-
class Type2
|
646
|
-
class << Type2
|
647
|
-
def parse(str)
|
648
|
-
t = new
|
649
|
-
t.parse(str)
|
650
|
-
t
|
651
|
-
end
|
652
|
-
end
|
653
|
-
|
654
|
-
def parse(str)
|
655
|
-
super(str)
|
656
|
-
if has_flag?(:TARGET_INFO)
|
657
|
-
enable(:context)
|
658
|
-
enable(:target_info)
|
659
|
-
super(str)
|
660
|
-
end
|
661
|
-
if ( (len = data_edge - head_size) > 0)
|
662
|
-
self.padding = "\0" * len
|
663
|
-
super(str)
|
664
|
-
end
|
665
|
-
end
|
666
|
-
|
667
|
-
def response(arg, opt = {})
|
668
|
-
usr = arg[:user]
|
669
|
-
pwd = arg[:password]
|
670
|
-
if usr.nil? or pwd.nil?
|
671
|
-
raise ArgumentError, "user and password have to be supplied"
|
672
|
-
end
|
673
|
-
|
674
|
-
if opt[:workstation]
|
675
|
-
ws = opt[:workstation]
|
676
|
-
else
|
677
|
-
ws = ""
|
678
|
-
end
|
679
|
-
|
680
|
-
if opt[:client_challenge]
|
681
|
-
cc = opt[:client_challenge]
|
682
|
-
else
|
683
|
-
cc = rand(MAX64)
|
684
|
-
end
|
685
|
-
cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
|
686
|
-
opt[:client_challenge] = cc
|
687
|
-
|
688
|
-
if has_flag?(:OEM) and opt[:unicode]
|
689
|
-
usr = NTLM::decode_utf16le(usr)
|
690
|
-
pwd = NTLM::decode_utf16le(pwd)
|
691
|
-
ws = NTLM::decode_utf16le(ws)
|
692
|
-
opt[:unicode] = false
|
693
|
-
end
|
694
|
-
if has_flag?(:UNICODE) and !opt[:unicode]
|
695
|
-
usr = NTLM::encode_utf16le(usr).gsub(/^\377\376/,'')
|
696
|
-
pwd = NTLM::encode_utf16le(pwd).gsub(/^\377\376/,'')
|
697
|
-
ws = NTLM::encode_utf16le(ws).gsub(/^\377\376/,'')
|
698
|
-
opt[:unicode] = true
|
699
|
-
end
|
700
|
-
tgt = self.target_name
|
701
|
-
ti = self.target_info
|
45
|
+
end
|
702
46
|
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
class Type3
|
742
|
-
class << Type3
|
743
|
-
def parse(str)
|
744
|
-
t = new
|
745
|
-
t.parse(str)
|
746
|
-
t
|
747
|
-
end
|
748
|
-
|
749
|
-
def create(arg, opt ={})
|
750
|
-
t = new
|
751
|
-
t.lm_response = arg[:lm_response]
|
752
|
-
t.ntlm_response = arg[:ntlm_response]
|
753
|
-
t.domain = arg[:domain]
|
754
|
-
t.user = arg[:user]
|
755
|
-
t.workstation = arg[:workstation]
|
756
|
-
|
757
|
-
if arg[:session_key]
|
758
|
-
t.enable(:session_key)
|
759
|
-
t.session_key = arg[session_key]
|
760
|
-
end
|
761
|
-
if arg[:flag]
|
762
|
-
t.enable(:session_key)
|
763
|
-
t.enable(:flag)
|
764
|
-
t.flag = arg[:flag]
|
765
|
-
end
|
766
|
-
t
|
767
|
-
end
|
768
|
-
end
|
47
|
+
class HTTP
|
48
|
+
alias old_request :request
|
49
|
+
private :old_request
|
50
|
+
|
51
|
+
def request req, body=nil, &block
|
52
|
+
resp = data = auth_data = nil
|
53
|
+
old_request req, body do |resp|
|
54
|
+
wwwauth = resp.header['www-authenticate'].split(",").collect{|x| x.strip} rescue ""
|
55
|
+
unless Net::HTTPUnauthorized === resp and auth_data = req.auth_data and
|
56
|
+
auth_data[0] == :ntlm and (wwwauth == 'NTLM' || wwwauth.is_a?(Array) && wwwauth.include?('NTLM')) ||
|
57
|
+
data = resp['www-authenticate'][/^NTLM (.*)/, 1]
|
58
|
+
data = false
|
59
|
+
yield resp if block_given?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
return resp if data == false
|
63
|
+
# not really sure if i'm supposed to just rewrite the request like this?
|
64
|
+
# and the body? what about redirects? the resp.content is just the text error message
|
65
|
+
# what about post data?
|
66
|
+
req.reuse
|
67
|
+
unless data
|
68
|
+
# first stage handshake. respond to challenge
|
69
|
+
# puts "* authenticating (0) ..."
|
70
|
+
# this time wait is true.
|
71
|
+
req.ntlm_auth(*auth_data[1..2])
|
72
|
+
request req, body, &block
|
73
|
+
else
|
74
|
+
# puts "* authenticating (1) ..."
|
75
|
+
challenge = Net::NTLM::Message.decode64 data
|
76
|
+
# challenge.target_name could be provided back as a prompt.
|
77
|
+
# maybe if password is unspecified, a callback can be used to provide
|
78
|
+
# a user prompt.
|
79
|
+
domain,dummy,userid = auth_data[1].rpartition('\\')
|
80
|
+
resp = challenge.response({:domain=>domain, :user => userid, :password => auth_data[2]}, {:ntlmv2 => true})
|
81
|
+
req['Authorization'] = 'NTLM ' + resp.encode64
|
82
|
+
old_request(req, body) { |resp| yield resp if block_given? }
|
83
|
+
resp
|
769
84
|
end
|
770
85
|
end
|
771
86
|
end
|
772
|
-
|
773
|
-
# extra stuff to make nltm auth usage as easy as basic for Net::HTTP
|
774
|
-
# classes
|
775
|
-
|
776
|
-
require 'net/http'
|
777
|
-
|
778
|
-
module HTTPHeader
|
779
|
-
# could also try an automatic authentication. sends as basic first, then
|
780
|
-
# resends if required, or whatever.
|
781
|
-
# seems kind of messy exposing this stuff here.
|
782
|
-
|
783
|
-
def auth_data
|
784
|
-
@auth_data
|
785
|
-
end
|
786
|
-
|
787
|
-
# can set wait - don't authenticate unless challenged. useful when reusing
|
788
|
-
# the connection (otherwise you handshake for each request). wait should
|
789
|
-
# probably become the default, allowing the type of authentication to be
|
790
|
-
# driven by a server challenge.
|
791
|
-
def ntlm_auth user, password, wait=false
|
792
|
-
@auth_data = [:ntlm, user, password]
|
793
|
-
self['Authorization'] = 'NTLM ' + Net::NTLM::Message::Type1.new.encode64 unless wait
|
794
|
-
end
|
795
|
-
end
|
796
|
-
|
797
|
-
# here we override the default Net::HTTP#request method, in order to hide the
|
798
|
-
# necessary handshaking. maybe a more generic scheme for hooking into this
|
799
|
-
# could be useful, for other auth types
|
800
|
-
|
801
|
-
# because of the handshaking i have to rewind body stream. maybe body stream
|
802
|
-
# shouldn't be sent when authenticating??
|
803
|
-
class HTTPRequest
|
804
|
-
def reuse
|
805
|
-
if body_stream
|
806
|
-
begin body_stream.rewind
|
807
|
-
rescue; raise "error rewinding body stream for authentication"
|
808
|
-
end
|
809
|
-
end
|
810
|
-
end
|
811
|
-
end
|
812
|
-
|
813
|
-
class HTTP
|
814
|
-
alias old_request :request
|
815
|
-
private :old_request
|
816
|
-
|
817
|
-
def request req, body=nil, &block
|
818
|
-
resp = data = auth_data = nil
|
819
|
-
old_request req, body do |resp|
|
820
|
-
wwwauth = resp.header['www-authenticate'].split(",").collect{|x| x.strip} rescue ""
|
821
|
-
unless Net::HTTPUnauthorized === resp and auth_data = req.auth_data and
|
822
|
-
auth_data[0] == :ntlm and (wwwauth == 'NTLM' || wwwauth.is_a?(Array) && wwwauth.include?('NTLM')) ||
|
823
|
-
data = resp['www-authenticate'][/^NTLM (.*)/, 1]
|
824
|
-
data = false
|
825
|
-
yield resp if block_given?
|
826
|
-
end
|
827
|
-
end
|
828
|
-
return resp if data == false
|
829
|
-
# not really sure if i'm supposed to just rewrite the request like this?
|
830
|
-
# and the body? what about redirects? the resp.content is just the text error message
|
831
|
-
# what about post data?
|
832
|
-
req.reuse
|
833
|
-
unless data
|
834
|
-
# first stage handshake. respond to challenge
|
835
|
-
# puts "* authenticating (0) ..."
|
836
|
-
# this time wait is true.
|
837
|
-
req.ntlm_auth(*auth_data[1..2])
|
838
|
-
request req, body, &block
|
839
|
-
else
|
840
|
-
# puts "* authenticating (1) ..."
|
841
|
-
challenge = Net::NTLM::Message.decode64 data
|
842
|
-
# challenge.target_name could be provided back as a prompt.
|
843
|
-
# maybe if password is unspecified, a callback can be used to provide
|
844
|
-
# a user prompt.
|
845
|
-
resp = challenge.response({:user => auth_data[1], :password => auth_data[2]}, {:ntlmv2 => true})
|
846
|
-
req['Authorization'] = 'NTLM ' + resp.encode64
|
847
|
-
old_request(req, body) { |resp| yield resp if block_given? }
|
848
|
-
resp
|
849
|
-
end
|
850
|
-
end
|
851
|
-
end
|
852
|
-
|
853
87
|
end
|
data/ntlm-http.gemspec
CHANGED
metadata
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pyu-ntlm-http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 77
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
version: 0.1.
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 2
|
10
|
+
- 1
|
11
|
+
version: 0.1.2.1
|
11
12
|
platform: ruby
|
12
13
|
authors:
|
13
|
-
|
14
|
+
- Kohei Kajimoto,Kingsley Hendrickse
|
14
15
|
autorequire: net/ntlm_http
|
15
16
|
bindir: bin
|
16
17
|
cert_chain: []
|
@@ -26,43 +27,45 @@ executables: []
|
|
26
27
|
extensions: []
|
27
28
|
|
28
29
|
extra_rdoc_files:
|
29
|
-
|
30
|
+
- README
|
30
31
|
files:
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
32
|
+
- ntlm-http.gemspec
|
33
|
+
- Rakefile
|
34
|
+
- README
|
35
|
+
- lib/net/ntlm.rb
|
36
|
+
- lib/net/ntlm_http.rb
|
37
|
+
- test/function_test.rb
|
38
|
+
- examples/http.rb
|
39
|
+
- examples/imap.rb
|
40
|
+
- examples/smtp.rb
|
40
41
|
has_rdoc: true
|
41
42
|
homepage: http://www.mindflowsolutions.net
|
42
43
|
licenses: []
|
43
44
|
|
44
45
|
post_install_message:
|
45
46
|
rdoc_options:
|
46
|
-
|
47
|
-
|
47
|
+
- --main
|
48
|
+
- README
|
48
49
|
require_paths:
|
49
|
-
|
50
|
+
- lib
|
50
51
|
required_ruby_version: !ruby/object:Gem::Requirement
|
51
52
|
none: false
|
52
53
|
requirements:
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
58
60
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
61
|
none: false
|
60
62
|
requirements:
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
66
69
|
requirements: []
|
67
70
|
|
68
71
|
rubyforge_project: rubyntlm
|