net-smtp-ntlm 0.0.1
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 +7 -0
- data/CHANGELOG.md +3 -0
- data/README.md +27 -0
- data/lib/net/smtp/ntlm/auth_ntlm.rb +16 -0
- data/lib/net/smtp/ntlm/message.rb +365 -0
- data/lib/net/smtp/ntlm/util.rb +108 -0
- data/lib/net/smtp/ntlm/version.rb +5 -0
- data/lib/net/smtp/ntlm.rb +27 -0
- data/net-smtp-ntlm.gemspec +21 -0
- metadata +91 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ce835096d258af930fc3a8bbea0f30751c91c734cb900c7dea399f17c23230b3
|
|
4
|
+
data.tar.gz: 7bc58f9680c6b5c5ad2995fcf793f99be819b1b17d7b569f3d968aaf29f08693
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a3587cced6b74a95f84ac44a9653d4dcd69a4a743278ba16e707738aae875a48f70f62994b29e4da689779f16e8e5c55800aa887d65c66b7371221371f9e3639
|
|
7
|
+
data.tar.gz: d299b436f46e4afb903e286295a709c1ad776794a0c77fe66bf827b1a7e1aa207abfa50b911c90d0c220a28d2053905cfd25e9ee781fbc378fd3a3fc08dd0422
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Net::SMTP::NTLM
|
|
2
|
+
|
|
3
|
+
Add-on for [net-smtp](https://github.com/ruby/net-smtp/) gem to add NTLM support.
|
|
4
|
+
|
|
5
|
+
Based on [ruby-ntlm](https://github.com/macks/ruby-ntlm) gem.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'net-smtp-ntlm'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
$ gem install net-smtp-ntlm
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
## Contributing
|
|
26
|
+
|
|
27
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/net-smtp.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Net::SMTP::NTLM
|
|
2
|
+
class AuthNtlm < Net::SMTP::Authenticator
|
|
3
|
+
auth_type :ntlm
|
|
4
|
+
|
|
5
|
+
def auth(user, secret)
|
|
6
|
+
if user.index('\\')
|
|
7
|
+
domain, user = user.split('\\', 2)
|
|
8
|
+
else
|
|
9
|
+
domain = ''
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
challenge = continue("AUTH NTLM #{Net::SMTP::NTLM.negotiate.to_base64}")
|
|
13
|
+
finish(Net::SMTP::NTLM.authenticate(challenge.unpack('m').first, user, domain, secret).to_base64)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
require 'net/smtp/ntlm/util'
|
|
2
|
+
|
|
3
|
+
module Net::SMTP::NTLM
|
|
4
|
+
class Message
|
|
5
|
+
|
|
6
|
+
include Net::SMTP::NTLM::Util
|
|
7
|
+
|
|
8
|
+
SSP_SIGNATURE = "NTLMSSP\0"
|
|
9
|
+
|
|
10
|
+
# [MS-NLMP] 2.2.2.5
|
|
11
|
+
FLAGS = {
|
|
12
|
+
:NEGOTIATE_UNICODE => 0x00000001, # Unicode character set encoding
|
|
13
|
+
:NEGOTIATE_OEM => 0x00000002, # OEM character set encoding
|
|
14
|
+
:REQUEST_TARGET => 0x00000004, # TargetName is supplied in challenge message
|
|
15
|
+
:UNUSED10 => 0x00000008,
|
|
16
|
+
:NEGOTIATE_SIGN => 0x00000010, # Session key negotiation for message signatures
|
|
17
|
+
:NEGOTIATE_SEAL => 0x00000020, # Session key negotiation for message confidentiality
|
|
18
|
+
:NEGOTIATE_DATAGRAM => 0x00000040, # Connectionless authentication
|
|
19
|
+
:NEGOTIATE_LM_KEY => 0x00000080, # LAN Manager session key computation
|
|
20
|
+
:UNUSED9 => 0x00000100,
|
|
21
|
+
:NEGOTIATE_NTLM => 0x00000200, # NTLM v1 protocol
|
|
22
|
+
:UNUSED8 => 0x00000400,
|
|
23
|
+
:ANONYMOUS => 0x00000800, # Anonymous connection
|
|
24
|
+
:OEM_DOMAIN_SUPPLIED => 0x00001000, # Domain field is present
|
|
25
|
+
:OEM_WORKSTATION_SUPPLIED => 0x00002000, # Workstations field is present
|
|
26
|
+
:UNUSED7 => 0x00004000,
|
|
27
|
+
:NEGOTIATE_ALWAYS_SIGN => 0x00008000,
|
|
28
|
+
:TARGET_TYPE_DOMAIN => 0x00010000, # TargetName is domain name
|
|
29
|
+
:TARGET_TYPE_SERVER => 0x00020000, # TargetName is server name
|
|
30
|
+
:UNUSED6 => 0x00040000,
|
|
31
|
+
:NEGOTIATE_EXTENDED_SECURITY => 0x00080000, # NTLM v2 session security
|
|
32
|
+
:NEGOTIATE_IDENTIFY => 0x00100000, # Requests identify level token
|
|
33
|
+
:UNUSED5 => 0x00200000,
|
|
34
|
+
:REQUEST_NON_NT_SESSION_KEY => 0x00400000, # LM session key is used
|
|
35
|
+
:NEGOTIATE_TARGET_INFO => 0x00800000, # Requests TargetInfo
|
|
36
|
+
:UNUSED4 => 0x01000000,
|
|
37
|
+
:NEGOTIATE_VERSION => 0x02000000, # Version field is present
|
|
38
|
+
:UNUSED3 => 0x04000000,
|
|
39
|
+
:UNUSED2 => 0x08000000,
|
|
40
|
+
:UNUSED1 => 0x10000000,
|
|
41
|
+
:NEGOTIATE_128 => 0x20000000, # 128bit encryption
|
|
42
|
+
:NEGOTIATE_KEY_EXCH => 0x40000000, # Explicit key exchange
|
|
43
|
+
:NEGOTIATE_56 => 0x80000000, # 56bit encryption
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# [MS-NLMP] 2.2.2.1
|
|
47
|
+
AV_PAIRS = {
|
|
48
|
+
:AV_EOL => 0,
|
|
49
|
+
:AV_NB_COMPUTER_NAME => 1,
|
|
50
|
+
:AV_NB_DOMAIN_NAME => 2,
|
|
51
|
+
:AV_DNS_COMPUTER_NAME => 3,
|
|
52
|
+
:AV_DNS_DOMAIN_NAME => 4,
|
|
53
|
+
:AV_DNS_TREE_NAME => 5,
|
|
54
|
+
:AV_FLAGS => 6,
|
|
55
|
+
:AV_TIMESTAMP => 7,
|
|
56
|
+
:AV_RESTRICTIONS => 8,
|
|
57
|
+
:AV_TARGET_NAME => 9,
|
|
58
|
+
:AV_CHANNEL_BINDINGS => 10,
|
|
59
|
+
}
|
|
60
|
+
AV_PAIR_NAMES = AV_PAIRS.invert
|
|
61
|
+
|
|
62
|
+
FLAGS.each do |name, val|
|
|
63
|
+
const_set(name, val)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
AV_PAIRS.each do |name, val|
|
|
67
|
+
const_set(name, val)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class ParseError < StandardError; end
|
|
71
|
+
|
|
72
|
+
attr_accessor :flag
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def self.parse(*args)
|
|
76
|
+
new.parse(*args)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def initialize(args = {})
|
|
80
|
+
@buffer = ''
|
|
81
|
+
@offset = 0
|
|
82
|
+
@flag = args[:flag] || self.class::DEFAULT_FLAGS
|
|
83
|
+
@domain = nil
|
|
84
|
+
@workstation = nil
|
|
85
|
+
@version = nil
|
|
86
|
+
@target_info = nil
|
|
87
|
+
@session_key = nil
|
|
88
|
+
@mic = nil
|
|
89
|
+
|
|
90
|
+
self.class::ATTRIBUTES.each do |key|
|
|
91
|
+
instance_variable_set("@#{key}", args[key]) if args[key]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def to_s
|
|
96
|
+
serialize
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def serialize_to_base64
|
|
100
|
+
[serialize].pack('m').delete("\r\n")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
alias to_base64 serialize_to_base64
|
|
104
|
+
|
|
105
|
+
def has_flag?(symbol)
|
|
106
|
+
(@flag & FLAGS[symbol]) != 0
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def set(symbol)
|
|
110
|
+
@flag |= FLAGS[symbol]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def clear(symbol)
|
|
114
|
+
@flag &= ~FLAGS[symbol]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def unicode?
|
|
118
|
+
has_flag?(:NEGOTIATE_UNICODE)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def inspect_flags
|
|
122
|
+
flags = []
|
|
123
|
+
FLAGS.sort_by(&:last).each do |name, val|
|
|
124
|
+
flags << name if (@flag & val).nonzero?
|
|
125
|
+
end
|
|
126
|
+
"[#{flags.join(', ')}]"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def inspect
|
|
130
|
+
variables = (instance_variables.map(&:to_sym) - [:@offset, :@buffer, :@flag]).sort.map {|name| "#{name}=#{instance_variable_get(name).inspect}, " }.join
|
|
131
|
+
"\#<#{self.class.name} #{variables}@flag=#{inspect_flags}>"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def parse(string)
|
|
137
|
+
@buffer = string
|
|
138
|
+
signature, type = string.unpack('a8V')
|
|
139
|
+
raise ParseError, 'Unknown signature' if signature != SSP_SIGNATURE
|
|
140
|
+
raise ParseError, "Wrong type (expected #{self.class::TYPE}, but got #{type})" if type != self.class::TYPE
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def append_payload(string, allocation_size = nil)
|
|
144
|
+
size = string.size
|
|
145
|
+
allocation_size ||= (size + 1) & ~1
|
|
146
|
+
string = string.ljust(allocation_size, "\0")
|
|
147
|
+
@buffer << string[0, allocation_size]
|
|
148
|
+
result = [size, allocation_size, @offset].pack('vvV')
|
|
149
|
+
@offset += allocation_size
|
|
150
|
+
result
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def fetch_payload(fields)
|
|
154
|
+
size, _, offset = fields.unpack('vvV')
|
|
155
|
+
return nil if size.zero?
|
|
156
|
+
@buffer[offset, size]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def encode_version(array)
|
|
160
|
+
array.pack('CCvx3C') # major, minor, build, ntlm revision
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def decode_version(string)
|
|
164
|
+
string.unpack('CCvx3C') # major, minor, build, ntlm revision
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def decode_av_pair(string)
|
|
168
|
+
result = []
|
|
169
|
+
string = string.dup
|
|
170
|
+
while true
|
|
171
|
+
id, length = string.slice!(0, 4).unpack('vv')
|
|
172
|
+
value = string.slice!(0, length)
|
|
173
|
+
|
|
174
|
+
case sym = AV_PAIR_NAMES[id]
|
|
175
|
+
when :AV_EOL
|
|
176
|
+
break
|
|
177
|
+
when :AV_NB_COMPUTER_NAME, :AV_NB_DOMAIN_NAME, :AV_DNS_COMPUTER_NAME, :AV_DNS_DOMAIN_NAME, :AV_DNS_TREE_NAME, :AV_TARGET_NAME
|
|
178
|
+
value = decode_utf16(value)
|
|
179
|
+
when :AV_FLAGS
|
|
180
|
+
value = data.unpack('V').first
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
result << [sym, value]
|
|
184
|
+
end
|
|
185
|
+
result
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def encode_av_pair(av_pair)
|
|
189
|
+
result = ''
|
|
190
|
+
av_pair.each do |(id, value)|
|
|
191
|
+
case id
|
|
192
|
+
when :AV_NB_COMPUTER_NAME, :AV_NB_DOMAIN_NAME, :AV_DNS_COMPUTER_NAME, :AV_DNS_DOMAIN_NAME, :AV_DNS_TREE_NAME, :AV_TARGET_NAME
|
|
193
|
+
value = encode_utf16(value)
|
|
194
|
+
when :AV_FLAGS
|
|
195
|
+
value = [data].pack('V')
|
|
196
|
+
end
|
|
197
|
+
result << [AV_PAIRS[id], value.size, value].pack('vva*')
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
result << [AV_EOL, 0].pack('vv')
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
# [MS-NLMP] 2.2.1.1
|
|
205
|
+
class Negotiate < Message
|
|
206
|
+
|
|
207
|
+
TYPE = 1
|
|
208
|
+
ATTRIBUTES = [:domain, :workstation, :version]
|
|
209
|
+
DEFAULT_FLAGS = [NEGOTIATE_UNICODE, NEGOTIATE_OEM, REQUEST_TARGET, NEGOTIATE_NTLM, NEGOTIATE_ALWAYS_SIGN, NEGOTIATE_EXTENDED_SECURITY].inject(:|)
|
|
210
|
+
|
|
211
|
+
attr_accessor(*ATTRIBUTES)
|
|
212
|
+
|
|
213
|
+
def parse(string)
|
|
214
|
+
super
|
|
215
|
+
@flag, domain, workstation, version = string.unpack('x12Va8a8a8')
|
|
216
|
+
@domain = fetch_payload(domain) if has_flag?(:OEM_DOMAIN_SUPPLIED)
|
|
217
|
+
@workstation = fetch_payload(workstation) if has_flag?(:OEM_WORKSTATION_SUPPLIED)
|
|
218
|
+
@version = decode_version(version) if has_flag?(:NEGOTIATE_VERSION)
|
|
219
|
+
self
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def serialize
|
|
223
|
+
@buffer = ''
|
|
224
|
+
@offset = 40 # (8 + 4) + 4 + (8 * 3)
|
|
225
|
+
|
|
226
|
+
if @domain
|
|
227
|
+
set(:OEM_DOMAIN_SUPPLIED)
|
|
228
|
+
domain = append_payload(@domain)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
if @workstation
|
|
232
|
+
set(:OEM_WORKSTATION_SUPPLIED)
|
|
233
|
+
workstation = append_payload(@workstation)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
if @version
|
|
237
|
+
set(:NEGOTIATE_VERSION)
|
|
238
|
+
version = encode_version(@version)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
[SSP_SIGNATURE, TYPE, @flag, domain, workstation, version].pack('a8VVa8a8a8') + @buffer
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
end # Negotiate
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# [MS-NLMP] 2.2.1.2
|
|
248
|
+
class Challenge < Message
|
|
249
|
+
|
|
250
|
+
TYPE = 2
|
|
251
|
+
ATTRIBUTES = [:target_name, :challenge, :target_info, :version]
|
|
252
|
+
DEFAULT_FLAGS = 0
|
|
253
|
+
|
|
254
|
+
attr_accessor(*ATTRIBUTES)
|
|
255
|
+
|
|
256
|
+
def parse(string)
|
|
257
|
+
super
|
|
258
|
+
target_name, @flag, @challenge, target_info, version = string.unpack('x12a8Va8x8a8a8')
|
|
259
|
+
@target_name = fetch_payload(target_name) if has_flag?(:REQUEST_TARGET)
|
|
260
|
+
@target_info = fetch_payload(target_info) if has_flag?(:NEGOTIATE_TARGET_INFO)
|
|
261
|
+
@version = decode_version(version) if has_flag?(:NEGOTIATE_VERSION)
|
|
262
|
+
|
|
263
|
+
@target_name &&= decode_utf16(@target_name) if unicode?
|
|
264
|
+
@target_info &&= decode_av_pair(@target_info)
|
|
265
|
+
|
|
266
|
+
self
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def serialize
|
|
270
|
+
@buffer = ''
|
|
271
|
+
@offset = 56 # (8 + 4) + 8 + 4 + (8 * 4)
|
|
272
|
+
|
|
273
|
+
@challenge ||= OpenSSL::Random.random_bytes(8)
|
|
274
|
+
|
|
275
|
+
if @target_name
|
|
276
|
+
set(:REQUEST_TARGET)
|
|
277
|
+
if unicode?
|
|
278
|
+
target_name = append_payload(encode_utf16(@target_name))
|
|
279
|
+
else
|
|
280
|
+
target_name = append_payload(@target_name)
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
if @target_info
|
|
285
|
+
set(:NEGOTIATE_TARGET_INFO)
|
|
286
|
+
target_info = append_payload(encode_av_pair(@target_info))
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
if @version
|
|
290
|
+
set(:NEGOTIATE_VERSION)
|
|
291
|
+
version = encode_version(@version)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
[SSP_SIGNATURE, TYPE, target_name, @flag, @challenge, target_info, version].pack('a8Va8Va8x8a8a8') + @buffer
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
end # Challenge
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# [MS-NLMP] 2.2.1.3
|
|
301
|
+
class Authenticate < Message
|
|
302
|
+
|
|
303
|
+
TYPE = 3
|
|
304
|
+
ATTRIBUTES = [:lm_response, :nt_response, :domain, :user, :workstation, :session_key, :version, :mic]
|
|
305
|
+
DEFAULT_FLAGS = [NEGOTIATE_UNICODE, REQUEST_TARGET, NEGOTIATE_NTLM, NEGOTIATE_ALWAYS_SIGN, NEGOTIATE_EXTENDED_SECURITY].inject(:|)
|
|
306
|
+
|
|
307
|
+
attr_accessor(*ATTRIBUTES)
|
|
308
|
+
|
|
309
|
+
def parse(string)
|
|
310
|
+
super
|
|
311
|
+
lm_response, nt_response, domain, user, workstation, session_key, @flag, version, mic = \
|
|
312
|
+
string.unpack('x12a8a8a8a8a8a8Va8a16')
|
|
313
|
+
|
|
314
|
+
@lm_response = fetch_payload(lm_response)
|
|
315
|
+
@nt_response = fetch_payload(nt_response)
|
|
316
|
+
@domain = fetch_payload(domain)
|
|
317
|
+
@user = fetch_payload(user)
|
|
318
|
+
@workstation = fetch_payload(workstation)
|
|
319
|
+
@session_key = fetch_payload(session_key) if has_flag?(:NEGOTIATE_KEY_EXCH)
|
|
320
|
+
@version = decode_version(version) if has_flag?(:NEGOTIATE_VERSION)
|
|
321
|
+
@mic = mic
|
|
322
|
+
|
|
323
|
+
if unicode?
|
|
324
|
+
@domain = decode_utf16(@domain)
|
|
325
|
+
@user = decode_utf16(@user)
|
|
326
|
+
@workstation = decode_utf16(@workstation)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
self
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def serialize
|
|
333
|
+
@buffer = ''
|
|
334
|
+
@offset = 88 # (8 + 4) + (8 * 6) + 4 + 8 + 16
|
|
335
|
+
|
|
336
|
+
lm_response = append_payload(@lm_response)
|
|
337
|
+
nt_response = append_payload(@nt_response)
|
|
338
|
+
|
|
339
|
+
if unicode?
|
|
340
|
+
domain = append_payload(encode_utf16(@domain))
|
|
341
|
+
user = append_payload(encode_utf16(@user))
|
|
342
|
+
workstation = append_payload(encode_utf16(@workstation))
|
|
343
|
+
else
|
|
344
|
+
domain = append_payload(@domain)
|
|
345
|
+
user = append_payload(@user)
|
|
346
|
+
workstation = append_payload(@workstation)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
if @session_key
|
|
350
|
+
set(:NEGOTIATE_KEY_EXCH)
|
|
351
|
+
session_key = append_payload(@session_key)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
if @version
|
|
355
|
+
set(:NEGOTIATE_VERSION)
|
|
356
|
+
version = encode_version(@version)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
[SSP_SIGNATURE, TYPE, lm_response, nt_response, domain, user, workstation, session_key, @flag, version, @mic].pack('a8Va8a8a8a8a8a8Va8a16') + @buffer
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
end # Authenticate
|
|
363
|
+
|
|
364
|
+
end # Message
|
|
365
|
+
end # NTLM
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require 'openssl'
|
|
2
|
+
|
|
3
|
+
module Net::SMTP::NTLM
|
|
4
|
+
module Util
|
|
5
|
+
|
|
6
|
+
LM_MAGIC_TEXT = 'KGS!@#$%'
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
if RUBY_VERSION >= '1.9'
|
|
11
|
+
|
|
12
|
+
def decode_utf16(str)
|
|
13
|
+
str.encode(Encoding::UTF_8, Encoding::UTF_16LE)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def encode_utf16(str)
|
|
17
|
+
str.to_s.encode(Encoding::UTF_16LE).force_encoding(Encoding::ASCII_8BIT)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
else
|
|
21
|
+
|
|
22
|
+
require 'iconv'
|
|
23
|
+
|
|
24
|
+
def decode_utf16(str)
|
|
25
|
+
Iconv.conv('UTF-8', 'UTF-16LE', str)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def encode_utf16(str)
|
|
29
|
+
Iconv.conv('UTF-16LE', 'UTF-8', str)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_des_keys(string)
|
|
35
|
+
keys = []
|
|
36
|
+
string = string.dup
|
|
37
|
+
until (key = string.slice!(0, 7)).empty?
|
|
38
|
+
# key is 56 bits
|
|
39
|
+
key = key.unpack('B*').first
|
|
40
|
+
str = ''
|
|
41
|
+
until (bits = key.slice!(0, 7)).empty?
|
|
42
|
+
str << bits
|
|
43
|
+
str << (bits.count('1').even? ? '1' : '0') # parity
|
|
44
|
+
end
|
|
45
|
+
keys << [str].pack('B*')
|
|
46
|
+
end
|
|
47
|
+
keys
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def encrypt(plain_text, key, key_length)
|
|
51
|
+
key = key.ljust(key_length, "\0")
|
|
52
|
+
keys = create_des_keys(key[0, key_length])
|
|
53
|
+
|
|
54
|
+
result = ''
|
|
55
|
+
cipher = OpenSSL::Cipher::DES.new
|
|
56
|
+
keys.each do |k|
|
|
57
|
+
cipher.encrypt
|
|
58
|
+
cipher.key = k
|
|
59
|
+
|
|
60
|
+
encrypted_text = cipher.update(plain_text)
|
|
61
|
+
encrypted_text << cipher.final
|
|
62
|
+
result << encrypted_text[0...8]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# [MS-NLMP] 3.3.1
|
|
69
|
+
def lm_v1_hash(password)
|
|
70
|
+
encrypt(LM_MAGIC_TEXT, password.upcase, 14)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# [MS-NLMP] 3.3.1
|
|
74
|
+
def nt_v1_hash(password)
|
|
75
|
+
OpenSSL::Digest::MD4.digest(encode_utf16(password))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# [MS-NLMP] 3.3.1
|
|
79
|
+
def ntlm_v1_response(challenge, password, options = {})
|
|
80
|
+
if options[:ntlm_v2_session]
|
|
81
|
+
challenge = challenge.b if challenge.respond_to?(:b)
|
|
82
|
+
client_challenge = options[:client_challenge] || OpenSSL::Random.random_bytes(8)
|
|
83
|
+
client_challenge = client_challenge.b if client_challenge.respond_to?(:b)
|
|
84
|
+
hash = OpenSSL::Digest::MD5.digest(challenge + client_challenge)[0, 8]
|
|
85
|
+
nt_response = encrypt(hash, nt_v1_hash(password), 21)
|
|
86
|
+
lm_response = client_challenge + ("\0" * 16)
|
|
87
|
+
else
|
|
88
|
+
nt_response = encrypt(challenge, nt_v1_hash(password), 21)
|
|
89
|
+
lm_response = encrypt(challenge, lm_v1_hash(password), 21)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
[nt_response, lm_response]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# [MS-NLMP] 3.3.2
|
|
97
|
+
def nt_v2_hash(user, password, domain)
|
|
98
|
+
user_domain = encode_utf16(user.upcase + domain)
|
|
99
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, nt_v1_hash(password), user_domain)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# [MS-NLMP] 3.3.2
|
|
103
|
+
def ntlm_v2_response(*)
|
|
104
|
+
raise NotImplemnetedError
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end # Util
|
|
108
|
+
end # NTLM
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'net/smtp'
|
|
2
|
+
require 'net/smtp/ntlm/util'
|
|
3
|
+
require 'net/smtp/ntlm/message'
|
|
4
|
+
require 'net/smtp/ntlm/auth_ntlm'
|
|
5
|
+
|
|
6
|
+
module Net::SMTP::NTLM
|
|
7
|
+
def self.negotiate(args = {})
|
|
8
|
+
Message::Negotiate.new(args)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.authenticate(challenge_message, user, domain, password, options = {})
|
|
12
|
+
challenge = Net::SMTP::NTLM::Message::Challenge.parse(challenge_message)
|
|
13
|
+
|
|
14
|
+
opt = options.merge({
|
|
15
|
+
:ntlm_v2_session => challenge.has_flag?(:NEGOTIATE_EXTENDED_SECURITY),
|
|
16
|
+
})
|
|
17
|
+
nt_response, lm_response = Net::SMTP::NTLM::Util.ntlm_v1_response(challenge.challenge, password, opt)
|
|
18
|
+
|
|
19
|
+
Net::SMTP::NTLM::Message::Authenticate.new(
|
|
20
|
+
:user => user,
|
|
21
|
+
:domain => domain,
|
|
22
|
+
:lm_response => lm_response,
|
|
23
|
+
:nt_response => nt_response
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end # NTLM
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), 'lib')
|
|
2
|
+
require 'net/smtp/ntlm/version'
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |spec|
|
|
5
|
+
spec.name = 'net-smtp-ntlm'
|
|
6
|
+
spec.version = Net::SMTP::NTLM::VERSION
|
|
7
|
+
spec.summary = 'Add-on for net-smtp gem to add NTLM support.'
|
|
8
|
+
spec.description = 'Add-on for net-smtp gem to add NTLM support.'
|
|
9
|
+
|
|
10
|
+
spec.authors = ['Fedosov Sergey (RnD Soft)']
|
|
11
|
+
spec.email = 'info@rnds.pro'
|
|
12
|
+
spec.homepage = 'https://rnds.pro'
|
|
13
|
+
|
|
14
|
+
spec.add_runtime_dependency 'net-smtp', '~> 0.4.0'
|
|
15
|
+
|
|
16
|
+
spec.add_development_dependency "rake"
|
|
17
|
+
spec.add_development_dependency "test-unit"
|
|
18
|
+
|
|
19
|
+
spec.files = %w[README.md CHANGELOG.md net-smtp-ntlm.gemspec] + Dir['lib/**/*.rb']
|
|
20
|
+
spec.require_paths = ['lib']
|
|
21
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: net-smtp-ntlm
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Fedosov Sergey (RnD Soft)
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date:
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: net-smtp
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.4.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.4.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: test-unit
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
description: Add-on for net-smtp gem to add NTLM support.
|
|
56
|
+
email: info@rnds.pro
|
|
57
|
+
executables: []
|
|
58
|
+
extensions: []
|
|
59
|
+
extra_rdoc_files: []
|
|
60
|
+
files:
|
|
61
|
+
- README.md
|
|
62
|
+
- CHANGELOG.md
|
|
63
|
+
- net-smtp-ntlm.gemspec
|
|
64
|
+
- lib/net/smtp/ntlm/auth_ntlm.rb
|
|
65
|
+
- lib/net/smtp/ntlm/message.rb
|
|
66
|
+
- lib/net/smtp/ntlm/util.rb
|
|
67
|
+
- lib/net/smtp/ntlm/version.rb
|
|
68
|
+
- lib/net/smtp/ntlm.rb
|
|
69
|
+
homepage: https://rnds.pro
|
|
70
|
+
licenses: []
|
|
71
|
+
metadata: {}
|
|
72
|
+
post_install_message:
|
|
73
|
+
rdoc_options: []
|
|
74
|
+
require_paths:
|
|
75
|
+
- lib
|
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '0'
|
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
|
+
requirements:
|
|
83
|
+
- - ">="
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
requirements: []
|
|
87
|
+
rubygems_version: 3.3.26
|
|
88
|
+
signing_key:
|
|
89
|
+
specification_version: 4
|
|
90
|
+
summary: Add-on for net-smtp gem to add NTLM support.
|
|
91
|
+
test_files: []
|