pyu-ntlm-http 0.1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,853 @@
1
+ #
2
+ # = net/ntlm.rb
3
+ #
4
+ # An NTLM Authentication Library for Ruby
5
+ #
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'
48
+
49
+ 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
+
298
+
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
316
+ end
317
+
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
336
+ end
337
+
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
+ }
483
+
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
500
+ end
501
+ 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
+ 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
702
+
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
769
+ end
770
+ end
771
+ 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
+ end