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 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
- PKG_VERSION = Net::NTLM::VERSION::STRING
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
+
@@ -100,7 +100,8 @@ module Net #:nodoc:
100
100
  end
101
101
 
102
102
  def encode_utf16le(str)
103
- swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
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)
@@ -1,853 +1,87 @@
1
1
  #
2
- # = net/ntlm.rb
2
+ # = net/ntlm_http.rb
3
3
  #
4
- # An NTLM Authentication Library for Ruby
4
+ # extra stuff to make nltm auth usage as easy as basic for Net::HTTP
5
+ # classes
5
6
  #
6
- # This code is a derivative of "dbf2.rb" written by yrock
7
- # and Minero Aoki. You can find original code here:
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
- class Int16LE < Field
300
- def initialize(opt)
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
- class Int32LE < Field
319
- def initialize(opt)
320
- super(opt)
321
- @size = 4
322
- end
323
-
324
- def parse(str, offset=0)
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
- class Int64LE < Field
339
- def initialize(opt)
340
- super(opt)
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
- class SecurityBuffer
485
- attr_accessor :active
486
- def initialize(opts)
487
- super()
488
- @value = opts[:value]
489
- @active = opts[:active].nil? ? true : opts[:active]
490
- @size = 8
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
- chal = self[:challenge].serialize
704
-
705
- if opt[:ntlmv2]
706
- ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, tgt, opt), :challenge => chal, :target_info => ti}
707
- lm_res = NTLM::lmv2_response(ar, opt)
708
- ntlm_res = NTLM::ntlmv2_response(ar, opt)
709
- elsif has_flag?(:NTLM2_KEY)
710
- ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
711
- lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
712
- else
713
- lm_res = NTLM::lm_response(pwd, chal)
714
- ntlm_res = NTLM::ntlm_response(pwd, chal)
715
- end
716
-
717
- Type3.create({
718
- :lm_response => lm_res,
719
- :ntlm_response => ntlm_res,
720
- :domain => tgt,
721
- :user => usr,
722
- :workstation => ws,
723
- :flag => self.flag
724
- })
725
- end
726
- end
727
-
728
-
729
- Type3 = Message.define{
730
- string :sign, {:size => 8, :value => SSP_SIGN}
731
- int32LE :type, {:value => 3}
732
- security_buffer :lm_response, {:value => ""}
733
- security_buffer :ntlm_response, {:value => ""}
734
- security_buffer :domain, {:value => ""}
735
- security_buffer :user, {:value => ""}
736
- security_buffer :workstation, {:value => ""}
737
- security_buffer :session_key, {:value => "", :active => false }
738
- int64LE :flag, {:value => 0, :active => false }
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = %q{pyu-ntlm-http}
3
- s.version = "0.1.1.1"
3
+ s.version = "0.1.2.1"
4
4
  s.date = %q{20-05-2009}
5
5
  s.summary = %q{Ruby/NTLM HTTP library.}
6
6
  s.email = %q{kingsley@mindflowsolutions.com}
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
- - 0
7
- - 1
8
- - 1
9
- - 1
10
- version: 0.1.1.1
7
+ - 0
8
+ - 1
9
+ - 2
10
+ - 1
11
+ version: 0.1.2.1
11
12
  platform: ruby
12
13
  authors:
13
- - Kohei Kajimoto,Kingsley Hendrickse
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
- - README
30
+ - README
30
31
  files:
31
- - ntlm-http.gemspec
32
- - Rakefile
33
- - README
34
- - lib/net/ntlm.rb
35
- - lib/net/ntlm_http.rb
36
- - test/function_test.rb
37
- - examples/http.rb
38
- - examples/imap.rb
39
- - examples/smtp.rb
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
- - --main
47
- - README
47
+ - --main
48
+ - README
48
49
  require_paths:
49
- - lib
50
+ - lib
50
51
  required_ruby_version: !ruby/object:Gem::Requirement
51
52
  none: false
52
53
  requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- segments:
56
- - 0
57
- version: "0"
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
- - !ruby/object:Gem::Version
63
- segments:
64
- - 0
65
- version: "0"
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