ruby-ntlm-namespace 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.
data/README.markdown ADDED
@@ -0,0 +1,77 @@
1
+ ruby-ntlm
2
+ =========
3
+
4
+ ruby-ntlm is NTLM authentication client for Ruby.
5
+ This library supports NTLM v1 only.
6
+
7
+ NTLM authentication is used in Microsoft's server products,
8
+ such as MS Exchange Server and IIS.
9
+
10
+ ruby-ntlm-namespace
11
+ -------------------
12
+
13
+ This is a fork of [mademaxus' namespace fix](https://github.com/mademaxus/ruby-ntlm) for the [macks' ruby-ntlm](https://github.com/macks/ruby-ntlm).
14
+
15
+ This gem release is only meant to help those experiencing the namespace issues with macks' ruby-ntlm and a simple dropin for Ruby on Rails gem files.
16
+
17
+
18
+ Install
19
+ -------
20
+
21
+ $ sudo gem install ruby-ntlm
22
+
23
+
24
+ Usage
25
+ -----
26
+
27
+ ### HTTP ###
28
+
29
+ require 'ntlm/http'
30
+ http = Net::HTTP.new('www.example.com')
31
+ request = Net::HTTP::Get.new('/')
32
+ request.ntlm_auth('User', 'Domain', 'Password')
33
+ response = http.request(request)
34
+
35
+ ### HTTP (using Mechanize) ###
36
+
37
+ require 'ntlm/mechanize'
38
+ mech = Mechanize.new
39
+ mech.auth('Domain\\User', 'Password')
40
+ mech.get('http://www.example.com/index.html')
41
+
42
+ ### IMAP ###
43
+
44
+ require 'ntlm/imap'
45
+ imap = Net::IMAP.new('imap.example.com')
46
+ imap.authenticate('NTLM', 'User', 'Domain', 'Password')
47
+
48
+ ### SMTP ###
49
+
50
+ require 'ntlm/smtp'
51
+ smtp = Net::SMTP.new('smtp.example.com')
52
+ smtp.start('localhost.localdomain', 'Domain\\User', 'Password', :ntlm) do |smtp|
53
+ smtp.send_mail(mail_body, from_addr, to_addr)
54
+ end
55
+
56
+
57
+ Author
58
+ ------
59
+
60
+ MATSUYAMA Kengo (<macksx@gmail.com>)
61
+
62
+
63
+ License
64
+ -------
65
+
66
+ MIT License.
67
+
68
+ Copyright (c) 2010 MATSUYAMA Kengo
69
+
70
+
71
+ References
72
+ ----------
73
+
74
+ * [MS-NLMP][]: NT LAN Manager (NTLM) Authentication Protocol Specification
75
+ [MS-NLMP]: http://msdn.microsoft.com/en-us/library/cc236621%28PROT.13%29.aspx
76
+ * [Ruby/NTLM][]: Another NTLM implementation for Ruby
77
+ [Ruby/NTLM]: http://rubyforge.org/projects/rubyntlm/
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'jeweler'
4
+ require 'rake/testtask'
5
+
6
+ task :default => :test
7
+
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = 'ruby-ntlm'
10
+ gem.summary = %Q{NTLM implementation for Ruby}
11
+ gem.description = %Q{NTLM implementation for Ruby.}
12
+ gem.email = 'macksx@gmail.com'
13
+ gem.homepage = 'http://github.com/macks/ruby-ntlm'
14
+ gem.authors = ['MATSUYAMA Kengo']
15
+ end
16
+
17
+ Rake::TestTask.new(:test) do |task|
18
+ task.libs << 'lib:test'
19
+ task.pattern = 'test/**/*_test.rb'
20
+ task.verbose = true
21
+ end
22
+
23
+ begin
24
+ require 'rcov/rcovtask'
25
+ Rcov::RcovTask.new do |task|
26
+ task.libs << 'lib:test'
27
+ task.pattern = 'test/**/*_test.rb'
28
+ task.verbose = true
29
+ end
30
+ rescue LoadError
31
+ task :rcov do
32
+ abort 'rcov is not available.'
33
+ end
34
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/lib/ntlm.rb ADDED
@@ -0,0 +1,34 @@
1
+ # vim: set et sw=2 sts=2:
2
+
3
+ require 'ntlm/util'
4
+ require 'ntlm/message'
5
+
6
+ module NTLM
7
+
8
+ begin
9
+ Version = File.read(File.dirname(__FILE__) + '/../VERSION').strip
10
+ rescue
11
+ Version = 'unknown'
12
+ end
13
+
14
+ def self.negotiate(args = {})
15
+ Message::Negotiate.new(args)
16
+ end
17
+
18
+ def self.authenticate(challenge_message, user, domain, password, options = {})
19
+ challenge = Message::Challenge.parse(challenge_message)
20
+
21
+ opt = options.merge({
22
+ :ntlm_v2_session => challenge.has_flag?(:NEGOTIATE_EXTENDED_SECURITY),
23
+ })
24
+ nt_response, lm_response = Util.ntlm_v1_response(challenge.challenge, password, opt)
25
+
26
+ Message::Authenticate.new(
27
+ :user => user,
28
+ :domain => domain,
29
+ :lm_response => lm_response,
30
+ :nt_response => nt_response
31
+ )
32
+ end
33
+
34
+ end # NTLM
data/lib/ntlm/http.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'ntlm'
2
+ require 'net/http'
3
+
4
+ module Net
5
+
6
+ module HTTPHeader
7
+ attr_reader :ntlm_auth_params
8
+
9
+ def ntlm_auth(user, domain, password)
10
+ @ntlm_auth_params = [user, domain, password]
11
+ end
12
+ end
13
+
14
+ class HTTP
15
+
16
+ unless method_defined?(:request_without_ntlm_auth)
17
+ alias request_without_ntlm_auth request
18
+ end
19
+
20
+ def request(req, body = nil, &block)
21
+ unless req.ntlm_auth_params
22
+ return request_without_ntlm_auth(req, body, &block)
23
+ end
24
+
25
+ unless started?
26
+ start do
27
+ req.delete('connection')
28
+ return request(req, body, &block)
29
+ end
30
+ end
31
+
32
+ # Negotiation
33
+ req['authorization'] = 'NTLM ' + ::NTLM.negotiate.to_base64
34
+ res = request_without_ntlm_auth(req, body)
35
+ challenge = res['www-authenticate'][/NTLM (.*)/, 1].unpack('m').first rescue nil
36
+
37
+ if challenge && res.code == '401'
38
+ # Authentication
39
+ user, domain, password = req.ntlm_auth_params
40
+ req['authorization'] = 'NTLM ' + ::NTLM.authenticate(challenge, user, domain, password).to_base64
41
+ req.body_stream.rewind if req.body_stream
42
+ request_without_ntlm_auth(req, body, &block) # We must re-use the connection.
43
+ else
44
+ yield res if block_given?
45
+ res
46
+ end
47
+ end
48
+
49
+ end # HTTP
50
+
51
+ end # Net
data/lib/ntlm/imap.rb ADDED
@@ -0,0 +1,37 @@
1
+ require 'ntlm'
2
+ require 'net/imap'
3
+
4
+ module Net
5
+ class IMAP
6
+ class ResponseParser
7
+ def continue_req
8
+ match(T_PLUS)
9
+ if lookahead.symbol == T_CRLF
10
+ return ContinuationRequest.new(ResponseText.new(nil, ''), @str)
11
+ else
12
+ match(T_SPACE)
13
+ return ContinuationRequest.new(resp_text, @str)
14
+ end
15
+ end
16
+ end # ResponseParser
17
+
18
+ class NTLMAuthenticator
19
+ def initialize(user, domain, password)
20
+ @user, @domain, @password = user, domain, password
21
+ @state = 0
22
+ end
23
+
24
+ def process(data)
25
+ case (@state += 1)
26
+ when 1
27
+ ::NTLM.negotiate.to_s
28
+ when 2
29
+ ::NTLM.authenticate(data, @user, @domain, @password).to_s
30
+ end
31
+ end
32
+ end # NTLMAuthenticator
33
+
34
+ add_authenticator 'NTLM', NTLMAuthenticator
35
+
36
+ end # IMAP
37
+ end # Net
@@ -0,0 +1,42 @@
1
+ require 'mechanize'
2
+ require 'ntlm/http'
3
+
4
+ class Mechanize
5
+ class Chain
6
+ class AuthHeaders
7
+
8
+ unless method_defined?(:handle_without_ntlm)
9
+ alias handle_without_ntlm handle
10
+ end
11
+
12
+ def handle(ctx, params)
13
+ if @auth_hash[params[:uri].host] == :ntlm && @user && @password
14
+ if @user.index('\\')
15
+ domain, user = @user.split('\\', 2)
16
+ end
17
+ params[:request].ntlm_auth(user, domain, @password)
18
+ end
19
+ handle_without_ntlm(ctx, params)
20
+ end
21
+ end
22
+ end
23
+
24
+ unless private_method_defined?(:fetch_page_without_ntlm)
25
+ alias fetch_page_without_ntlm fetch_page
26
+ end
27
+
28
+ private
29
+
30
+ def fetch_page(params)
31
+ begin
32
+ fetch_page_without_ntlm(params)
33
+ rescue Mechanize::ResponseCodeError => e
34
+ if e.response_code == '401' && e.page.header['www-authenticate'] =~ /NTLM/ && @auth_hash[e.page.uri.host] != :ntlm
35
+ @auth_hash[e.page.uri.host] = :ntlm
36
+ fetch_page_without_ntlm(params)
37
+ else
38
+ raise
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,361 @@
1
+ # vim: set et sw=2 sts=2:
2
+
3
+ require 'ntlm/util'
4
+
5
+ module NTLM
6
+ class Message
7
+
8
+ include Util
9
+
10
+ SSP_SIGNATURE = "NTLMSSP\0"
11
+
12
+ # [MS-NLMP] 2.2.2.5
13
+ FLAGS = {
14
+ :NEGOTIATE_UNICODE => 0x00000001, # Unicode character set encoding
15
+ :NEGOTIATE_OEM => 0x00000002, # OEM character set encoding
16
+ :REQUEST_TARGET => 0x00000004, # TargetName is supplied in challenge message
17
+ :UNUSED10 => 0x00000008,
18
+ :NEGOTIATE_SIGN => 0x00000010, # Session key negotiation for message signatures
19
+ :NEGOTIATE_SEAL => 0x00000020, # Session key negotiation for message confidentiality
20
+ :NEGOTIATE_DATAGRAM => 0x00000040, # Connectionless authentication
21
+ :NEGOTIATE_LM_KEY => 0x00000080, # LAN Manager session key computation
22
+ :UNUSED9 => 0x00000100,
23
+ :NEGOTIATE_NTLM => 0x00000200, # NTLM v1 protocol
24
+ :UNUSED8 => 0x00000400,
25
+ :ANONYMOUS => 0x00000800, # Anonymous connection
26
+ :OEM_DOMAIN_SUPPLIED => 0x00001000, # Domain field is present
27
+ :OEM_WORKSTATION_SUPPLIED => 0x00002000, # Workstations field is present
28
+ :UNUSED7 => 0x00004000,
29
+ :NEGOTIATE_ALWAYS_SIGN => 0x00008000,
30
+ :TARGET_TYPE_DOMAIN => 0x00010000, # TargetName is domain name
31
+ :TARGET_TYPE_SERVER => 0x00020000, # TargetName is server name
32
+ :UNUSED6 => 0x00040000,
33
+ :NEGOTIATE_EXTENDED_SECURITY => 0x00080000, # NTLM v2 session security
34
+ :NEGOTIATE_IDENTIFY => 0x00100000, # Requests identify level token
35
+ :UNUSED5 => 0x00200000,
36
+ :REQUEST_NON_NT_SESSION_KEY => 0x00400000, # LM session key is used
37
+ :NEGOTIATE_TARGET_INFO => 0x00800000, # Requests TargetInfo
38
+ :UNUSED4 => 0x01000000,
39
+ :NEGOTIATE_VERSION => 0x02000000, # Version field is present
40
+ :UNUSED3 => 0x04000000,
41
+ :UNUSED2 => 0x08000000,
42
+ :UNUSED1 => 0x10000000,
43
+ :NEGOTIATE_128 => 0x20000000, # 128bit encryption
44
+ :NEGOTIATE_KEY_EXCH => 0x40000000, # Explicit key exchange
45
+ :NEGOTIATE_56 => 0x80000000, # 56bit encryption
46
+ }
47
+
48
+ # [MS-NLMP] 2.2.2.1
49
+ AV_PAIRS = {
50
+ :AV_EOL => 0,
51
+ :AV_NB_COMPUTER_NAME => 1,
52
+ :AV_NB_DOMAIN_NAME => 2,
53
+ :AV_DNS_COMPUTER_NAME => 3,
54
+ :AV_DNS_DOMAIN_NAME => 4,
55
+ :AV_DNS_TREE_NAME => 5,
56
+ :AV_FLAGS => 6,
57
+ :AV_TIMESTAMP => 7,
58
+ :AV_RESTRICTIONS => 8,
59
+ :AV_TARGET_NAME => 9,
60
+ :AV_CHANNEL_BINDINGS => 10,
61
+ }
62
+ AV_PAIR_NAMES = AV_PAIRS.invert
63
+
64
+ FLAGS.each do |name, val|
65
+ const_set(name, val)
66
+ end
67
+
68
+ AV_PAIRS.each do |name, val|
69
+ const_set(name, val)
70
+ end
71
+
72
+ class ParseError < StandardError; end
73
+
74
+ attr_accessor :flag
75
+
76
+
77
+ def self.parse(*args)
78
+ new.parse(*args)
79
+ end
80
+
81
+ def initialize(args = {})
82
+ @buffer = ''
83
+ @offset = 0
84
+ @flag = args[:flag] || self.class::DEFAULT_FLAGS
85
+
86
+ self.class::ATTRIBUTES.each do |key|
87
+ instance_variable_set("@#{key}", args[key]) if args[key]
88
+ end
89
+ end
90
+
91
+ def to_s
92
+ serialize
93
+ end
94
+
95
+ def serialize_to_base64
96
+ [serialize].pack('m').delete("\r\n")
97
+ end
98
+
99
+ alias to_base64 serialize_to_base64
100
+
101
+ def has_flag?(symbol)
102
+ (@flag & FLAGS[symbol]) != 0
103
+ end
104
+
105
+ def set(symbol)
106
+ @flag |= FLAGS[symbol]
107
+ end
108
+
109
+ def clear(symbol)
110
+ @flag &= ~FLAGS[symbol]
111
+ end
112
+
113
+ def unicode?
114
+ has_flag?(:NEGOTIATE_UNICODE)
115
+ end
116
+
117
+ def inspect_flags
118
+ flags = []
119
+ FLAGS.sort_by(&:last).each do |name, val|
120
+ flags << name if (@flag & val).nonzero?
121
+ end
122
+ "[#{flags.join(', ')}]"
123
+ end
124
+
125
+ def inspect
126
+ variables = (instance_variables.map(&:to_sym) - [:@offset, :@buffer, :@flag]).sort.map {|name| "#{name}=#{instance_variable_get(name).inspect}, " }.join
127
+ "\#<#{self.class.name} #{variables}@flag=#{inspect_flags}>"
128
+ end
129
+
130
+ private
131
+
132
+ def parse(string)
133
+ @buffer = string
134
+ signature, type = string.unpack('a8V')
135
+ raise ParseError, 'Unknown signature' if signature != SSP_SIGNATURE
136
+ raise ParseError, "Wrong type (expected #{self.class::TYPE}, but got #{type})" if type != self.class::TYPE
137
+ end
138
+
139
+ def append_payload(string, allocation_size = nil)
140
+ size = string.size
141
+ allocation_size ||= (size + 1) & ~1
142
+ string = string.ljust(allocation_size, "\0")
143
+ @buffer << string[0, allocation_size]
144
+ result = [size, allocation_size, @offset].pack('vvV')
145
+ @offset += allocation_size
146
+ result
147
+ end
148
+
149
+ def fetch_payload(fields)
150
+ size, allocated_size, offset = fields.unpack('vvV')
151
+ return nil if size.zero?
152
+ @buffer[offset, size]
153
+ end
154
+
155
+ def encode_version(array)
156
+ array.pack('CCvx3C') # major, minor, build, ntlm revision
157
+ end
158
+
159
+ def decode_version(string)
160
+ string.unpack('CCvx3C') # major, minor, build, ntlm revision
161
+ end
162
+
163
+ def decode_av_pair(string)
164
+ result = []
165
+ string = string.dup
166
+ while true
167
+ id, length = string.slice!(0, 4).unpack('vv')
168
+ value = string.slice!(0, length)
169
+
170
+ case sym = AV_PAIR_NAMES[id]
171
+ when :AV_EOL
172
+ break
173
+ when :AV_NB_COMPUTER_NAME, :AV_NB_DOMAIN_NAME, :AV_DNS_COMPUTER_NAME, :AV_DNS_DOMAIN_NAME, :AV_DNS_TREE_NAME, :AV_TARGET_NAME
174
+ value = decode_utf16(value)
175
+ when :AV_FLAGS
176
+ value = data.unpack('V').first
177
+ end
178
+
179
+ result << [sym, value]
180
+ end
181
+ result
182
+ end
183
+
184
+ def encode_av_pair(av_pair)
185
+ result = ''
186
+ av_pair.each do |(id, value)|
187
+ case id
188
+ when :AV_NB_COMPUTER_NAME, :AV_NB_DOMAIN_NAME, :AV_DNS_COMPUTER_NAME, :AV_DNS_DOMAIN_NAME, :AV_DNS_TREE_NAME, :AV_TARGET_NAME
189
+ value = encode_utf16(value)
190
+ when :AV_FLAGS
191
+ value = [data].pack('V')
192
+ end
193
+ result << [AV_PAIRS[id], value.size, value].pack('vva*')
194
+ end
195
+
196
+ result << [AV_EOL, 0].pack('vv')
197
+ end
198
+
199
+
200
+ # [MS-NLMP] 2.2.1.1
201
+ class Negotiate < Message
202
+
203
+ TYPE = 1
204
+ ATTRIBUTES = [:domain, :workstation, :version]
205
+ DEFAULT_FLAGS = [NEGOTIATE_UNICODE, NEGOTIATE_OEM, REQUEST_TARGET, NEGOTIATE_NTLM, NEGOTIATE_ALWAYS_SIGN, NEGOTIATE_EXTENDED_SECURITY].inject(:|)
206
+
207
+ attr_accessor *ATTRIBUTES
208
+
209
+ def parse(string)
210
+ super
211
+ @flag, domain, workstation, version = string.unpack('x12Va8a8a8')
212
+ @domain = fetch_payload(domain) if has_flag?(:OEM_DOMAIN_SUPPLIED)
213
+ @workstation = fetch_payload(workstation) if has_flag?(:OEM_WORKSTATION_SUPPLIED)
214
+ @version = decode_version(version) if has_flag?(:NEGOTIATE_VERSION)
215
+ self
216
+ end
217
+
218
+ def serialize
219
+ @buffer = ''
220
+ @offset = 40 # (8 + 4) + 4 + (8 * 3)
221
+
222
+ if @domain
223
+ set(:OEM_DOMAIN_SUPPLIED)
224
+ domain = append_payload(@domain)
225
+ end
226
+
227
+ if @workstation
228
+ set(:OEM_WORKSTATION_SUPPLIED)
229
+ workstation = append_payload(@workstation)
230
+ end
231
+
232
+ if @version
233
+ set(:NEGOTIATE_VERSION)
234
+ version = encode_version(@version)
235
+ end
236
+
237
+ [SSP_SIGNATURE, TYPE, @flag, domain, workstation, version].pack('a8VVa8a8a8') + @buffer
238
+ end
239
+
240
+ end # Negotiate
241
+
242
+
243
+ # [MS-NLMP] 2.2.1.2
244
+ class Challenge < Message
245
+
246
+ TYPE = 2
247
+ ATTRIBUTES = [:target_name, :challenge, :target_info, :version]
248
+ DEFAULT_FLAGS = 0
249
+
250
+ attr_accessor *ATTRIBUTES
251
+
252
+ def parse(string)
253
+ super
254
+ target_name, @flag, @challenge, target_info, version = string.unpack('x12a8Va8x8a8a8')
255
+ @target_name = fetch_payload(target_name) if has_flag?(:REQUEST_TARGET)
256
+ @target_info = fetch_payload(target_info) if has_flag?(:NEGOTIATE_TARGET_INFO)
257
+ @version = decode_version(version) if has_flag?(:NEGOTIATE_VERSION)
258
+
259
+ @target_name &&= decode_utf16(@target_name) if unicode?
260
+ @target_info &&= decode_av_pair(@target_info)
261
+
262
+ self
263
+ end
264
+
265
+ def serialize
266
+ @buffer = ''
267
+ @offset = 56 # (8 + 4) + 8 + 4 + (8 * 4)
268
+
269
+ @challenge ||= OpenSSL::Random.random_bytes(8)
270
+
271
+ if @target_name
272
+ set(:REQUEST_TARGET)
273
+ if unicode?
274
+ target_name = append_payload(encode_utf16(@target_name))
275
+ else
276
+ target_name = append_payload(@target_name)
277
+ end
278
+ end
279
+
280
+ if @target_info
281
+ set(:NEGOTIATE_TARGET_INFO)
282
+ target_info = append_payload(encode_av_pair(@target_info))
283
+ end
284
+
285
+ if @version
286
+ set(:NEGOTIATE_VERSION)
287
+ version = encode_version(@version)
288
+ end
289
+
290
+ [SSP_SIGNATURE, TYPE, target_name, @flag, @challenge, target_info, version].pack('a8Va8Va8x8a8a8') + @buffer
291
+ end
292
+
293
+ end # Challenge
294
+
295
+
296
+ # [MS-NLMP] 2.2.1.3
297
+ class Authenticate < Message
298
+
299
+ TYPE = 3
300
+ ATTRIBUTES = [:lm_response, :nt_response, :domain, :user, :workstation, :session_key, :version, :mic]
301
+ DEFAULT_FLAGS = [NEGOTIATE_UNICODE, REQUEST_TARGET, NEGOTIATE_NTLM, NEGOTIATE_ALWAYS_SIGN, NEGOTIATE_EXTENDED_SECURITY].inject(:|)
302
+
303
+ attr_accessor *ATTRIBUTES
304
+
305
+ def parse(string)
306
+ super
307
+ lm_response, nt_response, domain, user, workstation, session_key, @flag, version, mic = \
308
+ string.unpack('x12a8a8a8a8a8a8Va8a16')
309
+
310
+ @lm_response = fetch_payload(lm_response)
311
+ @nt_response = fetch_payload(nt_response)
312
+ @domain = fetch_payload(domain)
313
+ @user = fetch_payload(user)
314
+ @workstation = fetch_payload(workstation)
315
+ @session_key = fetch_payload(session_key) if has_flag?(:NEGOTIATE_KEY_EXCH)
316
+ @version = decode_version(version) if has_flag?(:NEGOTIATE_VERSION)
317
+ @mic = mic
318
+
319
+ if unicode?
320
+ @domain = decode_utf16(@domain)
321
+ @user = decode_utf16(@user)
322
+ @workstation = decode_utf16(@workstation)
323
+ end
324
+
325
+ self
326
+ end
327
+
328
+ def serialize
329
+ @buffer = ''
330
+ @offset = 88 # (8 + 4) + (8 * 6) + 4 + 8 + 16
331
+
332
+ lm_response = append_payload(@lm_response)
333
+ nt_response = append_payload(@nt_response)
334
+
335
+ if unicode?
336
+ domain = append_payload(encode_utf16(@domain))
337
+ user = append_payload(encode_utf16(@user))
338
+ workstation = append_payload(encode_utf16(@workstation))
339
+ else
340
+ domain = append_payload(@domain)
341
+ user = append_payload(@user)
342
+ workstation = append_payload(@workstation)
343
+ end
344
+
345
+ if @session_key
346
+ set(:NEGOTIATE_KEY_EXCH)
347
+ session_key = append_payload(@session_key)
348
+ end
349
+
350
+ if @version
351
+ set(:NEGOTIATE_VERSION)
352
+ version = encode_version(@version)
353
+ end
354
+
355
+ [SSP_SIGNATURE, TYPE, lm_response, nt_response, domain, user, workstation, session_key, @flag, version, @mic].pack('a8Va8a8a8a8a8a8Va8a16') + @buffer
356
+ end
357
+
358
+ end # Authenticate
359
+
360
+ end # Message
361
+ end # NTLM
data/lib/ntlm/smtp.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'ntlm'
2
+ require 'net/smtp'
3
+
4
+ module Net
5
+ class SMTP
6
+
7
+ def capable_ntlm_auth?
8
+ auth_capable?('NTLM')
9
+ end
10
+
11
+ def auth_ntlm(user, secret)
12
+ check_auth_args(user, secret)
13
+ if user.index('\\')
14
+ domain, user = user.split('\\', 2)
15
+ else
16
+ domain = ''
17
+ end
18
+
19
+ res = critical {
20
+ r = get_response("AUTH NTLM #{::NTLM.negotiate.to_base64}")
21
+ check_auth_continue(r)
22
+ challenge = r.string.split(/ /, 2).last.unpack('m').first
23
+ get_response(::NTLM.authenticate(challenge, user, domain, secret).to_base64)
24
+ }
25
+ check_auth_response(res)
26
+ res
27
+ end
28
+
29
+ end # SMTP
30
+ end # Net
data/lib/ntlm/util.rb ADDED
@@ -0,0 +1,105 @@
1
+ # vim: set et sw=2 sts=2:
2
+
3
+ require 'openssl'
4
+
5
+ module NTLM
6
+ module Util
7
+
8
+ LM_MAGIC_TEXT = 'KGS!@#$%'
9
+
10
+ module_function
11
+
12
+ if RUBY_VERSION >= '1.9'
13
+
14
+ def decode_utf16(str)
15
+ str.encode(Encoding::UTF_8, Encoding::UTF_16LE)
16
+ end
17
+
18
+ def encode_utf16(str)
19
+ str.to_s.encode(Encoding::UTF_16LE).force_encoding(Encoding::ASCII_8BIT)
20
+ end
21
+
22
+ else
23
+
24
+ require 'iconv'
25
+
26
+ def decode_utf16(str)
27
+ Iconv.conv('UTF-8', 'UTF-16LE', str)
28
+ end
29
+
30
+ def encode_utf16(str)
31
+ Iconv.conv('UTF-16LE', 'UTF-8', str)
32
+ end
33
+
34
+ end
35
+
36
+ def create_des_keys(string)
37
+ keys = []
38
+ string = string.dup
39
+ until (key = string.slice!(0, 7)).empty?
40
+ # key is 56 bits
41
+ key = key.unpack('B*').first
42
+ str = ''
43
+ until (bits = key.slice!(0, 7)).empty?
44
+ str << bits
45
+ str << (bits.count('1').even? ? '1' : '0') # parity
46
+ end
47
+ keys << [str].pack('B*')
48
+ end
49
+ keys
50
+ end
51
+
52
+ def encrypt(plain_text, key, key_length)
53
+ key = key.ljust(key_length, "\0")
54
+ keys = create_des_keys(key[0, key_length])
55
+
56
+ result = ''
57
+ cipher = OpenSSL::Cipher::DES.new
58
+ keys.each do |k|
59
+ cipher.encrypt
60
+ cipher.key = k
61
+ result << cipher.update(plain_text)
62
+ end
63
+
64
+ result
65
+ end
66
+
67
+ # [MS-NLMP] 3.3.1
68
+ def lm_v1_hash(password)
69
+ encrypt(LM_MAGIC_TEXT, password.upcase, 14)
70
+ end
71
+
72
+ # [MS-NLMP] 3.3.1
73
+ def nt_v1_hash(password)
74
+ OpenSSL::Digest::MD4.digest(encode_utf16(password))
75
+ end
76
+
77
+ # [MS-NLMP] 3.3.1
78
+ def ntlm_v1_response(challenge, password, options = {})
79
+ if options[:ntlm_v2_session]
80
+ client_challenge = options[:client_challenge] || OpenSSL::Random.random_bytes(8)
81
+ hash = OpenSSL::Digest::MD5.digest(challenge + client_challenge)[0, 8]
82
+ nt_response = encrypt(hash, nt_v1_hash(password), 21)
83
+ lm_response = client_challenge + ("\0" * 16)
84
+ else
85
+ nt_response = encrypt(challenge, nt_v1_hash(password), 21)
86
+ lm_response = encrypt(challenge, lm_v1_hash(password), 21)
87
+ end
88
+
89
+ [nt_response, lm_response]
90
+ end
91
+
92
+
93
+ # [MS-NLMP] 3.3.2
94
+ def nt_v2_hash(user, password, domain)
95
+ user_domain = encode_utf16(user.upcase + domain)
96
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, nt_v1_hash(password), user_domain)
97
+ end
98
+
99
+ # [MS-NLMP] 3.3.2
100
+ def ntlm_v2_response(*)
101
+ raise NotImplemnetedError
102
+ end
103
+
104
+ end # Util
105
+ end # NTLM
@@ -0,0 +1,24 @@
1
+ # Compiling the Gem
2
+ # gem build ruby-ntlm-namespace.gemspec
3
+ # gem install ./ruby-ntlm-namespace-x.x.x.gem --no-ri --no-rdoc --local
4
+ #
5
+ # gem push ruby-ntlm-namespace-x.x.x.gem
6
+ # gem list -r ruby-ntlm-namespace
7
+ # gem install ruby-ntlm-namespace
8
+
9
+ $:.push File.expand_path('../lib', __FILE__)
10
+
11
+ Gem::Specification.new do |s|
12
+ s.name = 'ruby-ntlm-namespace'
13
+ s.version = '0.0.1'
14
+ s.authors = ['Remo Mueller']
15
+ s.email = 'remosm@gmail.com'
16
+ s.homepage = 'https://github.com/remomueller'
17
+ s.summary = "Gem release for mademaxus' fork of macks ruby-ntlm with namespacing fixed"
18
+ s.description = "Gem release for mademaxus' fork of macks ruby-ntlm with namespacing fixed"
19
+
20
+ s.platform = Gem::Platform::RUBY
21
+
22
+ s.files = Dir["{app,config,db,lib}/**/*"] + ["ruby-ntlm-namespace.gemspec", "Rakefile", "VERSION", "README.markdown"]
23
+ s.test_files = Dir["test/**/*"]
24
+ end
data/test/auth_test.rb ADDED
@@ -0,0 +1,27 @@
1
+ # vim: set et sw=2 sts=2:
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class AuthenticationTest < Test::Unit::TestCase
6
+
7
+ include NTLM::TestUtility
8
+ include NTLM::Util
9
+
10
+ def setup
11
+ @challenge = hex_to_bin("4e 54 4c 4d 53 53 50 00 02 00 00 00 0c 00 0c 00 38 00 00 00 05 82 01 00 11 11 11 11 11 11 11 11 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 44 00 6f 00 6d 00 61 00 69 00 6e 00")
12
+ end
13
+
14
+ def test_negotiate
15
+ assert_equal(hex_to_bin("4e 54 4c 4d 53 53 50 00 01 00 00 00 07 82 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"), NTLM.negotiate.to_s)
16
+ end
17
+
18
+ def test_authenticate
19
+ assert_equal(hex_to_bin("4e 54 4c 4d 53 53 50 00 03 00 00 00 18 00 18 00 58 00 00 00 18 00 18 00 70 00 00 00 0c 00 0c 00 88 00 00 00 08 00 08 00 94 00 00 00 00 00 00 00 9c 00 00 00 00 00 00 00 00 00 00 00 05 82 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 21 b0 c5 31 28 0e ed 8d 32 c3 1b ce b2 19 5a fd 58 2b b7 8e a0 d5 f2 78 8d 76 96 b7 58 49 16 14 2d 09 f0 a0 1f f2 35 10 be 2c ff 96 82 e0 e3 3b 44 00 6f 00 6d 00 61 00 69 00 6e 00 55 00 73 00 65 00 72 00"), NTLM.authenticate(@challenge, 'User', 'Domain', 'Password').to_s)
20
+
21
+ challenge = NTLM::Message::Challenge.parse(@challenge)
22
+ challenge.set(:NEGOTIATE_EXTENDED_SECURITY)
23
+
24
+ assert_equal(hex_to_bin("4e 54 4c 4d 53 53 50 00 03 00 00 00 18 00 18 00 58 00 00 00 18 00 18 00 70 00 00 00 0c 00 0c 00 88 00 00 00 08 00 08 00 94 00 00 00 00 00 00 00 9c 00 00 00 00 00 00 00 00 00 00 00 05 82 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 22 22 22 22 22 22 22 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c2 1e db 62 54 34 d2 13 34 1a 04 3d f3 01 6d f3 01 c9 32 b4 ae 97 1e ac 44 00 6f 00 6d 00 61 00 69 00 6e 00 55 00 73 00 65 00 72 00"), NTLM.authenticate(challenge.to_s, 'User', 'Domain', 'Password', :client_challenge => "\x22" * 8).to_s)
25
+ end
26
+
27
+ end
@@ -0,0 +1,43 @@
1
+ # vim: set et sw=2 sts=2:
2
+
3
+ require File.dirname(__FILE__) + '/test_helper'
4
+
5
+ class FunctionTest < Test::Unit::TestCase
6
+ # Test pattern is borrowed from pyton-ntlm
7
+
8
+ include NTLM::TestUtility
9
+ include NTLM::Util
10
+
11
+ def setup
12
+ @server_challenge = hex_to_bin('01 23 45 67 89 ab cd ef')
13
+ @client_challenge = "\xaa" * 8
14
+ @time = "\0" * 8
15
+ @workstation = 'COMPUTER'
16
+ @server_name = 'Server'
17
+ @user = 'User'
18
+ @domain = 'Domain'
19
+ @password = 'Password'
20
+ @random_session_key = "\55" * 16
21
+ end
22
+
23
+ def test_lm_v1_hash
24
+ assert_equal(hex_to_bin("e5 2c ac 67 41 9a 9a 22 4a 3b 10 8f 3f a6 cb 6d"), lm_v1_hash(@password))
25
+ end
26
+
27
+ def test_nt_v1_hash
28
+ assert_equal(hex_to_bin("a4 f4 9c 40 65 10 bd ca b6 82 4e e7 c3 0f d8 52"), nt_v1_hash(@password))
29
+ end
30
+
31
+ def test_ntlm_v1_response
32
+ nt_response, lm_response = ntlm_v1_response(@server_challenge, @password)
33
+ assert_equal(hex_to_bin("67 c4 30 11 f3 02 98 a2 ad 35 ec e6 4f 16 33 1c 44 bd be d9 27 84 1f 94"), nt_response, 'nt_response')
34
+ assert_equal(hex_to_bin("98 de f7 b8 7f 88 aa 5d af e2 df 77 96 88 a1 72 de f1 1c 7d 5c cd ef 13"), lm_response, 'lm_response')
35
+ end
36
+
37
+ def test_ntlm_v1_response_with_ntlm_v2_session_security
38
+ nt_response, lm_response = ntlm_v1_response(@server_challenge, @password, :ntlm_v2_session => true, :client_challenge => @client_challenge)
39
+ assert_equal(hex_to_bin("75 37 f8 03 ae 36 71 28 ca 45 82 04 bd e7 ca f8 1e 97 ed 26 83 26 72 32"), nt_response, 'nt_response')
40
+ assert_equal(hex_to_bin("aa aa aa aa aa aa aa aa 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"), lm_response, 'lm_response')
41
+ end
42
+
43
+ end
@@ -0,0 +1,20 @@
1
+ # vim: set et sw=2 sts=2:
2
+
3
+ require 'test/unit'
4
+
5
+ $LOAD_PATH << File.dirname(__FILE__) + '/../lib'
6
+ require 'ntlm'
7
+
8
+ module NTLM
9
+ module TestUtility
10
+
11
+ def bin_to_hex(bin)
12
+ bin.unpack('H*').first.gsub(/..(?=.)/, '\0 ')
13
+ end
14
+
15
+ def hex_to_bin(hex)
16
+ [hex.delete(' ')].pack('H*')
17
+ end
18
+
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-ntlm-namespace
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Remo Mueller
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-02-11 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Gem release for mademaxus' fork of macks ruby-ntlm with namespacing fixed
15
+ email: remosm@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/ntlm/http.rb
21
+ - lib/ntlm/imap.rb
22
+ - lib/ntlm/mechanize.rb
23
+ - lib/ntlm/message.rb
24
+ - lib/ntlm/smtp.rb
25
+ - lib/ntlm/util.rb
26
+ - lib/ntlm.rb
27
+ - ruby-ntlm-namespace.gemspec
28
+ - Rakefile
29
+ - VERSION
30
+ - README.markdown
31
+ - test/auth_test.rb
32
+ - test/function_test.rb
33
+ - test/test_helper.rb
34
+ homepage: https://github.com/remomueller
35
+ licenses: []
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 1.8.24
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: Gem release for mademaxus' fork of macks ruby-ntlm with namespacing fixed
58
+ test_files:
59
+ - test/auth_test.rb
60
+ - test/function_test.rb
61
+ - test/test_helper.rb