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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -0
  3. data/.travis.yml +0 -1
  4. data/LICENSE +20 -0
  5. data/Rakefile +8 -15
  6. data/lib/net/ntlm.rb +22 -654
  7. data/lib/net/ntlm/blob.rb +17 -0
  8. data/lib/net/ntlm/encode_util.rb +49 -0
  9. data/lib/net/ntlm/field.rb +35 -0
  10. data/lib/net/ntlm/field_set.rb +125 -0
  11. data/lib/net/ntlm/int16_le.rb +26 -0
  12. data/lib/net/ntlm/int32_le.rb +25 -0
  13. data/lib/net/ntlm/int64_le.rb +26 -0
  14. data/lib/net/ntlm/message.rb +115 -0
  15. data/lib/net/ntlm/message/type0.rb +16 -0
  16. data/lib/net/ntlm/message/type1.rb +43 -0
  17. data/lib/net/ntlm/message/type2.rb +126 -0
  18. data/lib/net/ntlm/message/type3.rb +68 -0
  19. data/lib/net/ntlm/security_buffer.rb +48 -0
  20. data/lib/net/ntlm/string.rb +35 -0
  21. data/lib/net/ntlm/version.rb +11 -0
  22. data/rubyntlm.gemspec +4 -1
  23. data/spec/lib/net/ntlm/blob_spec.rb +16 -0
  24. data/spec/lib/net/ntlm/encode_util_spec.rb +16 -0
  25. data/spec/lib/net/ntlm/field_set_spec.rb +33 -0
  26. data/spec/lib/net/ntlm/field_spec.rb +34 -0
  27. data/spec/lib/net/ntlm/int16_le_spec.rb +18 -0
  28. data/spec/lib/net/ntlm/int32_le_spec.rb +19 -0
  29. data/spec/lib/net/ntlm/int64_le_spec.rb +19 -0
  30. data/spec/lib/net/ntlm/message/type0_spec.rb +21 -0
  31. data/spec/lib/net/ntlm/message/type1_spec.rb +42 -0
  32. data/spec/lib/net/ntlm/message/type2_spec.rb +88 -0
  33. data/spec/lib/net/ntlm/message/type3_spec.rb +20 -0
  34. data/spec/lib/net/ntlm/message_spec.rb +17 -0
  35. data/spec/lib/net/ntlm/security_buffer_spec.rb +64 -0
  36. data/spec/lib/net/ntlm/string_spec.rb +72 -0
  37. data/spec/lib/net/ntlm/version_spec.rb +26 -0
  38. data/spec/lib/net/ntlm_spec.rb +121 -0
  39. data/spec/spec_helper.rb +21 -0
  40. data/spec/support/shared/examples/net/ntlm/field_shared.rb +25 -0
  41. data/spec/support/shared/examples/net/ntlm/fieldset_shared.rb +239 -0
  42. data/spec/support/shared/examples/net/ntlm/int_shared.rb +43 -0
  43. data/spec/support/shared/examples/net/ntlm/message_shared.rb +35 -0
  44. metadata +77 -5
  45. data/spec/unit/ntlm_spec.rb +0 -183
@@ -0,0 +1,17 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ BLOB_SIGN = 0x00000101
5
+
6
+ class Blob < FieldSet
7
+ int32LE :blob_signature, {:value => BLOB_SIGN}
8
+ int32LE :reserved, {:value => 0}
9
+ int64LE :timestamp, {:value => 0}
10
+ string :challenge, {:value => "", :size => 8}
11
+ int32LE :unknown1, {:value => 0}
12
+ string :target_info, {:value => "", :size => 0}
13
+ int32LE :unknown2, {:value => 0}
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ class EncodeUtil
5
+ if RUBY_VERSION == "1.8.7"
6
+ require "kconv"
7
+
8
+ # Decode a UTF16 string to a ASCII string
9
+ # @param [String] str The string to convert
10
+ def self.decode_utf16le(str)
11
+ Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
12
+ end
13
+
14
+ # Encodes a ASCII string to a UTF16 string
15
+ # @param [String] str The string to convert
16
+ def self.encode_utf16le(str)
17
+ swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
18
+ end
19
+
20
+ # Taggle the strings endianness between big/little and little/big
21
+ # @param [String] str The string to swap the endianness on
22
+ def self.swap16(str)
23
+ str.unpack("v*").pack("n*")
24
+ end
25
+ else # Use native 1.9 string encoding functions
26
+
27
+ # Decode a UTF16 string to a ASCII string
28
+ # @param [String] str The string to convert
29
+ def self.decode_utf16le(str)
30
+ str.force_encoding(Encoding::UTF_16LE)
31
+ str.encode(Encoding::UTF_8, Encoding::UTF_16LE).force_encoding('UTF-8')
32
+ end
33
+
34
+ # Encodes a ASCII string to a UTF16 string
35
+ # @param [String] str The string to convert
36
+ # @note This implementation may seem stupid but the problem is that UTF16-LE and UTF-8 are incompatiable
37
+ # encodings. This library uses string contatination to build the packet bytes. The end result is that
38
+ # you can either marshal the encodings elsewhere of simply know that each time you call encode_utf16le
39
+ # the function will convert the string bytes to UTF-16LE and note the encoding as UTF-8 so that byte
40
+ # concatination works seamlessly.
41
+ def self.encode_utf16le(str)
42
+ str = str.force_encoding('UTF-8') if [::Encoding::ASCII_8BIT,::Encoding::US_ASCII].include?(str.encoding)
43
+ str.dup.force_encoding('UTF-8').encode(Encoding::UTF_16LE, Encoding::UTF_8).force_encoding('UTF-8')
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,35 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ # base classes for primitives
5
+ # @private
6
+ class Field
7
+ attr_accessor :active, :value
8
+
9
+ def initialize(opts)
10
+ @value = opts[:value]
11
+ @active = opts[:active].nil? ? true : opts[:active]
12
+ @size = opts[:size].nil? ? 0 : opts[:size]
13
+ end
14
+
15
+ def size
16
+ @active ? @size : 0
17
+ end
18
+
19
+ # Serializer function for field data
20
+ # Exists in this class to be overridden by child classes
21
+ def serialize
22
+ raise NotImplementedError
23
+ end
24
+
25
+ # Parser function for field data
26
+ # Exists in this class to be overridden by child classes
27
+ def parse(str, offset=0)
28
+ raise NotImplementedError
29
+ end
30
+
31
+ end
32
+
33
+
34
+ end
35
+ end
@@ -0,0 +1,125 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ # base class of data structure
5
+ class FieldSet
6
+ class << FieldSet
7
+
8
+ # @macro string_security_buffer
9
+ # @method $1
10
+ # @method $1=
11
+ # @return [String]
12
+ def string(name, opts)
13
+ add_field(name, Net::NTLM::String, opts)
14
+ end
15
+
16
+ # @macro int16le_security_buffer
17
+ # @method $1
18
+ # @method $1=
19
+ # @return [Int16LE]
20
+ def int16LE(name, opts)
21
+ add_field(name, Net::NTLM::Int16LE, opts)
22
+ end
23
+
24
+ # @macro int32le_security_buffer
25
+ # @method $1
26
+ # @method $1=
27
+ # @return [Int32LE]
28
+ def int32LE(name, opts)
29
+ add_field(name, Net::NTLM::Int32LE, opts)
30
+ end
31
+
32
+ # @macro int64le_security_buffer
33
+ # @method $1
34
+ # @method $1=
35
+ # @return [Int64]
36
+ def int64LE(name, opts)
37
+ add_field(name, Net::NTLM::Int64LE, opts)
38
+ end
39
+
40
+ # @macro security_buffer
41
+ # @method $1
42
+ # @method $1=
43
+ # @return [SecurityBuffer]
44
+ def security_buffer(name, opts)
45
+ add_field(name, Net::NTLM::SecurityBuffer, opts)
46
+ end
47
+
48
+ def prototypes
49
+ @proto
50
+ end
51
+
52
+ def names
53
+ return [] if @proto.nil?
54
+ @proto.map{|n, t, o| n}
55
+ end
56
+
57
+ def types
58
+ return [] if @proto.nil?
59
+ @proto.map{|n, t, o| t}
60
+ end
61
+
62
+ def opts
63
+ return [] if @proto.nil?
64
+ @proto.map{|n, t, o| o}
65
+ end
66
+
67
+ private
68
+
69
+ def add_field(name, type, opts)
70
+ (@proto ||= []).push [name, type, opts]
71
+ define_accessor name
72
+ end
73
+
74
+ def define_accessor(name)
75
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
76
+ def #{name}
77
+ self['#{name}'].value
78
+ end
79
+
80
+ def #{name}=(val)
81
+ self['#{name}'].value = val
82
+ end
83
+ End
84
+ end
85
+ end
86
+
87
+ def initialize
88
+ @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
89
+ end
90
+
91
+ def serialize
92
+ @alist.map{|n, f| f.serialize }.join
93
+ end
94
+
95
+ def parse(str, offset=0)
96
+ @alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
97
+ end
98
+
99
+ def size
100
+ @alist.inject(0){|sum, a| sum += a[1].size}
101
+ end
102
+
103
+ def [](name)
104
+ a = @alist.assoc(name.to_s.intern)
105
+ raise ArgumentError, "no such field: #{name}" unless a
106
+ a[1]
107
+ end
108
+
109
+ def []=(name, val)
110
+ a = @alist.assoc(name.to_s.intern)
111
+ raise ArgumentError, "no such field: #{name}" unless a
112
+ a[1] = val
113
+ end
114
+
115
+ def enable(name)
116
+ self[name].active = true
117
+ end
118
+
119
+ def disable(name)
120
+ self[name].active = false
121
+ end
122
+ end
123
+
124
+ end
125
+ end
@@ -0,0 +1,26 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ class Int16LE < Field
5
+
6
+ def initialize(opt)
7
+ super(opt)
8
+ @size = 2
9
+ end
10
+
11
+ def parse(str, offset=0)
12
+ if @active and str.size >= offset + @size
13
+ @value = str[offset, @size].unpack("v")[0]
14
+ @size
15
+ else
16
+ 0
17
+ end
18
+ end
19
+
20
+ def serialize
21
+ [@value].pack("v")
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ class Int32LE < Field
5
+ def initialize(opt)
6
+ super(opt)
7
+ @size = 4
8
+ end
9
+
10
+ def parse(str, offset=0)
11
+ if @active and str.size >= offset + @size
12
+ @value = str.slice(offset, @size).unpack("V")[0]
13
+ @size
14
+ else
15
+ 0
16
+ end
17
+ end
18
+
19
+ def serialize
20
+ [@value].pack("V") if @active
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ class Int64LE < Field
5
+ def initialize(opt)
6
+ super(opt)
7
+ @size = 8
8
+ end
9
+
10
+ def parse(str, offset=0)
11
+ if @active and str.size >= offset + @size
12
+ d, u = str.slice(offset, @size).unpack("V2")
13
+ @value = (u * 0x100000000 + d)
14
+ @size
15
+ else
16
+ 0
17
+ end
18
+ end
19
+
20
+ def serialize
21
+ [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,115 @@
1
+ module Net
2
+ module NTLM
3
+
4
+ SSP_SIGN = "NTLMSSP\0"
5
+
6
+ FLAGS = {
7
+ :UNICODE => 0x00000001,
8
+ :OEM => 0x00000002,
9
+ :REQUEST_TARGET => 0x00000004,
10
+ :MBZ9 => 0x00000008,
11
+ :SIGN => 0x00000010,
12
+ :SEAL => 0x00000020,
13
+ :NEG_DATAGRAM => 0x00000040,
14
+ :NETWARE => 0x00000100,
15
+ :NTLM => 0x00000200,
16
+ :NEG_NT_ONLY => 0x00000400,
17
+ :MBZ7 => 0x00000800,
18
+ :DOMAIN_SUPPLIED => 0x00001000,
19
+ :WORKSTATION_SUPPLIED => 0x00002000,
20
+ :LOCAL_CALL => 0x00004000,
21
+ :ALWAYS_SIGN => 0x00008000,
22
+ :TARGET_TYPE_DOMAIN => 0x00010000,
23
+ :TARGET_INFO => 0x00800000,
24
+ :NTLM2_KEY => 0x00080000,
25
+ :KEY128 => 0x20000000,
26
+ :KEY56 => 0x80000000
27
+ }.freeze
28
+
29
+ FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
30
+
31
+ DEFAULT_FLAGS = {
32
+ :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
33
+ :TYPE2 => FLAGS[:UNICODE],
34
+ :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
35
+ }
36
+
37
+
38
+ # @private false
39
+ class Message < FieldSet
40
+ class << Message
41
+ def parse(str)
42
+ m = Type0.new
43
+ m.parse(str)
44
+ case m.type
45
+ when 1
46
+ t = Type1.parse(str)
47
+ when 2
48
+ t = Type2.parse(str)
49
+ when 3
50
+ t = Type3.parse(str)
51
+ else
52
+ raise ArgumentError, "unknown type: #{m.type}"
53
+ end
54
+ t
55
+ end
56
+
57
+ def decode64(str)
58
+ parse(Base64.decode64(str))
59
+ end
60
+ end
61
+
62
+ def has_flag?(flag)
63
+ (self[:flag].value & FLAGS[flag]) == FLAGS[flag]
64
+ end
65
+
66
+ def set_flag(flag)
67
+ self[:flag].value |= FLAGS[flag]
68
+ end
69
+
70
+ def dump_flags
71
+ FLAG_KEYS.each{ |k| print(k, "=", has_flag?(k), "\n") }
72
+ end
73
+
74
+ def serialize
75
+ deflag
76
+ super + security_buffers.map{|n, f| f.value}.join
77
+ end
78
+
79
+ def encode64
80
+ Base64.encode64(serialize).gsub(/\n/, '')
81
+ end
82
+
83
+ def decode64(str)
84
+ parse(Base64.decode64(str))
85
+ end
86
+
87
+ alias head_size size
88
+
89
+ def data_size
90
+ security_buffers.inject(0){|sum, a| sum += a[1].data_size}
91
+ end
92
+
93
+ def size
94
+ head_size + data_size
95
+ end
96
+
97
+
98
+ def security_buffers
99
+ @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
100
+ end
101
+
102
+ def deflag
103
+ security_buffers.inject(head_size){|cur, a|
104
+ a[1].offset = cur
105
+ cur += a[1].data_size
106
+ }
107
+ end
108
+
109
+ def data_edge
110
+ security_buffers.map{ |n, f| f.active ? f.offset : size}.min
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,16 @@
1
+ module Net
2
+ module NTLM
3
+ class Message
4
+
5
+ # sub class definitions
6
+ class Type0 < Message
7
+ string :sign, {:size => 8, :value => SSP_SIGN}
8
+ int32LE :type, {:value => 0}
9
+ end
10
+
11
+
12
+ end
13
+ end
14
+ end
15
+
16
+
@@ -0,0 +1,43 @@
1
+ module Net
2
+ module NTLM
3
+ class Message
4
+
5
+ # @private false
6
+ class Type1 < Message
7
+
8
+ string :sign, {:size => 8, :value => SSP_SIGN}
9
+ int32LE :type, {:value => 1}
10
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
11
+ security_buffer :domain, {:value => ""}
12
+ security_buffer :workstation, {:value => Socket.gethostname }
13
+ string :padding, {:size => 0, :value => "", :active => false }
14
+
15
+ class << Type1
16
+ # Parses a Type 1 Message
17
+ # @param [String] str A string containing Type 1 data
18
+ # @return [Type1] The parsed Type 1 message
19
+ def parse(str)
20
+ t = new
21
+ t.parse(str)
22
+ t
23
+ end
24
+ end
25
+
26
+ # @!visibility private
27
+ def parse(str)
28
+ super(str)
29
+ enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
30
+ enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
31
+ super(str)
32
+ if ( (len = data_edge - head_size) > 0)
33
+ self.padding = "\0" * len
34
+ super(str)
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+
43
+