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: []
|