rubyntlm 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  .yardoc
2
2
  /doc
3
+ Gemfile.lock
data/.travis.yml CHANGED
@@ -2,6 +2,10 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - 1.9.2
5
+ - 1.8.7
6
+ - 2.0.0
5
7
  - rbx-19mode
8
+ - rbx-18mode
6
9
  - ruby-head
7
- - jruby-19mode
10
+ - jruby-19mode
11
+
data/lib/net/ntlm.rb CHANGED
@@ -1,845 +1,869 @@
1
- # encoding: UTF-8
2
- #
3
- # = net/ntlm.rb
4
- #
5
- # An NTLM Authentication Library for Ruby
6
- #
7
- # This code is a derivative of "dbf2.rb" written by yrock
8
- # and Minero Aoki. You can find original code here:
9
- # http://jp.rubyist.net/magazine/?0013-CodeReview
10
- # -------------------------------------------------------------
11
- # Copyright (c) 2005,2006 yrock
12
- #
13
- # This program is free software.
14
- # You can distribute/modify this program under the terms of the
15
- # Ruby License.
16
- #
17
- # 2006-02-11 refactored by Minero Aoki
18
- # -------------------------------------------------------------
19
- #
20
- # All protocol information used to write this code stems from
21
- # "The NTLM Authentication Protocol" by Eric Glass. The author
22
- # would thank to him for this tremendous work and making it
23
- # available on the net.
24
- # http://davenport.sourceforge.net/ntlm.html
25
- # -------------------------------------------------------------
26
- # Copyright (c) 2003 Eric Glass
27
- #
28
- # Permission to use, copy, modify, and distribute this document
29
- # for any purpose and without any fee is hereby granted,
30
- # provided that the above copyright notice and this list of
31
- # conditions appear in all copies.
32
- # -------------------------------------------------------------
33
- #
34
- # The author also looked Mozilla-Firefox-1.0.7 source code,
35
- # namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
36
- # Jonathan Bastien-Filiatrault's libntlm-ruby.
37
- # "http://x2a.org/websvn/filedetails.php?
38
- # repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
39
- # The latter has a minor bug in its separate_keys function.
40
- # The third key has to begin from the 14th character of the
41
- # input string instead of 13th:)
42
- #--
43
- # $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
44
- #++
45
-
46
- require 'base64'
47
- require 'openssl'
48
- require 'openssl/digest'
49
- require 'socket'
50
-
51
- module Net
52
- module NTLM
53
- # @private
54
- module VERSION
55
- MAJOR = 0
56
- MINOR = 3
57
- TINY = 1
58
- STRING = [MAJOR, MINOR, TINY].join('.')
59
- end
60
-
61
- SSP_SIGN = "NTLMSSP\0"
62
- BLOB_SIGN = 0x00000101
63
- LM_MAGIC = "KGS!@\#$%"
64
- TIME_OFFSET = 11644473600
65
- MAX64 = 0xffffffffffffffff
66
-
67
- FLAGS = {
68
- :UNICODE => 0x00000001,
69
- :OEM => 0x00000002,
70
- :REQUEST_TARGET => 0x00000004,
71
- :MBZ9 => 0x00000008,
72
- :SIGN => 0x00000010,
73
- :SEAL => 0x00000020,
74
- :NEG_DATAGRAM => 0x00000040,
75
- :NETWARE => 0x00000100,
76
- :NTLM => 0x00000200,
77
- :NEG_NT_ONLY => 0x00000400,
78
- :MBZ7 => 0x00000800,
79
- :DOMAIN_SUPPLIED => 0x00001000,
80
- :WORKSTATION_SUPPLIED => 0x00002000,
81
- :LOCAL_CALL => 0x00004000,
82
- :ALWAYS_SIGN => 0x00008000,
83
- :TARGET_TYPE_DOMAIN => 0x00010000,
84
- :TARGET_INFO => 0x00800000,
85
- :NTLM2_KEY => 0x00080000,
86
- :KEY128 => 0x20000000,
87
- :KEY56 => 0x80000000
88
- }.freeze
89
-
90
- FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
91
-
92
- DEFAULT_FLAGS = {
93
- :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
94
- :TYPE2 => FLAGS[:UNICODE],
95
- :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
96
- }
97
-
98
-
99
- class << self
100
-
101
- # Decode a UTF16 string to a ASCII string
102
- # @param [String] str The string to convert
103
- def decode_utf16le(str)
104
- str.encode(Encoding::UTF_8, Encoding::UTF_16LE).force_encoding('UTF-8')
105
- end
106
-
107
- # Encodes a ASCII string to a UTF16 string
108
- # @param [String] str The string to convert
109
- # @note This implementation may seem stupid but the problem is that UTF16-LE and UTF-8 are incompatiable
110
- # encodings. This library uses string contatination to build the packet bytes. The end result is that
111
- # you can either marshal the encodings elsewhere of simply know that each time you call encode_utf16le
112
- # the function will convert the string bytes to UTF-16LE and note the encoding as UTF-8 so that byte
113
- # concatination works seamlessly.
114
- def encode_utf16le(str)
115
- str = str.force_encoding('UTF-8') if [::Encoding::ASCII_8BIT,::Encoding::US_ASCII].include?(str.encoding)
116
- str.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
117
- end
118
-
119
- # Conver the value to a 64-Bit Little Endian Int
120
- # @param [String] val The string to convert
121
- def pack_int64le(val)
122
- [val & 0x00000000ffffffff, val >> 32].pack("V2")
123
- end
124
-
125
- # Builds an array of strings that are 7 characters long
126
- # @param [String] str The string to split
127
- # @api private
128
- def split7(str)
129
- s = str.dup
130
- until s.empty?
131
- (ret ||= []).push s.slice!(0, 7)
132
- end
133
- ret
134
- end
135
-
136
- # Not sure what this is doing
137
- # @param [String] str String to generate keys for
138
- # @api private
139
- def gen_keys(str)
140
- split7(str).map{ |str7|
141
- bits = split7(str7.unpack("B*")[0]).inject('')\
142
- {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
143
- [bits].pack("B*")
144
- }
145
- end
146
-
147
- def apply_des(plain, keys)
148
- dec = OpenSSL::Cipher::DES.new
149
- keys.map {|k|
150
- dec.key = k
151
- dec.encrypt.update(plain)
152
- }
153
- end
154
-
155
- # Generates a Lan Manager Hash
156
- # @param [String] password The password to base the hash on
157
- def lm_hash(password)
158
- keys = gen_keys password.upcase.ljust(14, "\0")
159
- apply_des(LM_MAGIC, keys).join
160
- end
161
-
162
- # Generate a NTLM Hash
163
- # @param [String] password The password to base the hash on
164
- # @option opt :unicode (false) Unicode encode the password
165
- def ntlm_hash(password, opt = {})
166
- pwd = password.dup
167
- unless opt[:unicode]
168
- pwd = encode_utf16le(pwd)
169
- end
170
- OpenSSL::Digest::MD4.digest pwd
171
- end
172
-
173
- # Generate a NTLMv2 Hash
174
- # @param [String] user The username
175
- # @param [String] password The password
176
- # @param [String] target The domain or workstaiton to authenticate to
177
- # @option opt :unicode (false) Unicode encode the domain
178
- def ntlmv2_hash(user, password, target, opt={})
179
- ntlmhash = ntlm_hash(password, opt)
180
- userdomain = (user + target).upcase
181
- unless opt[:unicode]
182
- userdomain = encode_utf16le(userdomain)
183
- end
184
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
185
- end
186
-
187
- def lm_response(arg)
188
- begin
189
- hash = arg[:lm_hash]
190
- chal = arg[:challenge]
191
- rescue
192
- raise ArgumentError
193
- end
194
- chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
195
- keys = gen_keys hash.ljust(21, "\0")
196
- apply_des(chal, keys).join
197
- end
198
-
199
- def ntlm_response(arg)
200
- hash = arg[:ntlm_hash]
201
- chal = arg[:challenge]
202
- chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
203
- keys = gen_keys hash.ljust(21, "\0")
204
- apply_des(chal, keys).join
205
- end
206
-
207
- def ntlmv2_response(arg, opt = {})
208
- begin
209
- key = arg[:ntlmv2_hash]
210
- chal = arg[:challenge]
211
- ti = arg[:target_info]
212
- rescue
213
- raise ArgumentError
214
- end
215
- chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
216
-
217
- if opt[:client_challenge]
218
- cc = opt[:client_challenge]
219
- else
220
- cc = rand(MAX64)
221
- end
222
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
223
-
224
- if opt[:timestamp]
225
- ts = opt[:timestamp]
226
- else
227
- ts = Time.now.to_i
228
- end
229
- # epoch -> milsec from Jan 1, 1601
230
- ts = 10000000 * (ts + TIME_OFFSET)
231
-
232
- blob = Blob.new
233
- blob.timestamp = ts
234
- blob.challenge = cc
235
- blob.target_info = ti
236
-
237
- bb = blob.serialize
238
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
239
- end
240
-
241
- def lmv2_response(arg, opt = {})
242
- key = arg[:ntlmv2_hash]
243
- chal = arg[:challenge]
244
-
245
- chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
246
-
247
- if opt[:client_challenge]
248
- cc = opt[:client_challenge]
249
- else
250
- cc = rand(MAX64)
251
- end
252
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
253
-
254
- OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
255
- end
256
-
257
- def ntlm2_session(arg, opt = {})
258
- begin
259
- passwd_hash = arg[:ntlm_hash]
260
- chal = arg[:challenge]
261
- rescue
262
- raise ArgumentError
263
- end
264
-
265
- if opt[:client_challenge]
266
- cc = opt[:client_challenge]
267
- else
268
- cc = rand(MAX64)
269
- end
270
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
271
-
272
- keys = gen_keys passwd_hash.ljust(21, "\0")
273
- session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
274
- response = apply_des(session_hash, keys).join
275
- [cc.ljust(24, "\0"), response]
276
- end
277
- end
278
-
279
-
280
- # base classes for primitives
281
- # @private
282
- class Field
283
- attr_accessor :active, :value
284
-
285
- def initialize(opts)
286
- @value = opts[:value]
287
- @active = opts[:active].nil? ? true : opts[:active]
288
- end
289
-
290
- def size
291
- @active ? @size : 0
292
- end
293
- end
294
-
295
- class String < Field
296
- def initialize(opts)
297
- super(opts)
298
- @size = opts[:size]
299
- end
300
-
301
- def parse(str, offset=0)
302
- if @active and str.size >= offset + @size
303
- @value = str[offset, @size]
304
- @size
305
- else
306
- 0
307
- end
308
- end
309
-
310
- def serialize
311
- if @active
312
- @value
313
- else
314
- ""
315
- end
316
- end
317
-
318
- def value=(val)
319
- @value = val
320
- @size = @value.nil? ? 0 : @value.size
321
- @active = (@size > 0)
322
- end
323
- end
324
-
325
- class Int16LE < Field
326
- def initialize(opt)
327
- super(opt)
328
- @size = 2
329
- end
330
- def parse(str, offset=0)
331
- if @active and str.size >= offset + @size
332
- @value = str[offset, @size].unpack("v")[0]
333
- @size
334
- else
335
- 0
336
- end
337
- end
338
-
339
- def serialize
340
- [@value].pack("v")
341
- end
342
- end
343
-
344
- class Int32LE < Field
345
- def initialize(opt)
346
- super(opt)
347
- @size = 4
348
- end
349
-
350
- def parse(str, offset=0)
351
- if @active and str.size >= offset + @size
352
- @value = str.slice(offset, @size).unpack("V")[0]
353
- @size
354
- else
355
- 0
356
- end
357
- end
358
-
359
- def serialize
360
- [@value].pack("V") if @active
361
- end
362
- end
363
-
364
- class Int64LE < Field
365
- def initialize(opt)
366
- super(opt)
367
- @size = 8
368
- end
369
-
370
- def parse(str, offset=0)
371
- if @active and str.size >= offset + @size
372
- d, u = str.slice(offset, @size).unpack("V2")
373
- @value = (u * 0x100000000 + d)
374
- @size
375
- else
376
- 0
377
- end
378
- end
379
-
380
- def serialize
381
- [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
382
- end
383
- end
384
-
385
- # base class of data structure
386
- class FieldSet
387
- class << FieldSet
388
-
389
-
390
- # @macro string_security_buffer
391
- # @method $1
392
- # @method $1=
393
- # @return [String]
394
- def string(name, opts)
395
- add_field(name, String, opts)
396
- end
397
-
398
- # @macro int16le_security_buffer
399
- # @method $1
400
- # @method $1=
401
- # @return [Int16LE]
402
- def int16LE(name, opts)
403
- add_field(name, Int16LE, opts)
404
- end
405
-
406
- # @macro int32le_security_buffer
407
- # @method $1
408
- # @method $1=
409
- # @return [Int32LE]
410
- def int32LE(name, opts)
411
- add_field(name, Int32LE, opts)
412
- end
413
-
414
- # @macro int64le_security_buffer
415
- # @method $1
416
- # @method $1=
417
- # @return [Int64]
418
- def int64LE(name, opts)
419
- add_field(name, Int64LE, opts)
420
- end
421
-
422
- # @macro security_buffer
423
- # @method $1
424
- # @method $1=
425
- # @return [SecurityBuffer]
426
- def security_buffer(name, opts)
427
- add_field(name, SecurityBuffer, opts)
428
- end
429
-
430
- def prototypes
431
- @proto
432
- end
433
-
434
- def names
435
- @proto.map{|n, t, o| n}
436
- end
437
-
438
- def types
439
- @proto.map{|n, t, o| t}
440
- end
441
-
442
- def opts
443
- @proto.map{|n, t, o| o}
444
- end
445
-
446
- private
447
-
448
- def add_field(name, type, opts)
449
- (@proto ||= []).push [name, type, opts]
450
- define_accessor name
451
- end
452
-
453
- def define_accessor(name)
454
- module_eval(<<-End, __FILE__, __LINE__ + 1)
455
- def #{name}
456
- self['#{name}'].value
457
- end
458
-
459
- def #{name}=(val)
460
- self['#{name}'].value = val
461
- end
462
- End
463
- end
464
- end
465
-
466
- def initialize
467
- @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
468
- end
469
-
470
- def serialize
471
- @alist.map{|n, f| f.serialize }.join
472
- end
473
-
474
- def parse(str, offset=0)
475
- @alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
476
- end
477
-
478
- def size
479
- @alist.inject(0){|sum, a| sum += a[1].size}
480
- end
481
-
482
- def [](name)
483
- a = @alist.assoc(name.to_s.intern)
484
- raise ArgumentError, "no such field: #{name}" unless a
485
- a[1]
486
- end
487
-
488
- def []=(name, val)
489
- a = @alist.assoc(name.to_s.intern)
490
- raise ArgumentError, "no such field: #{name}" unless a
491
- a[1] = val
492
- end
493
-
494
- def enable(name)
495
- self[name].active = true
496
- end
497
-
498
- def disable(name)
499
- self[name].active = false
500
- end
501
- end
502
-
503
- class Blob < FieldSet
504
- int32LE :blob_signature, {:value => BLOB_SIGN}
505
- int32LE :reserved, {:value => 0}
506
- int64LE :timestamp, {:value => 0}
507
- string :challenge, {:value => "", :size => 8}
508
- int32LE :unknown1, {:value => 0}
509
- string :target_info, {:value => "", :size => 0}
510
- int32LE :unknown2, {:value => 0}
511
- end
512
-
513
- class SecurityBuffer < FieldSet
514
-
515
- int16LE :length, {:value => 0}
516
- int16LE :allocated, {:value => 0}
517
- int32LE :offset, {:value => 0}
518
-
519
- attr_accessor :active
520
- def initialize(opts)
521
- super()
522
- @value = opts[:value]
523
- @active = opts[:active].nil? ? true : opts[:active]
524
- @size = 8
525
- end
526
-
527
- def parse(str, offset=0)
528
- if @active and str.size >= offset + @size
529
- super(str, offset)
530
- @value = str[self.offset, self.length]
531
- @size
532
- else
533
- 0
534
- end
535
- end
536
-
537
- def serialize
538
- super if @active
539
- end
540
-
541
- def value
542
- @value
543
- end
544
-
545
- def value=(val)
546
- @value = val
547
- self.length = self.allocated = val.size
548
- end
549
-
550
- def data_size
551
- @active ? @value.size : 0
552
- end
553
- end
554
-
555
- # @private false
556
- class Message < FieldSet
557
- class << Message
558
- def parse(str)
559
- m = Type0.new
560
- m.parse(str)
561
- case m.type
562
- when 1
563
- t = Type1.parse(str)
564
- when 2
565
- t = Type2.parse(str)
566
- when 3
567
- t = Type3.parse(str)
568
- else
569
- raise ArgumentError, "unknown type: #{m.type}"
570
- end
571
- t
572
- end
573
-
574
- def decode64(str)
575
- parse(Base64.decode64(str))
576
- end
577
- end
578
-
579
- def has_flag?(flag)
580
- (self[:flag].value & FLAGS[flag]) == FLAGS[flag]
581
- end
582
-
583
- def set_flag(flag)
584
- self[:flag].value |= FLAGS[flag]
585
- end
586
-
587
- def dump_flags
588
- FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
589
- end
590
-
591
- def serialize
592
- deflag
593
- super + security_buffers.map{|n, f| f.value}.join
594
- end
595
-
596
- def encode64
597
- Base64.encode64(serialize).gsub(/\n/, '')
598
- end
599
-
600
- def decode64(str)
601
- parse(Base64.decode64(str))
602
- end
603
-
604
- alias head_size size
605
-
606
- def data_size
607
- security_buffers.inject(0){|sum, a| sum += a[1].data_size}
608
- end
609
-
610
- def size
611
- head_size + data_size
612
- end
613
-
614
-
615
- def security_buffers
616
- @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
617
- end
618
-
619
- def deflag
620
- security_buffers.inject(head_size){|cur, a|
621
- a[1].offset = cur
622
- cur += a[1].data_size
623
- }
624
- end
625
-
626
- def data_edge
627
- security_buffers.map{ |n, f| f.active ? f.offset : size}.min
628
- end
629
-
630
- # sub class definitions
631
- class Type0 < Message
632
- string :sign, {:size => 8, :value => SSP_SIGN}
633
- int32LE :type, {:value => 0}
634
- end
635
-
636
- # @private false
637
- class Type1 < Message
638
-
639
- string :sign, {:size => 8, :value => SSP_SIGN}
640
- int32LE :type, {:value => 1}
641
- int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
642
- security_buffer :domain, {:value => ""}
643
- security_buffer :workstation, {:value => Socket.gethostname }
644
- string :padding, {:size => 0, :value => "", :active => false }
645
-
646
- class << Type1
647
- # Parses a Type 1 Message
648
- # @param [String] str A string containing Type 1 data
649
- # @return [Type1] The parsed Type 1 message
650
- def parse(str)
651
- t = new
652
- t.parse(str)
653
- t
654
- end
655
- end
656
-
657
- # @!visibility private
658
- def parse(str)
659
- super(str)
660
- enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
661
- enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
662
- super(str)
663
- if ( (len = data_edge - head_size) > 0)
664
- self.padding = "\0" * len
665
- super(str)
666
- end
667
- end
668
- end
669
-
670
-
671
- # @private false
672
- class Type2 < Message
673
-
674
- string :sign, {:size => 8, :value => SSP_SIGN}
675
- int32LE :type, {:value => 2}
676
- security_buffer :target_name, {:size => 0, :value => ""}
677
- int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
678
- int64LE :challenge, {:value => 0}
679
- int64LE :context, {:value => 0, :active => false}
680
- security_buffer :target_info, {:value => "", :active => false}
681
- string :padding, {:size => 0, :value => "", :active => false }
682
-
683
- class << Type2
684
- # Parse a Type 2 packet
685
- # @param [String] str A string containing Type 2 data
686
- # @return [Type2]
687
- def parse(str)
688
- t = new
689
- t.parse(str)
690
- t
691
- end
692
- end
693
-
694
- # @!visibility private
695
- def parse(str)
696
- super(str)
697
- if has_flag?(:TARGET_INFO)
698
- enable(:context)
699
- enable(:target_info)
700
- super(str)
701
- end
702
- if ( (len = data_edge - head_size) > 0)
703
- self.padding = "\0" * len
704
- super(str)
705
- end
706
- end
707
-
708
- # Generates a Type 3 response based on the Type 2 Information
709
- # @return [Type3]
710
- # @option arg [String] :username The username to authenticate with
711
- # @option arg [String] :password The user's password
712
- # @option arg [String] :domain ('') The domain to authenticate to
713
- # @option opt [String] :workstation (Socket.gethostname) The name of the calling workstation
714
- # @option opt [Boolean] :use_default_target (False) Use the domain supplied by the server in the Type 2 packet
715
- # @note An empty :domain option authenticates to the local machine.
716
- # @note The :use_default_target has presidence over the :domain option
717
- def response(arg, opt = {})
718
- usr = arg[:user]
719
- pwd = arg[:password]
720
- domain = arg[:domain] ? arg[:domain] : ""
721
- if usr.nil? or pwd.nil?
722
- raise ArgumentError, "user and password have to be supplied"
723
- end
724
-
725
- if opt[:workstation]
726
- ws = opt[:workstation]
727
- else
728
- ws = Socket.gethostname
729
- end
730
-
731
- if opt[:client_challenge]
732
- cc = opt[:client_challenge]
733
- else
734
- cc = rand(MAX64)
735
- end
736
- cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
737
- opt[:client_challenge] = cc
738
-
739
- if has_flag?(:OEM) and opt[:unicode]
740
- usr = NTLM::decode_utf16le(usr)
741
- pwd = NTLM::decode_utf16le(pwd)
742
- ws = NTLM::decode_utf16le(ws)
743
- domain = NTLM::decode_utf16le(domain)
744
- opt[:unicode] = false
745
- end
746
-
747
- if has_flag?(:UNICODE) and !opt[:unicode]
748
- usr = NTLM::encode_utf16le(usr)
749
- pwd = NTLM::encode_utf16le(pwd)
750
- ws = NTLM::encode_utf16le(ws)
751
- domain = NTLM::encode_utf16le(domain)
752
- opt[:unicode] = true
753
- end
754
-
755
- if opt[:use_default_target]
756
- domain = self.target_name
757
- end
758
-
759
- ti = self.target_info
760
-
761
- chal = self[:challenge].serialize
762
-
763
- if opt[:ntlmv2]
764
- ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, domain, opt), :challenge => chal, :target_info => ti}
765
- lm_res = NTLM::lmv2_response(ar, opt)
766
- ntlm_res = NTLM::ntlmv2_response(ar, opt)
767
- elsif has_flag?(:NTLM2_KEY)
768
- ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
769
- lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
770
- else
771
- lm_res = NTLM::lm_response(pwd, chal)
772
- ntlm_res = NTLM::ntlm_response(pwd, chal)
773
- end
774
-
775
- Type3.create({
776
- :lm_response => lm_res,
777
- :ntlm_response => ntlm_res,
778
- :domain => domain,
779
- :user => usr,
780
- :workstation => ws,
781
- :flag => self.flag
782
- })
783
- end
784
- end
785
-
786
- # @private false
787
- class Type3 < Message
788
-
789
- string :sign, {:size => 8, :value => SSP_SIGN}
790
- int32LE :type, {:value => 3}
791
- security_buffer :lm_response, {:value => ""}
792
- security_buffer :ntlm_response, {:value => ""}
793
- security_buffer :domain, {:value => ""}
794
- security_buffer :user, {:value => ""}
795
- security_buffer :workstation, {:value => ""}
796
- security_buffer :session_key, {:value => "", :active => false }
797
- int64LE :flag, {:value => 0, :active => false }
798
-
799
- class << Type3
800
- # Parse a Type 3 packet
801
- # @param [String] str A string containing Type 3 data
802
- # @return [Type2]
803
- def parse(str)
804
- t = new
805
- t.parse(str)
806
- t
807
- end
808
-
809
- # Builds a Type 3 packet
810
- # @note All options must be properly encoded with either unicode or oem encoding
811
- # @return [Type3]
812
- # @option arg [String] :lm_response The LM hash
813
- # @option arg [String] :ntlm_response The NTLM hash
814
- # @option arg [String] :domain The domain to authenticate to
815
- # @option arg [String] :workstation The name of the calling workstation
816
- # @option arg [String] :session_key The session key
817
- # @option arg [Integer] :flag Flags for the packet
818
- def create(arg, opt ={})
819
- t = new
820
- t.lm_response = arg[:lm_response]
821
- t.ntlm_response = arg[:ntlm_response]
822
- t.domain = arg[:domain]
823
- t.user = arg[:user]
824
-
825
- if arg[:workstation]
826
- t.workstation = arg[:workstation]
827
- end
828
-
829
- if arg[:session_key]
830
- t.enable(:session_key)
831
- t.session_key = arg[session_key]
832
- end
833
-
834
- if arg[:flag]
835
- t.enable(:session_key)
836
- t.enable(:flag)
837
- t.flag = arg[:flag]
838
- end
839
- t
840
- end
841
- end
842
- end
843
- end
844
- end
845
- end
1
+ # encoding: UTF-8
2
+ #
3
+ # = net/ntlm.rb
4
+ #
5
+ # An NTLM Authentication Library for Ruby
6
+ #
7
+ # This code is a derivative of "dbf2.rb" written by yrock
8
+ # and Minero Aoki. You can find original code here:
9
+ # http://jp.rubyist.net/magazine/?0013-CodeReview
10
+ # -------------------------------------------------------------
11
+ # Copyright (c) 2005,2006 yrock
12
+ #
13
+ # This program is free software.
14
+ # You can distribute/modify this program under the terms of the
15
+ # Ruby License.
16
+ #
17
+ # 2006-02-11 refactored by Minero Aoki
18
+ # -------------------------------------------------------------
19
+ #
20
+ # All protocol information used to write this code stems from
21
+ # "The NTLM Authentication Protocol" by Eric Glass. The author
22
+ # would thank to him for this tremendous work and making it
23
+ # available on the net.
24
+ # http://davenport.sourceforge.net/ntlm.html
25
+ # -------------------------------------------------------------
26
+ # Copyright (c) 2003 Eric Glass
27
+ #
28
+ # Permission to use, copy, modify, and distribute this document
29
+ # for any purpose and without any fee is hereby granted,
30
+ # provided that the above copyright notice and this list of
31
+ # conditions appear in all copies.
32
+ # -------------------------------------------------------------
33
+ #
34
+ # The author also looked Mozilla-Firefox-1.0.7 source code,
35
+ # namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
36
+ # Jonathan Bastien-Filiatrault's libntlm-ruby.
37
+ # "http://x2a.org/websvn/filedetails.php?
38
+ # repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
39
+ # The latter has a minor bug in its separate_keys function.
40
+ # The third key has to begin from the 14th character of the
41
+ # input string instead of 13th:)
42
+ #--
43
+ # $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
44
+ #++
45
+
46
+ require 'base64'
47
+ require 'openssl'
48
+ require 'openssl/digest'
49
+ require 'socket'
50
+
51
+ module Net
52
+ module NTLM
53
+ # @private
54
+ module VERSION
55
+ MAJOR = 0
56
+ MINOR = 3
57
+ TINY = 2
58
+ STRING = [MAJOR, MINOR, TINY].join('.')
59
+ end
60
+
61
+ SSP_SIGN = "NTLMSSP\0"
62
+ BLOB_SIGN = 0x00000101
63
+ LM_MAGIC = "KGS!@\#$%"
64
+ TIME_OFFSET = 11644473600
65
+ MAX64 = 0xffffffffffffffff
66
+
67
+ FLAGS = {
68
+ :UNICODE => 0x00000001,
69
+ :OEM => 0x00000002,
70
+ :REQUEST_TARGET => 0x00000004,
71
+ :MBZ9 => 0x00000008,
72
+ :SIGN => 0x00000010,
73
+ :SEAL => 0x00000020,
74
+ :NEG_DATAGRAM => 0x00000040,
75
+ :NETWARE => 0x00000100,
76
+ :NTLM => 0x00000200,
77
+ :NEG_NT_ONLY => 0x00000400,
78
+ :MBZ7 => 0x00000800,
79
+ :DOMAIN_SUPPLIED => 0x00001000,
80
+ :WORKSTATION_SUPPLIED => 0x00002000,
81
+ :LOCAL_CALL => 0x00004000,
82
+ :ALWAYS_SIGN => 0x00008000,
83
+ :TARGET_TYPE_DOMAIN => 0x00010000,
84
+ :TARGET_INFO => 0x00800000,
85
+ :NTLM2_KEY => 0x00080000,
86
+ :KEY128 => 0x20000000,
87
+ :KEY56 => 0x80000000
88
+ }.freeze
89
+
90
+ FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
91
+
92
+ DEFAULT_FLAGS = {
93
+ :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
94
+ :TYPE2 => FLAGS[:UNICODE],
95
+ :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
96
+ }
97
+
98
+ class EncodeUtil
99
+ if RUBY_VERSION == "1.8.7"
100
+ require "kconv"
101
+
102
+ # Decode a UTF16 string to a ASCII string
103
+ # @param [String] str The string to convert
104
+ def self.decode_utf16le(str)
105
+ Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
106
+ end
107
+
108
+ # Encodes a ASCII string to a UTF16 string
109
+ # @param [String] str The string to convert
110
+ def self.encode_utf16le(str)
111
+ swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
112
+ end
113
+
114
+ # Taggle the strings endianness between big/little and little/big
115
+ # @param [String] str The string to swap the endianness on
116
+ def self.swap16(str)
117
+ str.unpack("v*").pack("n*")
118
+ end
119
+ else # Use native 1.9 string encoding functions
120
+
121
+ # Decode a UTF16 string to a ASCII string
122
+ # @param [String] str The string to convert
123
+ def self.decode_utf16le(str)
124
+ str.encode(Encoding::UTF_8, Encoding::UTF_16LE).force_encoding('UTF-8')
125
+ end
126
+
127
+ # Encodes a ASCII string to a UTF16 string
128
+ # @param [String] str The string to convert
129
+ # @note This implementation may seem stupid but the problem is that UTF16-LE and UTF-8 are incompatiable
130
+ # encodings. This library uses string contatination to build the packet bytes. The end result is that
131
+ # you can either marshal the encodings elsewhere of simply know that each time you call encode_utf16le
132
+ # the function will convert the string bytes to UTF-16LE and note the encoding as UTF-8 so that byte
133
+ # concatination works seamlessly.
134
+ def self.encode_utf16le(str)
135
+ str = str.force_encoding('UTF-8') if [::Encoding::ASCII_8BIT,::Encoding::US_ASCII].include?(str.encoding)
136
+ str.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
137
+ end
138
+ end
139
+ end
140
+
141
+ class << self
142
+
143
+ # Conver the value to a 64-Bit Little Endian Int
144
+ # @param [String] val The string to convert
145
+ def pack_int64le(val)
146
+ [val & 0x00000000ffffffff, val >> 32].pack("V2")
147
+ end
148
+
149
+ # Builds an array of strings that are 7 characters long
150
+ # @param [String] str The string to split
151
+ # @api private
152
+ def split7(str)
153
+ s = str.dup
154
+ until s.empty?
155
+ (ret ||= []).push s.slice!(0, 7)
156
+ end
157
+ ret
158
+ end
159
+
160
+ # Not sure what this is doing
161
+ # @param [String] str String to generate keys for
162
+ # @api private
163
+ def gen_keys(str)
164
+ split7(str).map{ |str7|
165
+ bits = split7(str7.unpack("B*")[0]).inject('')\
166
+ {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
167
+ [bits].pack("B*")
168
+ }
169
+ end
170
+
171
+ def apply_des(plain, keys)
172
+ dec = OpenSSL::Cipher::DES.new
173
+ keys.map {|k|
174
+ dec.key = k
175
+ dec.encrypt.update(plain)
176
+ }
177
+ end
178
+
179
+ # Generates a Lan Manager Hash
180
+ # @param [String] password The password to base the hash on
181
+ def lm_hash(password)
182
+ keys = gen_keys password.upcase.ljust(14, "\0")
183
+ apply_des(LM_MAGIC, keys).join
184
+ end
185
+
186
+ # Generate a NTLM Hash
187
+ # @param [String] password The password to base the hash on
188
+ # @option opt :unicode (false) Unicode encode the password
189
+ def ntlm_hash(password, opt = {})
190
+ pwd = password.dup
191
+ unless opt[:unicode]
192
+ pwd = EncodeUtil.encode_utf16le(pwd)
193
+ end
194
+ OpenSSL::Digest::MD4.digest pwd
195
+ end
196
+
197
+ # Generate a NTLMv2 Hash
198
+ # @param [String] user The username
199
+ # @param [String] password The password
200
+ # @param [String] target The domain or workstaiton to authenticate to
201
+ # @option opt :unicode (false) Unicode encode the domain
202
+ def ntlmv2_hash(user, password, target, opt={})
203
+ ntlmhash = ntlm_hash(password, opt)
204
+ userdomain = (user + target).upcase
205
+ unless opt[:unicode]
206
+ userdomain = EncodeUtil.encode_utf16le(userdomain)
207
+ end
208
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
209
+ end
210
+
211
+ def lm_response(arg)
212
+ begin
213
+ hash = arg[:lm_hash]
214
+ chal = arg[:challenge]
215
+ rescue
216
+ raise ArgumentError
217
+ end
218
+ chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
219
+ keys = gen_keys hash.ljust(21, "\0")
220
+ apply_des(chal, keys).join
221
+ end
222
+
223
+ def ntlm_response(arg)
224
+ hash = arg[:ntlm_hash]
225
+ chal = arg[:challenge]
226
+ chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
227
+ keys = gen_keys hash.ljust(21, "\0")
228
+ apply_des(chal, keys).join
229
+ end
230
+
231
+ def ntlmv2_response(arg, opt = {})
232
+ begin
233
+ key = arg[:ntlmv2_hash]
234
+ chal = arg[:challenge]
235
+ ti = arg[:target_info]
236
+ rescue
237
+ raise ArgumentError
238
+ end
239
+ chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
240
+
241
+ if opt[:client_challenge]
242
+ cc = opt[:client_challenge]
243
+ else
244
+ cc = rand(MAX64)
245
+ end
246
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
247
+
248
+ if opt[:timestamp]
249
+ ts = opt[:timestamp]
250
+ else
251
+ ts = Time.now.to_i
252
+ end
253
+ # epoch -> milsec from Jan 1, 1601
254
+ ts = 10000000 * (ts + TIME_OFFSET)
255
+
256
+ blob = Blob.new
257
+ blob.timestamp = ts
258
+ blob.challenge = cc
259
+ blob.target_info = ti
260
+
261
+ bb = blob.serialize
262
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
263
+ end
264
+
265
+ def lmv2_response(arg, opt = {})
266
+ key = arg[:ntlmv2_hash]
267
+ chal = arg[:challenge]
268
+
269
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
270
+
271
+ if opt[:client_challenge]
272
+ cc = opt[:client_challenge]
273
+ else
274
+ cc = rand(MAX64)
275
+ end
276
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
277
+
278
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
279
+ end
280
+
281
+ def ntlm2_session(arg, opt = {})
282
+ begin
283
+ passwd_hash = arg[:ntlm_hash]
284
+ chal = arg[:challenge]
285
+ rescue
286
+ raise ArgumentError
287
+ end
288
+
289
+ if opt[:client_challenge]
290
+ cc = opt[:client_challenge]
291
+ else
292
+ cc = rand(MAX64)
293
+ end
294
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
295
+
296
+ keys = gen_keys passwd_hash.ljust(21, "\0")
297
+ session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
298
+ response = apply_des(session_hash, keys).join
299
+ [cc.ljust(24, "\0"), response]
300
+ end
301
+ end
302
+
303
+
304
+ # base classes for primitives
305
+ # @private
306
+ class Field
307
+ attr_accessor :active, :value
308
+
309
+ def initialize(opts)
310
+ @value = opts[:value]
311
+ @active = opts[:active].nil? ? true : opts[:active]
312
+ end
313
+
314
+ def size
315
+ @active ? @size : 0
316
+ end
317
+ end
318
+
319
+ class String < Field
320
+ def initialize(opts)
321
+ super(opts)
322
+ @size = opts[:size]
323
+ end
324
+
325
+ def parse(str, offset=0)
326
+ if @active and str.size >= offset + @size
327
+ @value = str[offset, @size]
328
+ @size
329
+ else
330
+ 0
331
+ end
332
+ end
333
+
334
+ def serialize
335
+ if @active
336
+ @value
337
+ else
338
+ ""
339
+ end
340
+ end
341
+
342
+ def value=(val)
343
+ @value = val
344
+ @size = @value.nil? ? 0 : @value.size
345
+ @active = (@size > 0)
346
+ end
347
+ end
348
+
349
+ class Int16LE < Field
350
+ def initialize(opt)
351
+ super(opt)
352
+ @size = 2
353
+ end
354
+ def parse(str, offset=0)
355
+ if @active and str.size >= offset + @size
356
+ @value = str[offset, @size].unpack("v")[0]
357
+ @size
358
+ else
359
+ 0
360
+ end
361
+ end
362
+
363
+ def serialize
364
+ [@value].pack("v")
365
+ end
366
+ end
367
+
368
+ class Int32LE < Field
369
+ def initialize(opt)
370
+ super(opt)
371
+ @size = 4
372
+ end
373
+
374
+ def parse(str, offset=0)
375
+ if @active and str.size >= offset + @size
376
+ @value = str.slice(offset, @size).unpack("V")[0]
377
+ @size
378
+ else
379
+ 0
380
+ end
381
+ end
382
+
383
+ def serialize
384
+ [@value].pack("V") if @active
385
+ end
386
+ end
387
+
388
+ class Int64LE < Field
389
+ def initialize(opt)
390
+ super(opt)
391
+ @size = 8
392
+ end
393
+
394
+ def parse(str, offset=0)
395
+ if @active and str.size >= offset + @size
396
+ d, u = str.slice(offset, @size).unpack("V2")
397
+ @value = (u * 0x100000000 + d)
398
+ @size
399
+ else
400
+ 0
401
+ end
402
+ end
403
+
404
+ def serialize
405
+ [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
406
+ end
407
+ end
408
+
409
+ # base class of data structure
410
+ class FieldSet
411
+ class << FieldSet
412
+
413
+
414
+ # @macro string_security_buffer
415
+ # @method $1
416
+ # @method $1=
417
+ # @return [String]
418
+ def string(name, opts)
419
+ add_field(name, String, opts)
420
+ end
421
+
422
+ # @macro int16le_security_buffer
423
+ # @method $1
424
+ # @method $1=
425
+ # @return [Int16LE]
426
+ def int16LE(name, opts)
427
+ add_field(name, Int16LE, opts)
428
+ end
429
+
430
+ # @macro int32le_security_buffer
431
+ # @method $1
432
+ # @method $1=
433
+ # @return [Int32LE]
434
+ def int32LE(name, opts)
435
+ add_field(name, Int32LE, opts)
436
+ end
437
+
438
+ # @macro int64le_security_buffer
439
+ # @method $1
440
+ # @method $1=
441
+ # @return [Int64]
442
+ def int64LE(name, opts)
443
+ add_field(name, Int64LE, opts)
444
+ end
445
+
446
+ # @macro security_buffer
447
+ # @method $1
448
+ # @method $1=
449
+ # @return [SecurityBuffer]
450
+ def security_buffer(name, opts)
451
+ add_field(name, SecurityBuffer, opts)
452
+ end
453
+
454
+ def prototypes
455
+ @proto
456
+ end
457
+
458
+ def names
459
+ @proto.map{|n, t, o| n}
460
+ end
461
+
462
+ def types
463
+ @proto.map{|n, t, o| t}
464
+ end
465
+
466
+ def opts
467
+ @proto.map{|n, t, o| o}
468
+ end
469
+
470
+ private
471
+
472
+ def add_field(name, type, opts)
473
+ (@proto ||= []).push [name, type, opts]
474
+ define_accessor name
475
+ end
476
+
477
+ def define_accessor(name)
478
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
479
+ def #{name}
480
+ self['#{name}'].value
481
+ end
482
+
483
+ def #{name}=(val)
484
+ self['#{name}'].value = val
485
+ end
486
+ End
487
+ end
488
+ end
489
+
490
+ def initialize
491
+ @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
492
+ end
493
+
494
+ def serialize
495
+ @alist.map{|n, f| f.serialize }.join
496
+ end
497
+
498
+ def parse(str, offset=0)
499
+ @alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
500
+ end
501
+
502
+ def size
503
+ @alist.inject(0){|sum, a| sum += a[1].size}
504
+ end
505
+
506
+ def [](name)
507
+ a = @alist.assoc(name.to_s.intern)
508
+ raise ArgumentError, "no such field: #{name}" unless a
509
+ a[1]
510
+ end
511
+
512
+ def []=(name, val)
513
+ a = @alist.assoc(name.to_s.intern)
514
+ raise ArgumentError, "no such field: #{name}" unless a
515
+ a[1] = val
516
+ end
517
+
518
+ def enable(name)
519
+ self[name].active = true
520
+ end
521
+
522
+ def disable(name)
523
+ self[name].active = false
524
+ end
525
+ end
526
+
527
+ class Blob < FieldSet
528
+ int32LE :blob_signature, {:value => BLOB_SIGN}
529
+ int32LE :reserved, {:value => 0}
530
+ int64LE :timestamp, {:value => 0}
531
+ string :challenge, {:value => "", :size => 8}
532
+ int32LE :unknown1, {:value => 0}
533
+ string :target_info, {:value => "", :size => 0}
534
+ int32LE :unknown2, {:value => 0}
535
+ end
536
+
537
+ class SecurityBuffer < FieldSet
538
+
539
+ int16LE :length, {:value => 0}
540
+ int16LE :allocated, {:value => 0}
541
+ int32LE :offset, {:value => 0}
542
+
543
+ attr_accessor :active
544
+ def initialize(opts)
545
+ super()
546
+ @value = opts[:value]
547
+ @active = opts[:active].nil? ? true : opts[:active]
548
+ @size = 8
549
+ end
550
+
551
+ def parse(str, offset=0)
552
+ if @active and str.size >= offset + @size
553
+ super(str, offset)
554
+ @value = str[self.offset, self.length]
555
+ @size
556
+ else
557
+ 0
558
+ end
559
+ end
560
+
561
+ def serialize
562
+ super if @active
563
+ end
564
+
565
+ def value
566
+ @value
567
+ end
568
+
569
+ def value=(val)
570
+ @value = val
571
+ self.length = self.allocated = val.size
572
+ end
573
+
574
+ def data_size
575
+ @active ? @value.size : 0
576
+ end
577
+ end
578
+
579
+ # @private false
580
+ class Message < FieldSet
581
+ class << Message
582
+ def parse(str)
583
+ m = Type0.new
584
+ m.parse(str)
585
+ case m.type
586
+ when 1
587
+ t = Type1.parse(str)
588
+ when 2
589
+ t = Type2.parse(str)
590
+ when 3
591
+ t = Type3.parse(str)
592
+ else
593
+ raise ArgumentError, "unknown type: #{m.type}"
594
+ end
595
+ t
596
+ end
597
+
598
+ def decode64(str)
599
+ parse(Base64.decode64(str))
600
+ end
601
+ end
602
+
603
+ def has_flag?(flag)
604
+ (self[:flag].value & FLAGS[flag]) == FLAGS[flag]
605
+ end
606
+
607
+ def set_flag(flag)
608
+ self[:flag].value |= FLAGS[flag]
609
+ end
610
+
611
+ def dump_flags
612
+ FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
613
+ end
614
+
615
+ def serialize
616
+ deflag
617
+ super + security_buffers.map{|n, f| f.value}.join
618
+ end
619
+
620
+ def encode64
621
+ Base64.encode64(serialize).gsub(/\n/, '')
622
+ end
623
+
624
+ def decode64(str)
625
+ parse(Base64.decode64(str))
626
+ end
627
+
628
+ alias head_size size
629
+
630
+ def data_size
631
+ security_buffers.inject(0){|sum, a| sum += a[1].data_size}
632
+ end
633
+
634
+ def size
635
+ head_size + data_size
636
+ end
637
+
638
+
639
+ def security_buffers
640
+ @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
641
+ end
642
+
643
+ def deflag
644
+ security_buffers.inject(head_size){|cur, a|
645
+ a[1].offset = cur
646
+ cur += a[1].data_size
647
+ }
648
+ end
649
+
650
+ def data_edge
651
+ security_buffers.map{ |n, f| f.active ? f.offset : size}.min
652
+ end
653
+
654
+ # sub class definitions
655
+ class Type0 < Message
656
+ string :sign, {:size => 8, :value => SSP_SIGN}
657
+ int32LE :type, {:value => 0}
658
+ end
659
+
660
+ # @private false
661
+ class Type1 < Message
662
+
663
+ string :sign, {:size => 8, :value => SSP_SIGN}
664
+ int32LE :type, {:value => 1}
665
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
666
+ security_buffer :domain, {:value => ""}
667
+ security_buffer :workstation, {:value => Socket.gethostname }
668
+ string :padding, {:size => 0, :value => "", :active => false }
669
+
670
+ class << Type1
671
+ # Parses a Type 1 Message
672
+ # @param [String] str A string containing Type 1 data
673
+ # @return [Type1] The parsed Type 1 message
674
+ def parse(str)
675
+ t = new
676
+ t.parse(str)
677
+ t
678
+ end
679
+ end
680
+
681
+ # @!visibility private
682
+ def parse(str)
683
+ super(str)
684
+ enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
685
+ enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
686
+ super(str)
687
+ if ( (len = data_edge - head_size) > 0)
688
+ self.padding = "\0" * len
689
+ super(str)
690
+ end
691
+ end
692
+ end
693
+
694
+
695
+ # @private false
696
+ class Type2 < Message
697
+
698
+ string :sign, {:size => 8, :value => SSP_SIGN}
699
+ int32LE :type, {:value => 2}
700
+ security_buffer :target_name, {:size => 0, :value => ""}
701
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
702
+ int64LE :challenge, {:value => 0}
703
+ int64LE :context, {:value => 0, :active => false}
704
+ security_buffer :target_info, {:value => "", :active => false}
705
+ string :padding, {:size => 0, :value => "", :active => false }
706
+
707
+ class << Type2
708
+ # Parse a Type 2 packet
709
+ # @param [String] str A string containing Type 2 data
710
+ # @return [Type2]
711
+ def parse(str)
712
+ t = new
713
+ t.parse(str)
714
+ t
715
+ end
716
+ end
717
+
718
+ # @!visibility private
719
+ def parse(str)
720
+ super(str)
721
+ if has_flag?(:TARGET_INFO)
722
+ enable(:context)
723
+ enable(:target_info)
724
+ super(str)
725
+ end
726
+ if ( (len = data_edge - head_size) > 0)
727
+ self.padding = "\0" * len
728
+ super(str)
729
+ end
730
+ end
731
+
732
+ # Generates a Type 3 response based on the Type 2 Information
733
+ # @return [Type3]
734
+ # @option arg [String] :username The username to authenticate with
735
+ # @option arg [String] :password The user's password
736
+ # @option arg [String] :domain ('') The domain to authenticate to
737
+ # @option opt [String] :workstation (Socket.gethostname) The name of the calling workstation
738
+ # @option opt [Boolean] :use_default_target (False) Use the domain supplied by the server in the Type 2 packet
739
+ # @note An empty :domain option authenticates to the local machine.
740
+ # @note The :use_default_target has presidence over the :domain option
741
+ def response(arg, opt = {})
742
+ usr = arg[:user]
743
+ pwd = arg[:password]
744
+ domain = arg[:domain] ? arg[:domain] : ""
745
+ if usr.nil? or pwd.nil?
746
+ raise ArgumentError, "user and password have to be supplied"
747
+ end
748
+
749
+ if opt[:workstation]
750
+ ws = opt[:workstation]
751
+ else
752
+ ws = Socket.gethostname
753
+ end
754
+
755
+ if opt[:client_challenge]
756
+ cc = opt[:client_challenge]
757
+ else
758
+ cc = rand(MAX64)
759
+ end
760
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
761
+ opt[:client_challenge] = cc
762
+
763
+ if has_flag?(:OEM) and opt[:unicode]
764
+ usr = NTLM::EncodeUtil.decode_utf16le(usr)
765
+ pwd = NTLM::EncodeUtil.decode_utf16le(pwd)
766
+ ws = NTLM::EncodeUtil.decode_utf16le(ws)
767
+ domain = NTLM::EncodeUtil.decode_utf16le(domain)
768
+ opt[:unicode] = false
769
+ end
770
+
771
+ if has_flag?(:UNICODE) and !opt[:unicode]
772
+ usr = NTLM::EncodeUtil.encode_utf16le(usr)
773
+ pwd = NTLM::EncodeUtil.encode_utf16le(pwd)
774
+ ws = NTLM::EncodeUtil.encode_utf16le(ws)
775
+ domain = NTLM::EncodeUtil.encode_utf16le(domain)
776
+ opt[:unicode] = true
777
+ end
778
+
779
+ if opt[:use_default_target]
780
+ domain = self.target_name
781
+ end
782
+
783
+ ti = self.target_info
784
+
785
+ chal = self[:challenge].serialize
786
+
787
+ if opt[:ntlmv2]
788
+ ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, domain, opt), :challenge => chal, :target_info => ti}
789
+ lm_res = NTLM::lmv2_response(ar, opt)
790
+ ntlm_res = NTLM::ntlmv2_response(ar, opt)
791
+ elsif has_flag?(:NTLM2_KEY)
792
+ ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
793
+ lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
794
+ else
795
+ lm_res = NTLM::lm_response(pwd, chal)
796
+ ntlm_res = NTLM::ntlm_response(pwd, chal)
797
+ end
798
+
799
+ Type3.create({
800
+ :lm_response => lm_res,
801
+ :ntlm_response => ntlm_res,
802
+ :domain => domain,
803
+ :user => usr,
804
+ :workstation => ws,
805
+ :flag => self.flag
806
+ })
807
+ end
808
+ end
809
+
810
+ # @private false
811
+ class Type3 < Message
812
+
813
+ string :sign, {:size => 8, :value => SSP_SIGN}
814
+ int32LE :type, {:value => 3}
815
+ security_buffer :lm_response, {:value => ""}
816
+ security_buffer :ntlm_response, {:value => ""}
817
+ security_buffer :domain, {:value => ""}
818
+ security_buffer :user, {:value => ""}
819
+ security_buffer :workstation, {:value => ""}
820
+ security_buffer :session_key, {:value => "", :active => false }
821
+ int64LE :flag, {:value => 0, :active => false }
822
+
823
+ class << Type3
824
+ # Parse a Type 3 packet
825
+ # @param [String] str A string containing Type 3 data
826
+ # @return [Type2]
827
+ def parse(str)
828
+ t = new
829
+ t.parse(str)
830
+ t
831
+ end
832
+
833
+ # Builds a Type 3 packet
834
+ # @note All options must be properly encoded with either unicode or oem encoding
835
+ # @return [Type3]
836
+ # @option arg [String] :lm_response The LM hash
837
+ # @option arg [String] :ntlm_response The NTLM hash
838
+ # @option arg [String] :domain The domain to authenticate to
839
+ # @option arg [String] :workstation The name of the calling workstation
840
+ # @option arg [String] :session_key The session key
841
+ # @option arg [Integer] :flag Flags for the packet
842
+ def create(arg, opt ={})
843
+ t = new
844
+ t.lm_response = arg[:lm_response]
845
+ t.ntlm_response = arg[:ntlm_response]
846
+ t.domain = arg[:domain]
847
+ t.user = arg[:user]
848
+
849
+ if arg[:workstation]
850
+ t.workstation = arg[:workstation]
851
+ end
852
+
853
+ if arg[:session_key]
854
+ t.enable(:session_key)
855
+ t.session_key = arg[session_key]
856
+ end
857
+
858
+ if arg[:flag]
859
+ t.enable(:session_key)
860
+ t.enable(:flag)
861
+ t.flag = arg[:flag]
862
+ end
863
+ t
864
+ end
865
+ end
866
+ end
867
+ end
868
+ end
869
+ end