pyu-ntlm-http 0.1.1.1 → 0.1.2.1

Sign up to get free protection for your applications and to get access to all the features.
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