rubyntlm 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.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