rubyntlm 0.3.4 → 0.4.0
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.
- checksums.yaml +4 -4
- data/.rspec +3 -0
- data/.travis.yml +0 -1
- data/LICENSE +20 -0
- data/Rakefile +8 -15
- data/lib/net/ntlm.rb +22 -654
- data/lib/net/ntlm/blob.rb +17 -0
- data/lib/net/ntlm/encode_util.rb +49 -0
- data/lib/net/ntlm/field.rb +35 -0
- data/lib/net/ntlm/field_set.rb +125 -0
- data/lib/net/ntlm/int16_le.rb +26 -0
- data/lib/net/ntlm/int32_le.rb +25 -0
- data/lib/net/ntlm/int64_le.rb +26 -0
- data/lib/net/ntlm/message.rb +115 -0
- data/lib/net/ntlm/message/type0.rb +16 -0
- data/lib/net/ntlm/message/type1.rb +43 -0
- data/lib/net/ntlm/message/type2.rb +126 -0
- data/lib/net/ntlm/message/type3.rb +68 -0
- data/lib/net/ntlm/security_buffer.rb +48 -0
- data/lib/net/ntlm/string.rb +35 -0
- data/lib/net/ntlm/version.rb +11 -0
- data/rubyntlm.gemspec +4 -1
- data/spec/lib/net/ntlm/blob_spec.rb +16 -0
- data/spec/lib/net/ntlm/encode_util_spec.rb +16 -0
- data/spec/lib/net/ntlm/field_set_spec.rb +33 -0
- data/spec/lib/net/ntlm/field_spec.rb +34 -0
- data/spec/lib/net/ntlm/int16_le_spec.rb +18 -0
- data/spec/lib/net/ntlm/int32_le_spec.rb +19 -0
- data/spec/lib/net/ntlm/int64_le_spec.rb +19 -0
- data/spec/lib/net/ntlm/message/type0_spec.rb +21 -0
- data/spec/lib/net/ntlm/message/type1_spec.rb +42 -0
- data/spec/lib/net/ntlm/message/type2_spec.rb +88 -0
- data/spec/lib/net/ntlm/message/type3_spec.rb +20 -0
- data/spec/lib/net/ntlm/message_spec.rb +17 -0
- data/spec/lib/net/ntlm/security_buffer_spec.rb +64 -0
- data/spec/lib/net/ntlm/string_spec.rb +72 -0
- data/spec/lib/net/ntlm/version_spec.rb +26 -0
- data/spec/lib/net/ntlm_spec.rb +121 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/shared/examples/net/ntlm/field_shared.rb +25 -0
- data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +239 -0
- data/spec/support/shared/examples/net/ntlm/int_shared.rb +43 -0
- data/spec/support/shared/examples/net/ntlm/message_shared.rb +35 -0
- metadata +77 -5
- data/spec/unit/ntlm_spec.rb +0 -183
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92fe9fbd41088f3bb6dd585c16c9b105a91820bc
|
4
|
+
data.tar.gz: 7cb29dfd82e71ff8075f143889da01ca406d41d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6fd7a286d8a55f4ff6aeb7b0bc24d8dd1c37b4daaaa73e6f1ac747de520690085ea026eeec4e3d0c7b1c3225189c5b8591de0361c5e12b32ff54a30ec9da569a
|
7
|
+
data.tar.gz: d4ab713acacb9e2450d66e8bcdb814a9cdadabcd65b29b6743889434cfec3e77aa30ef475ec4871c34b5b2de81488503433b716c316954a30e1b8df2da69cc75
|
data/.rspec
ADDED
data/.travis.yml
CHANGED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Paul Morton, Matt Zukowski, Kohei Kajimoto
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1,21 +1,14 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
|
3
|
-
task :default => [:spec]
|
4
|
-
|
5
3
|
|
6
4
|
require 'rspec/core/rake_task'
|
7
|
-
|
8
|
-
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
9
7
|
task :default => :spec
|
10
|
-
|
11
|
-
desc "Run specs unit tests"
|
12
|
-
RSpec::Core::RakeTask.new do |t|
|
13
|
-
t.pattern = "./spec/unit/*_spec.rb"
|
14
|
-
end
|
15
|
-
|
8
|
+
|
16
9
|
desc "Generate code coverage"
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
task :coverage do
|
11
|
+
ENV['COVERAGE'] = 'true'
|
12
|
+
Rake::Task["spec"].execute
|
13
|
+
end
|
14
|
+
|
data/lib/net/ntlm.rb
CHANGED
@@ -10,9 +10,6 @@
|
|
10
10
|
# -------------------------------------------------------------
|
11
11
|
# Copyright (c) 2005,2006 yrock
|
12
12
|
#
|
13
|
-
# This program is free software.
|
14
|
-
# You can distribute/modify this program under the terms of the
|
15
|
-
# Ruby License.
|
16
13
|
#
|
17
14
|
# 2006-02-11 refactored by Minero Aoki
|
18
15
|
# -------------------------------------------------------------
|
@@ -25,10 +22,6 @@
|
|
25
22
|
# -------------------------------------------------------------
|
26
23
|
# Copyright (c) 2003 Eric Glass
|
27
24
|
#
|
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
25
|
# -------------------------------------------------------------
|
33
26
|
#
|
34
27
|
# The author also looked Mozilla-Firefox-1.0.7 source code,
|
@@ -48,95 +41,35 @@ require 'openssl'
|
|
48
41
|
require 'openssl/digest'
|
49
42
|
require 'socket'
|
50
43
|
|
44
|
+
# Load Order is important here
|
45
|
+
require 'net/ntlm/field'
|
46
|
+
require 'net/ntlm/int16_le'
|
47
|
+
require 'net/ntlm/int32_le'
|
48
|
+
require 'net/ntlm/int64_le'
|
49
|
+
require 'net/ntlm/string'
|
50
|
+
|
51
|
+
require 'net/ntlm/field_set'
|
52
|
+
require 'net/ntlm/blob'
|
53
|
+
require 'net/ntlm/security_buffer'
|
54
|
+
require 'net/ntlm/message'
|
55
|
+
require 'net/ntlm/message/type0'
|
56
|
+
require 'net/ntlm/message/type1'
|
57
|
+
require 'net/ntlm/message/type2'
|
58
|
+
require 'net/ntlm/message/type3'
|
59
|
+
|
60
|
+
|
61
|
+
require 'net/ntlm/encode_util'
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
51
66
|
module Net
|
52
67
|
module NTLM
|
53
|
-
# @private
|
54
|
-
module VERSION
|
55
|
-
MAJOR = 0
|
56
|
-
MINOR = 3
|
57
|
-
TINY = 4
|
58
|
-
STRING = [MAJOR, MINOR, TINY].join('.')
|
59
|
-
end
|
60
68
|
|
61
|
-
SSP_SIGN = "NTLMSSP\0"
|
62
|
-
BLOB_SIGN = 0x00000101
|
63
69
|
LM_MAGIC = "KGS!@\#$%"
|
64
70
|
TIME_OFFSET = 11644473600
|
65
71
|
MAX64 = 0xffffffffffffffff
|
66
72
|
|
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.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
73
|
|
141
74
|
class << self
|
142
75
|
|
@@ -300,570 +233,5 @@ module Net
|
|
300
233
|
end
|
301
234
|
end
|
302
235
|
|
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
236
|
end
|
869
237
|
end
|