ruby-ntlm-namespace 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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