mechanize-ntlm 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (171) hide show
  1. data/CHANGELOG.rdoc +480 -0
  2. data/EXAMPLES.rdoc +171 -0
  3. data/FAQ.rdoc +11 -0
  4. data/GUIDE.rdoc +122 -0
  5. data/LICENSE.rdoc +340 -0
  6. data/Manifest.txt +169 -0
  7. data/README.rdoc +60 -0
  8. data/Rakefile +44 -0
  9. data/examples/flickr_upload.rb +23 -0
  10. data/examples/mech-dump.rb +7 -0
  11. data/examples/proxy_req.rb +9 -0
  12. data/examples/rubyforge.rb +21 -0
  13. data/examples/spider.rb +11 -0
  14. data/lib/mechanize-ntlm.rb +7 -0
  15. data/lib/www/mechanize.rb +582 -0
  16. data/lib/www/mechanize/chain.rb +34 -0
  17. data/lib/www/mechanize/chain/auth_headers.rb +82 -0
  18. data/lib/www/mechanize/chain/body_decoding_handler.rb +43 -0
  19. data/lib/www/mechanize/chain/connection_resolver.rb +78 -0
  20. data/lib/www/mechanize/chain/custom_headers.rb +23 -0
  21. data/lib/www/mechanize/chain/handler.rb +9 -0
  22. data/lib/www/mechanize/chain/header_resolver.rb +48 -0
  23. data/lib/www/mechanize/chain/parameter_resolver.rb +23 -0
  24. data/lib/www/mechanize/chain/post_connect_hook.rb +0 -0
  25. data/lib/www/mechanize/chain/pre_connect_hook.rb +22 -0
  26. data/lib/www/mechanize/chain/request_resolver.rb +32 -0
  27. data/lib/www/mechanize/chain/response_body_parser.rb +40 -0
  28. data/lib/www/mechanize/chain/response_header_handler.rb +51 -0
  29. data/lib/www/mechanize/chain/response_reader.rb +41 -0
  30. data/lib/www/mechanize/chain/ssl_resolver.rb +36 -0
  31. data/lib/www/mechanize/chain/uri_resolver.rb +73 -0
  32. data/lib/www/mechanize/content_type_error.rb +16 -0
  33. data/lib/www/mechanize/cookie.rb +72 -0
  34. data/lib/www/mechanize/cookie_jar.rb +191 -0
  35. data/lib/www/mechanize/file.rb +73 -0
  36. data/lib/www/mechanize/file_response.rb +62 -0
  37. data/lib/www/mechanize/file_saver.rb +39 -0
  38. data/lib/www/mechanize/form.rb +359 -0
  39. data/lib/www/mechanize/form/button.rb +8 -0
  40. data/lib/www/mechanize/form/check_box.rb +13 -0
  41. data/lib/www/mechanize/form/field.rb +28 -0
  42. data/lib/www/mechanize/form/file_upload.rb +24 -0
  43. data/lib/www/mechanize/form/image_button.rb +23 -0
  44. data/lib/www/mechanize/form/multi_select_list.rb +69 -0
  45. data/lib/www/mechanize/form/option.rb +51 -0
  46. data/lib/www/mechanize/form/radio_button.rb +38 -0
  47. data/lib/www/mechanize/form/select_list.rb +45 -0
  48. data/lib/www/mechanize/headers.rb +12 -0
  49. data/lib/www/mechanize/history.rb +67 -0
  50. data/lib/www/mechanize/inspect.rb +90 -0
  51. data/lib/www/mechanize/monkey_patch.rb +37 -0
  52. data/lib/www/mechanize/page.rb +145 -0
  53. data/lib/www/mechanize/page/base.rb +10 -0
  54. data/lib/www/mechanize/page/frame.rb +22 -0
  55. data/lib/www/mechanize/page/link.rb +50 -0
  56. data/lib/www/mechanize/page/meta.rb +10 -0
  57. data/lib/www/mechanize/pluggable_parsers.rb +103 -0
  58. data/lib/www/mechanize/redirect_limit_reached_error.rb +18 -0
  59. data/lib/www/mechanize/redirect_not_get_or_head_error.rb +20 -0
  60. data/lib/www/mechanize/response_code_error.rb +25 -0
  61. data/lib/www/mechanize/unsupported_scheme_error.rb +10 -0
  62. data/lib/www/mechanize/util.rb +76 -0
  63. data/lib/www/ntlm-http/lib/net/ntlm_http.rb +854 -0
  64. data/mechanize.gemspec +24 -0
  65. data/test/chain/test_argument_validator.rb +14 -0
  66. data/test/chain/test_custom_headers.rb +18 -0
  67. data/test/chain/test_parameter_resolver.rb +35 -0
  68. data/test/chain/test_request_resolver.rb +29 -0
  69. data/test/chain/test_response_reader.rb +24 -0
  70. data/test/data/htpasswd +1 -0
  71. data/test/data/server.crt +16 -0
  72. data/test/data/server.csr +12 -0
  73. data/test/data/server.key +15 -0
  74. data/test/data/server.pem +15 -0
  75. data/test/helper.rb +127 -0
  76. data/test/htdocs/alt_text.html +10 -0
  77. data/test/htdocs/bad_form_test.html +9 -0
  78. data/test/htdocs/button.jpg +0 -0
  79. data/test/htdocs/empty_form.html +6 -0
  80. data/test/htdocs/file_upload.html +26 -0
  81. data/test/htdocs/find_link.html +41 -0
  82. data/test/htdocs/form_multi_select.html +16 -0
  83. data/test/htdocs/form_multival.html +37 -0
  84. data/test/htdocs/form_no_action.html +18 -0
  85. data/test/htdocs/form_no_input_name.html +16 -0
  86. data/test/htdocs/form_select.html +16 -0
  87. data/test/htdocs/form_select_all.html +16 -0
  88. data/test/htdocs/form_select_none.html +17 -0
  89. data/test/htdocs/form_select_noopts.html +10 -0
  90. data/test/htdocs/form_set_fields.html +14 -0
  91. data/test/htdocs/form_test.html +188 -0
  92. data/test/htdocs/frame_test.html +30 -0
  93. data/test/htdocs/google.html +13 -0
  94. data/test/htdocs/iframe_test.html +16 -0
  95. data/test/htdocs/index.html +6 -0
  96. data/test/htdocs/link with space.html +5 -0
  97. data/test/htdocs/meta_cookie.html +11 -0
  98. data/test/htdocs/no_title_test.html +6 -0
  99. data/test/htdocs/relative/tc_relative_links.html +21 -0
  100. data/test/htdocs/tc_bad_links.html +5 -0
  101. data/test/htdocs/tc_base_link.html +8 -0
  102. data/test/htdocs/tc_blank_form.html +11 -0
  103. data/test/htdocs/tc_checkboxes.html +19 -0
  104. data/test/htdocs/tc_encoded_links.html +5 -0
  105. data/test/htdocs/tc_follow_meta.html +8 -0
  106. data/test/htdocs/tc_form_action.html +48 -0
  107. data/test/htdocs/tc_links.html +18 -0
  108. data/test/htdocs/tc_no_attributes.html +16 -0
  109. data/test/htdocs/tc_pretty_print.html +17 -0
  110. data/test/htdocs/tc_radiobuttons.html +17 -0
  111. data/test/htdocs/tc_referer.html +10 -0
  112. data/test/htdocs/tc_relative_links.html +19 -0
  113. data/test/htdocs/tc_textarea.html +23 -0
  114. data/test/htdocs/unusual______.html +5 -0
  115. data/test/servlets.rb +339 -0
  116. data/test/ssl_server.rb +48 -0
  117. data/test/test_authenticate.rb +71 -0
  118. data/test/test_bad_links.rb +25 -0
  119. data/test/test_blank_form.rb +16 -0
  120. data/test/test_checkboxes.rb +61 -0
  121. data/test/test_content_type.rb +13 -0
  122. data/test/test_cookie_class.rb +338 -0
  123. data/test/test_cookie_jar.rb +343 -0
  124. data/test/test_cookies.rb +123 -0
  125. data/test/test_encoded_links.rb +20 -0
  126. data/test/test_errors.rb +49 -0
  127. data/test/test_follow_meta.rb +69 -0
  128. data/test/test_form_action.rb +44 -0
  129. data/test/test_form_as_hash.rb +61 -0
  130. data/test/test_form_button.rb +38 -0
  131. data/test/test_form_no_inputname.rb +15 -0
  132. data/test/test_forms.rb +575 -0
  133. data/test/test_frames.rb +25 -0
  134. data/test/test_get_headers.rb +52 -0
  135. data/test/test_gzipping.rb +22 -0
  136. data/test/test_hash_api.rb +45 -0
  137. data/test/test_history.rb +142 -0
  138. data/test/test_history_added.rb +16 -0
  139. data/test/test_html_unscape_forms.rb +39 -0
  140. data/test/test_if_modified_since.rb +20 -0
  141. data/test/test_keep_alive.rb +31 -0
  142. data/test/test_links.rb +120 -0
  143. data/test/test_mech.rb +259 -0
  144. data/test/test_mechanize_file.rb +47 -0
  145. data/test/test_multi_select.rb +106 -0
  146. data/test/test_no_attributes.rb +13 -0
  147. data/test/test_option.rb +18 -0
  148. data/test/test_page.rb +67 -0
  149. data/test/test_pluggable_parser.rb +145 -0
  150. data/test/test_post_form.rb +34 -0
  151. data/test/test_pretty_print.rb +22 -0
  152. data/test/test_radiobutton.rb +75 -0
  153. data/test/test_redirect_limit_reached.rb +41 -0
  154. data/test/test_redirect_verb_handling.rb +45 -0
  155. data/test/test_referer.rb +39 -0
  156. data/test/test_relative_links.rb +40 -0
  157. data/test/test_request.rb +13 -0
  158. data/test/test_response_code.rb +52 -0
  159. data/test/test_save_file.rb +48 -0
  160. data/test/test_scheme.rb +48 -0
  161. data/test/test_select.rb +106 -0
  162. data/test/test_select_all.rb +15 -0
  163. data/test/test_select_none.rb +15 -0
  164. data/test/test_select_noopts.rb +16 -0
  165. data/test/test_set_fields.rb +44 -0
  166. data/test/test_ssl_server.rb +20 -0
  167. data/test/test_subclass.rb +14 -0
  168. data/test/test_textarea.rb +45 -0
  169. data/test/test_upload.rb +109 -0
  170. data/test/test_verbs.rb +25 -0
  171. metadata +284 -0
@@ -0,0 +1,10 @@
1
+ module WWW
2
+ class Mechanize
3
+ class UnsupportedSchemeError < RuntimeError
4
+ attr_accessor :scheme
5
+ def initialize(scheme)
6
+ @scheme = scheme
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,76 @@
1
+ require 'cgi'
2
+
3
+ module WWW
4
+ class Mechanize
5
+ class Util
6
+ CODE_DIC = {
7
+ :JIS => "ISO-2022-JP",
8
+ :EUC => "EUC-JP",
9
+ :SJIS => "SHIFT_JIS",
10
+ :UTF8 => "UTF-8", :UTF16 => "UTF-16", :UTF32 => "UTF-32"}
11
+
12
+ class << self
13
+ def build_query_string(parameters, enc=nil)
14
+ parameters.map { |k,v|
15
+ if k
16
+ # WEBrick::HTTP.escape* has some problems about m17n on ruby-1.9.*.
17
+ [CGI.escape(k.to_s), CGI.escape(v.to_s)].join("=")
18
+ =begin
19
+ [WEBrick::HTTPUtils.escape_form(k.to_s),
20
+ WEBrick::HTTPUtils.escape_form(v.to_s)].join("=")
21
+ =end
22
+
23
+ end
24
+ }.compact.join('&')
25
+ end
26
+
27
+ def to_native_charset(s, code=nil)
28
+ if Mechanize.html_parser == Nokogiri::HTML
29
+ return unless s
30
+ code ||= detect_charset(s)
31
+ Iconv.iconv("UTF-8", code, s).join("")
32
+ else
33
+ s
34
+ end
35
+ end
36
+
37
+ def from_native_charset(s, code)
38
+ if Mechanize.html_parser == Nokogiri::HTML
39
+ return unless s
40
+ Iconv.iconv(code, "UTF-8", s).join("")
41
+ else
42
+ return s
43
+ end
44
+ end
45
+
46
+ def html_unescape(s)
47
+ return s unless s
48
+ s.gsub(/&(\w+|#[0-9]+);/) { |match|
49
+ number = case match
50
+ when /&(\w+);/
51
+ Mechanize.html_parser::NamedCharacters[$1]
52
+ when /&#([0-9]+);/
53
+ $1.to_i
54
+ end
55
+
56
+ number ? ([number].pack('U') rescue match) : match
57
+ }
58
+ end
59
+
60
+ def detect_charset(src)
61
+ tmp = NKF.guess(src || "<html></html>")
62
+ if RUBY_VERSION >= "1.9.0"
63
+ enc = tmp.to_s.upcase
64
+ else
65
+ enc = NKF.constants.find{|c|
66
+ NKF.const_get(c) == tmp
67
+ }
68
+ enc = CODE_DIC[enc.intern]
69
+ end
70
+ enc || "ISO-8859-1"
71
+ end
72
+
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,854 @@
1
+ #
2
+ # = net/ntlm.rb
3
+ #
4
+ # An NTLM Authentication Library for Ruby
5
+ #
6
+ # This code is a derivative of "dbf2.rb" written by yrock
7
+ # and Minero Aoki. You can find original code here:
8
+ # http://jp.rubyist.net/magazine/?0013-CodeReview
9
+ # -------------------------------------------------------------
10
+ # Copyright (c) 2005,2006 yrock
11
+ #
12
+ # This program is free software.
13
+ # You can distribute/modify this program under the terms of the
14
+ # Ruby License.
15
+ #
16
+ # 2006-02-11 refactored by Minero Aoki
17
+ # -------------------------------------------------------------
18
+ #
19
+ # All protocol information used to write this code stems from
20
+ # "The NTLM Authentication Protocol" by Eric Glass. The author
21
+ # would thank to him for this tremendous work and making it
22
+ # available on the net.
23
+ # http://davenport.sourceforge.net/ntlm.html
24
+ # -------------------------------------------------------------
25
+ # Copyright (c) 2003 Eric Glass
26
+ #
27
+ # Permission to use, copy, modify, and distribute this document
28
+ # for any purpose and without any fee is hereby granted,
29
+ # provided that the above copyright notice and this list of
30
+ # conditions appear in all copies.
31
+ # -------------------------------------------------------------
32
+ #
33
+ # The author also looked Mozilla-Firefox-1.0.7 source code,
34
+ # namely, security/manager/ssl/src/nsNTLMAuthModule.cpp and
35
+ # Jonathan Bastien-Filiatrault's libntlm-ruby.
36
+ # "http://x2a.org/websvn/filedetails.php?
37
+ # repname=libntlm-ruby&path=%2Ftrunk%2Fntlm.rb&sc=1"
38
+ # The latter has a minor bug in its separate_keys function.
39
+ # The third key has to begin from the 14th character of the
40
+ # input string instead of 13th:)
41
+ #--
42
+ # $Id: ntlm.rb,v 1.1 2006/10/05 01:36:52 koheik Exp $
43
+ #++
44
+
45
+ require 'base64'
46
+ require 'openssl'
47
+ require 'openssl/digest'
48
+
49
+ module Net #:nodoc:
50
+ module NTLM
51
+
52
+ module VERSION #:nodoc:
53
+ MAJOR = 0
54
+ MINOR = 1
55
+ TINY = 1
56
+ STRING = [MAJOR, MINOR, TINY].join('.')
57
+ end
58
+
59
+ SSP_SIGN = "NTLMSSP\0"
60
+ BLOB_SIGN = 0x00000101
61
+ LM_MAGIC = "KGS!@\#$%"
62
+ TIME_OFFSET = 11644473600
63
+ MAX64 = 0xffffffffffffffff
64
+
65
+ FLAGS = {
66
+ :UNICODE => 0x00000001,
67
+ :OEM => 0x00000002,
68
+ :REQUEST_TARGET => 0x00000004,
69
+ # :UNKNOWN => 0x00000008,
70
+ :SIGN => 0x00000010,
71
+ :SEAL => 0x00000020,
72
+ # :UNKNOWN => 0x00000040,
73
+ :NETWARE => 0x00000100,
74
+ :NTLM => 0x00000200,
75
+ # :UNKNOWN => 0x00000400,
76
+ # :UNKNOWN => 0x00000800,
77
+ :DOMAIN_SUPPLIED => 0x00001000,
78
+ :WORKSTATION_SUPPLIED => 0x00002000,
79
+ :LOCAL_CALL => 0x00004000,
80
+ :ALWAYS_SIGN => 0x00008000,
81
+ :TARGET_TYPE_DOMAIN => 0x00010000,
82
+ :TARGET_INFO => 0x00800000,
83
+ :NTLM2_KEY => 0x00080000,
84
+ :KEY128 => 0x20000000,
85
+ :KEY56 => 0x80000000
86
+ }
87
+
88
+ FLAG_KEYS = FLAGS.keys.sort{|a, b| FLAGS[a] <=> FLAGS[b] }
89
+
90
+ DEFAULT_FLAGS = {
91
+ :TYPE1 => FLAGS[:UNICODE] | FLAGS[:OEM] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY],
92
+ :TYPE2 => FLAGS[:UNICODE],
93
+ :TYPE3 => FLAGS[:UNICODE] | FLAGS[:REQUEST_TARGET] | FLAGS[:NTLM] | FLAGS[:ALWAYS_SIGN] | FLAGS[:NTLM2_KEY]
94
+ }
95
+
96
+ # module functions
97
+ class << self
98
+ def decode_utf16le(str)
99
+ Kconv.kconv(swap16(str), Kconv::ASCII, Kconv::UTF16)
100
+ end
101
+
102
+ def encode_utf16le(str)
103
+ swap16(Kconv.kconv(str, Kconv::UTF16, Kconv::ASCII))
104
+ end
105
+
106
+ def pack_int64le(val)
107
+ [val & 0x00000000ffffffff, val >> 32].pack("V2")
108
+ end
109
+
110
+ def swap16(str)
111
+ str.unpack("v*").pack("n*")
112
+ end
113
+
114
+ def split7(str)
115
+ s = str.dup
116
+ until s.empty?
117
+ (ret ||= []).push s.slice!(0, 7)
118
+ end
119
+ ret
120
+ end
121
+
122
+ def gen_keys(str)
123
+ split7(str).map{ |str7|
124
+ bits = split7(str7.unpack("B*")[0]).inject('')\
125
+ {|ret, tkn| ret += tkn + (tkn.gsub('1', '').size % 2).to_s }
126
+ [bits].pack("B*")
127
+ }
128
+ end
129
+
130
+ def apply_des(plain, keys)
131
+ dec = OpenSSL::Cipher::DES.new
132
+ keys.map {|k|
133
+ dec.key = k
134
+ dec.encrypt.update(plain)
135
+ }
136
+ end
137
+
138
+ def lm_hash(password)
139
+ keys = gen_keys password.upcase.ljust(14, "\0")
140
+ apply_des(LM_MAGIC, keys).join
141
+ end
142
+
143
+ def ntlm_hash(password, opt = {})
144
+ pwd = password.dup
145
+ unless opt[:unicode]
146
+ pwd = encode_utf16le(pwd)
147
+ end
148
+ OpenSSL::Digest::MD4.digest pwd
149
+ end
150
+
151
+ def ntlmv2_hash(user, password, target, opt={})
152
+ ntlmhash = ntlm_hash(password, opt)
153
+ userdomain = (user + target).upcase
154
+ unless opt[:unicode]
155
+ userdomain = encode_utf16le(userdomain)
156
+ end
157
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, ntlmhash, userdomain)
158
+ end
159
+
160
+ # responses
161
+ def lm_response(arg)
162
+ begin
163
+ hash = arg[:lm_hash]
164
+ chal = arg[:challenge]
165
+ rescue
166
+ raise ArgumentError
167
+ end
168
+ chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
169
+ keys = gen_keys hash.ljust(21, "\0")
170
+ apply_des(chal, keys).join
171
+ end
172
+
173
+ def ntlm_response(arg)
174
+ hash = arg[:ntlm_hash]
175
+ chal = arg[:challenge]
176
+ chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
177
+ keys = gen_keys hash.ljust(21, "\0")
178
+ apply_des(chal, keys).join
179
+ end
180
+
181
+ def ntlmv2_response(arg, opt = {})
182
+ begin
183
+ key = arg[:ntlmv2_hash]
184
+ chal = arg[:challenge]
185
+ ti = arg[:target_info]
186
+ rescue
187
+ raise ArgumentError
188
+ end
189
+ chal = NTL::pack_int64le(chal) if chal.is_a?(Integer)
190
+
191
+ if opt[:client_challenge]
192
+ cc = opt[:client_challenge]
193
+ else
194
+ cc = rand(MAX64)
195
+ end
196
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
197
+
198
+ if opt[:timestamp]
199
+ ts = opt[:timestamp]
200
+ else
201
+ ts = Time.now.to_i
202
+ end
203
+ # epoch -> milsec from Jan 1, 1601
204
+ ts = 10000000 * (ts + TIME_OFFSET)
205
+
206
+ blob = Blob.new
207
+ blob.timestamp = ts
208
+ blob.challenge = cc
209
+ blob.target_info = ti
210
+
211
+ bb = blob.serialize
212
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + bb) + bb
213
+ end
214
+
215
+ def lmv2_response(arg, opt = {})
216
+ key = arg[:ntlmv2_hash]
217
+ chal = arg[:challenge]
218
+
219
+ chal = NTLM::pack_int64le(chal) if chal.is_a?(Integer)
220
+
221
+ if opt[:client_challenge]
222
+ cc = opt[:client_challenge]
223
+ else
224
+ cc = rand(MAX64)
225
+ end
226
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
227
+
228
+ OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, chal + cc) + cc
229
+ end
230
+
231
+ def ntlm2_session(arg, opt = {})
232
+ begin
233
+ passwd_hash = arg[:ntlm_hash]
234
+ chal = arg[:challenge]
235
+ rescue
236
+ raise ArgumentError
237
+ end
238
+
239
+ if opt[:client_challenge]
240
+ cc = opt[:client_challenge]
241
+ else
242
+ cc = rand(MAX64)
243
+ end
244
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
245
+
246
+ keys = gen_keys passwd_hash.ljust(21, "\0")
247
+ session_hash = OpenSSL::Digest::MD5.digest(chal + cc).slice(0, 8)
248
+ response = apply_des(session_hash, keys).join
249
+ [cc.ljust(24, "\0"), response]
250
+ end
251
+ end
252
+
253
+
254
+ # base classes for primitives
255
+ class Field
256
+ attr_accessor :active, :value
257
+
258
+ def initialize(opts)
259
+ @value = opts[:value]
260
+ @active = opts[:active].nil? ? true : opts[:active]
261
+ end
262
+
263
+ def size
264
+ @active ? @size : 0
265
+ end
266
+ end
267
+
268
+ class String < Field
269
+ def initialize(opts)
270
+ super(opts)
271
+ @size = opts[:size]
272
+ end
273
+
274
+ def parse(str, offset=0)
275
+ if @active and str.size >= offset + @size
276
+ @value = str[offset, @size]
277
+ @size
278
+ else
279
+ 0
280
+ end
281
+ end
282
+
283
+ def serialize
284
+ if @active
285
+ @value
286
+ else
287
+ ""
288
+ end
289
+ end
290
+
291
+ def value=(val)
292
+ @value = val
293
+ @size = @value.nil? ? 0 : @value.size
294
+ @active = (@size > 0)
295
+ end
296
+ end
297
+
298
+
299
+ class Int16LE < Field
300
+ def initialize(opt)
301
+ super(opt)
302
+ @size = 2
303
+ end
304
+ def parse(str, offset=0)
305
+ if @active and str.size >= offset + @size
306
+ @value = str[offset, @size].unpack("v")[0]
307
+ @size
308
+ else
309
+ 0
310
+ end
311
+ end
312
+
313
+ def serialize
314
+ [@value].pack("v")
315
+ end
316
+ end
317
+
318
+ class Int32LE < Field
319
+ def initialize(opt)
320
+ super(opt)
321
+ @size = 4
322
+ end
323
+
324
+ def parse(str, offset=0)
325
+ if @active and str.size >= offset + @size
326
+ @value = str.slice(offset, @size).unpack("V")[0]
327
+ @size
328
+ else
329
+ 0
330
+ end
331
+ end
332
+
333
+ def serialize
334
+ [@value].pack("V") if @active
335
+ end
336
+ end
337
+
338
+ class Int64LE < Field
339
+ def initialize(opt)
340
+ super(opt)
341
+ @size = 8
342
+ end
343
+
344
+ def parse(str, offset=0)
345
+ if @active and str.size >= offset + @size
346
+ d, u = str.slice(offset, @size).unpack("V2")
347
+ @value = (u * 0x100000000 + d)
348
+ @size
349
+ else
350
+ 0
351
+ end
352
+ end
353
+
354
+ def serialize
355
+ [@value & 0x00000000ffffffff, @value >> 32].pack("V2") if @active
356
+ end
357
+ end
358
+
359
+ # base class of data structure
360
+ class FieldSet
361
+ class << FieldSet
362
+ def define(&block)
363
+ c = Class.new(self)
364
+ def c.inherited(subclass)
365
+ proto = @proto
366
+ subclass.instance_eval {
367
+ @proto = proto
368
+ }
369
+ end
370
+ c.module_eval(&block)
371
+ c
372
+ end
373
+
374
+ def string(name, opts)
375
+ add_field(name, String, opts)
376
+ end
377
+
378
+ def int16LE(name, opts)
379
+ add_field(name, Int16LE, opts)
380
+ end
381
+
382
+ def int32LE(name, opts)
383
+ add_field(name, Int32LE, opts)
384
+ end
385
+
386
+ def int64LE(name, opts)
387
+ add_field(name, Int64LE, opts)
388
+ end
389
+
390
+ def security_buffer(name, opts)
391
+ add_field(name, SecurityBuffer, opts)
392
+ end
393
+
394
+ def prototypes
395
+ @proto
396
+ end
397
+
398
+ def names
399
+ @proto.map{|n, t, o| n}
400
+ end
401
+
402
+ def types
403
+ @proto.map{|n, t, o| t}
404
+ end
405
+
406
+ def opts
407
+ @proto.map{|n, t, o| o}
408
+ end
409
+
410
+ private
411
+
412
+ def add_field(name, type, opts)
413
+ (@proto ||= []).push [name, type, opts]
414
+ define_accessor name
415
+ end
416
+
417
+ def define_accessor(name)
418
+ module_eval(<<-End, __FILE__, __LINE__ + 1)
419
+ def #{name}
420
+ self['#{name}'].value
421
+ end
422
+
423
+ def #{name}=(val)
424
+ self['#{name}'].value = val
425
+ end
426
+ End
427
+ end
428
+ end
429
+
430
+ def initialize
431
+ @alist = self.class.prototypes.map{ |n, t, o| [n, t.new(o)] }
432
+ end
433
+
434
+ def serialize
435
+ @alist.map{|n, f| f.serialize }.join
436
+ end
437
+
438
+ def parse(str, offset=0)
439
+ @alist.inject(offset){|cur, a| cur += a[1].parse(str, cur)}
440
+ end
441
+
442
+ def size
443
+ @alist.inject(0){|sum, a| sum += a[1].size}
444
+ end
445
+
446
+ def [](name)
447
+ a = @alist.assoc(name.to_s.intern)
448
+ raise ArgumentError, "no such field: #{name}" unless a
449
+ a[1]
450
+ end
451
+
452
+ def []=(name, val)
453
+ a = @alist.assoc(name.to_s.intern)
454
+ raise ArgumentError, "no such field: #{name}" unless a
455
+ a[1] = val
456
+ end
457
+
458
+ def enable(name)
459
+ self[name].active = true
460
+ end
461
+
462
+ def disable(name)
463
+ self[name].active = false
464
+ end
465
+ end
466
+
467
+
468
+ Blob = FieldSet.define {
469
+ int32LE :blob_signature, {:value => BLOB_SIGN}
470
+ int32LE :reserved, {:value => 0}
471
+ int64LE :timestamp, {:value => 0}
472
+ string :challenge, {:value => "", :size => 8}
473
+ int32LE :unknown1, {:value => 0}
474
+ string :target_info, {:value => "", :size => 0}
475
+ int32LE :unknown2, {:value => 0}
476
+ }
477
+
478
+ SecurityBuffer = FieldSet.define {
479
+ int16LE :length, {:value => 0}
480
+ int16LE :allocated, {:value => 0}
481
+ int32LE :offset, {:value => 0}
482
+ }
483
+
484
+ class SecurityBuffer
485
+ attr_accessor :active
486
+ def initialize(opts)
487
+ super()
488
+ @value = opts[:value]
489
+ @active = opts[:active].nil? ? true : opts[:active]
490
+ @size = 8
491
+ end
492
+
493
+ def parse(str, offset=0)
494
+ if @active and str.size >= offset + @size
495
+ super(str, offset)
496
+ @value = str[self.offset, self.length]
497
+ @size
498
+ else
499
+ 0
500
+ end
501
+ end
502
+
503
+ def serialize
504
+ super if @active
505
+ end
506
+
507
+ def value
508
+ @value
509
+ end
510
+
511
+ def value=(val)
512
+ @value = val
513
+ self.length = self.allocated = val.size
514
+ end
515
+
516
+ def data_size
517
+ @active ? @value.size : 0
518
+ end
519
+ end
520
+
521
+ class Message < FieldSet
522
+ class << Message
523
+ def parse(str)
524
+ m = Type0.new
525
+ m.parse(str)
526
+ case m.type
527
+ when 1
528
+ t = Type1.parse(str)
529
+ when 2
530
+ t = Type2.parse(str)
531
+ when 3
532
+ t = Type3.parse(str)
533
+ else
534
+ raise ArgumentError, "unknown type: #{m.type}"
535
+ end
536
+ t
537
+ end
538
+
539
+ def decode64(str)
540
+ parse(Base64.decode64(str))
541
+ end
542
+ end
543
+
544
+ def has_flag?(flag)
545
+ (self[:flag].value & FLAGS[flag]) == FLAGS[flag]
546
+ end
547
+
548
+ def set_flag(flag)
549
+ self[:flag].value |= FLAGS[flag]
550
+ end
551
+
552
+ def dump_flags
553
+ FLAG_KEYS.each{ |k| print(k, "=", flag?(k), "\n") }
554
+ end
555
+
556
+ def serialize
557
+ deflag
558
+ super + security_buffers.map{|n, f| f.value}.join
559
+ end
560
+
561
+ def encode64
562
+ Base64.encode64(serialize).gsub(/\n/, '')
563
+ end
564
+
565
+ def decode64(str)
566
+ parse(Base64.decode64(str))
567
+ end
568
+
569
+ alias head_size size
570
+
571
+ def data_size
572
+ security_buffers.inject(0){|sum, a| sum += a[1].data_size}
573
+ end
574
+
575
+ def size
576
+ head_size + data_size
577
+ end
578
+
579
+
580
+ private
581
+
582
+ def security_buffers
583
+ @alist.find_all{|n, f| f.instance_of?(SecurityBuffer)}
584
+ end
585
+
586
+ def deflag
587
+ security_buffers.inject(head_size){|cur, a|
588
+ a[1].offset = cur
589
+ cur += a[1].data_size
590
+ }
591
+ end
592
+
593
+ def data_edge
594
+ security_buffers.map{ |n, f| f.active ? f.offset : size}.min
595
+ end
596
+
597
+ # sub class definitions
598
+
599
+ Type0 = Message.define {
600
+ string :sign, {:size => 8, :value => SSP_SIGN}
601
+ int32LE :type, {:value => 0}
602
+ }
603
+
604
+ Type1 = Message.define {
605
+ string :sign, {:size => 8, :value => SSP_SIGN}
606
+ int32LE :type, {:value => 1}
607
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE1] }
608
+ security_buffer :domain, {:value => "", :active => false}
609
+ security_buffer :workstation, {:value => "", :active => false}
610
+ string :padding, {:size => 0, :value => "", :active => false }
611
+ }
612
+
613
+ class Type1
614
+ class << Type1
615
+ def parse(str)
616
+ t = new
617
+ t.parse(str)
618
+ t
619
+ end
620
+ end
621
+
622
+ def parse(str)
623
+ super(str)
624
+ enable(:domain) if has_flag?(:DOMAIN_SUPPLIED)
625
+ enable(:workstation) if has_flag?(:WORKSTATION_SUPPLIED)
626
+ super(str)
627
+ if ( (len = data_edge - head_size) > 0)
628
+ self.padding = "\0" * len
629
+ super(str)
630
+ end
631
+ end
632
+ end
633
+
634
+ Type2 = Message.define{
635
+ string :sign, {:size => 8, :value => SSP_SIGN}
636
+ int32LE :type, {:value => 2}
637
+ security_buffer :target_name, {:size => 0, :value => ""}
638
+ int32LE :flag, {:value => DEFAULT_FLAGS[:TYPE2]}
639
+ int64LE :challenge, {:value => 0}
640
+ int64LE :context, {:value => 0, :active => false}
641
+ security_buffer :target_info, {:value => "", :active => false}
642
+ string :padding, {:size => 0, :value => "", :active => false }
643
+ }
644
+
645
+ class Type2
646
+ class << Type2
647
+ def parse(str)
648
+ t = new
649
+ t.parse(str)
650
+ t
651
+ end
652
+ end
653
+
654
+ def parse(str)
655
+ super(str)
656
+ if has_flag?(:TARGET_INFO)
657
+ enable(:context)
658
+ enable(:target_info)
659
+ super(str)
660
+ end
661
+ if ( (len = data_edge - head_size) > 0)
662
+ self.padding = "\0" * len
663
+ super(str)
664
+ end
665
+ end
666
+
667
+ def response(arg, opt = {})
668
+ usr = arg[:user]
669
+ pwd = arg[:password]
670
+ if usr.nil? or pwd.nil?
671
+ raise ArgumentError, "user and password have to be supplied"
672
+ end
673
+
674
+ if opt[:workstation]
675
+ ws = opt[:workstation]
676
+ else
677
+ ws = ""
678
+ end
679
+
680
+ if opt[:client_challenge]
681
+ cc = opt[:client_challenge]
682
+ else
683
+ cc = rand(MAX64)
684
+ end
685
+ cc = NTLM::pack_int64le(cc) if cc.is_a?(Integer)
686
+ opt[:client_challenge] = cc
687
+
688
+ if has_flag?(:OEM) and opt[:unicode]
689
+ usr = NTLM::decode_utf16le(usr)
690
+ pwd = NTLM::decode_utf16le(pwd)
691
+ ws = NTLM::decode_utf16le(ws)
692
+ opt[:unicode] = false
693
+ end
694
+
695
+ if has_flag?(:UNICODE) and !opt[:unicode]
696
+ usr = NTLM::encode_utf16le(usr)
697
+ pwd = NTLM::encode_utf16le(pwd)
698
+ ws = NTLM::encode_utf16le(ws)
699
+ opt[:unicode] = true
700
+ end
701
+
702
+ tgt = self.target_name
703
+ ti = self.target_info
704
+
705
+ chal = self[:challenge].serialize
706
+
707
+ if opt[:ntlmv2]
708
+ ar = {:ntlmv2_hash => NTLM::ntlmv2_hash(usr, pwd, tgt, opt), :challenge => chal, :target_info => ti}
709
+ lm_res = NTLM::lmv2_response(ar, opt)
710
+ ntlm_res = NTLM::ntlmv2_response(ar, opt)
711
+ elsif has_flag?(:NTLM2_KEY)
712
+ ar = {:ntlm_hash => NTLM::ntlm_hash(pwd, opt), :challenge => chal}
713
+ lm_res, ntlm_res = NTLM::ntlm2_session(ar, opt)
714
+ else
715
+ lm_res = NTLM::lm_response(pwd, chal)
716
+ ntlm_res = NTLM::ntlm_response(pwd, chal)
717
+ end
718
+
719
+ Type3.create({
720
+ :lm_response => lm_res,
721
+ :ntlm_response => ntlm_res,
722
+ :domain => tgt,
723
+ :user => usr,
724
+ :workstation => ws,
725
+ :flag => self.flag
726
+ })
727
+ end
728
+ end
729
+
730
+
731
+ Type3 = Message.define{
732
+ string :sign, {:size => 8, :value => SSP_SIGN}
733
+ int32LE :type, {:value => 3}
734
+ security_buffer :lm_response, {:value => ""}
735
+ security_buffer :ntlm_response, {:value => ""}
736
+ security_buffer :domain, {:value => ""}
737
+ security_buffer :user, {:value => ""}
738
+ security_buffer :workstation, {:value => ""}
739
+ security_buffer :session_key, {:value => "", :active => false }
740
+ int64LE :flag, {:value => 0, :active => false }
741
+ }
742
+
743
+ class Type3
744
+ class << Type3
745
+ def parse(str)
746
+ t = new
747
+ t.parse(str)
748
+ t
749
+ end
750
+
751
+ def create(arg, opt ={})
752
+ t = new
753
+ t.lm_response = arg[:lm_response]
754
+ t.ntlm_response = arg[:ntlm_response]
755
+ t.domain = arg[:domain]
756
+ t.user = arg[:user]
757
+ t.workstation = arg[:workstation]
758
+
759
+ if arg[:session_key]
760
+ t.enable(:session_key)
761
+ t.session_key = arg[session_key]
762
+ end
763
+ if arg[:flag]
764
+ t.enable(:session_key)
765
+ t.enable(:flag)
766
+ t.flag = arg[:flag]
767
+ end
768
+ t
769
+ end
770
+ end
771
+ end
772
+ end
773
+ end
774
+
775
+ # extra stuff to make nltm auth usage as easy as basic for Net::HTTP
776
+ # classes
777
+
778
+ require 'net/http'
779
+
780
+ module HTTPHeader
781
+ # could also try an automatic authentication. sends as basic first, then
782
+ # resends if required, or whatever.
783
+ # seems kind of messy exposing this stuff here.
784
+
785
+ def auth_data
786
+ @auth_data
787
+ end
788
+
789
+ # can set wait - don't authenticate unless challenged. useful when reusing
790
+ # the connection (otherwise you handshake for each request). wait should
791
+ # probably become the default, allowing the type of authentication to be
792
+ # driven by a server challenge.
793
+ def ntlm_auth user, password, wait=false
794
+ @auth_data = [:ntlm, user, password]
795
+ self['Authorization'] = 'NTLM ' + Net::NTLM::Message::Type1.new.encode64 unless wait
796
+ end
797
+ end
798
+
799
+ # here we override the default Net::HTTP#request method, in order to hide the
800
+ # necessary handshaking. maybe a more generic scheme for hooking into this
801
+ # could be useful, for other auth types
802
+
803
+ # because of the handshaking i have to rewind body stream. maybe body stream
804
+ # shouldn't be sent when authenticating??
805
+ class HTTPRequest
806
+ def reuse
807
+ if body_stream
808
+ begin body_stream.rewind
809
+ rescue; raise "error rewinding body stream for authentication"
810
+ end
811
+ end
812
+ end
813
+ end
814
+
815
+ class HTTP
816
+ alias old_request :request
817
+ private :old_request
818
+
819
+ def request req, body=nil, &block
820
+ resp = data = auth_data = nil
821
+ old_request req, body do |resp|
822
+ unless Net::HTTPUnauthorized === resp and auth_data = req.auth_data and
823
+ auth_data[0] == :ntlm and resp['www-authenticate'] == 'NTLM' ||
824
+ data = resp['www-authenticate'][/^NTLM (.*)/, 1]
825
+ data = false
826
+ yield resp if block_given?
827
+ end
828
+ end
829
+ return resp if data == false
830
+ # not really sure if i'm supposed to just rewrite the request like this?
831
+ # and the body? what about redirects? the resp.content is just the text error message
832
+ # what about post data?
833
+ req.reuse
834
+ unless data
835
+ # first stage handshake. respond to challenge
836
+ # puts "* authenticating (0) ..."
837
+ # this time wait is true.
838
+ req.ntlm_auth(*auth_data[1..2])
839
+ request req, body, &block
840
+ else
841
+ # puts "* authenticating (1) ..."
842
+ challenge = Net::NTLM::Message.decode64 data
843
+ # challenge.target_name could be provided back as a prompt.
844
+ # maybe if password is unspecified, a callback can be used to provide
845
+ # a user prompt.
846
+ resp = challenge.response({:user => auth_data[1], :password => auth_data[2]}, {:ntlmv2 => true})
847
+ req['Authorization'] = 'NTLM ' + resp.encode64
848
+ old_request(req, body) { |resp| yield resp if block_given? }
849
+ resp
850
+ end
851
+ end
852
+ end
853
+
854
+ end